Skip to content

fix(core): refuse a compaction reply that calls a tool - #49

Open
TaewoooPark wants to merge 1 commit into
mainfrom
fix/compaction-tool-call
Open

TaewoooPark wants to merge 1 commit into
mainfrom
fix/compaction-tool-call

Conversation

@TaewoooPark

Copy link
Copy Markdown
Owner

Problem

When the context reaches compactAt, summarizeTranscript asks the model for a handoff summary and tells it not to call a tool. A model that is in the middle of work does not always stop. It may answer the way it answers any turn: one line of intent, then a tool call.

content:    "Let me check the current state of the working tree first."
tool_calls: [bash {"command": "git status"}]

The tool call is dropped. The line that came with it is not empty, so it passes the "no summary" check and becomes the summary. buildCompactedHistory then replaces the transcript with the person's messages and that one sentence. The next turn starts with no record of what was done, decided or planned, and the model has to rebuild all of it from the working tree.

The docstring already states the intent: a model that answers with a tool call has not written a summary, and the caller should treat that as a failed compaction. The code enforced this only when nothing was left after removing inline <tool_call> blocks, and it did not look at tool calls the server had already parsed into toolCalls.

Change

  • fix(core): refuse a compaction reply that calls a tool
    • A reply with parsed toolCalls, or with an inline <tool_call> block, now throws: "the model called a tool instead of writing a summary".
    • The loop already handles a failed compaction. It keeps the full transcript, emits a compaction failed … continuing with the full transcript notice, and compacts again once the next request crosses the limit.
    • Nothing else changes: the prompt, the request, what is kept, and how a valid summary is used.

Why this rule

What it costs. A reply that wrote a real summary and then also called a tool is now refused too, where before its text was kept. That costs one more summary request on a later turn, and the transcript stays whole in the meantime. Taking a one-line preamble as the summary costs the whole transcript. Compaction starts at compactAt (below 1), so the window has room for the turns until the next attempt.

Alternatives considered:

  • A minimum length. Any threshold is arbitrary, and a short session can have a short, valid summary.
  • Checking for the requested section headings. This depends on how a model formats its summary.
  • Retrying at once without tools. The tools are registered on purpose, because the template drops reasoning otherwise.
  • A retry loop inside summarizeTranscript. It adds code for what the loop's next-turn retry already does.

Tests

pnpm typecheck, pnpm test (1677 passed), pnpm lint:tools and pnpm build all pass.

Both tests below fail on main:

  • summarizeTranscript refuses an empty reply, as before. It now also refuses an inline <tool_call> reply and a reply with parsed toolCalls. This replaces the old expectation that a tool call is dropped and the text kept.
  • In the loop, a reply that starts working instead of summarising leaves the transcript (and its tool result) in place. The next turn compacts with the real summary.

The summary request tells the model not to call a tool. A model that keeps
working instead answers with one line of intent and a call, such as "Let me
check the current state first." With the call stripped, that line was not
empty, so it became the summary and replaced the whole transcript: the next
turn started with the task and one sentence.

A reply that calls a tool, as a parsed tool call or as an inline <tool_call>
block, is now a failed compaction. The loop already handles that case: it
keeps the full transcript, warns, and compacts again once the next request
crosses the limit.
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