Skip to content

Runtime Overview ​

A runtime is what actually executes your agents. Helix Agents provides multiple runtimes, each suited to different deployment scenarios. Your agent definitions work identically across all runtimes - only the execution environment changes.

What Is a Runtime? ​

The runtime orchestrates the agent execution loop:

  1. Initialize agent state
  2. Call the LLM with messages and tools
  3. Execute any tool calls
  4. Handle sub-agents
  5. Persist state
  6. Stream events
  7. Repeat until completion

Different runtimes handle these steps differently based on their execution model.

Available Runtimes ​

RuntimePackageBest For
JavaScript@helix-agents/runtime-jsDevelopment, testing, simple deployments
Temporal@helix-agents/runtime-temporalProduction, long-running agents, durability
Cloudflare@helix-agents/runtime-cloudflareEdge deployment, serverless, global scale
DBOS@helix-agents/runtime-dbosPostgres-backed durable execution

v7 Feature Parity Matrix ​

All major v7 capabilities are supported across runtimes:

FeatureJSTemporalCFW WorkflowsCF DODBOS
HITL — client-executed tools✅✅✅✅✅
Approval gates (requireApproval)✅✅✅✅✅
Persistent sub-agents✅✅✅✅✅
Remote sub-agents (consumer)✅✅✅✅✅
Persistent-companion continuation²✅✅✅✅✅
Stateless suspension model✅✅✅✅✅
Multi-turn continuation via execute(sessionId)¹✅✅✅✅✅
Workspaces✅❌❌✅❌
Cross-process / cross-replica interrupt✅✅✅✅✅
Prompt caching (LLMConfig.cache)✅✅✅✅❌
Memory auto-injection / extraction✅❌✅✅❌
Completion strategies + prompt-prefix check³✅✅✅✅❌ (planned)

Every runtime starts each run with the same run-start commit (one atomic store write that appends the input, applies any rewind and creates the run record), so a losing concurrent entry writes nothing, and a run that loses its session to a newer run stops as superseded (errorDetail.code === 'state_run_superseded'), never failed, on every runtime. What decides that a run's previous owner is gone differs: a lease on JS, the owner workflow being closed on Temporal and DBOS, the owner instance on CFW Workflows, the DO's synchronous claim on the CF DO. See the upgrade guide.

¹ A SECOND execute(agent, msg, { sessionId }) against an already-completed session continues the conversation into a new turn, on every runtime. CFW Workflows runs each turn in its own write-once instance (see Cloudflare Workflows → Multi-Turn Conversations). See Concepts → Sessions. The HTTP path (POST /chat → ai-sdk handle-chat-stream "continuing session" branch → execute(sessionId)) inherits this support; agent-server's /start (startAgent) is fresh-start-only by design (remote-sub-agent protocol).

² Persistent-companion continuation — re-consulting an already-completed persistent child (via companion__spawnAgent / companion__sendMessage) continues on its preserved session (memory retained, fresh typed output) instead of throwing/deleting. This is the maker→critic loop. It is a different mechanism from root multi-turn continuation (row above): it works on all five runtimes including CFW Workflows, because each round runs as a fresh child workflow/instance with a per-continuation id keyed on the consult's tool call (for example …__continue__<parent-run suffix>-<stepCount>-<toolCallId> on CFW Workflows), the same per-run-instance approach root execute(sessionId) uses on CFW Workflows. See Sub-Agents → Re-consulting a persistent companion and the v0.40 migration guide.

³ Completion strategies — per-call forced_tool / native_output selection from adapter capabilities, early refusal of an outputSchema agent whose model has no strategy, and the preserved-thinking prompt-prefix check. Without them, forced completion on Claude Opus 5.5, Sonnet 5.5, Fable 5.1 and Mythos 5.1 fails (those models reject forced tool choice). See Finishing Agents → Completion strategies.

Cloudflare Has Two Approaches

The Cloudflare runtime offers two execution models: Workflows (step-level durability with D1) and Durable Objects (unlimited streaming with built-in SQLite). See Cloudflare Runtime for comparison.

