-
Notifications
You must be signed in to change notification settings - Fork 2
plat 142
PLAT-142 — Pulse's own review evidence has the same orphaned-tool-call gap as the chat UI, and it is bigger there
| Coordination | Value |
|---|---|
| Assigned agent | unassigned |
| Ticket state |
partially implemented — recovery shipped for the regular/message_sequence step path on Claude Code; todo-task steps and the other three providers are explicit follow-up |
| Last synchronized | 2026-08-19 |
-
Priority: P1 — this is not display. Pulse's Gate/reviewers read
execution-attempt-*-conversation.jsonto judge whether a step's work is legitimate. A tool call recorded with no result is evidence with a hole in it, and on some workflows most of the evidence has one. -
Owner:
pkg/orchestrator/context_aware_bridge.go(captureToolCallEvent, the durable-evidence writer),pkg/orchestrator/agents/workflow/step_based_workflow/controller_execution.go(saveExecutionConversationLogs).
While reverifying PLAT-141's chat-UI fix, the same question was asked of Pulse's own evidence files rather than only the chat.
745 execution-attempt logs across every workflow in this workspace:
tool calls total: 15,601
missing a result: 2,563 (16.4%)
testing 88% (1474/1677)
social-media 9%
tectonicusadaytrading 10%
hetznerssh 9%
salesoutreach 7%
rtslatency 7%
instagram 7%
linkedin 3%
upwork 3%
Agent.emitTypedEvent (mcpagent/agent/agent.go:3095) is a single fan-out
point: one raw event reaches every tracer AND every listener in one call. It is
genuine pub/sub, not accidental duplication — tracers feed the live chat UI,
listeners feed ContextAwareBridge's durable per-step record, and both exist
for real, different reasons (ephemeral display vs. permanent audit trail).
The bug is upstream of that fan-out. For an interactive-transport tool call, no
ToolCallEndEvent is ever constructed and passed to emitTypedEvent at all —
this is inherent, not a routing failure: a terminal pane carries no structured
tool events, so there is nothing to emit one from (confirmed for all four
providers in PLAT-141). Because the gap is upstream of the fan-out, it hits
every consumer identically. PLAT-141 recovered it for the chat UI only, by
patching one downstream consumer; ContextAwareBridge never received the
equivalent fix and had zero compensation until this ticket.
The exact code path that emits an orphaned Start with no matching End was not conclusively located. Two real, confirmed mechanisms exist and either could be the active one for a given call:
-
claudecode_transcript_stream.go's live tailer (streamClaudeTranscript) polls the JSONL transcript every 250ms and exits onctx.Done()without a final read — a genuine race for a tool call fast enough to complete between the last poll and turn-end cancellation (every measured call was 35-41ms, well under one poll interval). This mechanism is opt-in viaWithStreamTranscriptand, as far as could be confirmed, is not enabled for this platform's workflow steps —family-serveris the only caller found. - The interactive adapter's own end-of-turn reconstruction
(
readClaudeTranscriptMessages, called fromclaudecode_interactive_adapter.go:656) — referenced by a comment inmcpagent/agent/tool_registry.go:210("a wrapped Run already receives the CLI transcript's canonical tool intent/result pair") as the canonical source for interactive-transport tool events. The exact point where mcpagent turns that reconstructed history intoToolCallStart/ToolCallEndAgentEvents was not located in this session.
Do not assume (1) is the live mechanism without checking whether
WithStreamTranscript is set anywhere in this platform's call path first —
that was checked here and appeared not to be, but was not exhaustively proven.
backfillMissingToolResults (tool_call_backfill.go), called once at the top
of saveExecutionConversationLogs — the single function every regular and
message_sequence step's evidence write already funnels through. It does not
reimplement transcript reading: it calls the exact same
claudecode.ToolResultsFromTranscript PLAT-141 built and verified for the chat
UI, resolved against the step's own executing agent's session handle (already
available at this call site — no new plumbing to find it).
Only entries in the exact orphaned shape are touched —
toolCallEntryLooksOrphaned: an id, no result, no error, zero duration. A call
that genuinely failed or genuinely returned an empty string already has a real
outcome and is left alone.
-
Todo-task steps.
saveTodoTaskExecutionLogis a second, separate evidence-writing function with its owntoolCallsparameter and does not yet call the backfill. Regular andmessage_sequencesteps — the majority of workflow steps in this workspace — are covered; todo-task steps are not. - codexcli, cursorcli, picli. Same architecture, same gap, per PLAT-141. Each already has transcript-reading infrastructure to build on.
-
The upstream fix. The correct long-term shape is one reconciliation at
Agent.emitTypedEventitself, so every current and future consumer — chat UI, Pulse evidence, and whatever reads the stream next — receives a complete event without each needing its own patch. That is a cross-repo change to mcpagent's core turn lifecycle and needs a live run to verify; it was not attempted blind in this session. What shipped here and in PLAT-141 are two call sites sharing one recovery function, which is the safe, verifiable version of the same idea — not the final architecture.
- A
message_sequenceor regular step'sexecution-attempt-*-conversation.jsonhas notool_callsentry with an id, no result, no error and zero duration, when Claude Code's transcript has a result for that id. -
testing's 88% figure is re-measured after a live run and drops sharply. - Todo-task steps and the other three providers are picked up as separate, explicitly-scoped work — not silently assumed covered by this ticket.
Auto-synced from docs/ on main. Edit there, not here.