Add read_agent_trace fleet verb (orchestrator tiers only) - #599
Conversation
Lets an orchestrator or nested orchestrator read a worker's on-disk turns.jsonl directly, so a cancelled or interrupted worker's completed work is no longer invisible once its in-memory session record is gone. Every response is bounded (turn window, entry count, per-entry chars), tolerates partially written/malformed lines, and reports a clean error for an unknown target. Fixes a latest-symlink double-count in directory enumeration by resolving symlinks and de-duping by real path. progress_note for leaf workers is a separate follow-up, not included here.
Review on #599 found two real issues: - findAgentTraceDir walked the whole (flat, shared) subagents/ tree from the root workdirBase, so a Tier-2 nested orchestrator could read any worker's trace, not just its own descendants. Wired assertCanTargetAgent (authority.ts's first live call site) using the worker's own SubAgentSessionStore id and the store's existing parentSessionId chain, rather than reshaping the on-disk layout — the disk tree is deliberately flat across the whole fleet (shared by worktrees and intervention logs too), so a structural per-subtree root would be a much larger change. To make that id available, run.ts now names a worker's trace directory after its session-store id when one is supplied (task-tool.ts passes it), instead of always minting a fresh disk-only id. - The per-entry, entry-count, and turn-window caps multiply (500 * 4,000 = 2,000,000 chars). Added a total-output character cap that stops filling entries once reached and reports the remainder via the existing `omitted` block. Noted but not changed: readAllTurns loads each full segment before bounds apply. Segments are already bounded to ~256KB by the writer, so this isn't unbounded, but avoiding the read entirely needs a line-count index or a streaming reader — left as a follow-up rather than expanding this fix.
de35d90 to
a438026
Compare
|
Addressed both issues. 1. Cross-subtree leak — fixed by wiring `assertCanTargetAgent`. Instead: `run.ts` now names a worker's on-disk trace directory after its `SubAgentSessionStore` id when the caller supplies one (`task-tool.ts` passes `session.id`), instead of always minting a fresh disk-only uuid. That makes the store's existing `parentSessionId` chain usable as the authority boundary. `trace-tool.ts`'s handler now calls `assertCanTargetAgent({id: actorId, tier}, target, sessions.list())` before touching disk — `authority.ts`'s first live call site, as flagged. Tier 1 (no authority context passed) is unrestricted, matching its actual rule. An actor with no resolvable session id fails closed (denied), consistent with CL-6941's unresolved-tier-denies precedent. New tests in `trace-tool.test.ts` ("descendant-only scoping" describe block): orchestratorA can read its own descendant workerA1, cannot read sibling-subtree workerY, an unresolvable actor id is denied outright, and Tier 1 can read anyone. 2. Aggregate cap — added `MAX_TRACE_TOTAL_CHARS` (20,000 chars). `readAllTurns` full-segment read: left as-is, with a code comment explaining why. Each segment is already bounded to ~256KB by the writer (`createSegmentedJSONLWriter`'s `DEFAULT_MAX_SEGMENT_BYTES`), so this isn't unbounded the way reading one giant `turns.jsonl` would be — but avoiding the read for segments outside the requested window needs either a line-count index or a streaming reader. Please ticket that separately as suggested rather than folding it into this PR. Rebased onto latest `origin/main` (past #597). `bun run check` (lint, typecheck, build, full suite) is green: 5434 pass, 0 fail. Both `run.ts` and `authority.ts` hunks are additive/minimal (one new field on `RunSubAgentParams`, one line changing how the workdir id is chosen, three lines in `authority.ts`'s comment) to stay out of #600's way. |
Summary
Implements the
read_agent_tracehalf of CL-6951. Every sub-agent worker already writes its full turn history toturns.jsonlunder its own workdir, but nothing in the runtime reads it back — so a cancelled or interrupted worker's completed work was invisible to the orchestrator even though it was sitting on disk. This gives the orchestrator (and nested orchestrator) tiers a tool to read it directly.src/subagent/trace-reader.ts— locates a worker's trace directory under the caller'ssubagents/tree and reads itsturns.jsonlsegments (reusinglistSegmentFilesfromincremental-jsonl.ts), flattening turns into typed entries (text/thinking/tool_call/tool_result/error).src/subagent/trace-tool.ts— theread_agent_tracetool definition/handler wrapping the reader.src/subagent/run.ts/src/agent/tools.ts— mount the tool the same waytask/search_agentsare mounted: gated throughassertTierMayMountFleetVerb(leaf directors never get it), unconditionally for the primary (Tier 1) session.src/subagent/authority.ts— comment update;read_agent_tracemoves from "future verb" to "implemented."Bounding strategy
Every response path is capped, with hard maximums the caller cannot exceed regardless of what it asks for:
fromTurn/toTurnargs).limitarg).truncated: trueon the entry.kindsfilter narrows to specific entry types.Whenever anything is left out, the result carries a machine-readable
omittedblock (turnsBefore/turnsAfter/reason/hint) telling the caller exactly what was skipped and whatfromTurn/toTurnto pass to page for the rest. No argument combination returns an unbounded blob.Malformed / missing input
parseWarnings; it never throws.AgentTraceNotFoundErrormessage via the tool result, not an exception.latestsymlink double-countDirectory enumeration (
listUniqueSubdirs) resolves every entry viarealpathand de-duplicates by real path, deriving the reported name from the resolved path's own basename rather than the rawreaddirentry name. Alatest-style symlink pointing at a sibling directory therefore can never be counted as a second, distinct entry regardless ofreaddirordering. Covered bytrace-reader.test.ts'slistUniqueSubdirssuite, including the exact "latest plus its target enumerates once" case.Left out (by design)
progress_notefor leaf workers — separate ticket, not touched here.spawn_agent/wait_agents/list_agents/send_input/interrupt_agent/close_agent/resume_agent/followup_taskverbs already listed inFLEET_VERBS— unimplemented, unaffected.Test plan
bun test src/subagent/trace-reader.test.ts— 17 tests: dedup/enumeration, recursive lookup, bounding (turn window, entry limit, truncation), malformed-line tolerance, kind filtering, missing target.bun test src/subagent/trace-tool.test.ts— tool-level error/format behavior.bun test src/subagent/run-authority.test.ts src/subagent/authority.test.ts— existing tier-gate tests still pass.bun run check— lint, typecheck, build, full test suite (5399 pass, 0 fail).