@helix-agents/core
Core agent framework functionality - types, factories, state management, LLM interface, store interfaces, stream utilities, and orchestration functions.
Installation
npm install @helix-agents/coreAgent Definition
defineAgent
Create an agent configuration.
import { defineAgent } from '@helix-agents/core';
import { z } from 'zod';
const MyAgent = defineAgent({
name: 'my-agent',
description: 'Does something useful',
systemPrompt: 'You are a helpful assistant.',
stateSchema: z.object({ count: z.number().default(0) }),
outputSchema: z.object({ result: z.string() }),
tools: [myTool],
llmConfig: { model: openai('gpt-4o') },
maxSteps: 10,
});Types:
AgentConfig<TStateSchema, TOutputSchema>- Full agent configurationAgent<TState, TOutput>- Shorthand for configured agentLLMConfig- Model configuration (model, temperature, maxOutputTokens, providerOptions)
Tool Definition
defineTool
Create a tool that agents can use.
import { defineTool } from '@helix-agents/core';
import { z } from 'zod';
const myTool = defineTool({
name: 'my_tool',
description: 'Does something',
inputSchema: z.object({ query: z.string() }),
outputSchema: z.object({ result: z.string() }),
execute: async (input, context) => {
return { result: 'done' };
},
});Types:
Tool<TInput, TOutput>- Tool definitionToolConfig<TInput, TOutput>- Tool configurationToolContext- Context passed to execute function
createSubAgentTool
Create a tool that invokes a sub-agent. The sub-agent must have an outputSchema defined.
import { createSubAgentTool, defineAgent } from '@helix-agents/core';
import { z } from 'zod';
// First, define the sub-agent with an outputSchema
const WorkerAgent = defineAgent({
name: 'worker-agent',
systemPrompt: 'You process tasks.',
outputSchema: z.object({ result: z.string() }),
llmConfig: { model: openai('gpt-4o') },
});
// Then create a sub-agent tool from it
const delegateTool = createSubAgentTool(
WorkerAgent, // The agent config (must have outputSchema)
z.object({ task: z.string() }), // Input schema for the tool
{ description: 'Delegate to worker' } // Optional description override
);Signature:
function createSubAgentTool<TInput, TOutputSchema>(
agent: AgentConfig<any, TOutputSchema>,
inputSchema: TInput,
options?: { description?: string }
): SubAgentTool<TInput, TOutputSchema>;Types:
SubAgentTool- Sub-agent tool definition
Tool Utilities
import {
SUBAGENT_TOOL_PREFIX, // 'subagent__'
FINISH_TOOL_NAME, // '__finish__'
isSubAgentTool, // Check if tool name is sub-agent
isFinishTool, // Check if tool name is __finish__
createFinishTool, // Create finish tool from schema
} from '@helix-agents/core';State Types
Session vs Run Identifiers
The framework uses two identifiers for tracking agent execution:
| Identifier | Purpose | Usage |
|---|---|---|
sessionId | Primary key for all state storage operations | Use for loading/saving state, messages, checkpoints |
runId | Execution metadata identifying a specific run | Use for logging, tracing, debugging |
Key distinction:
- A session is a conversation container. It contains all messages, custom state, and checkpoints.
- A run is a single execution within a session. When a session is interrupted and resumed, a new run starts but continues the same session.
- Multiple runs can occur within a single session (e.g.,
execute→interrupt→resumecreates 2 runs in 1 session).
Best practices:
- Use
sessionIdfor all state store operations - Pass the same
sessionIdto continue a conversation - Use
runIdfor tracking/tracing specific executions
AgentState
The full state structure for a running agent (used internally for execution and checkpoints).
interface AgentState<TState, TOutput> {
sessionId: string; // Primary key for session
runId: string; // Current run identifier
agentType: string;
streamId: string;
status: AgentStatus;
stepCount: number;
customState: TState;
messages: Message[];
output?: TOutput;
error?: string;
parentSessionId?: string;
subSessionRefs: SubSessionRef[]; // References to child agent runs
aborted: boolean;
abortReason?: string;
}Message Types
type Message = SystemMessage | UserMessage | AssistantMessage | ToolResultMessage;
interface SystemMessage {
role: 'system';
content: string;
metadata?: Record<string, unknown>; // Optional metadata
}
interface UserMessage {
role: 'user';
content: string;
metadata?: Record<string, unknown>; // Optional metadata
}
interface AssistantMessage {
role: 'assistant';
content?: string;
toolCalls?: ToolCallRequest[];
thinking?: ThinkingContent;
metadata?: Record<string, unknown>; // Optional metadata
}
interface ToolResultMessage {
role: 'tool';
toolCallId: string;
toolName: string;
content: string;
metadata?: Record<string, unknown>; // Optional metadata
}Message Metadata
All message types support an optional metadata field for attaching arbitrary data.
import {
COMMON_METADATA_KEYS,
stripMetadata,
filterByMetadata,
getMessagesWithMetadataKey,
} from '@helix-agents/core';COMMON_METADATA_KEYS
Standard metadata key constants:
| Key | Value | Description |
|---|---|---|
SOURCE | 'source' | Origin of the message (e.g., 'web-ui', 'api') |
HIDDEN | 'hidden' | Whether to hide from UI |
TIMESTAMP | 'timestamp' | When the message was created |
DURATION | 'duration' | Processing time in ms |
MODEL | 'model' | LLM model used |
IS_SUB_AGENT | 'isSubAgent' | Whether from a sub-agent |
PARENT_SESSION_ID | 'parentSessionId' | Parent session ID for sub-agents |
TAGS | 'tags' | Array of tags |
stripMetadata
Remove metadata from a message (returns a new object):
const cleanMessage = stripMetadata(message);
// cleanMessage.metadata is undefinedfilterByMetadata
Filter messages by metadata predicate:
const webMessages = filterByMetadata(messages, (m) => m.source === 'web-ui');
const hiddenMessages = filterByMetadata(messages, (m) => m.hidden === true);getMessagesWithMetadataKey
Find messages containing a specific metadata key:
const messagesWithSource = getMessagesWithMetadataKey(messages, 'source');State Helpers
import {
isAssistantMessage,
isToolResultMessage,
stripThinking, // Remove thinking from messages
getSubSessionRefsByType, // Filter sub-agent refs
getToolResultsFromMessages,
createInitialAgentState,
} from '@helix-agents/core';Stream Types
Stream Chunks
All chunk types emitted during agent execution:
type StreamChunk =
| TextDeltaChunk // Token-by-token text
| ThinkingChunk // Reasoning content
| ToolStartChunk // Tool invocation starting
| ToolEndChunk // Tool execution complete
| SubAgentStartChunk // Sub-agent starting
| SubAgentEndChunk // Sub-agent complete
| CustomEventChunk // Custom events from tools
| StatePatchChunk // RFC 6902 state patches
| ErrorChunk // Error events
| OutputChunk // Structured output
| RunInterruptedChunk // Agent interrupted
| RunResumedChunk // Agent resumed
| RunPausedChunk // Agent paused for confirmation
| CheckpointCreatedChunk // Checkpoint saved
| ExecutorSupersededChunk // Executor superseded
| StepCommittedChunk // Step changes committed
| StepDiscardedChunk // Step changes discarded
| StreamResyncChunk; // Stream recovery notificationAll stream chunks have a common base with optional step field for cleanup targeting:
interface BaseChunk {
type: string;
agentId: string;
agentType: string;
step?: number; // Step number for cleanup targeting (added for crash recovery)
}StreamResyncChunk
Emitted when a stream is resynced after crash recovery, rollback, or branching. Clients should use this to refresh their UI state.
interface StreamResyncChunk extends BaseChunk {
type: 'stream_resync';
checkpointId: string; // Checkpoint ID that was restored
stepCount: number; // Step count at the checkpoint
messageCount: number; // Message count at the checkpoint
fromSequence: number; // Stream sequence at the checkpoint
reason: 'crash_recovery' | 'rollback' | 'branch';
}Chunk Type Guards
import {
isTextDeltaChunk,
isThinkingChunk,
isToolStartChunk,
isToolEndChunk,
isSubAgentStartChunk,
isSubAgentEndChunk,
isCustomEventChunk,
isStatePatchChunk,
isErrorChunk,
isOutputChunk,
isStreamEnd,
// Interrupt/Resume type guards
isRunInterruptedChunk,
isRunResumedChunk,
isRunPausedChunk,
isCheckpointCreatedChunk,
isExecutorSupersededChunk,
isStepCommittedChunk,
isStepDiscardedChunk,
// Recovery type guard
isStreamResyncChunk,
} from '@helix-agents/core';Schemas
import {
StreamChunkSchema, // Zod schema for chunks
StreamMessageSchema, // Zod schema for messages
} from '@helix-agents/core';Runtime Types
StepResult
Result from an LLM generation step:
type StepResult<TOutput> =
| TextStepResult
| ToolCallsStepResult
| StructuredOutputStepResult<TOutput>
| ErrorStepResult;
interface TextStepResult {
type: 'text';
content: string;
thinking?: ThinkingContent;
shouldStop: boolean;
stopReason?: StopReason;
}
interface ToolCallsStepResult {
type: 'tool_calls';
content?: string;
toolCalls: ParsedToolCall[];
subAgentCalls: ParsedSubAgentCall[];
thinking?: ThinkingContent;
stopReason?: StopReason;
}StopReason
Normalized stop reasons from LLM providers:
type StopReason =
| 'end_turn' // Normal completion
| 'stop_sequence' // Hit stop sequence
| 'tool_use' // Tool call requested
| 'max_tokens' // Token limit (error)
| 'content_filter' // Safety filter (error)
| 'refusal' // Model refused (error)
| 'error' // Generation error
| 'unknown'; // Unrecognized
import { isErrorStopReason } from '@helix-agents/core';Execution Types
interface AgentExecutionHandle<TOutput> {
readonly sessionId: string;
readonly runId: string;
stream(): Promise<AsyncIterable<StreamChunk> | null>;
result(): Promise<AgentResult<TOutput>>;
abort(reason?: string): Promise<void>;
getState(): Promise<AgentState<unknown, TOutput>>;
canResume(): Promise<CanResumeResult>;
resume(options?: ResumeOptions): Promise<AgentExecutionHandle<TOutput>>;
retry(options?: RetryOptions): Promise<AgentExecutionHandle<TOutput>>;
}
interface AgentResult<TOutput> {
status: AgentStatus;
output?: TOutput;
error?: string;
}
interface CanResumeResult {
canResume: boolean;
reason?: string;
}RetryOptions
Options for the retry() method.
interface RetryOptions {
mode?: 'from_checkpoint' | 'from_start';
checkpointId?: string;
message?: string;
abortSignal?: AbortSignal;
}State Management
ImmerStateTracker
Track state mutations with RFC 6902 patches.
import { ImmerStateTracker, createImmerStateTracker } from '@helix-agents/core';
const tracker = new ImmerStateTracker(initialState, {
arrayDeltaMode: 'append_only', // or 'full_replace'
});
// Make mutations
tracker.update((draft) => {
draft.notes.push({ content: 'New note' });
});
// Get patches
const patches = tracker.getPatches();
// Get current state
const currentState = tracker.getState();
// Reset patch tracking
tracker.resetPatches();Types:
ImmerStateTrackerOptions- Configuration optionsMergeChanges- Function type for merging state
convertImmerPatchToRFC6902
Convert Immer patches to RFC 6902 format.
import { convertImmerPatchToRFC6902 } from '@helix-agents/core';
const rfc6902Patch = convertImmerPatchToRFC6902(immerPatch);LLM Module
LLMAdapter Interface
Interface for LLM providers:
interface LLMAdapter {
generateStep(input: LLMGenerateInput): Promise<StepResult<unknown>>;
}
interface LLMGenerateInput {
messages: Message[];
tools: Tool[];
config: LLMConfig;
abortSignal?: AbortSignal;
callbacks?: LLMStreamCallbacks;
agentId: string;
agentType: string;
}
interface LLMStreamCallbacks {
onTextDelta?: (delta: string) => void;
onThinking?: (content: string, isComplete: boolean) => void;
onToolCall?: (toolCall: ParsedToolCall) => void;
onError?: (error: Error) => void;
}MockLLMAdapter
Mock adapter for testing:
import { MockLLMAdapter } from '@helix-agents/core';
const mock = new MockLLMAdapter([
{ type: 'text', content: 'Hello!' },
{ type: 'tool_calls', toolCalls: [{ id: 't1', name: 'search', arguments: {} }] },
{ type: 'structured_output', output: { result: 'done' } },
]);Stop Reason Mapping
import {
mapVercelFinishReason,
mapOpenAIFinishReason,
mapAnthropicStopReason,
mapGeminiFinishReason,
} from '@helix-agents/core';Store Interfaces
SessionStateStore
Interface for session-centric state persistence. Uses sessionId as the primary key for all operations. Supports atomic operations for safe concurrent modifications from parallel tool execution.
interface SessionStateStore {
// Session lifecycle
createSession<TState>(sessionId: string, options?: CreateSessionOptions<TState>): Promise<void>;
sessionExists(sessionId: string): Promise<boolean>;
deleteSession(sessionId: string): Promise<void>;
// State operations
loadState<TState, TOutput>(sessionId: string): Promise<SessionState<TState, TOutput> | null>;
saveState(sessionId: string, state: UntypedSessionState): Promise<void>;
// Atomic operations (safe for parallel tool execution)
appendMessages(sessionId: string, messages: Message[]): Promise<void>;
mergeCustomState(sessionId: string, changes: MergeChanges): Promise<{ warnings: string[] }>;
updateStatus(
sessionId: string,
status: AgentStatus,
context?: { interruptContext?: InterruptContext }
): Promise<void>;
incrementStepCount(sessionId: string): Promise<number>;
// Sub-agent management
addSubSessionRefs(
sessionId: string,
refs: Array<{
subSessionId: string;
agentType: string;
parentToolCallId: string;
startedAt: number;
}>
): Promise<void>;
updateSubSessionRef(
sessionId: string,
update: {
subSessionId: string;
status: 'running' | 'completed' | 'failed';
completedAt?: number;
}
): Promise<void>;
getSubSessionRefs(sessionId: string): Promise<SubSessionRef[]>;
// Message queries
getMessages(sessionId: string, options?: GetMessagesOptions): Promise<PaginatedMessages>;
getMessageCount(sessionId: string): Promise<number>;
// Message cleanup (for crash recovery)
truncateMessages(sessionId: string, messageCount: number): Promise<void>;
// Checkpoint operations
getCheckpoint(checkpointId: string): Promise<Checkpoint | null>;
getLatestCheckpoint(sessionId: string): Promise<Checkpoint | null>;
listCheckpoints(sessionId: string, options?: ListCheckpointsOptions): Promise<PaginatedCheckpoints>;
createCheckpoint(sessionId: string, params: CreateCheckpointParams): Promise<string>;
// Staging operations (for atomic step commits)
stageChanges(sessionId: string, stepId: string, changes: StagedChanges): Promise<void>;
getStaged(sessionId: string, stepId: string): Promise<StagedChanges[] | null>;
promoteStaging(sessionId: string, stepId: string): Promise<void>;
discardStaging(sessionId: string, stepId: string): Promise<void>;
// Distributed coordination
compareAndSetStatus(
sessionId: string,
expectedStatuses: AgentStatus[],
newStatus: AgentStatus,
context?: { interruptContext?: InterruptContext }
): Promise<boolean>;
incrementResumeCount(sessionId: string): Promise<number>;
// Run tracking
createRun(sessionId: string, runId: string, metadata: RunMetadata): Promise<void>;
updateRunStatus(runId: string, status: RunStatus, updates?: RunStatusUpdate): Promise<void>;
getCurrentRun(sessionId: string): Promise<RunRecord | null>;
listRuns(sessionId: string): Promise<RunRecord[]>;
}
interface ListCheckpointsOptions {
offset?: number;
limit?: number;
}
### Run Tracking Types
```typescript
// Status of a run
type RunStatus = 'running' | 'completed' | 'failed' | 'interrupted';
// Metadata when creating a run
interface RunMetadata {
turn: number; // Turn number (1 for execute, incremented for resume)
startSequence?: number; // Stream sequence at start
}
// Updates when changing run status
interface RunStatusUpdate {
stepCount?: number;
completedAt?: number;
error?: string;
}
// Full run record
interface RunRecord {
runId: string;
sessionId: string;
turn: number;
status: RunStatus;
stepCount: number;
startedAt: number;
completedAt?: number;
error?: string;
}Run Tracking Lifecycle:
- On
execute():createRun()is called withturn: 1 - On
resume():createRun()is called with incremented turn number - On completion/failure/interrupt:
updateRunStatus()is called with final status
Query Methods:
getCurrentRun(sessionId): Returns the most recent (active) run for a sessionlistRuns(sessionId): Returns all runs for a session (for debugging/auditing)
interface PaginatedCheckpoints { items: CheckpointMeta[]; total: number; hasMore: boolean; }
interface GetMessagesOptions { offset?: number; // Starting position (default: 0) limit?: number; // Max messages (default: 50) includeThinking?: boolean; // Include thinking content (default: true) }
interface PaginatedMessages { messages: Message[]; total: number; offset: number; limit: number; hasMore: boolean; }
### StreamManager
Interface for real-time streaming:
```typescript
interface StreamManager {
// Create a writer for emitting chunks (implicitly creates stream)
createWriter(streamId: string, runId: string, agentType: string): Promise<StreamWriter>;
// Create a reader to consume chunks
createReader(streamId: string): Promise<StreamReader | null>;
// Create a resumable reader (optional, for crash recovery)
createResumableReader?(
streamId: string,
options?: ResumableReaderOptions
): Promise<ResumableStreamReader | null>;
// Mark stream as complete
endStream(streamId: string, output?: unknown): Promise<void>;
// Mark stream as failed
failStream(streamId: string, error: string): Promise<void>;
// Stream cleanup (for crash recovery)
cleanupToStep(streamId: string, stepCount: number): Promise<void>;
resetStream(streamId: string): Promise<void>;
// Stream info (for snapshot status)
getStreamInfo?(streamId: string): Promise<StreamInfo | null>;
}
interface StreamInfo {
status: 'active' | 'paused' | 'ended' | 'failed';
latestSequence: number;
chunkCount: number;
}
interface StreamWriter {
write(chunk: StreamChunk): Promise<void>;
close(): Promise<void>; // Closes this writer, NOT the stream
}
interface StreamReader extends AsyncIterable<StreamChunk> {
[Symbol.asyncIterator](): AsyncIterator<StreamChunk>;
close(): Promise<void>;
}
interface ResumableStreamReader extends StreamReader {
readonly currentSequence: number;
readonly totalChunks: number;
readonly latestSequence: number;
}Stream Utilities
Stream Filters
import {
filterByAgentId,
filterByAgentType,
filterByType,
excludeTypes,
filterWith,
combineStreams,
take,
skip,
collectText,
collectAll,
} from '@helix-agents/core';
// Filter by agent
const filtered = filterByAgentId(stream, 'agent-123');
// Filter by chunk type
const textOnly = filterByType(stream, ['text_delta']);
// Exclude types
const noThinking = excludeTypes(stream, ['thinking']);
// Collect all text
const fullText = await collectText(stream);State Streaming
import { CustomStateStreamer, createStateStreamer } from '@helix-agents/core';
const streamer = createStateStreamer({
streamManager,
streamId: 'run-123',
});
// Emit state patches
await streamer.emitPatch(patches);State Projection
import { createStateProjection, StreamProjector } from '@helix-agents/core';
// Project subset of state
const projection = createStateProjection<FullState, { count: number }>((state) => ({
count: state.count,
}));Resumable Stream Handler
import { createResumableStreamHandler, extractResumePosition } from '@helix-agents/core';
const handler = createResumableStreamHandler({
streamManager,
});
// Handle request with resume support
const response = await handler.handle({
streamId: 'run-123',
resumeAt: extractResumePosition(lastEventId),
});Orchestration
initializeAgentState
Create initial state from input:
import { initializeAgentState } from '@helix-agents/core';
const state = initializeAgentState({
agent,
input: 'Hello', // or { message: 'Hello', state: { ... } }
runId: 'run-123',
streamId: 'run-123',
parentSessionId: undefined,
});buildMessagesForLLM
Prepare messages with system prompt:
import { buildMessagesForLLM } from '@helix-agents/core';
const messages = buildMessagesForLLM(state.messages, agent.systemPrompt, state.customState);buildEffectiveTools
Get tools including __finish__:
import { buildEffectiveTools } from '@helix-agents/core';
const tools = buildEffectiveTools(agent);planStepProcessing
Analyze LLM result and plan actions:
import { planStepProcessing } from '@helix-agents/core';
const plan = planStepProcessing(stepResult, {
outputSchema: agent.outputSchema,
});
// plan.assistantMessagePlan - For creating assistant message
// plan.pendingToolCalls - Tools to execute
// plan.pendingSubAgentCalls - Sub-agents to invoke
// plan.statusUpdate - Status change to apply
// plan.isTerminal - Whether execution should stop
// plan.output - Parsed output (if __finish__ called)shouldStopExecution
Check if agent should stop:
import { shouldStopExecution, determineFinalStatus } from '@helix-agents/core';
const shouldStop = shouldStopExecution(stepResult, stepCount, {
maxSteps: 10,
stopWhen: (result) => result.type === 'text' && result.content.includes('DONE'),
});
const finalStatus = determineFinalStatus(stepResult);Message Builders
import {
createAssistantMessage,
createToolResultMessage,
createSubAgentResultMessage,
} from '@helix-agents/core';
const assistantMsg = createAssistantMessage(plan.assistantMessagePlan);
const toolResult = createToolResultMessage({
toolCallId: 'tc1',
toolName: 'search',
result: { data: 'found' },
success: true,
});Recovery
recoverConversation
Resume a conversation from stored state:
import { recoverConversation, loadConversationMessages } from '@helix-agents/core';
const { messages, canResume } = await recoverConversation({
stateStore,
runId: 'run-123',
});
// Or just load messages
const messages = await loadConversationMessages(stateStore, 'run-123');Error Types
Agent Errors
Errors for interrupt/resume and distributed coordination:
import {
AgentAlreadyRunningError,
AgentNotResumableError,
FencingTokenMismatchError,
StaleStateError,
ExecutorSupersededError,
} from '@helix-agents/core';AgentAlreadyRunningError
Thrown when attempting to execute/resume an agent that is already running.
class AgentAlreadyRunningError extends Error {
readonly sessionId: string;
readonly currentStatus: string;
}AgentNotResumableError
Thrown when attempting to resume an agent that cannot be resumed.
class AgentNotResumableError extends Error {
readonly sessionId: string;
readonly currentStatus: string;
}StaleStateError
Thrown when a state save fails due to version mismatch (optimistic concurrency).
class StaleStateError extends Error {
readonly sessionId: string;
readonly expectedVersion: number;
readonly actualVersion: number;
}ExecutorSupersededError
Thrown when an executor is superseded by another executor. This is a graceful shutdown signal, not a failure.
class ExecutorSupersededError extends Error {
readonly sessionId: string;
}FencingTokenMismatchError
Thrown when a fencing token doesn't match expected value (split-brain detection).
class FencingTokenMismatchError extends Error {
readonly sessionId: string;
readonly expectedToken: number;
}Checkpoints
Types and utilities for checkpoint management.
Checkpoint Types
import type { Checkpoint, CheckpointMeta, ParsedCheckpointId } from '@helix-agents/core';
interface Checkpoint<TState = unknown, TOutput = unknown> {
id: string; // Unique checkpoint ID
sessionId: string; // Session this checkpoint belongs to
stepCount: number; // Step count when created
timestamp: number; // Creation time (ms since epoch)
state: AgentState<TState, TOutput>; // Complete agent state
messageCount: number; // Message count at checkpoint (for recovery coordination)
streamSequence: number; // Stream sequence at checkpoint (for resumption)
}
interface CheckpointMeta {
id: string;
sessionId: string;
stepCount: number;
timestamp: number;
status: AgentStatus;
}Checkpoint ID Utilities
import {
generateCheckpointId,
parseCheckpointId,
CHECKPOINT_ID_VERSION,
CHECKPOINT_ID_PREFIX,
} from '@helix-agents/core';
// Generate a new checkpoint ID
const id = generateCheckpointId('session-123', 5);
// Returns: 'cpv1-session-123-s5-t1703123456789-a1b2c3'
// Parse a checkpoint ID
const parsed = parseCheckpointId(id);
// Returns: { version: 1, sessionId: 'session-123', stepCount: 5, timestamp: 1703123456789, random: 'a1b2c3' }Lock Manager
Interface for distributed lock coordination.
LockManager Interface
import type {
LockManager,
DistributedLock,
LockAcquisitionResult,
AcquireOptions,
} from '@helix-agents/core';
interface LockManager {
readonly holderId: string;
acquire(resource: string, options: AcquireOptions): Promise<LockAcquisitionResult>;
extend(lock: DistributedLock, ttlMs: number): Promise<DistributedLock | null>;
release(lock: DistributedLock): Promise<boolean>;
withLock<T>(
resource: string,
options: { ttlMs: number; heartbeatMs?: number },
fn: (lock: DistributedLock) => Promise<T>
): Promise<T>;
}
interface DistributedLock {
readonly resource: string;
readonly lockId: string;
readonly fencingToken: number; // Monotonic token for split-brain prevention
readonly acquiredAt: number;
readonly expiresAt: number;
readonly holderId: string;
}
interface AcquireOptions {
ttlMs: number; // Lock TTL in milliseconds
wait?: boolean; // Wait for lock if held
waitTimeoutMs?: number; // Max wait time
}NoOpLockManager
No-op implementation for single-process deployments:
import { NoOpLockManager } from '@helix-agents/core';
const lockManager = new NoOpLockManager();
// All operations succeed immediatelyLock Errors
import { LockNotAcquiredError, LockLostError } from '@helix-agents/core';
class LockNotAcquiredError extends Error {
readonly resource: string;
readonly heldBy: string;
}
class LockLostError extends Error {
readonly resource: string;
readonly fencingToken: number;
}Status Types
The framework uses different status types for different domains:
SessionStatus (Storage)
Used for persistent session state in SessionState.status:
type SessionStatus =
| 'active' // Session is ready for execution
| 'completed' // Session finished successfully
| 'failed' // Session encountered an error
| 'interrupted' // User interrupted, resumable
| 'paused'; // Waiting for input (e.g., tool confirmation)AgentStatusValue (Runtime)
Used during agent execution:
import { AgentStatusValues } from '@helix-agents/core';
AgentStatusValues.RUNNING; // 'running' - currently executing
AgentStatusValues.COMPLETED; // 'completed' - finished successfully
AgentStatusValues.FAILED; // 'failed' - encountered error
AgentStatusValues.PAUSED; // 'paused' - waiting for confirmation
AgentStatusValues.WAITING_TOOL; // 'waiting_tool' - awaiting tool results
AgentStatusValues.INTERRUPTED; // 'interrupted' - user interruptedResumableStreamStatus (Streams)
Used for stream state in FrontendSnapshot.status:
type ResumableStreamStatus =
| 'active' // Stream is active, events flowing
| 'paused' // Stream is paused
| 'ended' // Stream completed (note: 'ended', not 'completed')
| 'failed'; // Stream failedSubSessionStatusValue (Sub-agents)
Used for tracking sub-agent lifecycle:
import { SubSessionStatusValues } from '@helix-agents/core';
SubSessionStatusValues.RUNNING; // 'running'
SubSessionStatusValues.COMPLETED; // 'completed'
SubSessionStatusValues.FAILED; // 'failed'Status Conversion
The framework provides helpers to convert between storage and runtime statuses:
import {
sessionStatusToAgentStatus,
agentStatusToSessionStatus,
} from '@helix-agents/core';
// Storage → Runtime
sessionStatusToAgentStatus('active'); // Returns 'running'
// Runtime → Storage
agentStatusToSessionStatus('running'); // Returns 'active'
agentStatusToSessionStatus('waiting_tool'); // Returns 'active'Key distinction:
SessionStatususes'active'for ready/running state (storage perspective)AgentStatusValueuses'running'for active execution (runtime perspective)ResumableStreamStatususes'ended'for completion (stream lifecycle)
InterruptContext
Context stored when an agent is interrupted:
import type { InterruptContext } from '@helix-agents/core';
import { InterruptContextSchema } from '@helix-agents/core';
interface InterruptContext {
reason?: string; // Why interrupted (e.g., 'user_requested')
pendingToolCallId?: string; // Tool call waiting for confirmation
pendingToolName?: string; // Tool name
stepCount: number; // Step count at interruption
timestamp: number; // When interrupted
}
// Zod schema for validation
const validated = InterruptContextSchema.parse(context);Utilities
createToolContext
Create a tool execution context:
import { createToolContext } from '@helix-agents/core';
const context = createToolContext({
agentId: 'run-123',
agentType: 'my-agent',
stateTracker,
streamWriter,
});Logger Types
import { noopLogger, consoleLogger, type Logger } from '@helix-agents/core';
const logger: Logger = {
debug: (msg, data) => { ... },
info: (msg, data) => { ... },
warn: (msg, data) => { ... },
error: (msg, data) => { ... },
};Type Re-exports
The package re-exports Draft from Immer for tool authors:
import type { Draft } from '@helix-agents/core';
// Use in updateState callbacks
context.updateState((draft: Draft<MyState>) => {
draft.items.push(newItem);
});