Summary
ACP sessions spawned via sessions_spawn(runtime: "acp") complete successfully but never trigger the subagent_ended hook or the subagent announce pipeline. The orchestrator/parent session never receives a completion announcement, creating a silent stall.
Expected Behavior
When an ACP session finishes, the parent session should receive a completion announcement (same as embedded/Pi subagent runs), and the subagent_ended hook should fire.
Actual Behavior
- ACP session completes and emits
done event internally
- Stream log shows
:start → :stall (60s idle) — but no lifecycle event reaches the agent event bus
subagent_ended hook never fires
- Announce pipeline never triggers
- Parent session waiting via
sessions_yield is never woken
Root Cause (from source investigation)
The embedded Pi runtime (pi-embedded-subscribe.handlers.lifecycle.ts) calls emitAgentEvent({ stream: "lifecycle", data: { phase: "end" } }) on completion, which the subagent registry listener picks up → completeSubagentRun → emitSubagentEndedHookForRun → announce flow.
The ACPX plugin runtime (runtime.ts) yields { type: "done" } to its caller but this is not bridged into the gateway's emitAgentEvent bus. ACPX manages its own lifecycle internally (service.ts:lifecycleRevision) without emitting to the shared agent event system.
So the announce pipeline is architecturally sound — it just never receives the signal from ACP runs.
Proposed Fix
When an ACP run yields { type: "done" }, the gateway's ACP handler should call:
emitAgentEvent({
runId,
stream: "lifecycle",
data: { phase: "end", endedAt: Date.now() }
});
This would make all existing infrastructure work: subagent_ended hook fires, announce pipeline delivers results to requester session, sessions_yield wakes up.
Workaround
Currently using a background stream-log watcher that polls .acp-stream.jsonl files for :stall events every 10s and cross-references active-tasks.json for git branch changes. Works but adds ~70s latency (60s stall + 10s scan).
Environment
- OpenClaw v2026.3.13 (commit f6e5b67)
- acpx v0.1.16
- ACP backend: claude (via Anthropic proxy)
Relevant Source Locations
auth-profiles-*.js:92532 — onAgentEvent listener for lifecycle events
auth-profiles-*.js:92344 — completeSubagentRun
auth-profiles-*.js:92375 — emitSubagentEndedHookForRun
- ACPX
runtime.ts:409 — yields { type: "done" } (not bridged to agent event bus)
- ACPX
service.ts:39-100 — internal lifecycle management
- Pi runtime:
pi-embedded-subscribe.handlers.lifecycle.ts — correctly emits lifecycle events
Summary
ACP sessions spawned via
sessions_spawn(runtime: "acp")complete successfully but never trigger thesubagent_endedhook or the subagent announce pipeline. The orchestrator/parent session never receives a completion announcement, creating a silent stall.Expected Behavior
When an ACP session finishes, the parent session should receive a completion announcement (same as embedded/Pi subagent runs), and the
subagent_endedhook should fire.Actual Behavior
doneevent internally:start→:stall(60s idle) — but no lifecycle event reaches the agent event bussubagent_endedhook never firessessions_yieldis never wokenRoot Cause (from source investigation)
The embedded Pi runtime (
pi-embedded-subscribe.handlers.lifecycle.ts) callsemitAgentEvent({ stream: "lifecycle", data: { phase: "end" } })on completion, which the subagent registry listener picks up →completeSubagentRun→emitSubagentEndedHookForRun→ announce flow.The ACPX plugin runtime (
runtime.ts) yields{ type: "done" }to its caller but this is not bridged into the gateway'semitAgentEventbus. ACPX manages its own lifecycle internally (service.ts:lifecycleRevision) without emitting to the shared agent event system.So the announce pipeline is architecturally sound — it just never receives the signal from ACP runs.
Proposed Fix
When an ACP run yields
{ type: "done" }, the gateway's ACP handler should call:This would make all existing infrastructure work:
subagent_endedhook fires, announce pipeline delivers results to requester session,sessions_yieldwakes up.Workaround
Currently using a background stream-log watcher that polls
.acp-stream.jsonlfiles for:stallevents every 10s and cross-referencesactive-tasks.jsonfor git branch changes. Works but adds ~70s latency (60s stall + 10s scan).Environment
Relevant Source Locations
auth-profiles-*.js:92532—onAgentEventlistener for lifecycle eventsauth-profiles-*.js:92344—completeSubagentRunauth-profiles-*.js:92375—emitSubagentEndedHookForRunruntime.ts:409— yields{ type: "done" }(not bridged to agent event bus)service.ts:39-100— internal lifecycle managementpi-embedded-subscribe.handlers.lifecycle.ts— correctly emits lifecycle events