Skip to content

fix(agent-core-v2): suppress task notifications before loop quiescence on remove - #3720

Merged
sailist merged 1 commit into
MoonshotAI:mainfrom
sailist:bug-195-09-10-task-settle-suppress-order
Sep 10, 2026
Merged

fix(agent-core-v2): suppress task notifications before loop quiescence on remove#3720
sailist merged 1 commit into
MoonshotAI:mainfrom
sailist:bug-195-09-10-task-settle-suppress-order

Conversation

@sailist

@sailist sailist commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Related Issue

Follow-up to #3717 (stacked on it — merge after that PR). Addresses a Codex review finding on #3717: terminal-notification suppression must happen before awaiting loop quiescence during agent removal.

Problem

#3717 moved stopAllOnExit behind the loop/prompt quiescence wait in AgentLifecycleService.remove() so that no new tasks can be registered while tasks are being stopped. But stopAllOnExit also suppresses terminal notifications for detached tasks, and delaying it moved that suppression behind the quiescence wait too. A detached task settling naturally in that window was not yet suppressed, so notifyAgentTask() called AgentLoopService.notify() — starting a spurious model turn (or keeping loop.settled() pending) in the middle of teardown.

What changed

  • IAgentTaskService gains suppressAllTerminalNotifications(), extracted from stopAllOnExit (which now delegates to it); behavior of stopAllOnExit itself is unchanged.
  • AgentLifecycleService.remove() calls suppressAllTerminalNotifications() before the quiescence loop, so tasks settling mid-teardown stay silent, while stopAllOnExit still runs after quiescence to stop everything that remains.
  • Tests: the existing remove test now asserts the ordering (suppress before prompt drain, stop after); FakeTaskService in the task-tools tests implements the new interface method. Test count unchanged.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue (external PRs: the issue must have a maintainer's /approve).
  • 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 Sep 10, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 03606ea

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@sailist
sailist force-pushed the bug-195-09-10-task-settle-suppress-order branch from d43dfb4 to 5008638 Compare September 10, 2026 14:30
@pkg-pr-new

pkg-pr-new Bot commented Sep 10, 2026

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

commit: 03606ea

@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: d43dfb42e6

ℹ️ 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".

managed.closing = true;
this.onWillCloseEmitter.fire(agent);
const handle = managed.handle;
await handle.accessor.get(IAgentTaskService).suppressAllTerminalNotifications();

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 Add the required user-facing changeset

This fixes observable CLI behavior by preventing background-task notifications from initiating work while an agent is being removed, but the commit contains no .changeset/ entry, so the fix will be omitted from the user-facing release changelog. Add a patch changeset for @moonshot-ai/kimi-code as required by the repository workflow.

AGENTS.md reference: AGENTS.md:L86-L86

Useful? React with 👍 / 👎.

@sailist
sailist force-pushed the bug-195-09-10-task-settle-suppress-order branch from 5008638 to ccbfe69 Compare September 10, 2026 14:34

@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: ccbfe6923d

ℹ️ 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 784 to 786
const active = this.list(true);
await Promise.allSettled(
active

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 Make suppression cover tasks created during teardown

When an in-flight tool registers a detached task, or a foreground task detaches, after this one-time active/detached snapshot and then settles before the post-quiescence stop, it remains unsuppressed. The later stopAllOnExit() scan cannot repair this because list(true) excludes the now-terminal task, so fireTerminalEffects() can still call loop.notify() during remove(), recreating the spurious turn or prolonged quiescence wait this change is intended to prevent. Make teardown suppression sticky for future registrations/detaches until disposal, or apply it atomically when tasks settle during teardown.

Useful? React with 👍 / 👎.

@sailist
sailist force-pushed the bug-195-09-10-task-settle-suppress-order branch from ccbfe69 to d393414 Compare September 10, 2026 14:41

@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: d3934142d1

ℹ️ 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 +1032 to +1034
if (this.exitSuppressionArmed && this.isDetached(entry)) {
entry.terminalNotificationSuppressed = true;
}

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 Recheck suppression after the persistence await

When a detached task enters settleTask() immediately before remove(), it can evaluate this new condition while exitSuppressionArmed is false and then yield in await this.persistLive(entry). Removal can arm suppression during that await, yet settlement resumes at fireTerminalEffects() with terminalNotificationSuppressed unset; the notification builder also does not consult exitSuppressionArmed, so loop.notify() can still initiate work during teardown. The new sticky flag fixes tasks that begin settlement later, but it must also be checked after the await or when terminal effects are fired.

Useful? React with 👍 / 👎.

@sailist
sailist force-pushed the bug-195-09-10-task-settle-suppress-order branch from d393414 to fe43193 Compare September 10, 2026 14:46

@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: fe43193ea8

ℹ️ 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 +783 to +785
async suppressAllTerminalNotifications(): Promise<void> {
if (this.keepAliveOnExit()) return;
this.exitSuppressionArmed = true;

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 Honor suppression in in-flight notification builds

When a detached task finishes just before remove() and notifyAgentTask() is already awaiting its output snapshot, the task's terminal state was recorded before this flag was armed. After the await, buildAgentTaskNotificationContext() checks only the task/ghost terminalNotificationSuppressed field, not exitSuppressionArmed, so it can still call loop.notify() and start a spurious turn or keep loop.settled() pending during teardown. Make the global flag visible to notification construction as well as settlement.

Useful? React with 👍 / 👎.

@sailist
sailist force-pushed the bug-195-09-10-task-settle-suppress-order branch from fe43193 to ddc8d4b Compare September 10, 2026 14:53

@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: ddc8d4be04

ℹ️ 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".


private isTerminalNotificationSuppressed(taskId: string): boolean {
return (
this.exitSuppressionArmed ||

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 Recheck suppression before calling loop.notify

When remove() starts in the microtask between buildAgentTaskNotificationContext() returning and notifyAgentTask() resuming from its await, this new global check has already run, while lifecycleActive() remains true until killSpace(). The caller can therefore still reach loop.notify(), allowing the non-turn-scoped task notification to start a teardown turn or keep loop.settled() pending. Fresh evidence after the earlier review is the remaining post-build await boundary at lines 1101–1106, which has no suppression recheck; consult isTerminalNotificationSuppressed() again immediately before notifying the loop.

Useful? React with 👍 / 👎.

@sailist
sailist force-pushed the bug-195-09-10-task-settle-suppress-order branch from ddc8d4b to eff4bd4 Compare September 10, 2026 14:59

@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: eff4bd4476

ℹ️ 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".


async suppressAllTerminalNotifications(): Promise<void> {
if (this.keepAliveOnExit()) return;
this.exitSuppressionArmed = true;

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 Drop already-queued task notifications when suppressing

If a detached task has already reached loop.notify() immediately before remove() arms suppression, its handle remains in pendingNotificationRequests; this method only blocks future notification construction and never drops that queued request. AgentLifecycleService.remove() cancels prompt reservations via pendingPromptIds, but task notifications are loop nudges, and AgentLoopService.settled() continues waiting for undropped nudges, so the notification can still start a turn and delay teardown. Drop the pending task-notification handles when arming suppression.

Useful? React with 👍 / 👎.

@sailist
sailist force-pushed the bug-195-09-10-task-settle-suppress-order branch from eff4bd4 to f3ca7b1 Compare September 10, 2026 15:06

@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: f3ca7b1dac

ℹ️ 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".

}

async suppressAllTerminalNotifications(): Promise<void> {
if (this.keepAliveOnExit()) return;

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 Suppress notifications even when tasks stay alive

When task.keepAliveOnExit is enabled, this early return leaves terminal notifications unsuppressed even though AgentLifecycleService.remove() calls this method specifically before its quiescence loop. If a retained detached task settles while remove() is awaiting prompt or loop drainage, it can still call loop.notify(), start an unwanted model turn, and delay teardown; keeping the process alive should skip stopping it, not skip teardown-time notification suppression.

Useful? React with 👍 / 👎.

@sailist
sailist force-pushed the bug-195-09-10-task-settle-suppress-order branch from f3ca7b1 to 03606ea Compare September 10, 2026 15:12
@sailist
sailist merged commit 221d112 into MoonshotAI:main Sep 10, 2026
15 checks passed
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