1. Operator Action
The flow begins when an operator clicks Run in the Forge Console.The dashboard calls the interpreter.startWorkflow Convex mutation. This is a public mutation protected by requirePermission(ctx, “workflow:execute”).
| Detail | Value |
|---|---|
| File | convex/interpreter.ts |
| Function | startWorkflow (public mutation, line ~2060) |
| Auth | requirePermission(ctx, "workflow:execute") |
| Delegates to | startWorkflowImpl() (private, line ~1977) |
startWorkflowImpl does three things:
- Loads the workflow definition — fetches the
workflowsandworkflowVersionsdocuments, pinning the YAML definition at run start (immutable for the run’s lifetime). - Resolves the gateway — checks for a pinned
gatewayIdfrom the active deployment, or falls back to the org’s default gateway. - Creates the
workflowRunsrecord — inserts a new row with status"running", a freshtraceIdandrootSpanId, then starts the durable workflow:
workflow.start() call uses @convex-dev/workflow’s WorkflowManager to launch a durable workflow. The workflow survives Convex function restarts and resumes from the last committed step.2. DAG Resolution
Inside the durable workflow handler (lobsterXInterpreter), the interpreter parses the YAML and computes the execution graph.
Deterministic graph resolution — no LLM tokens burned. Convex walks the DAG, the LLM executes steps.
| Detail | Value |
|---|---|
| File | convex/interpreter.ts |
| Key function | resolveExecutionLayers() (from convex/lib/interpreter_utils.ts) |
| Input | Array of LobsterXStep objects parsed from YAML |
| Output | Ordered execution layers — steps within a layer can run in parallel |
- Find ready steps — steps whose
depends_ondependencies are all completed or skipped, and which have not been dispatched, failed, or cancelled. - Evaluate conditions — if a step has a
conditionfield, evaluate it against accumulated outputs and variables. Steps where the condition is not met are markedskipped. - Dispatch — each ready step is dispatched based on its type: agent step, approval gate, or sub-workflow.
- Await completion —
step.awaitEvent({ name: "completion" })blocks until a completion event arrives. - Process and loop — the completed step’s outputs are accumulated, variables updated, and the loop returns to step 1.
3. Governance Evaluation
Before any step reaches the network, the
A fail-fast gate pipeline. The first rejection terminates evaluation and returns a structured GovernanceDecision.
dispatchStep action (in convex/dispatch.ts) calls the governance engine before doing anything else:
evaluateGovernance() in convex/trust_fabric/engine.ts, which runs the dispatch pipeline — 10 sequential gates:
| # | Gate | Error Code | Purpose |
|---|---|---|---|
| 1 | gatewayHealth | gateway_unreachable | Is the target gateway reachable? |
| 2 | agentStatus | agent_disabled | Is the agent enabled and active? |
| 3 | concurrency | agent_busy | Is the agent under its max concurrent step limit? |
| 4 | rateLimit | rate_limited | Has the agent exceeded its dispatch rate? |
| 5 | budgetAgent | budget_exceeded | Has the agent’s monthly spend exceeded its ceiling? |
| 6 | budgetEnvelopes | budget_exceeded | Has any scoped budget envelope been breached? |
| 7 | trustLevel | trust_level_insufficient | Does the agent meet the gateway’s minimum trust level? |
| 8 | contextTrust | context_untrusted | Is the execution context trusted for this agent’s role? |
| 9 | policyRules | policy_blocked | Do dispatch policies permit this action? |
| 10 | approvalRequired | — | Does this step require human approval before proceeding? |
Decision Outcomes
The engine returns aGovernanceDecision with one of three dispositions:
| Disposition | What happens | Code path |
|---|---|---|
allow | Step proceeds to dispatch | Normal flow continues |
block | Step is marked failed, SafetyGateError thrown | stepResults.markFailed + audit event |
hold | Approval request created, step waits | trust_fabric.approvals.mutations.create |
dispatchStep action retries retryable blocks up to 3 times with exponential backoff (1s, 2s, 4s) before giving up.
persistGovernanceDecision().
Cross-links: Governance Pipeline | Safety Gates | Trust Fabric Overview | Approvals | Agent Trust
4. Dispatch
If the Trust Fabric allows the step,dispatchStep performs three operations atomically before hitting the network.
Atomic concurrency check, mark step running, then fire-and-forget HTTP dispatch to Bridge.
| Detail | Value |
|---|---|
| File | convex/dispatch.ts |
| Function | dispatchStep (internalAction, line ~69) |
| Key mutation | stepResults.atomicCheckConcurrencyAndMarkRunning |
Step-by-step:
a) Atomic concurrency check + mark running A single mutation combines the concurrency gate andmarkRunning to eliminate the TOCTOU race between separate query + mutation calls:
agent_busy and a SafetyGateError is thrown.
b) Build callback URL
The callback URL is derived from CONVEX_SITE_URL (or transformed from CONVEX_URL):
dispatchStep() method sends the request to Bridge with 3-attempt retry and exponential backoff (1s, 4s, 16s):
5. Adapter Execution
Bridge receives the dispatch, ACKs immediately, and delegates to the adapter.Async execution router. Receives dispatch, returns 202 immediately, executes via adapter in the background.
Bridge endpoint
| Detail | Value |
|---|---|
| File | packages/bridge/src/server.ts |
| Route | POST /api/workflow/dispatch (line ~503) |
| Auth | authenticateToken middleware (bearer token) |
202 Accepted immediately, then delegates execution asynchronously:
Dispatch orchestrator
| Detail | Value |
|---|---|
| File | packages/bridge/src/dispatch-orchestrator.ts |
| Function | dispatchStepV2() (line ~319) |
- Resolves the adapter based on the
providerfield in the request. In multi-adapter mode, Bridge loads multiple adapters fromBRIDGE_ADAPTERSenv var. - Assembles the
ExecutionRequest— builds the full request with output strategy, model, timeout, and tools. - Calls
adapter.execute(request)— this is where the actual LLM call happens, with timeout enforcement viaAbortController. - Normalizes the output via
OutputNormalizer— parses structured output, extracts file content, resolves output strategies. - Builds the callback payload with status, outputs, telemetry (tokens, cost, model), and conversation messages.
Adapter example: Vercel AI
| Detail | Value |
|---|---|
| File | packages/adapter-vercel-ai/src/provider.ts |
| Class | VercelAIProvider implements ExecutionAdapter |
generateText():
ExecutionAdapter interface but use their native SDKs.
Cross-links: Bridge Architecture | Writing Adapters | Gateways
6. Callback
When the adapter finishes (or fails), Bridge delivers the result back to Convex.Bridge POSTs the result to Convex’s /api/workflow/step-complete endpoint with 3x retry.
Bridge side
| Detail | Value |
|---|---|
| File | packages/bridge/src/dispatch-orchestrator.ts |
| Function | callbackWithRetry() (invoked at line ~502) |
Convex side
| Detail | Value |
|---|---|
| File | convex/http.ts |
| Route | POST /api/workflow/step-complete (line ~28) |
| Auth | X-Gateway-Token header verified against gateway registry |
- Verify gateway token — looks up gateway by token hash.
- Parse and validate — validates against
stepCompleteSchema, range-checks numeric fields (tokens, cost). - Idempotency check —
insertIfNotDuplicateprevents duplicate processing of the same callback. - Check workflow status — if the run is cancelled, ACK but discard the result.
- Update step result —
markStepCompletedormarkStepFaileddepending on status. - Log audit event — records
step_completedorstep_failedwith full detail. - Record telemetry — inserts a
tokenEventsrecord for cost attribution. - Schedule post-execution governance — evaluates budget thresholds, triggers alerts if spend is approaching limits.
- Persist logs and conversation — stores step logs as NDJSON blobs, persists conversation transcripts to agent threads.
- Fire completion event — resumes the waiting interpreter:
fireCompletion mutation sends an event to the durable workflow, which unblocks the interpreter’s awaitEvent("completion") call.
Cross-links: Audit Trail | Traces | Analytics
7. DAG Advance
Back in the interpreter, the completion event is received and the DAG advances.The event loop processes the completion, accumulates outputs, resolves new dependencies, and dispatches the next wave of steps.
| Detail | Value |
|---|---|
| File | convex/interpreter.ts |
| Event | awaitEvent({ name: "completion" }) (line ~677) |
| Completion handler | processAgentCompletion() from convex/lib/interpreter/completion_handlers.ts |
- Parse the completion event — extracts
stepId,type,status,outputs,error. - Validate outputs — if the step declares
outputsin the.lobsterXdefinition, the interpreter validates and coerces them. - Accumulate state — outputs are merged into
accumulatedOutputs[stepId]and declared variables are updated. - Save checkpoint — a workflow checkpoint is saved so a restart can resume from this point.
- Resolve next steps — the loop returns to step 1, finding steps whose dependencies are now satisfied.
- Termination check — when all steps are completed, skipped, failed, or cancelled, the loop exits.
"completed" and logs the final audit event.
Cross-links: Workflow Execution Flow | Checkpoints | Execution Overview
Failure Paths
Things go wrong. Here is what happens at each layer.Governance blocks the step
| Trigger | Behavior |
|---|---|
Any gate returns block | dispatchStep retries retryable blocks up to 3x, then marks the step failed via stepResults.markFailed, logs safety_gate_rejected audit event, throws SafetyGateError. |
Gate returns hold | An approval request is created. The step stays in running status, waiting for human approval via the Approval Lifecycle. |
SafetyGateError and applies the step’s on_failure policy:
halt(default) — the workflow stops, recording a failure snapshot for restart.skip— the step is marked skipped, downstream steps cascade-skip if all their dependencies were skipped.retry_once— the step is re-dispatched once. If it fails again, it halts.retry_once_then_escalate— an escalation approval is created. A human can approve retry or abort.
Adapter execution fails
| Trigger | Behavior |
|---|---|
| Adapter throws | Bridge catches the error, builds a failed callback payload with error_code classification, delivers callback to Convex. |
| Timeout | AbortController cancels the adapter after timeoutMs. Bridge sends a timed_out callback. A best-effort cancel is sent to the adapter. |
Callback delivery fails
| Trigger | Behavior |
|---|---|
| Callback HTTP error | Bridge retries up to 3x with backoff. If all attempts fail, the error is logged but the step remains in running status on the Convex side. |
| Convex timeout enforcer | Convex’s cron-based timeout enforcer independently detects steps that have been running beyond their timeout. It marks them failed and fires a completion event, even if Bridge’s callback never arrived. |
Duplicate callbacks
The HTTP handler usesinsertIfNotDuplicate for atomic idempotency. If Bridge retries a callback that Convex already processed, the duplicate is ACKed ({ received: true, deduplicated: true }) without side effects. If the timeout enforcer already handled a timed-out step, any late gateway callback only reconciles cost — it does not replay audit events or fire a second completion.
DAG stall detection
If the interpreter finds no ready steps and no in-flight steps, but not all steps are resolved, it throws a stall error:Data Shape Reference
Key types that flow through this pipeline:| Type | Location | Used at |
|---|---|---|
LobsterXDocument | @tangogroup/shared | Interpreter YAML parsing |
LobsterXStep | @tangogroup/shared | Step definitions within the DAG |
GovernanceDecision | convex/trust_fabric/types.ts | Trust Fabric evaluation result |
GovernanceEvaluationContext | convex/trust_fabric/engine.ts | Pre-fetched context for gate evaluation |
ExecutionRequest | @tangogroup/bridge | Bridge adapter input |
ExecutionResult | @tangogroup/bridge | Bridge adapter output |
CallbackPayload | packages/bridge/src/dispatch-orchestrator.ts | Bridge-to-Convex callback body |
StepCompleteEvent | convex/lib/interpreter/helpers.ts | Parsed completion event in interpreter |
Summary Diagram
The complete flow, layer by layer:END-TO-END DATA FLOW
interpreter.startWorkflowresolveExecutionLayers() builds the DAGevaluateGovernance() returns allow / block / holdexecute() on the gateway (LLM call happens here)/api/workflow/step-complete with 3x retryfireCompletion, interpreter resumes and advances the DAGNext Steps
Workflow Execution Flow
The 8-phase execution flow from the architecture perspective.
Governance Pipeline
The full 11-gate dispatch pipeline and 5-gate launch pipeline.
Approval Lifecycle
What happens when a step is held for human approval.
Bridge & Adapters
Bridge architecture, adapter loading, and the ExecutionAdapter interface.
Safety Gates
Detailed reference for each safety gate and its error codes.
Interpreter Deep Dive
The 2200-line workflow engine: DAG resolution, conditions, failure handling.
Writing Adapters
How to implement a new gateway adapter.
Audit Trail
Every governance decision and step event in the append-only log.
.lobsterX Format
The YAML workflow definition language.

