Skip to content

@helix-agents/runtime-js

JavaScript runtime for in-process agent execution. Non-durable execution suitable for development, testing, and single-process deployments.

Installation

bash
npm install @helix-agents/runtime-js

JSAgentExecutor

The main executor class for running agents in-process.

Constructor

typescript
import { JSAgentExecutor } from '@helix-agents/runtime-js';

const executor = new JSAgentExecutor(
  stateStore, // StateStore implementation
  streamManager, // StreamManager implementation
  llmAdapter // LLMAdapter implementation
);

execute

Start executing an agent.

typescript
const handle = await executor.execute(MyAgent, 'Hello, agent!');

// Or with initial state
const handle = await executor.execute(MyAgent, {
  message: 'Hello',
  state: { userId: 'user-123' },
});

Parameters:

  • agent - Agent configuration from defineAgent()
  • input - String message or { message: string, state?: Partial<TState> }

Returns: AgentExecutionHandle<TOutput>

getHandle

Get a handle to an existing execution.

typescript
const handle = await executor.getHandle(MyAgent, 'run-123');

if (handle) {
  const result = await handle.result();
}

Parameters:

  • agent - Agent configuration
  • runId - Run identifier

Returns: AgentExecutionHandle | null

canResume

Check if an execution can be resumed.

typescript
const result = await executor.canResume(MyAgent, 'run-123');

if (result.canResume) {
  // Can continue execution
} else {
  console.log('Cannot resume:', result.reason);
}

Returns:

typescript
interface CanResumeResult {
  canResume: boolean;
  reason?: string;
  state?: AgentState;
}

resume

Resume a paused or interrupted execution.

typescript
const handle = await executor.resume(MyAgent, 'run-123', {
  additionalMessage: 'Continue with this context',
});

AgentExecutionHandle

Handle returned by execute() for interacting with a running agent.

Properties

typescript
handle.runId; // Unique run identifier (readonly)

stream

Get a stream of events from the execution. Returns null if streaming is not available.

typescript
const stream = await handle.stream();

if (stream) {
  for await (const chunk of stream) {
    switch (chunk.type) {
      case 'text_delta':
        process.stdout.write(chunk.delta);
        break;
      case 'tool_start':
        console.log(`Tool: ${chunk.toolName}`);
        break;
      case 'tool_end':
        console.log(`Result: ${JSON.stringify(chunk.result)}`);
        break;
    }
  }
}

result

Wait for the execution to complete and get the result.

typescript
const result = await handle.result();

if (result.status === 'completed') {
  console.log('Output:', result.output);
} else if (result.status === 'failed') {
  console.error('Error:', result.error);
}

Returns:

typescript
interface AgentResult<TOutput> {
  status: 'running' | 'completed' | 'failed' | 'paused' | 'waiting_tool';
  output?: TOutput;
  error?: string;
}

abort

Cancel the execution.

typescript
await handle.abort('User requested cancellation');

Usage Example

typescript
import { JSAgentExecutor } from '@helix-agents/runtime-js';
import { InMemoryStateStore, InMemoryStreamManager } from '@helix-agents/store-memory';
import { VercelAIAdapter } from '@helix-agents/llm-vercel';
import { defineAgent, defineTool } from '@helix-agents/core';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

// Define agent
const MyAgent = defineAgent({
  name: 'my-agent',
  systemPrompt: 'You are a helpful assistant.',
  outputSchema: z.object({ response: z.string() }),
  tools: [
    defineTool({
      name: 'greet',
      description: 'Generate a greeting',
      inputSchema: z.object({ name: z.string() }),
      outputSchema: z.object({ greeting: z.string() }),
      execute: async ({ name }) => ({ greeting: `Hello, ${name}!` }),
    }),
  ],
  llmConfig: { model: openai('gpt-4o-mini') },
});

// Create executor
const executor = new JSAgentExecutor(
  new InMemoryStateStore(),
  new InMemoryStreamManager(),
  new VercelAIAdapter()
);

// Execute agent
const handle = await executor.execute(MyAgent, 'Greet John');

// Stream results
for await (const chunk of (await handle.stream()) ?? []) {
  if (chunk.type === 'text_delta') {
    process.stdout.write(chunk.delta);
  }
}

// Get final result
const result = await handle.result();
console.log('\nOutput:', result.output);

Behavior

Parallel Tool Execution

The JS runtime executes independent tool calls in parallel:

typescript
// If LLM requests multiple tool calls, they run concurrently
const tools = [searchTool, fetchTool, analyzeTool];
// All three execute in parallel

Sub-Agent Handling

Sub-agents are executed recursively in the same process:

typescript
// When a sub-agent tool is invoked:
// 1. New execution context is created
// 2. Sub-agent runs to completion
// 3. Result is returned to parent

Abort Handling

Abort signals are propagated to tool executions:

typescript
defineTool({
  execute: async (input, context) => {
    // Check abort signal
    if (context.abortSignal?.aborted) {
      throw new Error('Execution aborted');
    }

    // Long-running operation
    await doWork();
  },
});

Limitations

  • No crash recovery - If the process dies, execution state is lost
  • Single process - No distribution across workers
  • No timeout isolation - Tool timeouts must be handled manually

For production workloads requiring durability, use @helix-agents/runtime-temporal.

See Also

Released under the MIT License.