Skip to content
This repository was archived by the owner on Sep 8, 2026. It is now read-only.
This repository was archived by the owner on Sep 8, 2026. It is now read-only.

agent memory: structured truncated tool_result on the wire (not Tool: crumbs) #549

Closed

Description

@btipling

Parent

#548 (agent session architecture)

Problem

formatPromptWithHistory drops every tool_run row (lib/sessionStore.ts).
Those cards are display-only (plan #345). The model already saw tool results
this turn; on the next turn it gets only user/assistant/system/error
prose, flattened into one { prompt } string.

Operators see: the agent re-reads the same files, forgets what it grepped,
never builds session memory, and talks like a new hire each send.

We persist up to 8 MiB of those cards in Blob/localStorage and then
omit them from inference.

Goal (locked direction)

Put truncated tool_result on the wire as structured messages — not
Tool: … lines stuffed into formatPromptWithHistory.

Peer harnesses (OpenCode / Pi / Oh My Pi) send a real message array:

user / assistant + tool_use / tool_result (thinking only if the
provider wants it). That is how the next turn still knows what was read.

Invincible today: single-shot { prompt } + display-only tool_run.

Success: after a turn that read_files / greps / change_dirs, the next
user message can be “use what you already found” and the model does not
rediscover the tree from zero.

How (directional — lock in create-plan)

  • Host/server keep a model-facing trace (call id, name, args, truncated
    result, ok) distinct from the Wasm paint card (tool_run stays
    display-primary).
  • Default send: bounded result (paths + status + short excerpt). OpenCode
    ~2k lines / 50 KB; Pi ~2k chars at compact time. Do not re-send
    TOOL_RUN_PREVIEW_MAX_CHARS (100k) per item.
  • Prefer AI SDK / Gateway native tool-result messages over a flattened
    transcript. Flattening is a fallback if a provider cannot take the array —
    still truncated, still paired (never a call without its result).
  • Abort/cancel: persist committed tools the same way we persist change_dir
    (honest, not a hallucinated summary of a cancelled mid-tool).

Constraints (lock)

Peer harness notes

Idea Take
Native tool_use / tool_result in the messages array This issue
Truncate tool output; do not omit This issue
Never split a call from its result at a cut #552
Files touched listed in the compact summary #552
Skills catalog vs full bodies #557

Non-goals

Suggested next

create-plan against #548 A1 before coding.

Orrery (2026-08-15)

  • Hygiene: few summary lines in the model-facing result; verbose exec/test output stays on the sandbox disk (file path in the result). Same Anthropic “parallel Claudes” lesson as their exec tool. Caps for that excerpt land in the A1 create-plan table — do not default to TOOL_RUN_PREVIEW_MAX_CHARS.
  • Assembly order: model → tools → system → history is locked before the request is built (inference: cache-stable two-block system prompt (don’t bust the KV prefix) #558). Structured messages are the history part, not a second system dump.

Related tools (not this board)

Tool-side shaping (so there is something honest to fold):

  • #562 first-class search
  • #563 windowed read_file
  • #564 stale str_replace window
  • #565 exec summary + disk log

Those issues are sandbox/tools, not agent-session. This issue remains structured tool_result on the wire.

Activity

  1. changed the title [-]agent memory: fold tool-run evidence into the model prompt (stop re-reading every turn)[/-] [+]agent memory: structured truncated tool_result on the wire (not Tool: crumbs)[/+] on Aug 15, 2026
  2. btipling commented on Aug 18, 2026

    @btipling
    OwnerAuthor

    This is A1 for the “every send is my first turn” report (parent #548 2026-08-18).

    The model is not amnesiac. We throw away the turn’s execution graph and feed it a sanitized User: / Assistant: string (formatPromptWithHistory — lib/sessionStore.ts). No tool_use / tool_result, no toolCallId, so prior work looks like ungrounded third-party prose.

    Concrete lock for the A1 create-plan (from the same research pass):

    1. lib/agent/runAgent.ts already uses streamText + stepCountIs. Persist result.response.messages (or onFinish({ response })) — the full ModelMessage[] (tool-calls, role: 'tool' results, reasoning parts, final text) — not the terminal assistant string.
    2. Replay that array on turn N+1. Do not add more Tool: crumbs to formatPromptWithHistory.
    3. Truncate results (paths + status + short excerpt). Never send TOOL_RUN_PREVIEW_MAX_CHARS (100k) per item. LLM payload ≠ tool_run paint JSON (feature-divide).
    4. Keep call/result pairs atomic (abort/cancel: persist committed pairs like change_dir).
    5. Per-provider: DeepSeek needs reasoning_content replay; Anthropic thinking needs providerMetadata. SessionStore CoT paint stays ephemeral — decide model-facing replay in this plan, do not dump thinking into Redis/Blob.

    Peer: Pi split tool result (llm vs UI); Codex inner cycle = reasoning + function_call + function_call_output before yielding to the operator.

  3. btipling commented on Aug 19, 2026

    @btipling
    OwnerAuthor

    2026-08-19 source-level peer research (Codex / DeepSeek / OpenCode / Oh My Pi)

    A1 is still the first-turn fix. These are locks for the create-plan, not a new issue.

    Mechanism we already have

    Invincible Functions are already OpenCode-shaped: every POST reloads. There is no process-local ContextManager vector that can drift from disk. The bug is that we reload paint rows and flatten them (formatPromptWithHistory → { prompt }, drop tool_run).

    Steal OpenCode’s invariant, implemented as a durable model-facing projection:

    One streamText per provider turn; persist response.messages (bounded / truncated); reload that array on the next Function. Do not re-derive it from Wasm tool_run cards.

    Carrier: session-owned (envelope pointer + Blob sidecar, or a distinct object — lock in create-plan). Paint stays tool_run (feature-divide). LLM payload ≠ UI payload (Pi).

    Truncate / spill, don’t omit

    Peer Oversized tool bytes
    DeepSeek spill to session file; locator + retrieval hint in the result
    OpenCode tool-output/ file, 7-day TTL; settlement fails if retain fails
    Oh My Pi 50 KB → artifact://; images → blob:sha256:
    Codex truncate stub; compact drops tool output from the live vector

    Our cloud analogue: verbose bytes on sandbox disk (already #565 for exec) or a Blob object; the structured tool_result carries a bounded excerpt + path. Do not default to TOOL_RUN_PREVIEW_MAX_CHARS (100k). OpenCode’s honesty bar is worth taking: if we cannot retain the full output, the tool did not succeed.

    Interrupted closer

    DeepSeek / OMP: crash mid-turn → synthetic interrupted / aborted, keep committed tool pairs. We already persist change_dir on cancel. A1 must persist committed tool_use+tool_result the same way, plus an honest stop reason, so turn N+1 does not treat a torn assistant string as a finished mind.

    Persist ≠ fresh

    A1 snapshots are not live file views. #277 already empties the freshness ledger every POST. After A1 the model will see the old read_file and think it has a grant — it does not. That composition is #693 (plan with A1). Do not rewrite tool_result when the file changes; do not put fingerprints on the session.

    Skip

    Codex in-memory vector as authority; DeepSeek JSONL as our SoT; OMP snapcompact PNGs; stuffing paint JSON into the Gateway.

  4. btipling commented on Sep 4, 2026

    @btipling
    OwnerAuthor

    Done by PR #937 (9736c37), which shipped plan #936.

    Next-turn seed is the structured truncated tool_result Blob behind meta.modelMessagesPointer, not formatPromptWithHistory crumbs. Parent #548 stays open (compaction / viewport / remaining A-rows).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions