Temporal Execution Model
OrcFlows uses Temporal as its durable execution engine — every workflow run is a real Temporal workflow, and every step is a Temporal activity. This is what gives OrcFlows reliability properties that a typical request/response automation engine doesn't have.
Why Temporal
- Durability — the full event history of an execution (every step's input, output, retry, and signal) is persisted by Temporal. If the worker process crashes or is redeployed mid-execution, the workflow resumes exactly where it left off.
- Retries — each step's
max_retries/retry_delaybecomes a TemporalRetryPolicyon its activity. Transient failures (a flaky API, a rate limit) are retried automatically without re-running earlier steps. - Long waits are free — a human-in-the-loop approval step can block on a signal for days without consuming any compute; Temporal just persists the wait state.
- Observability — the Temporal Web UI (port 8081) shows every workflow execution's full history independent of the OrcFlows UI, useful for debugging at the infrastructure level.
Task queues
The worker (cmd/worker) registers two task queues:
| Queue | Purpose |
|---|---|
goagents-main | Workflow orchestration (RunWorkflow, RunScheduledWorkflow, poller workflows) plus heavy activities: agent, orchestrator, code, stt, tts, document processing. |
goagents-fast | Lightweight activities (transform, set, condition, http, etc.) that complete in well under a second, so they aren't queued behind long-running agent calls. |
Each step type is statically classified as "fast" or not; the workflow function dispatches the activity to the appropriate queue.
Workflow functions
| Workflow | Started by |
|---|---|
RunWorkflow | Manual trigger, webhook, public API run, signal-resumed retries. |
RunScheduledWorkflow | Temporal Schedule for cron-triggered workflows. |
EmailPollerWorkflow, TelegramPollerWorkflow, etc. | Temporal Schedules for event/poller triggers — periodically poll an external API and start RunWorkflow for each new item found. |
RunWorkflow walks dsl.Steps, building an execution context (steps, trigger, vars, secret, input) that grows as each step completes. Steps whose depends_on/branch_deps are satisfied and whose condition evaluates truthy are dispatched as activities (in parallel where the DAG allows); their outputs are merged back into the context for downstream steps.
Activities
Every node type's Execute(ctx, config, inputs) runs as a Temporal activity (internal/execution/activities.go), registered against nodes.GlobalRegistry. Activity inputs/outputs are the StepInput/StepOutput types from pkg/dsl. Special-purpose activities also exist for:
MarkStepWaitingActivity/MarkApprovalCompletedActivity— human-in-the-loop bookkeeping.CompleteWorkflowActivity— final status update + cleanup (e.g. cancelling atimerstep).
Retries & resumption
POST /api/v1/executions/{id}/retry starts a fresh RunWorkflow execution with PreloadedOutputs populated from the failed run's completed steps. The workflow function checks outputs[step.ID] before dispatching each activity — if a result is already present (including for approval steps, which won't re-prompt), it's reused and the step is skipped.
Signals
workflow.GetSignalChannel(ctx, "approve-<step_id>") is how human approvals are delivered (see Human-in-the-Loop) — POST /api/v1/executions/{id}/signal calls Client.SignalWorkflow with an arbitrary signal name and JSON payload, so the same mechanism can be used for any "wait for an external event" pattern (e.g. resuming a voice session).
Encryption at rest
Set TEMPORAL_ENCRYPTION_KEY (a base64-encoded 32-byte AES-256 key) to encrypt workflow payloads in Temporal's datastore — recommended in production since step inputs/outputs may contain sensitive data before it's redacted for display.