#212

Show what each tool call ran and returned: names, inputs, output and outcomes

Opencalledforthwants to merge intofrom

What

On web, every tool row showed a name and a status and nothing else: tool.updated carried only an id, title, kind and status, and the projection dropped everything the runtime already knew. This gives tool calls their payloads, end to end:

  • Wire (protocol v17). tool.updated now carries:
    • toolName: the provider's own name, unchanged (Bash, Grep, mcp__<server>__<tool>), separate from the human title;
    • input: bounded JSON (4 KiB, strings cut at 2 KiB), with edit bodies (old_string, new_string, content, edits[] bodies, ...) stripped and paths kept;
    • output: bounded to 16 KiB as JSON-encoded bytes. Past that it keeps the first 4 KiB and the newest end, with omittedBytes between them;
    • outputDelta: streamed output appended with a shared appendToolOutput. Past the cap the newest output stays visible;
    • locations (files touched), lineChanges (only when the provider reports them), and startedAt / finishedAt.
  • Outcomes. The status gains declined and cancelled as outcomes of their own, distinct from completed and failed.
  • No diffs in chat. No diff or file body crosses the wire. Line counts come only from what the provider reported (Claude Code's structured patch or git diff); nothing is diffed here.
  • Images stay in the reply. Images a tool returns are not part of its output. The artifact block filed in the reply now names its toolCallId.
  • Claude Code fills every field. Name, input, output, locations, lines and times are all set. An auto-denied call reads as declined, and an interrupted Bash command as cancelled.
  • Streaming. A per-turn tracker turns providers that resend their whole output into append deltas. A delta too large for one event goes out as the whole bounded output instead. When a turn ends, any call still open is settled with a tool.updated just before the terminal event: declined if the user refused its permission, cancelled otherwise. A failure after the user refused the permission reads as declined, and after a stop as cancelled.
  • Storage (migration 20). turn_activity gains payload_json (tool rows only, enforced by a CHECK) and payload_bytes. The projection folds with the same applyToolUpdate the clients use. A history page reads every call's small state, but only the payloads it has room for.
  • Frame budgets. History pages and thread snapshots carry tool payloads newest first within 256 KiB, and never take the page past 896 KiB with its messages and reasoning. The call that doesn't fit gets a shrunk output. Older calls carry an output marker naming the bytes left out, so "not loaded" reads differently from "no output". A restart marks tools left open by a dead process as cancelled.
  • Clients. The fold, the store, history and snapshot hydration hold all of the above. toolPart leads with the provider name, puts input and output where the existing presenters read them, and passes the structured output, files, lines and timing through. Declined and cancelled show as settled, not as errors. There are no new renderers.

The desktop app stays on its Convex path. It compiles and its tests pass unchanged.

Why

Without input or output, a tool row tells the user nothing about what the agent ran or what came back. The runtime already had this data. It was being dropped at the protocol boundary.

Compatibility

  • Protocol version 16 → 17. A v16 client's status enum rejects declined/cancelled along with the event.
  • Migration 20. Adds two columns to turn_activity. Existing rows are kept as they are. Calls recorded before it have no payload, because providers never sent one.

Tests

  • protocol: 279 passed. New: truncation, chunked streaming equal to bounding the whole, surrogate safety, input bounds, the fold, schema refusals.
  • desktop suite, which includes agent-runtime: 675 passed. New: the Claude mapping (Bash, MCP, Edit/Write/MultiEdit stripping and line counts, auto-deny, interrupt, image results, huge results), delta streaming, declined/cancelled/settlement.
  • server: 832 passed, 7 skipped. New: storage and fold parity, page and snapshot budgets, replay versus late-join snapshot, restart recovery, the v19 → v20 upgrade, declined/cancelled through the thread service, and the tool image toolCallId across a restart.
  • environment-client: 254 passed. app-core: 445 passed. web: 310 passed.
  • Typecheck is clean for protocol, contract, runtime, view, environment-client, shared, app-core, server, web, desktop and mobile.

Follow-ups

  • Tool names for the ACP providers. Cursor's and OpenCode's tool names aren't extracted yet, so their rows still lead with the title.
  • Renderers. Dedicated tool row renderers for these payloads: command and output panes, the truncation marker, files touched, line counts, timing, and a tool's images under its row while the turn runs.

🤖 Generated with Claude Code

Activity

31
cloudflare-workers-and-pages[bot]21d ago

🚀 Deploying Preview to Cloudflare 🚀

Preview Deployments by commit

StatusDeployment URLCommitUpdated (UTC)See this deployment's details
  • Build: Failed ❌

View logs ↗
4141fa02026-10-09T17:40:27.345ZView logs ↗
  • Build: Failed ❌

View logs ↗
7d4d8462026-10-09T17:23:45.537ZView logs ↗
  • Build: Failed ❌

View logs ↗
f66e5472026-10-09T17:05:29.799ZView logs ↗
  • Build: Failed ❌

View logs ↗
2bd30e12026-10-09T12:34:59.878ZView logs ↗
  • Build: Failed ❌

View logs ↗
c5b068c2026-10-09T12:18:51.904ZView logs ↗
  • Build: Failed ❌

View logs ↗
e1254982026-10-09T12:15:12.783ZView logs ↗
  • Build: Failed ❌

View logs ↗
f770b342026-10-09T12:01:32.997ZView logs ↗
  • Build: Failed ❌

View logs ↗
661ec462026-10-09T11:38:16.642ZView logs ↗
greptile-apps[bot]21d ago


[High impact] Adds database schema migration and tool call tracking.

The PR still needs the reconnect fix to accept shorter final results before merging.

Findings

  1.  Reconnect keeps old tool output ▶

Summary

This PR carries tool names, arguments, results, files, line counts, timing, and outcomes through the runtime, protocol, storage, and clients.

  • Claude Code calls keep their names and results through a turn.
  • Tool updates carry bounded details and distinct outcomes.
  • History keeps tool details within page and snapshot size limits.
  • Thread views keep tool details across live updates and reconnects.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Provider tool events] --> B[Runtime projection]
  B --> C[Bounded tool updates]
  C --> D[SQLite state and payload]
  C --> E[Live client fold]
  D --> F[Budgeted history or snapshot]
  F --> E
  E --> G[Tool message parts]

Reviews (7) · Last reviewed commit: "Prefer the newer output for held calls; ..." · Reviewed by Greptile

greptile-apps[bot]reviewed21d ago
greptile-apps[bot]reviewed21d ago
greptile-apps[bot]reviewed21d ago
greptile-apps[bot]reviewed21d ago
calledforthreviewed21d ago
calledforthreviewed21d ago
greptile-apps[bot]reviewed21d ago
greptile-apps[bot]reviewed21d ago
calledforthreviewed21d ago
calledforthreviewed21d ago
greptile-apps[bot]reviewed21d ago
greptile-apps[bot]reviewed21d ago