Skip to content

refactor: move tool-result metadata into a structured note side channel#1437

Merged
RealKai42 merged 9 commits into
mainfrom
kaiyi/biarritz-v2
Jul 7, 2026
Merged

refactor: move tool-result metadata into a structured note side channel#1437
RealKai42 merged 9 commits into
mainfrom
kaiyi/biarritz-v2

Conversation

@RealKai42

@RealKai42 RealKai42 commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Tool-produced model-facing metadata (ReadMediaFile summaries, Read status lines, MCP image-compression captions) was baked into tool output as <system> text. That in-band scheme leaked raw markup into every UI, forced three separately-maintained stripping/normalization copies (live context, transcript, vis) that had silently drifted apart, and made it impossible to distinguish tool metadata from user data that merely contains the literal tag.

This PR separates facts from views:

  • ExecutableToolResult gains note: content routed to the model but never to UIs. Formatting is the producing tool's choice; the channel itself adds nothing.
  • Records and history store facts: raw output plus structured isError/note.
  • The model view is rendered exactly once, at the LLM projection boundary, by renderToolResultForModel: plain-text ERROR: prefix for failures, empty-output placeholder, note appended verbatim. The transcript copy is deleted and vis's model view now calls the same function, fixing their drifted empty-output checks.
  • ReadMediaFile / Read / MCP captions write note; tool outputs stay pure data. Text-only results keep a single text part (note joined with a newline) so provider tool content stays a plain string across OpenAI-compatible backends.
  • All UI-side <system> stripping is removed (stripSystemTags and call sites deleted). Failed tools show their own error text with the structured isError flag. User data containing a literal <system> tag is no longer eaten from display.

Legacy records

Records written before this change keep their inline <system> text and replay verbatim — no wire migration, no version bump (the note field is additive in both directions). The model view of old sessions stays byte-identical to what the model originally saw; UIs show the legacy text as-is. This deliberately avoids a migration that would have to classify arbitrary historical text as tool metadata vs. user data quoting it.

Behavior changes

  • Model: identical metadata content; notes now trail the tool result instead of leading it, with provider-neutral wording ("the attached image"). Error/empty statuses are plain text without any tag.
  • UIs: failed tools show the tool's own error output styled via isError, without the model-facing scaffold line. Sessions recorded before this change keep showing the old inline text.
  • The unreleased media-chip original-dimensions display is withdrawn (it parsed the metadata text; a structured field can bring it back later).

Testing

  • agent-core: 3494 passed (unit matrix for the render function, e2e note round-trip through normalization/records, resume/live parity incl. verbatim legacy replay, blobref round-trip)
  • vis-server: 140 passed; kimi-code: 2189 passed (the 6 failures in test/cli/update/preflight.test.ts rollout gating are pre-existing on a pristine tree and unrelated)
  • Per-provider serialization of the new tool-message shape verified against captured request bodies (Anthropic, Kimi, OpenAI legacy/responses, Google GenAI)

RealKai42 added 3 commits July 6, 2026 15:49
…and web UIs

Tool results carry <system> blocks as side-channel notes for the model (ReadMediaFile summaries, Read status, MCP image captions, error/empty sentinels). Keep them in history for the model, but strip them at every core-to-UI boundary so they no longer render as plain text. vis is intentionally left untouched to preserve the model's-eye view for debugging.
…<system> tags

Unwrap the tool error/empty sentinels (<system>ERROR: ...</system>, <system>Tool output is empty.</system>) instead of deleting them: keep the human-readable text and drop only the tags. Otherwise a failed or empty tool result rendered as a blank output, indistinguishable from a rendering bug. The model still reads the wrapped form in history.
Tool-produced model-facing metadata (ReadMediaFile summaries, Read status
lines, MCP image-compression captions) was baked into tool output as
<system> text, so every UI had to strip it back out and three copies of
the model-view normalization had silently drifted apart.

- ExecutableToolResult gains `note`: content rendered to the model but
  never to UIs; records and history now store the raw output plus the
  structured isError/note fields
- the model view is rendered exactly once at the LLM projection boundary
  by renderToolResultForModel; the transcript and vis hand-copies are
  deleted (vis now calls the same function for its model view, fixing
  their drifted empty-output checks)
- ReadMediaFile / Read / MCP captions write `note`; tool outputs stay
  pure data, and text-only results keep a single text part (note joined
  with a newline) so provider tool content stays a plain string