Comparison ​

Execution Model ​

FeatureJS RuntimeTemporal RuntimeCloudflare WorkflowsCloudflare DODBOS
DurabilityNone (in-memory)Full (workflow persistence)Step-levelCheckpoint-basedFull (Postgres-backed)
Crash RecoveryNoYesYes (step-level)Yes (checkpoint)Yes (durable workflow)
Timeout HandlingManualPer-activity timeoutsPer-step timeoutsManualPer-step timeouts
Retry LogicManualAutomatic with configAutomatic per stepManualAutomatic per step
Multi-ProcessNoYes (workers)Yes (edge nodes)Yes (edge nodes)Yes (Postgres coordinated)
ConcurrencyRun-start commit + run leaseRun-start commit + owner workflow livenessRun-start commit + owner instanceRun-start commit in the DO's synchronous claimRun-start commit + owner workflow

State & Streaming ​

FeatureJS RuntimeTemporal RuntimeCloudflare WorkflowsCloudflare DODBOS
State StoreMemory/RedisMemory/Redis/PGD1 DatabaseDO SQLitePostgres
Stream ManagerMemory/RedisMemory/RedisDurable ObjectsDirect WebSocket/SSEMemory/Redis
Streaming LimitNoneNone1000 subrequestsUnlimited (FREE)None
Stream ResumptionhandleChatStreamhandleChatStreamhandleChatStreamhandleChatStreamhandleChatStream
Snapshot resumeCheckpoint cursorCheckpoint cursorCheckpoint cursorCheckpoint cursorCheckpoint cursor
Sub-Agent ExecutionIn-processChild workflowsNested workflow callsSibling DOs (via subAgentNamespace)Child workflows

Operational ​

FeatureJS RuntimeTemporal RuntimeCloudflare WorkflowsCloudflare DODBOS
InfrastructureNoneTemporal ServerD1 + Workflow + DODO onlyPostgres
ComplexityLowMedium-HighMediumLow-MediumMedium
CostHosting onlyTemporal Cloud or self-hostD1 + subrequestsDO duration onlyPostgres + hosting
ScalingVerticalHorizontal (workers)Automatic (edge)Automatic (edge)Horizontal (workers)
HibernationN/AN/AYes (v7 stateless)Yes (cost savings)N/A

Method Availability ​

All runtimes support the same executor methods:

MethodJSTemporalCloudflareDBOS
execute()✅✅✅✅
resume()✅✅✅✅
retry()✅✅✅✅
getHandle()✅✅✅✅
submitToolResult()✅✅✅✅
interrupt()✅✅✅✅
abort()✅✅✅✅

submitToolResult, interrupt, and abort are also available as durable HTTP routes when running through @helix-agents/agent-server (see POST /chat/{id}/submit-tool-result, POST /chat/{id}/interrupt, POST /chat/{id}/abort).

Choosing a Runtime ​

Use JavaScript Runtime When: ​

  • Development and testing - Fast iteration without infrastructure
  • Simple deployments - Single-process, short-lived agents
  • Prototyping - Explore ideas before production setup
  • Serverless functions - Lambda/Cloud Functions where execution is short
typescript
import { JSAgentExecutor, InMemoryStateStore, InMemoryStreamManager } from '@helix-agents/sdk';

const executor = new JSAgentExecutor(
  new InMemoryStateStore(),
  new InMemoryStreamManager(),
  llmAdapter
);

Use Temporal Runtime When: ​

  • Production workloads - Need reliability and observability
  • Long-running agents - Execution may take hours or days
  • Crash recovery - Agent must survive process restarts
  • Complex orchestration - Multi-agent systems with dependencies
typescript
import { TemporalAgentExecutor } from '@helix-agents/runtime-temporal';

const executor = new TemporalAgentExecutor({
  client: temporalClient,
  stateStore: redisStateStore,
  streamManager: redisStreamManager,
  workflowName: 'agent_workflow',
  taskQueue: 'agents',
});

Use Cloudflare Runtime When: ​

  • Global edge deployment - Low latency worldwide
  • Serverless architecture - No servers to manage
  • Cloudflare ecosystem - Already using Workers, D1, DO
  • Cost optimization - Pay per request, automatic scaling

Workflows approach - Step-level durability, D1 integration:

typescript
import { CloudflareAgentExecutor } from '@helix-agents/runtime-cloudflare';

const executor = new CloudflareAgentExecutor({
  workflowBinding: env.AGENT_WORKFLOW,
  stateStore: d1StateStore,
  streamManager: doStreamManager,
});

Durable Objects approach - Unlimited streaming, simpler architecture:

typescript
import { createAgentServer, DOFrontendExecutor } from '@helix-agents/runtime-cloudflare/do';

export const AgentServer = createAgentServer<Env>({
  llmAdapter: ({ env }) => new VercelAIAdapter({ model: openai('gpt-6-luna') }),
  agents: registry,
});

// In the Worker (the chat route, createCloudflareChatHandler, builds one for you)
const executor = new DOFrontendExecutor({ namespace: env.AGENTS, agentName: 'my-agent' });

See Cloudflare Runtime for detailed comparison of both approaches.

Use DBOS Runtime When: ​

  • Postgres-backed durability - You already run Postgres and don't want Temporal infrastructure
  • Durable execution - Long-running agents that must survive restarts
  • Postgres ecosystem - Leverage existing PG tooling for observability and querying
typescript
import { DBOSAgentExecutor } from '@helix-agents/runtime-dbos';

const executor = new DBOSAgentExecutor({ stateStore, streamManager, llmAdapter });

DBOS parallel-tool state visibility ​

When using the DBOS runtime, each tool call runs as an independent durable step that loads state.customState fresh from Postgres at the top of its execution. This means a parallel sibling tool that calls ctx.getState() to read a key written by another sibling within the same LLM step will see the pre-sibling value, not the cumulative state written by the other sibling. This is by design — DBOS steps must be idempotent for workflow replay determinism.

Tools that need to read sibling writes within a single step should be:

  • Sequenced via a single parent tool that orchestrates the work, or
  • Run on the JS or Cloudflare DO runtimes, which apply in-memory state merging between parallel tool calls before persisting.

Common API ​

All runtimes implement the AgentExecutor interface:

typescript
interface AgentExecutor {
  // Execute an agent with input
  execute<TState, TOutput>(
    agent: AgentConfig<...>,
    input: string | { message: string; state?: Partial<TState> },
    options?: ExecuteOptions
  ): Promise<AgentExecutionHandle<TOutput>>;

  // Reconnect to existing session
  getHandle<TState, TOutput>(
    agent: AgentConfig<...>,
    sessionId: string
  ): Promise<AgentExecutionHandle<TOutput> | null>;
}

The returned handle provides:

typescript
interface AgentExecutionHandle<TOutput> {
  runId: string;

  // Stream real-time events
  stream(): Promise<AsyncIterable<StreamChunk> | null>;

  // Wait for final result
  result(): Promise<AgentResult<TOutput>>;

  // Cancel execution
  abort(reason?: string): Promise<void>;

  // Get current state
  getState(): Promise<AgentState<unknown, TOutput>>;

  // Check if resumable
  canResume(): Promise<CanResumeResult>;

  // Resume execution
  resume(options?: ResumeOptions): Promise<AgentExecutionHandle<TOutput>>;
}

Swapping Runtimes ​

Because all runtimes share the same interface, swapping is straightforward:

typescript
// Development
const executor =
  process.env.NODE_ENV === 'production'
    ? new TemporalAgentExecutor({
        /* production config */
      })
    : new JSAgentExecutor(memoryStore, memoryStream, llmAdapter);

// Same usage regardless of runtime
const handle = await executor.execute(myAgent, 'Research AI agents');
const result = await handle.result();

This is the core philosophy of Helix Agents: define once, execute anywhere.

Next Steps ​

Released under the MIT License.