Skip to content

Background sub-agents & shells aren't trackable to completion — forward background_tasks_changed / task_updated status #865

Description

@xintaofei

Summary

When the model launches a background Bash(run_in_background) or an async
sub-agent during a turn, that work keeps running after end_turn. The client has
no reliable ACP signal for (a) the set of background tasks currently outstanding,
or (b) when each one finishes. The launch tool result returns immediately with a
backgroundTaskId, so the corresponding ACP tool_call reaches completed at
launch, not at process exit — and nothing later flips it.

Already in the SDK stream, but dropped

The adapter consumes, but does not forward, the signals that carry this:

  • background_tasks_changed is explicitly dropped
    (case "background_tasks_changed": break;), with a comment describing it as
    exactly the UI signal — "the full live background-task set on every membership
    change"
    .
  • task_updated { patch.status: completed | failed | killed } and
    task_notification only prune the internal subagentParentToolUseIds
    attribution map; a terminal status is never translated into a tool_call_update
    for the client.

Impact

An ACP client that tries to reconstruct background-task state from the CLI's
on-disk JSONL transcript can't do it reliably:

  • a finished background shell record carries only backgroundTaskId +
    interruptedno exit code, no status;
  • an async sub-agent frequently has only a launch record with no matching
    <task-notification> (still running, or completed after the client stopped
    tailing).

So a "N background tasks running" indicator built on the transcript gets stuck
long after the work finished. The authoritative signal exists in the SDK stream
but never reaches the client. This affects any ACP client, not just ours.

Proposal (additive, backward compatible)

  1. Forward background_tasks_changed as the full live set (ids + kind +
    status), e.g. on _meta / an ext-notification. Because it's a level signal, a
    client just mirrors it — no counting, no settlement bookkeeping, no races.
  2. (Optional) Translate task_updated { status } into a tool_call_update
    (completed / failed) on the originating Task tool call, using the
    subagentParentToolUseIds map you already maintain, so per-sub-agent
    completion shows inline.

Both can be gated behind a _meta key / capability flag to keep the default wire
shape untouched.

Prior art

The SDK itself frames background_tasks_changed as a level signal "for surfaces";
VS Code delivers background-command completion with the real exit code. The data
is available — it just isn't forwarded.


Environment: @agentclientprotocol/claude-agent-acp@0.58.1 (behavior described
from src/acp-agent.ts; line numbers omitted since they drift). Companion issue:
#864 (session-level active/idle signal for post-turn activity). Happy to send a PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions