feat(runtime): streamAgentTurn — one run-turn event contract over box/executor/chat backends#454
Merged
Merged
Conversation
…/executor/chat backends
tangletools
approved these changes
Jul 3, 2026
tangletools
left a comment
Contributor
There was a problem hiding this comment.
✅ Auto-approved drewstone PR — 0711ca33
This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.
tangletools · auto-approval · reason: drewstone_author · 2026-07-03T07:12:54Z
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.
Closes #452
What
streamAgentTurn(backend, prompt, { signal?, timeoutMs? }) → AsyncGenerator<RuntimeStreamEvent>— ONE run-a-turn primitive over the three execution substrates, pluscollectAgentTurn(stream)to drain it into{ finalText, usage, events, status, error? }. Exported from the./loopsbarrel. Version 0.85.0.AgentTurnBackendis a closed discriminated union:{ kind: 'box', box }— oneSandboxInstance.streamPromptcall, projected through the EXISTINGmapSandboxEvent/extractLlmCallEvent(sandbox-events.ts). No re-mapping.{ kind: 'executor', factory }— a one-shotExecutor(cli-bridge / router / BYO) adapted through the EXISTINGinlineSandboxClient(the one executor→box adapter), then driven by the same box path. Settle/teardown lifecycle stays in that adapter.{ kind: 'chat', backend }— one turn through an in-processAgentExecutionBackend(resolveAgentBackendoutput), normalized by the EXISTINGnormalizeBackendStreamEvent.Stream envelope:
backend_start→ incremental events → (backend_erroron failure) →final. The terminalfinalevent is guaranteed on every non-thrown path and carriestext(final text) +metadata.tokenUsage {input, output}/metadata.costUsd?/metadata.model?. Caller abort ends withstatus: 'aborted'; a blowntimeoutMsdeadline withstatus: 'failed'— cancellation stays distinguishable from a missed deadline.Reused vs new
Reused (the point of the issue):
mapSandboxEvent+extractLlmCallEvent,inlineSandboxClient,normalizeBackendStreamEvent,newRuntimeSession,scoreKnowledgeReadiness,BackendTransportError→ typedBackendErrorDetail. New code is thin adapters + the terminal-event normalization (one accumulator fold) — no new stream parser.Supporting root fix:
inlineSandboxClient's pseudo-boxstreamPromptnow accepts{ signal }and chains it into the executor's spawn signal (it previously ignored the second argument every realSandboxInstancehonors), so abort reachesexec.execute— for this primitive AND forrunLoopdriving inline boxes.Honest-usage detail:
mapSandboxEventstampsagentRunNameas the model label on events that carried no model; the fold excludes that run label fromusage.modelso the terminal usage never reports a fabricated model.Tests (offline, 9 new)
Per backend kind — box via
inProcessSandboxClient, executor via a stubExecutorFactory, chat via a stubAgentExecutionBackend:finalcarries tokenUsage / costUsd / modelcollectAgentTurnround-trips the summary; throws on a stream with no terminal eventstatus: 'aborted', executor teardown observed, partial text preservedtimeoutMsexpiry →status: 'failed'with the deadline in the errorbackend_error+final failedin-band (generator never throws)Gate: lint clean, typecheck clean, build clean, full suite 1221 passed / 2 pre-existing skips.
docs:api+docs:freshnessgreen (canonical-api decision-table row added).Motivating consumer: physim#70 — its four hand-written provider-stream→AgentEvent generators become thin mappers over this primitive.