Introduction
When an agent works on a complex task, it may run for minutes — reading files, executing commands, reasoning through problems, and producing output incrementally. Streaming lets your application display progress, log tool activity, and react to errors in real time rather than waiting for the agent to finish. Agent-Ctrl provides streaming through four callback methods on the builder. These callbacks are invoked as the agent’s JSON Lines output is parsed, and the same callback API works identically across Claude Code, Codex, OpenCode, Pi, and Gemini.Using executeStreaming()
To enable streaming, register one or more callbacks and call executeStreaming() instead of execute():
executeStreaming() returns the final AgentResponse just like execute() does. The callbacks provide real-time visibility into the work, but the complete result is always available at the end for inspection, storage, or further processing.
Callback Reference
onText(callable $handler): static
Called whenever the agent produces text content. The handler receives a single string argument containing the text fragment. Text is delivered incrementally — each call may contain a word, a sentence, or a paragraph depending on how the agent’s CLI emits output.
onToolUse(callable $handler): static
Called whenever the agent invokes a tool or receives a tool result. The handler receives three arguments:
string $tool— The tool name (e.g.,'bash','file_change','web_search','tool_result')array $input— The tool’s input parameters as an associative array?string $output— The tool’s output, ornullif the tool has not completed yet
CommandExecution items become 'bash' tool calls with ['command' => '...'] input, and Codex FileChange items become 'file_change' tool calls with ['path' => '...', 'action' => '...'] input.
onComplete(callable $handler): static
Called exactly once when the agent finishes and the final AgentResponse is assembled. The handler receives the complete response object:
onError(callable $handler): static
Called when the agent emits an error event during streaming. These are operational errors reported by the agent itself (e.g., a tool failure, a rate limit, or a malformed request), not PHP exceptions. The handler receives two arguments:
string $message— The error description?string $code— An optional error code (agent-specific)
Streaming Without Callbacks
You can callexecuteStreaming() without registering any callbacks. In this case, the builder still processes the streaming output internally (emitting events for the wiretap() system), but no user-facing callbacks are invoked. The final AgentResponse is returned normally.
When to Use execute() Instead
Use execute() when you only care about the final result and do not need incremental updates. Internally, execute() delegates to the same streaming infrastructure — it simply does not register a stream handler. The performance characteristics are identical; the only difference is whether callbacks fire during execution.
How Streaming Works Internally
Understanding the internal streaming pipeline can help with debugging and advanced usage:-
Process execution. The bridge launches the CLI binary via the
SandboxCommandExecutor, which runs the process and captures stdout in real time. -
JSON Lines buffering. Raw process output arrives in arbitrary-sized chunks. A
JsonLinesBufferaccumulates bytes until complete JSON Lines (newline-delimited) are available. -
Event parsing. Each complete JSON line is decoded and passed through the agent-specific
StreamEvent::fromArray()factory, which produces typed event objects (text events, tool use events, error events, etc.). -
Callback dispatch. Typed events are normalized into the common callback signatures (
onText,onToolUse,onError) and dispatched to your handlers. -
Event system. In parallel, the builder dispatches internal events (
AgentTextReceived,AgentToolUsed,AgentErrorOccurred,StreamChunkProcessed, etc.) that can be observed through thewiretap()system. - Final parse. After the process completes, the full stdout is re-parsed to extract the authoritative final data (text, tool calls, session ID, usage). This ensures no data is lost due to streaming chunk boundaries.
Combining Streaming with the Console Logger
For development and debugging, you can combine user-facing streaming callbacks with the built-inAgentCtrlConsoleLogger to see both the agent’s output and detailed execution telemetry:
onText callback displays the agent’s actual output.