- all UI-side <system> stripping is removed; failed tools show their own
  error text with the structured isError flag
- wire protocol 1.4 -> 1.5 migrates existing records' tool-produced
  <system> blocks into `note` on resume
@changeset-bot

changeset-bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ca5f65f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jul 6, 2026

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@moonshot-ai/kimi-code@ca5f65f
npx https://pkg.pr.new/@moonshot-ai/kimi-code@ca5f65f

commit: ca5f65f

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5cb44dba41

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/agent-core/src/agent/records/migration/v1.5.ts Outdated
Comment thread packages/agent-core/src/agent/records/migration/v1.5.ts Outdated
Comment thread packages/agent-core/src/mcp/output.ts Outdated
- "The attached image was downsampled" replaces directional wording that
  depended on provider serialization order (inline media vs flatten-and-
  re-attach)
- drop the 1.4 -> 1.5 wire migration: legacy records replay verbatim, so
  the model view of old sessions stays byte-identical to what the model
  originally saw and UIs show the legacy <system> text as-is; this also
  removes the risk of the migration misclassifying user data that quotes
  tool metadata, and the additive note field needs no version bump
- pass optional result fields as undefined instead of conditional spreads
  (repo convention)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 49eeb40238

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/agent-core/src/loop/types.ts
Comment thread packages/agent-core/src/mcp/output.ts Outdated
…system-tag guard

- normalizeToolResult now keeps a note only when it is a non-empty string:
  tools and finalize hooks are arbitrary JS, and a malformed note (null,
  number, object) would previously persist into the record and crash every
  subsequent LLM projection of the session. Everything downstream now
  trusts note to be string | undefined.
- the TUI tool body suppression matches the full <system-reminder> tag
  instead of any <system prefix: reminder piggy-backing stays hidden,
  while real output that merely starts with a literal <system> tag (file
  contents, MCP text) stays visible, covered through the real
  ToolCallComponent path.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8173a1ccb2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/agent-core/src/tools/builtin/file/read.ts
RealKai42 added 3 commits July 7, 2026 00:55
…em from text

compressImageContentParts now returns { parts, captions } — captions come
back from the compressor as structured data and are never inserted into
the parts, so the MCP pipeline no longer pattern-matches text to move
them into the note side channel. Tool output that merely quotes a
caption (a doc, a log, a test fixture) stays verbatim in the output.
Also corrects the stale claim that prompt ingestion uses this helper
(it compresses per image while constructing the part).
…sedContentParts

The package-root comment still described compressImageContentParts as the
input-stage helper every ingestion site calls; prompt ingestion compresses
per image with compressBase64ForModel / compressImageForModel, and the MCP
pipeline is the walker's only caller. Also export the walker's
CompressedContentParts return type so public-API consumers can name it.
…rness verdicts from tool output

The error/empty status text is model-only after the note refactor (UIs
render the raw output and style failures via the structured isError
flag), so the earlier plain-text wording served no remaining audience.
Wrapping the statuses in <system> gives every piece of system-generated
text inside a tool result the same marker:

- failed calls get '<system>ERROR: Tool execution failed.</system>'
  unconditionally — the ERROR:-prefix guard is removed, so the harness
  verdict can no longer be confused with tool output that happens to
  start with error-like text
- empty outputs render as '<system>Tool output is empty.</system>'; the
  plain placeholder the loop layer bakes into records is still
  recognized and upgraded at projection time

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4ec78e71cb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/agent-core/src/services/message/transcript.ts Outdated
@RealKai42

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ca5f65fbf0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +534 to +535
output: input.renderedLines.join('\n'),
note: `<system>${this.finishMessage(input)}</system>`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve empty Read results without placeholder

When Read returns zero rendered lines (empty file, EOF range, or empty tail), this now emits output: '' with the status in note, but the loop normalizer still rewrites empty string outputs to the literal Tool output is empty. before recording/sending events. That means users see a generic placeholder and the model receives both the placeholder and the note, even though the note already makes the projected tool message non-empty; handle non-empty note as sufficient content or avoid returning an empty raw output here.

Useful? React with 👍 / 👎.

@RealKai42 RealKai42 merged commit 743f66e into main Jul 7, 2026
10 checks passed
@RealKai42 RealKai42 deleted the kaiyi/biarritz-v2 branch July 7, 2026 03:40
@github-actions github-actions Bot mentioned this pull request Jul 7, 2026
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