Skip to content

session/update keeps arriving after session/prompt resolves — expose a session-level active/idle signal #864

Description

@xintaofei

Summary

claude-agent-acp resolves the session/prompt request on the SDK result
message (mapped to a stopReason such as end_turn). This is deliberate — it
unlocks the client for the next prompt without waiting for the trailing idle. But
the Claude session is frequently not idle at that moment: after a background
sub-agent's task-notification the model resumes and keeps producing, and
autonomous turns can fire later. The adapter forwards that activity as
session/update notifications on the same session, after the turn's stopReason
was already returned
, with no session-level signal telling the client the
session is still working.

Result: a client shows "idle/done" the instant end_turn lands, even though more
output is still streaming in; and a client that follows the ACP spec (updates must
be sent before the session/prompt response) is entitled to drop those
post-settlement updates and silently lose the output.

Root cause

ACP has a single signal — the pending session/prompt RPC — that conflates two
things a Claude session needs to keep separate:

  • "the turn is settled" → the user can send the next prompt;
  • "the session is idle" → nothing is being produced anymore.

For responsiveness the adapter settles the turn early (on result), so from the
client's side these two collapse into one, and the "still working" phase after
end_turn becomes invisible / unframed.

Already in the SDK stream, but not forwarded

session_state_changed (idle/active) is consumed only to backstop turn
settlement — the code comment notes idle carries no turn identity, and it can lag — and its active/idle state is never surfaced to the client (the only
client-facing effect on that branch is a title check). So the signal that would
let a client bracket the post-turn activity exists, but doesn't reach the wire.

Proposal (additive, backward compatible)

Keep the current result-based settlement (don't regress responsiveness), and
also forward session_state_changed as a session-level active/idle
notification distinct from the turn stopReason
— e.g. on _meta / an
ext-notification. That lets a client:

  • keep a "still working" indicator until the session is genuinely idle;
  • treat the span between end_turn and the next idle as a well-defined
    out-of-turn episode, so post-settlement session/updates can be rendered
    instead of dropped.

Optionally, tag out-of-turn session/updates with an origin marker (the adapter
already distinguishes task-notification-origin messages internally) so clients
can label them without inferring from timing.

Prior art

Zed's own client models "busy" as "the session/prompt RPC is still pending";
VS Code surfaces post-turn background completion via a system-initiated
notification. Both rely on a signal that survives the turn boundary — which is
exactly what's missing here.


Environment: @agentclientprotocol/claude-agent-acp@0.58.1 (behavior described
from src/acp-agent.ts; line numbers omitted since they drift). This is one of
two related issues; the companion (reliable background-task tracking) is linked in
a comment. Happy to send a PR if you're open to the direction.

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