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
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

¹ A SECOND execute(agent, msg, { sessionId }) against an already-completed session continues the conversation into a new turn. On CFW Workflows this is blocked by the write-once workflow-instance-id model (agent__<name>__<sessionId> cannot be recreated after the run closes) — tracked in GitLab #109. On all other runtimes, 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 suffix id (…__continue__<stepCount>__<toolCallId>) — the write-once base instance id that blocks root execute(sessionId) on CFW Workflows is sidestepped. See Sub-Agents → Re-consulting a persistent companion and the v0.40 migration guide.

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)
ConcurrencyStatus check + OCCWorkflow ID uniquenessWorkflow instanceDO isolationWorkflow ID + Postgres

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
Content ReplayBuilt-inBuilt-inBuilt-inBuilt-inBuilt-in
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, DOWorkflowExecutor } from '@helix-agents/runtime-cloudflare';

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

const executor = new DOWorkflowExecutor({
  agentNamespace: env.AGENTS,
});

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.