Skip to content

feat: detach foreground tasks to background#821

Open
kermanx wants to merge 6 commits into
mainfrom
xtr/foreground-task-detach
Open

feat: detach foreground tasks to background#821
kermanx wants to merge 6 commits into
mainfrom
xtr/foreground-task-detach

Conversation

@kermanx

@kermanx kermanx commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Related Issue

No linked issue. This addresses a maintainer-requested core/API capability for moving foreground shell and subagent tasks into the background.

Problem

Foreground Bash and subagent tasks are managed as blocking tool calls. If a model starts one without run_in_background, there was no core RPC path for a client to detach that already-running foreground task and continue treating it as a background task.

What changed

  • Added a background-task detach path through the agent-core RPC/API surfaces and SDK bindings.
  • Unified foreground and background Bash/subagent execution under BackgroundManager, so tasks can start foreground and later detach without switching lifecycle owners.
  • Moved task-specific output, abort, detach, timeout, and notification behavior into the manager/task boundaries with focused coverage for Bash, subagent, RPC events, persistence, and task tools.
  • Added a patch changeset for the affected packages.

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.

Verification:

  • pnpm --filter @moonshot-ai/agent-core typecheck
  • pnpm --filter @moonshot-ai/agent-core exec vitest run test/tools/background/task-tools.test.ts
  • pnpm --filter @moonshot-ai/agent-core exec vitest run test/tools/bash.test.ts test/tools/shell-quoting.test.ts test/tools/shell-cancel.test.ts
  • pnpm --filter @moonshot-ai/agent-core exec vitest run test/agent/background/agent-timeout.test.ts test/agent/background/manager.test.ts test/agent/background/rpc-events.test.ts test/tools/background/task-tools.test.ts test/tools/agent.test.ts test/tools/builtin-current.test.ts test/session/lifecycle-hooks.test.ts test/agent/bg-idle-notification-repro.test.ts test/agent/background/ids.test.ts test/tools/bash.test.ts test/tools/bash-env.test.ts test/tools/shell-cancel.test.ts test/tools/shell-quoting.test.ts
  • git diff HEAD --check
  • Read-only diff audit found no internal identifier leaks; one Bash foreground failure handling finding was fixed before this PR.

@changeset-bot

changeset-bot Bot commented Jun 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: bb71ceb

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

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

@pkg-pr-new

pkg-pr-new Bot commented Jun 16, 2026

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

commit: bb71ceb

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

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

outcome: Outcome,
): Promise<Outcome> {
if (timeoutMs === undefined || timeoutMs <= 0) return NEVER;
return sleep(timeoutMs).then(() => outcome);

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 timeout timers after lifecycle races

When a task finishes before its deadline, this sleep() promise is the losing branch of Promise.race (for example in runTaskLifecycle() and wait()), but the underlying timeout is neither cleared nor unref()ed. A quick foreground Bash call now leaves the default 60s timer alive, and a quick background Bash call leaves the default 10-minute timer alive, so short-lived SDK/CLI processes can hang until those deadlines even though the task is already terminal; use a cancelable/unref'd timeout or clear it in the race cleanup.

Useful? React with 👍 / 👎.

@kermanx

kermanx commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator Author

@codex

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

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

};
stream.once('end', done);
stream.once('close', done);
stream.once('error', done);

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 Surface process stream errors instead of completing

When stdout or stderr emits an error while the process itself exits successfully (for example an SSH/channel read failure), this handler resolves the stream promise and ProcessBackgroundTask.start() can report the task as completed with partial or missing output. The foreground Bash path now relies on this helper too, so users can see a successful command even though output capture failed; reject on non-abort stream errors and only suppress the expected errors from cancellation/stream destruction.

Useful? React with 👍 / 👎.

Comment on lines +34 to +35
const requestAbort = (): void => {
this.abort?.();
this.abortController.abort();

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 Preserve abort reasons when stopping subagents

For a foreground subagent cancelled by the user after registration, the manager aborts the sink signal but this callback aborts the subagent controller with no reason, so SessionSubagentHost and child tool calls see a generic AbortError instead of the original user-cancellation reason. That regresses the existing isUserCancellation(signal.reason) handling used to report deliberate interrupts and fire interrupt hooks; forward the sink/original abort reason when aborting the subagent controller.

Useful? React with 👍 / 👎.

@kermanx

kermanx commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator Author

@codex

@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: 64ad9611a2

ℹ️ 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 +253 to +257
taskId = this.backgroundManager.registerTask(
new ProcessBackgroundTask(proc, command, description, onProcessOutput),
{
detached: startsInBackground,
timeoutMs,

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 Avoid persisting hidden foreground command logs

For normal foreground Bash calls (run_in_background omitted), startsInBackground is false but the command is still registered with BackgroundManager; that manager immediately persists task metadata and ProcessBackgroundTask appends the complete stdout/stderr stream to tasks/<id>/output.log. Since terminal detached:false tasks are later filtered out of task listings, ordinary foreground commands can leave undiscoverable full-output logs in the session directory, which is a disk-growth regression for large or frequent foreground commands. Please skip/delete persistence for tasks that finish without being detached, or only start writing the task log once detach happens.

Useful? React with 👍 / 👎.

kermanx added 2 commits June 17, 2026 12:48
Foreground Bash commands (run_in_background omitted) were registered with
the BackgroundManager as detached:false, which immediately persisted task
metadata and streamed the full stdout/stderr to tasks/<id>/output.log. Those
terminal detached:false tasks are filtered out of every listing, so ordinary
foreground commands left undiscoverable full-output logs in the session
directory — a disk-growth regression for large or frequent commands.

Foreground tasks now keep their output in memory and touch disk only once
they actually detach (or spill past a 1 MiB in-memory bound). On detach the
buffered pre-detach output is flushed first so output.log stays the complete,
in-order record. A foreground task that finishes without detaching or
spilling writes nothing to disk. Spilled/detached records are persisted and
kept as before — not deleted.
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