Repository navigation
Write negotiation as a standard tool-use agent: one toolkit, native function calling, MCP for host CLIs - #106
Merged
Merged
Conversation
Plan and design contract: one Write toolkit (recall, read, submit operations), a standard endpoint loop and a host-run loop over a per-batch MCP adapter, with no look budget or turn cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Write toolkit (recall, read, submit_operations) is the single definition of what a reasoner may do during a batch. The endpoint reasoner runs a standard function-calling loop over it; Claude Code and Codex run their own loop with the toolkit served by a per-batch stdio MCP adapter and their system prompt replaced. No look budget, turn cap, forced last turn or format retry. Hermes and Muse keep the text protocol, which now looks through the same toolkit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…se Write loop Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without --strict-mcp-config every MCP server in the user's Claude config loads into the headless reasoner. Observed live: their tool definitions alone came to ~230k tokens and every single-shot call (text-protocol Write, repair, Manage review) failed with 'Prompt is too long' on Haiku. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Store's write is split into prepare and persist; a check runs prepare and persists nothing, so its verdicts are the write's. submit_operations uses it, so refusals such as a valid_from later than the cited evidence or an in-place rewrite of a changed fact return to the model inside the same run instead of surfacing afterwards through the single-shot text repair. Found by the first paired live run: Haiku submissions passed the sheet check, then the Store refused them and the text repair could not recover. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every refused first submission in the OpenRouter probes (Haiku 4.5, Gemini 2.5 Flash) set valid_from to an announced future effective date, which the Store refuses. The schema now says what the Store accepts, so the model can get it right on the first submission instead of spending a turn on the refusal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reworded description did not change behaviour: in the follow-up OpenRouter probe Haiku 4.5 and Gemini 2.5 Flash still set valid_from to the announced future date in 2 of 3 runs each. Wording is an experiment variable; a change not shown to help does not stay. This reverts commit 34d0000. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Write negotiation now uses the industry-standard tool-use agent pattern — the way current LLMs are trained, evaluated and deployed, and the way to get the most out of them. Every major agent stack (the Anthropic and OpenAI tool-use APIs, Claude Code, Codex, the agent SDKs, MCP) has converged on the same mechanics: the model gets typed tools, decides what to call and how often, sees each result as a structured tool message, and stops when it judges it is done. Our Write loop worked against every one of those:
## Your reply. The model never saw a real assistant/tool turn structure.PR #105's benchmark located where the cost actually was. Keeping history in a Codex thread (
resume) cut client prompt characters by two thirds but model input tokens by under a tenth, and ran slower — the model sees the full history every turn whoever holds it, so "sharing context" is not the lever. The large gains in its app-server arm came from one process per batch and from removing the host's coding-agent prompt and tools. The standard pattern delivers exactly those, on both Claude Code and Codex, without depending on Codex's experimental app-server protocol. This PR therefore supersedes the session-reuse transports in #105.What changed
recall,read,submit_operations. It is the only definition of tool semantics — handle extension from tool results, active-only reads, submission validation. Tools never write the Store; an accepted submission goes through the single write path.tools, onetoolmessage per call, ends when the model stops calling tools.agent_memory.mcp.write_session); posture is tools-only (Claude:--strict-mcp-config,--tools "", only the toolkit allowed; Codex: user config and rules ignored, read-only sandbox, server required and pre-approved, local execution features disabled); the distillation instructions replace the host's system prompt.Storenow splits a write into prepare and persist;check_manyruns prepare and persists nothing, so its verdicts are the write's. Refusals come back as tool errors with per-operation reasons and the model resubmits; the latest accepted submission is the answer. No accepted submission → nothing written, backlog and Watermark untouched (same guarantee as Keep archived backlog pending when executor output is incomplete #80). Only sibling collisions or concurrent Store changes still reach the single-shot repair.--strict-mcp-config.docs/design/write-negotiation.md;host-adapters.md, design index, plan, README anddocs/TODO.mdupdated.Invariants kept: 2 (single write path), 5 (no model client in core — the endpoint loop lives in the executor), 8 (one toolkit; transports carry no tool semantics), 9 (Recall unchanged).
Verification
uv run ruff check .,uv run mypy,uv run pytest— 845 passed, coverage 91%.claudeandcodexbinaries start the real MCP adapter from their own flags and distill a batch end to end; red-team for injection through recalled bodies, handles named only inside recalled text, foreign e-mail addresses, malformed arguments, and an adapter that never writes the Store.valid_fromdates after the sheet check had accepted them). That is why the Store check is part of this PR.experiments/runs/native-tools-latency/in the main working tree (the experiment tree is untracked by design).text= the endpoint as a plain prompt-in/text-out callable):valid_fromto the announced Saturday, later than the cited messages (the Store refuses that). Rewording the schema description did not change it (second run, revision 34d0000), so that change was reverted. The second run also hit a pre-existing Store naming edge where Gemini looped seven refusals and ended without a submission — nothing was written and the backlog stayed, as designed. Both are tracked indocs/TODO.md.Trade-offs and follow-ups
docs/TODO.md.valid_from, and a supersede into another group with the same subject derives the predecessor's own name.🤖 Generated with Claude Code