Skip to content

Spawned analysis: have an agent review a session's work - #6

Merged
tdwd merged 2 commits into
mainfrom
spawned-analysis
Aug 27, 2026
Merged

tdwd merged 2 commits into
mainfrom
spawned-analysis

Conversation

@tdwd

@tdwd tdwd commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Stage 3 of the connected-sessions plan. analyse starts a separate agent to review what another session changed; analysis collects the answer and what it cost.

Two commits: the gittest extraction stands alone and is reviewable on its own, then the feature.

Four decisions, each measured rather than argued

Asynchronous, because the timeout is not ours. A synchronous call blocks the caller's tool timeout, which belongs to the calling agent. A review that outruns it returns nothing and has already spent the money.

The reviewer is handed its evidence and given nothing else. No coordination tools — it has nothing to ask anyone. It runs under a permission mode that answers but cannot act, tested rather than assumed. Everything it needs is in the prompt, so the prompt is what the tests assert on.

A failed turn is a result, not an error. agent.ClaudeRun carries Failure as a field. Returning a Go error would tell every caller to discard the value — putting the bill out of reach in the one case where it is surprising. An error means no envelope came back, so there is no accounting to add.

Cost is per session, never written to disk. The same one-word turn measured at $0.012 and $0.237 depending on whether its context was read from cache or written to it, so a per-run figure alone is unreadable. The sidebar carries the total as a third badge beside claims and mail, accented while running and faint once settled.

Tests

The suite neither spends money nor needs the CLI. It used to do both: a probe measured one unstubbed run at $0.106, and CI — where claude is not installed — reported green on a spawn that had failed. coord.WithReviewer is the seam, and it replaced a method that existed only for tests.

Every wiring point fails when broken, verified by breaking it:

broken test that catches it
case "analyse" / case "analysis" TestAnalyseIsCallableOverMCP
json:"question" / json:"id" same, plus TestTheQuestionReachesTheReviewer
cost dropped on a refused turn TestFailedTurnKeepsItsCost
failed run excluded from the total TestAFailedRunStillCountsAgainstSpend
the jobs bound TestJobsAreBounded — kept 55, want 50
the lifetime context TestCloseStopsARunningAnalysis
the sidebar badge TestSidebarShowsWhatAnalysesCost

Verified live once, opt-in behind DECK_LIVE: a real reviewer answered ZEPHYR_RETRY_BUDGET in 3.4s for $0.1069, with the token split and the session total agreeing.

Documented in README.md (both tools, the new badge) and docs/architecture.md (the four decisions, including an explicit warning not to "tidy" the Failure field into an error return).

tdwd added 2 commits August 27, 2026 18:31
gitx, coord, ui and the smoke test had all written their own "make a
repository with one commit", and they had already drifted: one resolved
symlinks and the others did not. That is the difference that registers a
single directory as two projects, because t.TempDir hands back a
symlinked path on macOS and git reports the resolved one.

internal/gittest holds it once. No test builds or queries git by hand any
more; the only remaining callers are gitx.run, the demo seeder, and the
helper itself.

The resolution is now a rule rather than a comment. Removing it used to
break nothing at all, which is how four copies managed to disagree about
it; TestRepoReturnsAResolvedPath fails on both its assertions without it.
analyse starts a separate agent to review what another session changed and
reports what the review cost. analysis collects it. The caller does not
wait: a real review outlasts the tool timeout of whatever asked for it,
and that timeout belongs to the calling agent rather than to Deck, so a
synchronous answer would be money nobody receives.

The reviewer is handed the diff and given nothing else. It has no
coordination tools because it has nothing to ask anyone, and it runs in a
mode that answers a question but refuses to act, so a review cannot become
an edit. Everything it needs is in the prompt, which is what makes that
prompt worth testing.

A turn that ran and refused is a result, not an error. agent.ClaudeRun
carries Failure as a field, because Go's convention tells a caller to
discard the value alongside an error and that would put the bill out of
reach in the one case where it is surprising. An error means the opposite:
no envelope came back, so there is no accounting to add and inventing a
figure would be worse than the gap.

Cost is per session and never written to disk. A per-run figure alone is
hard to read — the same one-word turn measured at $0.012 and $0.237
depending on whether its context was read from cache or written to it — so
the running total is what makes a pattern visible. The sidebar carries it
as a third badge beside claims and mail, accented while a review is in
flight and faint once settled, following the rule already stated there.

Close cancels every run the coordinator started. Without it, quitting Deck
mid-review left an agent running and billing with no surface left to show
it on.

The spawn is a field rather than a direct call, so the suite neither spends
money nor needs the CLI: a probe measured one unstubbed test run at $0.106,
and CI, where claude is not installed, reported green on a spawn that had
failed. WithReviewer is the seam, and it replaced a method that existed
only for tests.

Verified live once: a real reviewer answered ZEPHYR_RETRY_BUDGET in 3.4s
for $0.1069, with the token split and the session total agreeing.
@tdwd
tdwd merged commit 8b99f68 into main Aug 27, 2026
3 checks passed
@tdwd
tdwd deleted the spawned-analysis branch August 27, 2026 18:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant