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/
No black box. Each step is a typed-frame node you can edit, monitor, and replace.
Standard streaming voice loop runs the front: VAD, STT, turn-detection.
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.
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.
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.
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-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.
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.
Result rides as ContextFrame(payload={kind:batch_result, batch_result, ...}, provenance=batch:
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.
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.
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.
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.
The marketplace template is the graph. Every customisation below is a config change or a single-node addition - never a rewrite.
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.
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.
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.
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.
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.
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