Skip to content

fix(agent-core): trim orphan tool calls from projected context after resume#723

Open
meymchen wants to merge 1 commit into
MoonshotAI:mainfrom
meymchen:fix/orphan-tool-calls-after-resume
Open

fix(agent-core): trim orphan tool calls from projected context after resume#723
meymchen wants to merge 1 commit into
MoonshotAI:mainfrom
meymchen:fix/orphan-tool-calls-after-resume

Conversation

@meymchen

Copy link
Copy Markdown

Related Issue

Resolve #705
Resolve #701
Resolve #660
Resolve #269
Related #520

Problem

When a session crashes or is killed while a tool call is still in flight, the wire records contain an assistant message with tool_calls but no matching tool result. After kimi resume, ContextMemory.messages projected that malformed tail unchanged, so the next LLM request failed with:

400 an assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. The following tool_call_ids did not have response messages: ...

Manual compaction hit the same error because it also projected the open tool exchange into the compaction prompt.

What changed

  • ContextMemory.project() now applies trimTrailingOpenToolExchange() after micro-compaction, so every consumer of the projected history (regular turns, compaction, debug/export views) receives a provider-safe message list.
  • Added ContextMemory.cleanupOrphanedToolCalls() which truncates the trailing open exchange from _history, clears pendingToolResultIds / openSteps, and flushes any deferred messages once the exchange is closed.
  • Agent.resume() calls cleanupOrphanedToolCalls() immediately after records.replay() and before background.reconcile(), so background notifications and new user input are no longer deferred forever.
  • Added the context.cleanup_orphan_tool_calls wire record type so replay remains self-consistent.
  • Hardened trimTrailingOpenToolExchange() to leave histories that end in orphan tool messages (no preceding assistant) unchanged instead of returning [].

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked related issues, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

@changeset-bot

changeset-bot Bot commented Jun 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5b78bcd

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

This PR includes changesets to release 2 packages
Name Type
@moonshot-ai/agent-core Patch
@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

@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: 5b78bcda7e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +201 to +203
return trimTrailingOpenToolExchange(
project(this.agent.microCompaction.compact(messages)),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Clear pending tool state when compacting it away

When a full/manual compaction runs while the tail contains an unresolved or partially resolved tool exchange, this projection now removes that exchange from the compaction prompt, so applyCompaction can replace the whole prefix with a summary. However applyCompaction only clears openSteps and leaves pendingToolResultIds set; after this live compaction completes, any later appendUserMessage still sees an open tool exchange and goes into deferredMessages, so the next model request is built without the user's new prompt until a restart happens to run resume cleanup. Please clear the pending tool state when the open exchange is compacted away.

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

1 participant