Skip to content

@helix-agents/agent-server

HTTP server for hosting agents remotely. Provides AgentServer for agent lifecycle management, HTTP adapters for routing, and SSE streaming utilities.

Installation

bash
npm install @helix-agents/agent-server

AgentServer

The main class for hosting agents over HTTP.

Constructor

typescript
import { AgentServer } from '@helix-agents/agent-server';

const server = new AgentServer(config: AgentServerConfig);

AgentServerConfig

typescript
interface AgentServerConfig {
  /** Registry of agents this server can run. Keys are agent type identifiers. */
  agents: Record<string, AgentConfig<any, any>>;

  /** State store for persisting session state */
  stateStore: SessionStateStore;

  /** Stream manager for real-time event streaming */
  streamManager: StreamManager;

  /** Executor (any runtime) */
  executor: AgentExecutor;
}

Methods

startAgent

Start a new agent execution. Idempotent — returns the existing session if already running.

typescript
const response = await server.startAgent(request: RemoteStartRequest): Promise<RemoteStartResponse>;

Parameters:

typescript
interface RemoteStartRequest {
  sessionId: string; // Unique session identifier
  agentType: string; // Must match a key in the agents registry
  message: string | UserInputMessage[]; // Initial user message (string or structured array)
  state?: Record<string, unknown>; // Optional initial custom state
  metadata?: Record<string, string>; // Optional metadata
}

Returns:

typescript
interface RemoteStartResponse {
  sessionId: string;
  streamId: string;
  runId: string;
}

Errors:

  • NOT_FOUND — Unknown agentType
  • ALREADY_COMPLETED — Session already completed or failed

resumeAgent

Resume an interrupted or paused agent execution.

typescript
const response = await server.resumeAgent(request: RemoteResumeRequest): Promise<RemoteStartResponse>;

Parameters:

typescript
interface RemoteResumeRequest {
  sessionId: string;
  message?: string | UserInputMessage[]; // Optional message to send on resume
}

Errors:

  • NOT_FOUND — Session not found
  • ALREADY_COMPLETED — Session already completed or failed

streamAgent

Subscribe to agent execution events. Returns an async iterable of transport events.

typescript
const events = server.streamAgent(
  sessionId: string,
  fromSequence?: number
): AsyncIterable<TransportEvent>;

Parameters:

  • sessionId — Session to stream
  • fromSequence — Skip events up to this sequence number (for reconnection)

Transport events:

typescript
type TransportEvent =
  | { type: 'chunk'; chunk: StreamChunk; sequence: number }
  | { type: 'end'; output?: unknown; state?: Record<string, unknown> }
  | { type: 'error'; error: string; recoverable: boolean };

getStatus

Get the current execution status for a session.

typescript
const status = await server.getStatus(sessionId: string): Promise<RemoteStatusResponse>;

Returns:

typescript
interface RemoteStatusResponse {
  sessionId: string;
  runId?: string;
  status: 'running' | 'completed' | 'failed' | 'interrupted' | 'paused';
  stepCount: number;
  output?: unknown;
  state?: Record<string, unknown>;
  error?: string;
  isExecuting: boolean;
  streamId: string;
  latestSequence: number; // Current stream position from StreamManager.getStreamInfo()
}

latestSequence reflects the actual stream position from the stream manager. Clients can use this value as fromSequence when reconnecting to /sse to avoid replaying already-seen events.

interruptAgent

Soft stop — the agent can be resumed later. Only works for sessions started or resumed on this AgentServer instance (see Handle Tracking).

typescript
await server.interruptAgent(sessionId: string, reason?: string): Promise<void>;

Errors:

  • NOT_FOUND — Session not found, or no active execution handle on this server instance

abortAgent

Hard stop — the agent cannot be resumed. Only works for sessions started or resumed on this AgentServer instance (see Handle Tracking).

typescript
await server.abortAgent(sessionId: string, reason?: string): Promise<void>;

Errors:

  • NOT_FOUND — Session not found, or no active execution handle on this server instance

Handle Tracking

AgentServer tracks active execution handles in memory. When startAgent() or resumeAgent() is called, the returned handle is stored so that interruptAgent() and abortAgent() can act on it. Handles are automatically cleaned up when execution completes (success or failure).

Limitation: Interrupt and abort only work on the same server instance that started the execution. After a server restart, in-flight handles are lost — interrupt/abort calls for pre-restart sessions return NOT_FOUND. The session state is still recoverable via resumeAgent() since state is persisted in the state store. For cross-instance interrupt support, use a durable runtime (Temporal or Cloudflare).

AgentServerError

Error class thrown by AgentServer methods.

typescript
class AgentServerError extends Error {
  code:
    | 'NOT_FOUND'
    | 'ALREADY_RUNNING'
    | 'ALREADY_COMPLETED'
    | 'INVALID_REQUEST'
    | 'INTERNAL_ERROR';
}

HTTP status mapping:

CodeHTTP Status
NOT_FOUND404
ALREADY_RUNNING409
ALREADY_COMPLETED409
INVALID_REQUEST400
INTERNAL_ERROR500

HTTP Adapters

createHttpAdapter

Creates a generic HTTP handler from an AgentServer instance. Framework-agnostic.

typescript
import { createHttpAdapter } from '@helix-agents/agent-server';

const handler: AgentHttpHandler = createHttpAdapter(server);

AgentHttpHandler signature:

typescript
type AgentHttpHandler = (request: AgentHttpRequest) => Promise<AgentHttpResponse>;

AgentHttpRequest:

typescript
interface AgentHttpRequest {
  method: string;
  path: string;
  body?: unknown;
  query?: Record<string, string>;
}

AgentHttpResponse:

typescript
interface AgentHttpResponse {
  status: number;
  headers: Record<string, string>;
  body: string | ReadableStream<Uint8Array>;
}

Routes handled:

MethodPathAction
POST/startStart agent execution
POST/resumeResume agent execution
GET/sseSSE event stream
GET/statusGet execution status
POST/interruptSoft stop
POST/abortHard stop

createExpressAdapter

Wraps an AgentHttpHandler as Express middleware.

typescript
import { createExpressAdapter } from '@helix-agents/agent-server';

const middleware = createExpressAdapter(handler: AgentHttpHandler);

app.use('/agents', middleware);

Features:

  • Uses req.path (relative to mount point) for routing
  • Handles both string and ReadableStream responses
  • Pipes SSE streams with proper disconnect cleanup

SSE Utilities

createSSEStream

Creates a Server-Sent Events stream with automatic heartbeats.

typescript
import { createSSEStream } from '@helix-agents/agent-server';

const { stream, writer } = createSSEStream();

SSEStreamWriter:

typescript
interface SSEStreamWriter {
  writeEvent(event: SSEEvent): void;
  close(): void;
}

interface SSEEvent {
  id?: string;
  event: string;
  data: string;
}

The stream automatically sends :heartbeat comments every 15 seconds. Cleanup runs on close() or stream cancellation.

HttpRemoteAgentTransport

Client-side transport for communicating with a remote AgentServer. Defined in @helix-agents/core.

typescript
import { HttpRemoteAgentTransport } from '@helix-agents/core';

Constructor

typescript
interface HttpTransportConfig {
  /** Base URL of the remote agent server */
  url: string;

  /** Static headers or async function returning headers */
  headers?:
    | Record<string, string>
    | (() => Record<string, string> | Promise<Record<string, string>>);

  /** Maximum retry attempts for failed requests (default: 3) */
  maxRetries?: number;

  /** Base delay between retries in ms (default: 1000) */
  retryBaseDelayMs?: number;
}

Methods

Implements the RemoteAgentTransport interface:

typescript
interface RemoteAgentTransport {
  start(request: RemoteStartRequest): Promise<RemoteStartResponse>;
  resume(request: RemoteResumeRequest): Promise<RemoteStartResponse>;
  stream(
    sessionId: string,
    options?: {
      fromSequence?: number;
      signal?: AbortSignal;
    }
  ): AsyncIterable<TransportEvent>;
  getStatus(sessionId: string): Promise<RemoteStatusResponse>;
  interrupt(sessionId: string, reason?: string): Promise<void>;
  abort(sessionId: string, reason?: string): Promise<void>;
}

Retry Behavior

  • 5xx errors — Retried with exponential backoff (baseDelay * 2^attempt)
  • 4xx errors — Not retried
  • Network errors — Retried with backoff

SSE Parsing

The transport uses native ReadableStream parsing (not EventSource) for SSE consumption. It handles:

  • Multi-line data: fields
  • id: fields for sequence tracking
  • event: fields for event type discrimination
  • :heartbeat comments (ignored)
  • AbortSignal for stream cancellation

createRemoteSubAgentTool

Factory function for creating a tool that delegates to a remote agent. Defined in @helix-agents/core.

typescript
import { createRemoteSubAgentTool } from '@helix-agents/core';

Signature

typescript
function createRemoteSubAgentTool<TInput, TOutput>(
  name: string,
  config: {
    description: string;
    inputSchema: z.ZodType<TInput>;
    outputSchema: z.ZodType<TOutput>;
    transport: RemoteAgentTransport;
    remoteAgentType: string;
    timeoutMs: number;
  }
): RemoteSubAgentTool<TInput, TOutput>;

Parameters

ParameterTypeDescription
namestringTool name (automatically prefixed with subagent__)
descriptionstringDescription shown to the LLM
inputSchemaz.ZodTypeZod schema for tool input
outputSchemaz.ZodTypeZod schema for expected output
transportRemoteAgentTransportTransport instance for HTTP communication
remoteAgentTypestringAgent type identifier (must match server registry key)
timeoutMsnumberMaximum execution time in milliseconds

RemoteSubAgentTool

typescript
interface RemoteSubAgentTool<TInput, TOutput> extends Tool<TInput, TOutput> {
  _isRemoteSubAgent: true;
  _remoteConfig: {
    transport: RemoteAgentTransport;
    remoteAgentType: string;
    timeoutMs: number;
  };
}

Input-to-Message Mapping

When the tool executes, the input is converted to a user message for the remote agent. Recognized field names (in priority order):

  1. message
  2. query
  3. text
  4. content
  5. Falls back to JSON.stringify(input)

Runtime Behavior

All three runtimes provide first-class remote sub-agent support:

  • JS Runtime — Intercepts via isRemoteSubAgentTool() for enhanced handling: stream proxying, SubSessionRef tracking with remote metadata, timeout management via AbortSignal, reconnection on resume
  • Temporal Runtime — Dedicated executeRemoteSubAgentCall activity with deterministic session IDs, crash recovery via transport.getStatus(), heartbeat-based reconnection, stream proxying, and interrupt propagation
  • Cloudflare Runtime — Dedicated executeRemoteSubAgentCall step with deterministic session IDs, crash recovery, stream proxying, interrupt propagation via abort-check interval, and timeout enforcement

The tool's built-in execute() method exists as a fallback but is not used by any of the pre-built runtimes.

Types

All types are exported from @helix-agents/agent-server:

typescript
import type {
  AgentServerConfig,
  AgentHttpHandler,
  AgentHttpRequest,
  AgentHttpResponse,
} from '@helix-agents/agent-server';

Transport protocol types are exported from @helix-agents/core:

typescript
import type {
  RemoteStartRequest,
  RemoteResumeRequest,
  RemoteStartResponse,
  RemoteStatusResponse,
  RemoteAgentErrorResponse,
  TransportEvent,
  RemoteAgentTransport,
} from '@helix-agents/core';

See Also

Released under the MIT License.