Repository navigation
LOU-V2: typed, versioned event stream (agent.stream()) - #64
Merged
Merged
Conversation
WIP before merging main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # README.md
…nerate llms Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # README.md # llms-full.txt # src/createAgent.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
agent.stream(input, { signal })(oncreateAgent) andAgentExecutor.stream(options)return anAgentRun: anAsyncIterable<AgentEvent>that also hasresult: Promise<ExecutionResult>.AgentEventis a discriminated union. Every event has{ type, runId, seq (0, 1, 2, ...), timestamp (ISO), v: 1 }. The types arerun.start,step.start,text.delta,text.done,tool.start,tool.done,tool.error,approval.requested,step.done,errorandrun.done.run.doneis always last and exactly once.AGENT_EVENT_SCHEMA_VERSION, every event type,AgentEventOf<T>,AgentEventType,AgentRun, and the guardsisAgentEvent(for parsing events from SSE/WebSocket),isToolEvent,isTextEventandisStepEvent.stream()(andsupportsStreaming(model)is not false), each step is streamed:text.deltaper chunk, tool calls fromtool-callchunks or else from thetoolCallspromise, which is where the 'ai'-SDK adapters put them. The 'ai' SDK spelling'tool-calls'is normalised. Withoutstream(), the step callsgenerate()and emits onetext.deltaplustext.done.docs/streaming.mdwith the full event table, ordering guarantees, versioning policy, a terminal example and an SSE example. README gets a feature bullet, a streaming snippet under the quick example and a docs link.llms.txtandllms-full.txtare regenerated.Why
LOU-V2: the audit found no streaming through the executor. This event schema is the base for the UI hooks ticket and for SSE/WebSocket transports.
How
The stream uses the same loop as
execute(); it is not a fork.AgentExecutor.stream()callsexecute()with three changes:onEventcallback is composed with a translator. The existingstart,text-complete,tool-call,tool-resultanderrorevents map torun.start,text.done,tool.start,tool.done/tool.erroranderror.breakaborts.runStepOrAbort(step.start/step.done),pauseForApproval(approval.requested) andgenerateInSpan, where it swaps how one model step is obtained.So hooks, validation, approvals, the parallel tool batch, checkpoints, abort, the
chatspan and failure compaction all run unchanged. Without the sink (send()/execute()) the code path is identical to before.Decisions:
resultalone drives it to completion.resultrejects likesend()would, and is pre-marked as handled so leaving it unawaited never causes an unhandled rejection. The iterator never throws on a run failure; you geterrorthenrun.done { finishReason: 'error' }.AgentExecutor.stream.tool.done.resultis JSON-normalised (undefinedbecomesnull).argsis the parsed JSON ({}if invalid). Errors are{ name, message }.How verified
Ran on the result of merging
origin/mainat995f649:npx tsc --noEmit: cleannpm run lint:✖ 433 problems (0 errors, 433 warnings)(no new warnings)npm run build && npm run build --workspace=packages/create-loushy-agent: OKnpm run test:coverage:Test Files 119 passed (119),Tests 1669 passed | 4 skipped (1673), thresholds methttp.testtimeout,guardrailsslow-command,sandbox-wiringtimeout. Each passes alone. The machine was shared with other agents' test runs.npm run fallow:✓ 0 above threshold · 2818 analyzednpm run test:types:Tests 19 passed,Type Errors no errorsnpm run docs:verify-snippets:all 65 snippet(s) type-check against src/; 6 also run cleanlynpm run docs:llms:check: cleantypecheckandtypecheck:server: clean (no agent-forge files touched; theSimpleAgentinterface gainedstream)New tests:
src/execution/agentStream.test.ts, usingmockModel. It covers event order for a text-only run, a tool run (whose result deep-equalssend()'s), parallel calls (starts in call order, done in completion order), a tool error, an approval pause, abort mid-stream, an already-aborted signal, an earlybreak(the tool sees the abort),resultwithout iterating (events still readable afterwards), nostream(),supportsStreamingreturning false, a provider failure, a failure outside any step, anerrorchunk and single iteration. Every test checks thatseqhas no gaps,run.doneis last exactly once, steps are paired, and every event survivesJSON.parse(JSON.stringify(e))(toStrictEqual).agentStream.aiSdk.test.ts: streams throughOpenAIProviderwith a mockedstreamText.agentEvents.test-d.ts: type tests for narrowing.Notes for reviewer
AgentExecutor.tshas small additions:stream(), the three sink calls, and acallerparameter onvalidateExecuteOptions.generateStep.tshas a 5-line swap ingenerateInSpan.createAgent.tsmerged with LOU-W4 sessions;send,streamandsessionshare one options builder. Nothing touchesresume.ts,agentRunState.ts,ApprovalGate.tsortoolBatch.ts.aiSdkProvider.tsis unchanged. ItsfullStreamdoes not yield tool-call chunks, so the executor reads them fromtoolCalls.resumeAfterApproval()has no streaming variant yet.ExecutionResult,AgentEventUsagedeliberately copies only the three token fields. Extend it if cost should be streamed.🤖 Generated with Claude Code