Repository navigation
ACP: turns started by a background task outcome emit no state_update (no running, no idle) #557
Description
Summary
When a background task (e.g. bash with runInBackground: true) completes while the session is idle, the core correctly starts a new turn and the model answers unprompted. Over ACP, however, that turn has no busy cycle: no state_update {"state":"running"} before it and no state_update {"state":"idle"} after it. An ACP client sees assistant content arrive in an idle session and has no terminator telling it when that turn is over.
Environment
- aether 0.9.10 (Homebrew),
aether acp, ACP v2 - macOS (Apple Silicon)
- Model:
deepseek:deepseek-v4-pro, modeBuildfrom the batteries-included preset - Raw JSON-RPC client (no SDK), every frame logged with a timestamp
Reproduction
initialize→session/new→session/set_config_option(mode=Build)session/prompt: "Use the bash tool with run_in_background=true to run exactly:sleep 25; echo LATE_TASK_DONE. Do not wait for it. Immediately end your turn by replying only: started"- Keep observing for 90 s after the turn's
idle.
Observed frames (seconds since start):
3.543 agent_message "started"
3.543 state_update {"state":"idle","stopReason":"end_turn"} <- turn 0 ends, task still running
4.196 tool_call_update {status: in_progress} (1 Hz heartbeat while idle)
27.245 tool_call_update {status: completed, "LATE_TASK_DONE ..."}
28.148 agent_message_chunk "LATE_TASK_DONE"
28.174 agent_message "LATE_TASK_DONE"
28.174 _aether/session_usage {agent_name: "Build", ...} <- an LLM call ran
(no state_update for the rest of the 90 s window)
Expected
The task-initiated turn is framed like any other turn:
27.245 tool_call_update {status: completed}
state_update {"state":"running"}
28.174 agent_message "LATE_TASK_DONE"
state_update {"state":"idle","stopReason":"end_turn"}
Second consequence: a prompt in that window absorbs the unprompted reply
Sending session/prompt at the moment the task's completed frame arrives (sleep 15 variant):
17.368 tool_call_update {status: completed}
17.368 -> session/prompt "Reply only with: second"
17.368 <- {"messageId": ...} accepted, because the actor is still Idle
17.369 state_update {"state":"running"}
18.560 agent_message "LATE_TASK_DONE" <- the task-initiated reply ...
19.653 agent_message "second" <- ... lands in the prompted turn
19.653 state_update {"state":"idle","stopReason":"end_turn"}
The client can't tell that LATE_TASK_DONE was not a response to its prompt. The window is the full duration of the follow-up LLM call, not a narrow race. With the fix below, the session would visibly be running, and the prompt would get the same -32600 that any prompt during a running turn already gets, which a client can handle.
Cause
- The core starts the turn and emits
TurnEvent::Startedfor task outcomes too:begin_turnviaenqueue_task_outcome(aether-core/src/core/agent.rs#L320-L337). - The ACP session actor only enters
TurnState::Running(and sendsrunning) fromaccept_prompt(aether-cli/src/acp/session/actor.rs#L534-L555). - In the agent-event loop, a turn end only triggers
finish_turnwhile the actor isRunning(actor.rs#L323-L330), so the task-initiated turn'sEndedis dropped as well.
Suggested fix
In the runtime.agent_rx branch of the actor loop, when the actor is Idle and the event is AgentEvent::Turn(TurnEvent::Started { .. }), switch to TurnState::Running and send state_update running. The existing turn_outcome() check then emits the matching idle through finish_turn. Started events for prompted turns arrive while the actor is already Running, so they would not be affected.
One edge case to decide on: a task outcome that starts a turn while the actor is in Preparing for an incoming prompt.
I'm happy to send a PR for this if you agree with the approach.
Context
I maintain a Rust crate that wraps several coding-agent CLIs over ACP behind one interface, and I'm evaluating Aether for it. Following up on background work is exactly what other harnesses get wrong. Aether's core already does it right, and this issue is only about the ACP framing.
Activity
jcarver989 commentedon Oct 2, 2026
Hey @MartinSchlott thanks for the bug report!
I think this one should be a pretty easy fix, and should be able to get it into the next Aether release for you (likely on or before Monday).
jcarver989 commentedon Oct 3, 2026
#559 Implements a fix for this and will go out with the next Aether release.
One important thing to note is that Aether's behavior within the
corecrate has long been to queue incoming prompts while the agent is busy and insert them into context at the next available opportunity. However, this was not exposed via ACP, or available in the TUI ACP client (wisp) that ships withaether-cli-- now it is.So for your ACP client you'll have a choice of disallowing sending prompts while the agent is busy, or sending them through. In the event you send them through, they'll be queued
MartinSchlott commentedon Oct 3, 2026
Thx for the swift response. BTW Your project was in a recommendation of ChatGPT regarding OpenCode alternatives in Rust. Congratulations, it is really a cool project.
jcarver989 commentedon Oct 3, 2026
Thanks! FYI this fix is included in this release: https://github.com/contextbridge/aether/releases/tag/aether-agent-cli-v0.9.11