@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
npm install @helix-agents/agent-serverAgentServer
The main class for hosting agents over HTTP.
Constructor
import { AgentServer } from '@helix-agents/agent-server';
const server = new AgentServer(config: AgentServerConfig);AgentServerConfig
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.
const response = await server.startAgent(request: RemoteStartRequest): Promise<RemoteStartResponse>;Parameters:
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:
interface RemoteStartResponse {
sessionId: string;
streamId: string;
runId: string;
}Errors:
NOT_FOUND— UnknownagentTypeALREADY_COMPLETED— Session already completed or failed
resumeAgent
Resume an interrupted or paused agent execution.
const response = await server.resumeAgent(request: RemoteResumeRequest): Promise<RemoteStartResponse>;Parameters:
interface RemoteResumeRequest {
sessionId: string;
message?: string | UserInputMessage[]; // Optional message to send on resume
}Errors:
NOT_FOUND— Session not foundALREADY_COMPLETED— Session already completed or failed
streamAgent
Subscribe to agent execution events. Returns an async iterable of transport events.
const events = server.streamAgent(
sessionId: string,
fromSequence?: number
): AsyncIterable<TransportEvent>;Parameters:
sessionId— Session to streamfromSequence— Skip events up to this sequence number (for reconnection)
Transport events:
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.
const status = await server.getStatus(sessionId: string): Promise<RemoteStatusResponse>;Returns:
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).
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).
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.
class AgentServerError extends Error {
code:
| 'NOT_FOUND'
| 'ALREADY_RUNNING'
| 'ALREADY_COMPLETED'
| 'INVALID_REQUEST'
| 'INTERNAL_ERROR';
}HTTP status mapping:
| Code | HTTP Status |
|---|---|
NOT_FOUND | 404 |
ALREADY_RUNNING | 409 |
ALREADY_COMPLETED | 409 |
INVALID_REQUEST | 400 |
INTERNAL_ERROR | 500 |
HTTP Adapters
createHttpAdapter
Creates a generic HTTP handler from an AgentServer instance. Framework-agnostic.
import { createHttpAdapter } from '@helix-agents/agent-server';
const handler: AgentHttpHandler = createHttpAdapter(server);AgentHttpHandler signature:
type AgentHttpHandler = (request: AgentHttpRequest) => Promise<AgentHttpResponse>;AgentHttpRequest:
interface AgentHttpRequest {
method: string;
path: string;
body?: unknown;
query?: Record<string, string>;
}AgentHttpResponse:
interface AgentHttpResponse {
status: number;
headers: Record<string, string>;
body: string | ReadableStream<Uint8Array>;
}Routes handled:
| Method | Path | Action |
|---|---|---|
| POST | /start | Start agent execution |
| POST | /resume | Resume agent execution |
| GET | /sse | SSE event stream |
| GET | /status | Get execution status |
| POST | /interrupt | Soft stop |
| POST | /abort | Hard stop |
createExpressAdapter
Wraps an AgentHttpHandler as Express middleware.
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
ReadableStreamresponses - Pipes SSE streams with proper disconnect cleanup
SSE Utilities
createSSEStream
Creates a Server-Sent Events stream with automatic heartbeats.
import { createSSEStream } from '@helix-agents/agent-server';
const { stream, writer } = createSSEStream();SSEStreamWriter:
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.
import { HttpRemoteAgentTransport } from '@helix-agents/core';Constructor
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:
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 trackingevent:fields for event type discrimination:heartbeatcomments (ignored)AbortSignalfor stream cancellation
createRemoteSubAgentTool
Factory function for creating a tool that delegates to a remote agent. Defined in @helix-agents/core.
import { createRemoteSubAgentTool } from '@helix-agents/core';Signature
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
| Parameter | Type | Description |
|---|---|---|
name | string | Tool name (automatically prefixed with subagent__) |
description | string | Description shown to the LLM |
inputSchema | z.ZodType | Zod schema for tool input |
outputSchema | z.ZodType | Zod schema for expected output |
transport | RemoteAgentTransport | Transport instance for HTTP communication |
remoteAgentType | string | Agent type identifier (must match server registry key) |
timeoutMs | number | Maximum execution time in milliseconds |
RemoteSubAgentTool
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):
messagequerytextcontent- 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,SubSessionReftracking withremotemetadata, timeout management viaAbortSignal, reconnection on resume - Temporal Runtime — Dedicated
executeRemoteSubAgentCallactivity with deterministic session IDs, crash recovery viatransport.getStatus(), heartbeat-based reconnection, stream proxying, and interrupt propagation - Cloudflare Runtime — Dedicated
executeRemoteSubAgentCallstep 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:
import type {
AgentServerConfig,
AgentHttpHandler,
AgentHttpRequest,
AgentHttpResponse,
} from '@helix-agents/agent-server';Transport protocol types are exported from @helix-agents/core:
import type {
RemoteStartRequest,
RemoteResumeRequest,
RemoteStartResponse,
RemoteStatusResponse,
RemoteAgentErrorResponse,
TransportEvent,
RemoteAgentTransport,
} from '@helix-agents/core';See Also
- Remote Agents Guide — Concepts, patterns, and production setup
- Remote Agents Example — Full working example
- Sub-Agents Guide — Local sub-agent orchestration