You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The agentic turn loop lives inside the engine (AgenticLoop in packages/agents), and AgentImpl.stream() delegates directly to it. This was the goal of the core API refactoring (#2034, #2372, #1595). However, the CLI still has compensating layers that duplicate engine-owned responsibilities ? and these layers only exist in the CLI's React app, not in ACP/Zed or other consumers.
This divergence was discovered while fixing #2653 (ACP/Zed: task tool crash + todo_pause loop-break). Two specific divergences were found:
1. Scheduler creation: CLI has its own factory, ACP uses the engine fallback
The CLI registers a schedulerFactoryProvider via interactiveToolScheduler.ts that wraps scheduler.schedule in a proper closure. ACP/Zed does not register one, so the subagent falls back to initInteractiveScheduler() in packages/agents/src/core/subagentExecution.ts. That fallback had a receiver-loss bug (schedule: scheduler.schedule without binding this) that the CLI's factory masked. We fixed the symptom in #2655, but the root cause is that scheduler creation is not fully consolidated in the engine ? each consumer shouldn't need to provide its own correctly-binding factory.
2. Todo continuation: CLI has a React-layer loop on top of the engine loop
The CLI's useTodoContinuation.ts hook fires a brand newagent.stream() call after the engine loop finishes, if there are pending todos. This is a second, outer continuation loop that overlaps with what should be engine-owned behavior. It also has its own todoPaused gate (useTodoPausePreserver.ts) that was masking the fact that AgenticLoop.buildNextMessage() didn't stop on todo_pause.
We fixed AgenticLoop.buildNextMessage() to check for successful todo_pause (#2655), but the CLI still has the redundant React continuation layer. ACP/Zed has no such layer and relies entirely on the engine.
Files involved:
packages/cli/src/ui/hooks/useTodoContinuation.ts ? React continuation loop (fires new agent.stream())
packages/agents/src/core/TodoContinuationService.ts ? engine-side service (exists but partially bypassed)
packages/agents/src/core/MessageStreamOrchestrator.ts ? checks todoPauseSeen but doesn't see real tool responses from AgenticLoop
3. MessageStreamOrchestrator pause detection is wired to the wrong layer
MessageStreamOrchestrator checks for todo_pause via ToolCallResponse events from Turn.run(), but the real Turn only emits ToolCallRequest events. The actual tool responses come back later from AgenticLoop's scheduler ? which MessageStreamOrchestrator never sees. This means the orchestrator's todoPauseSeen detection only works in tests that mock Turn.run() to emit synthetic ToolCallResponse events. The existing MessageStreamOrchestrator.todoPause.test.ts tests mock this path and don't reflect real execution.
Context: Prior refactoring work
This is a continuation of the multi-issue effort to move the agentic loop and all engine behavior into the packages/agents package:
The engine loop is in the right place. What remains is removing the CLI's duplicated/overlapping layers so there is one continuation and scheduling path for all consumers.
Proposed direction
Consolidate scheduler creation into the engine. The initInteractiveScheduler() fallback in subagentExecution.ts should be the canonical path. The CLI's interactiveToolScheduler.ts factory should be removed or reduced to configuration only (not method-binding). No consumer should need to know that scheduler.schedule requires a closure to preserve this.
Move todo continuation into the engine. The React useTodoContinuation hook's logic (checking for pending todos, generating continuation prompts, deciding whether to continue) should move into the engine ? either into AgenticLoop itself or into a dedicated continuation controller that AgentImpl.stream() uses. The CLI should become a pure event consumer.
Fix the MessageStreamOrchestrator pause detection. Either route real tool responses through the orchestrator, or move the pause check entirely into AgenticLoop.buildNextMessage() (where our Fix ACP/Zed: task tool crash + todo_pause loop-break (Closes #2653) #2655 fix already partially lives). The orchestrator's existing tests need to reflect real execution paths, not mocked ToolCallResponse events.
Remove or deprecate the React-layer pause gate.useTodoPausePreserver.ts and the todoPaused state in useTodoContinuation.ts should become unnecessary once the engine authoritatively handles pause.
Add integration tests that exercise the full agent.stream() ? AgenticLoop path for both CLI and ACP-style consumers, so behavioral parity is enforced.
Why 0.11.0
This is an architecture refactor that touches multiple packages. It should not block 0.10.0 feature work. The immediate bugs from the divergence were fixed in #2655 (merged to 0.10.0). This issue tracks the full consolidation.
Problem
The agentic turn loop lives inside the engine (
AgenticLoopinpackages/agents), andAgentImpl.stream()delegates directly to it. This was the goal of the core API refactoring (#2034, #2372, #1595). However, the CLI still has compensating layers that duplicate engine-owned responsibilities ? and these layers only exist in the CLI's React app, not in ACP/Zed or other consumers.This divergence was discovered while fixing #2653 (ACP/Zed:
tasktool crash +todo_pauseloop-break). Two specific divergences were found:1. Scheduler creation: CLI has its own factory, ACP uses the engine fallback
The CLI registers a
schedulerFactoryProviderviainteractiveToolScheduler.tsthat wrapsscheduler.schedulein a proper closure. ACP/Zed does not register one, so the subagent falls back toinitInteractiveScheduler()inpackages/agents/src/core/subagentExecution.ts. That fallback had a receiver-loss bug (schedule: scheduler.schedulewithout bindingthis) that the CLI's factory masked. We fixed the symptom in #2655, but the root cause is that scheduler creation is not fully consolidated in the engine ? each consumer shouldn't need to provide its own correctly-binding factory.Files involved:
packages/agents/src/core/subagentExecution.ts? engine fallback (now fixed)packages/cli/src/runtime/interactiveToolScheduler.ts? CLI's separate factorypackages/core/src/config/toolRegistryFactory.ts?getInteractiveSubagentSchedulerFactory()indirection2. Todo continuation: CLI has a React-layer loop on top of the engine loop
The CLI's
useTodoContinuation.tshook fires a brand newagent.stream()call after the engine loop finishes, if there are pending todos. This is a second, outer continuation loop that overlaps with what should be engine-owned behavior. It also has its owntodoPausedgate (useTodoPausePreserver.ts) that was masking the fact thatAgenticLoop.buildNextMessage()didn't stop ontodo_pause.We fixed
AgenticLoop.buildNextMessage()to check for successfultodo_pause(#2655), but the CLI still has the redundant React continuation layer. ACP/Zed has no such layer and relies entirely on the engine.Files involved:
packages/cli/src/ui/hooks/useTodoContinuation.ts? React continuation loop (fires newagent.stream())packages/cli/src/ui/hooks/useTodoPausePreserver.ts? React pause gatepackages/cli/src/ui/containers/AppContainer/hooks/useTodoContinuationFlow.ts? orchestrationpackages/agents/src/core/TodoContinuationService.ts? engine-side service (exists but partially bypassed)packages/agents/src/core/MessageStreamOrchestrator.ts? checkstodoPauseSeenbut doesn't see real tool responses fromAgenticLoop3.
MessageStreamOrchestratorpause detection is wired to the wrong layerMessageStreamOrchestratorchecks fortodo_pauseviaToolCallResponseevents fromTurn.run(), but the realTurnonly emitsToolCallRequestevents. The actual tool responses come back later fromAgenticLoop's scheduler ? whichMessageStreamOrchestratornever sees. This means the orchestrator'stodoPauseSeendetection only works in tests that mockTurn.run()to emit syntheticToolCallResponseevents. The existingMessageStreamOrchestrator.todoPause.test.tstests mock this path and don't reflect real execution.Context: Prior refactoring work
This is a continuation of the multi-issue effort to move the agentic loop and all engine behavior into the
packages/agentspackage:agent.stream()instead of assembling the agentic loopagent.stream()(Fixes Interactive CLI must consume agent.stream() instead of assembling the agentic loop (part of #1595) #2372)The engine loop is in the right place. What remains is removing the CLI's duplicated/overlapping layers so there is one continuation and scheduling path for all consumers.
Proposed direction
Consolidate scheduler creation into the engine. The
initInteractiveScheduler()fallback insubagentExecution.tsshould be the canonical path. The CLI'sinteractiveToolScheduler.tsfactory should be removed or reduced to configuration only (not method-binding). No consumer should need to know thatscheduler.schedulerequires a closure to preservethis.Move todo continuation into the engine. The React
useTodoContinuationhook's logic (checking for pending todos, generating continuation prompts, deciding whether to continue) should move into the engine ? either intoAgenticLoopitself or into a dedicated continuation controller thatAgentImpl.stream()uses. The CLI should become a pure event consumer.Fix the
MessageStreamOrchestratorpause detection. Either route real tool responses through the orchestrator, or move the pause check entirely intoAgenticLoop.buildNextMessage()(where our Fix ACP/Zed: task tool crash + todo_pause loop-break (Closes #2653) #2655 fix already partially lives). The orchestrator's existing tests need to reflect real execution paths, not mockedToolCallResponseevents.Remove or deprecate the React-layer pause gate.
useTodoPausePreserver.tsand thetodoPausedstate inuseTodoContinuation.tsshould become unnecessary once the engine authoritatively handles pause.Add integration tests that exercise the full
agent.stream()?AgenticLooppath for both CLI and ACP-style consumers, so behavioral parity is enforced.Why 0.11.0
This is an architecture refactor that touches multiple packages. It should not block 0.10.0 feature work. The immediate bugs from the divergence were fixed in #2655 (merged to 0.10.0). This issue tracks the full consolidation.
@coderabbitai please create a plan