Skip to content

fix(agent-core-v2): settle killed subagent runs only after the child loop goes idle - #1759

Merged
sailist merged 1 commit into
MoonshotAI:mainfrom
sailist:fix/subagent-kill-settle-race
Jul 16, 2026
Merged

fix(agent-core-v2): settle killed subagent runs only after the child loop goes idle#1759
sailist merged 1 commit into
MoonshotAI:mainfrom
sailist:fix/subagent-kill-settle-race

Conversation

@sailist

@sailist sailist commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Related Issue

No linked issue — the problem is explained below.

Problem

Manually stopping a background subagent and resuming it right after (e.g. the main agent auto-resuming on the task.killed notification) could fail with an "already running and cannot run concurrently" error. The killed task used to settle — and send its notification — the moment the abort landed, while the child agent loop was still unwinding (runTurn had not yet released the active turn). The resume guard (ensureOwnedIdleSubagent, which reads loop.status().state) therefore still saw the loop as running and rejected the resume.

What changed

In packages/agent-core-v2/src/session/subagent/runAgentTurn.ts:

  • awaitRun now cancels by turn id (loop.cancel(turn.id, reason)) instead of loop.cancel(undefined, …), so an abort also reaches this run's turn while it is still queued, not just the loop's currently active turn.
  • awaitTurn no longer races turn.result against the abort signal. On abort it cancels the turn first, then waits for the turn's terminal result. The loop only goes idle once runTurn unwinds (releaseActiveTurn), so task settlement, the task.killed notification, and the resume guard no longer observe the run as finished before that. A turn that never responds to the cancel stays bounded by the task layer's SIGTERM grace, not by an early rejection here.
  • After the wait, the original abort reason is rethrown (signal.throwIfAborted()) so consumers that match the reason by identity (isAbortError / error === sink.signal.reason) still tell a kill apart from a failure.
  • Removed the now-unused abortPromise helper.

Regression coverage in test/agent/task/idle-notification-repro.test.ts: a new "kill ordering vs child loop unwind" test with a child agent whose in-flight LLM call only rejects 200ms after the abort lands. It asserts that stop() settles the task as killed only after the child loop reports idle, and that the task.killed notification then reaches the main agent.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, 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.

…loop goes idle

- awaitRun: cancel by turn id so aborts also reach turns still queued, not just the active turn
- awaitTurn: cancel first, then wait for turn.result instead of racing against the abort signal, so task settlement, the task.killed notification, and the resume guard never observe the run as finished while the loop is still unwinding
- rethrow the original abort reason so consumers keep matching it by identity
- add regression test for the manual stop -> auto-resume "already running" race
@changeset-bot

changeset-bot Bot commented Jul 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 4572bb8

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 16, 2026

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

commit: 4572bb8

@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: 4572bb8a8f

ℹ️ 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 +110 to +114
/**
* Await a turn's terminal result, cancelling it first when the run aborts.
*
* This deliberately does NOT race `turn.result` against the abort signal:
* the loop only goes idle once `runTurn` unwinds (`releaseActiveTurn`), and

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 Move helper rationale into the file header

packages/agent-core-v2/AGENTS.md says comments in this package must live solely in the top-of-file /** */ block and never beside functions, methods, or statements. This new helper-level JSDoc, along with the inline cancellation notes added in the same function, violates that package rule, so please fold the rationale into the file header or remove it.

Useful? React with 👍 / 👎.

@sailist
sailist merged commit 9e3e670 into MoonshotAI:main Jul 16, 2026
15 checks passed
This was referenced Jul 16, 2026
ywh114 pushed a commit to ywh114/kimi-code that referenced this pull request Jul 19, 2026
…loop goes idle (MoonshotAI#1759)

- awaitRun: cancel by turn id so aborts also reach turns still queued, not just the active turn
- awaitTurn: cancel first, then wait for turn.result instead of racing against the abort signal, so task settlement, the task.killed notification, and the resume guard never observe the run as finished while the loop is still unwinding
- rethrow the original abort reason so consumers keep matching it by identity
- add regression test for the manual stop -> auto-resume "already running" race
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