Repository navigation
Land the trace projection and snapshot endpoint #22
Description
Part of #1
Question
Build the trace projection decided in Project T3's activity stream onto mindwalk's Trace model. That ticket settled every design question; this one lands the code. Read its resolution comment first — it is the spec.
Shape:
- New endpoint
GET /api/mindwalk/threads/:threadIdreturning{ trace, citymap }, alongside the existingapps/server/src/citymap/. - Order per mindwalk
server.go:834-841: trace (paths only) → citymap withghostPaths→ backfillTarget.fileId→ recomputeComputeStatswith the real file count. - Copy verbatim from
.repos/mindwalk/internal/adapter/adapter.goandinternal/model/stats.go:actionFor,targetsFor,extractPaths,extractCommandPaths,commandReadPaths,parsePathHits,normalizePath,repoPathExists,verifyCommand,searchCommand,readCommand,ComputeStats. Same bet asscene/andplayback/— mistranslation renders almost right and costs days. - Write fresh: a per-provider adapter reshaping an activity into
{ tool, input, result }. Claude is near-trivial (its payload already is that shape). Codex mapscommandActions[]. - Fold at
tool.completed; SQL-filter totool.completed/tool.denied/context-compactionbefore parsing. - In-memory cache keyed on the thread's activity count.
- Lens parameter (
main/agent(launchId)) — a no-op today, consumed by Project the agent graph and per-agent traces #21. - Empty citymap instead of a 500 when the build fails.
Do not widen packages/shared/src/toolActivity.ts. See the resolution for why.
Verification
Port the citymap builder to the T3 server set the bar: differentially verified against the real Go binary, zero diffs. Do the same here — run mindwalk serve against a Claude session and diff its /api/sessions/:key/trace against ours for the same thread, or golden-fixture the ported string functions against the Go tests in adapter_test.go (1,068 lines of them, free).
Note the input differs — mindwalk reads Claude's JSONL, we read T3's event store — so expect divergence where T3 stores less. Characterise each difference rather than chasing zero.
Verified facts to build against (measured, not assumed): Bash is 66% of all tool calls and Grep/Glob are absent entirely; file_path is snake_case; Claude's payload is { toolName, input, result: { content, is_error } }; Codex's commandActions[] is { command, name, path, type }; the largest thread is 1,283 tool.completed rows / 15.4 MB.
Activity
Dillpickleschmidt commentedon Aug 1, 2026
Answer
Landed on branch
feat/trace-projection-endpoint(commitf70e59227, pushed, no PR).GET /api/mindwalk/threads/:threadId?lens=returns{ trace, citymap }, built in mindwalk's order (server.go:834-841): trace with paths only → citymap with those paths asghostPaths→ backfillTarget.fileId→ recomputeComputeStatswith the real file count. Code lives inapps/server/src/mindwalk/; the wire contract ispackages/contracts/src/trace.ts, shaped byte-identically toapps/web/src/mindwalk/types.ts.Differentially verified against the real Go binary
The citymap builder set the bar at zero diffs against
mindwalk. That bar isn't reachable here — the inputs genuinely differ — so here is the characterisation instead. Threadc4e71440("Redesign plant growth system", the largest in the store, 1,287 rows) ran as Claude session21d3c20e;mindwalk tracewas run over that session's JSONL and diffed against our projection of the same thread.Event count differs: 1,284 ours vs 1,819 mindwalk's, and ours is the more correct number. The JSONL has 1,819
tool_useblocks but only 1,348 unique ids — Claude Code replays history into the file on resume and compaction, and mindwalk emits an event per replay. T3's activity log is append-only and records each call once. The remaining 65 (1,348 unique − 1,283tool.completed) are calls the JSONL has and T3's store does not, spread evenly at ~5% across every tool (Bash −23, Edit −15, Read −13, Write −2 …) rather than concentrated in any one type — the fingerprint of dropped ingest events, not of a projection gap. Recorded as fog; it is a T3 ingest-completeness question, not this ticket's.Ran over all 21 real threads: 3,805 events, no crashes, largest thread projected in 76 ms.
Deviations from mindwalk, all deliberate
commandActionstargets are not weak — as the design decided. Typed discriminator with an absolute path from Codex's own sandbox parser beats a shell string.exact, notestimated— a second deviation of the same character, not previously enumerated. mindwalk sniffs Codex failures out of output text because rollout JSONL is all it has; T3 storesstatusandexitCodestructurally. Reporting "estimated" here would be exactly the lie that grade exists to prevent.exec/js/js_replbranches inactionFor/targetsFor. Those parse Codex's free-form JavaScript orchestration wrapper out of rollout JSONL. T3 talks to the Codex app-server, which delivers acommandExecutionitem with a plain shell string, so the ~200 lines of JS-source parsing behind them are unreachable. Everything else in the copy list is verbatim.What the port confirmed
exec, 724edit, 543read, 265search. Of 3,971 targets, 3,030 are weak — scraped out of Bash. Without the scraping,searchand the "seen" level would be near-empty.Observability.Readsisestimatedon essentially every thread, andunavailableon the few that only ran shell commands. Honest and correct.verifyfires almost never in practice: 1 event across 3,805. mindwalk's patterns arego test/npm test/pnpm test/pytest/cargo test/make test; this environment runsvp test runand CMake. Faithful to upstream and left alone — recorded as fog, sinceeditsAfterLastVerifyand the verify bar are dead here until the list grows.Two things the build settled that the design had not
getThreadShellByIdonly returns active threads, so an archived thread would have projected with the wrong adapter and rendered a dark, empty city. Caught by the service test. Now readsProjectionThreadSessionRepository.data.toolName/data.item/data.toolis present.Cache
Exact, keyed on
COUNT(*)of the thread's activities (per lens), no TTL — the log is append-only, so the count is a perfect fingerprint. A repository test pins that an upsert of an existing row does not move it. Read is SQL-filtered to the three kinds the projection wants, which skips the majority of a long thread's payload bytes before parsing. Ghost re-layout on a cache miss measures 40–80 ms on a 15.7k-file repo; the expensive walk stays cached per root inCitymapBuilder.Degradation
A failed citymap build returns an empty citymap and the trace anyway — you lose the buildings, not the timeline. Verified by test.
Tests
107 in
apps/server/src/mindwalk/plus 4 for the new repository queries.TestCommandReadPaths,TestBuildEventClassifiesBashSearchCommands,TestSummarizeToolTruncatesCommandAtRuneBoundaryand all three ofstats_test.goare ported case for case from the Go suite. Claude and Codex adapter fixtures are trimmed copies of realpayload_jsonrows. Nothing asserts ACP or OpenCode behaviour beyond the graceful fallback, per the ticket.fix(server): address Macroscope review on the trace projection
fix(server): fold repo state and message content into the snapshot key
fix(server): never cache a degraded mindwalk snapshot
fix(server): address Macroscope review on the trace projection
fix(server): fold repo state and message content into the snapshot key
fix(server): never cache a degraded mindwalk snapshot