Streaming to Batch ETL Handoff hero
Strongly Certified · Streaming Workflow

Streaming to Batch ETL Handoff

Voice agent fires a batch ETL mid-turn, awaits the result, grounds the next reply on it.

Bridge between Strongly's streaming runtime and the batch runtime. Each TurnEndFrame POSTs to /api/v1/workflows//execute; the executionId rides as a BridgeFrame to the await node which polls until terminal status, then folds the batch result into conversation memory as a typed ContextFrame for the next LLM turn.

≤250ms
Spawn HTTP RTT (p95)
2 fps
Default poll cadence
ADR-S16
Memory feedback edge

The voice loop, end-to-end.

No black box. Each step is a typed-frame node you can edit, monitor, and replace.

01

Standard streaming voice loop runs the front: VAD, STT, turn-detection.

02

On each TurnEndFrame, streaming-batch-spawn POSTs to Meteor's /api/v1/workflows/:id/execute with a templated triggerInputs payload (session_id, turn_id, user transcript) and emits a BridgeFrame carrying the executionId. Pass-through always fires so the voice loop never blocks.

03

streaming-batch-await polls /api/v1/executions/:id every 2s; on status=completed it emits a ContextFrame{kind:batch_result, batch_result, handoff_id, workflow_id, duration_seconds} that the conversation-memory node folds into the next LLM request as a context block.

04

LLM grounds the assistant reply on the freshly refreshed batch data. Failures (BatchExecutionFailed / BatchAwaitTimeout / BatchSpawnHttpError) flow as ErrorFrames on the bridge nodes' error_out ports.

Built for production. Day Two-ready.

Streaming graph contract, observability, and cost discipline come standard. The agent ships with a full test suite that runs in CI on every node version bump.

Streaming ↔ batch bridge

streaming-batch-spawn POSTs to /api/v1/workflows/:id/execute and emits BridgeFrame(handoff_id, workflow_id, spawned_at). The original frame always passes through so the voice loop never blocks on Meteor's RTT.

Plan §6.3BridgeFrame typedFire-and-forget

Async result fan-in

streaming-batch-await runs one polling task per BridgeFrame; concurrent handoffs resolve independently. Configurable poll interval + overall timeout + per-poll request timeout. Per-poll timeouts are recoverable; only the overall deadline fires BatchAwaitTimeout.

Concurrent pollsIndependent timeoutsTight cancellation

Typed ContextFrame for memory

Result rides as ContextFrame(payload={kind:batch_result, batch_result, ...}, provenance=batch:, label=batch_result:) so conversation-memory's standard ADR-S11 context-block path renders it directly into the LLM system prompt.

ADR-S11Provenance-taggedNo bespoke wiring

Memory feedback edge

LLM response feeds back into conversation-memory as the assistant turn (ADR-S16 feedback edge with max_iterations: 1000). The graph_validator accepts the cycle.

ADR-S16Coherent memoryValidator-clean

Typed bridge failures

BatchSpawnConfigError, BatchSpawnHttpError, BatchSpawnReadTimeout, BatchSpawnDecodeError on the spawn side; BatchAwaitConfigError, BatchAwaitHttpError, BatchAwaitDecodeError, BatchExecutionFailed, BatchAwaitTimeout on the await side. Routed to the auto-injected streaming-errors sink so batch failures never block the voice loop.

Canonical error_typeErrorFrame substrateVoice loop never blocks

Live span tree

Spawn POST + await polls + per-iteration result all land on the call's span tree per ADR-S14, with handoff_id, workflow_id, attempts, duration_seconds in span attributes. Replay any handoff, any session.

ADR-S14Handoff IDsReplayable

Real services. Your stack.

Every dependency is a registered Strongly service or a model you control. Swap any one of them in the install wizard. The graph stays intact.

Spawn node
streaming-batch-spawn 1.0.0 - emits BridgeFrame on TurnEndFrame
Await node
streaming-batch-await 1.0.0 - polls executions API, emits typed ContextFrame on success
STT / LLM / TTS
All swappable in the wizard - whisper-1 / gpt-4o-mini / tts-1 default
Meteor REST
POST /api/v1/workflows/:id/execute + GET /api/v1/executions/:id - resolved from STRONGLY_SERVICES.platform.api_url

Tune it. Don't fork it.

The marketplace template is the graph. Every customisation below is a config change or a single-node addition - never a rewrite.

Pick the batch workflow

Set spawn.config.workflow_id at deploy time to the Meteor _id of the batch ETL workflow. The wizard prompts for it in the 'Pick Batch Workflow' step.

Spawn cadence

Default fires one batch run per TurnEndFrame. Change spawn.config.spawn_on to TextFrame for per-utterance, or gate upstream with a streaming-conditional to fire only on specific intents.

Slow batch workflows

Bump await.config.timeout_seconds to comfortably exceed the batch's longest-known runtime. Default is 120s; the BatchAwaitTimeout ErrorFrame fires only after this deadline.

Trigger inputs

spawn.config.trigger_inputs_template is an arbitrary JSON object. Default carries {session_id, turn_id, user_text, purpose}; add caller_id, locale, or any field the batch workflow consumes.

Failure routing

Wire spawn.error_out and await.error_out into a streaming-errors node configured with severity:warn and a webhook sink to surface batch failures to ops without blocking the voice loop.

Production. Not pilots.

We don't leave until it runs. Talk to a forward-deployed engineer about deploying Streaming to Batch ETL Handoff into your environment with your STT, your LLM, your TTS, your data.

Schedule a Demo