FleetManager API Reference
The FleetManager class is the primary entry point for using herdctl programmatically. It provides a simple, high-level API to initialize and run a fleet of agents with minimal configuration.
Installation
Section titled “Installation”npm install @herdctl/core# orpnpm add @herdctl/coreQuick Start
Section titled “Quick Start”import { FleetManager } from '@herdctl/core';
const manager = new FleetManager({ configPath: './herdctl.yaml', stateDir: './.herdctl',});
await manager.initialize();await manager.start();
// Subscribe to eventsmanager.on('job:created', (payload) => { console.log(`Job ${payload.job.id} created for ${payload.agentName}`);});
// Graceful shutdownprocess.on('SIGINT', async () => { await manager.stop(); process.exit(0);});Constructor
Section titled “Constructor”new FleetManager(options)
Section titled “new FleetManager(options)”Creates a new FleetManager instance.
const manager = new FleetManager(options: FleetManagerOptions);Parameters
Section titled “Parameters”| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
options.stateDir | string | Yes | — | Path to the state directory (e.g., .herdctl). Created if it doesn’t exist. Stores job artifacts, session state, and logs. |
options.configPath | string | No | Auto-discover | Path to herdctl.yaml. Can be absolute, relative, or a directory path. If not provided, searches up from cwd. |
options.logger | FleetManagerLogger | No | Console logger | Custom logger with debug, info, warn, error methods. |
options.checkInterval | number | No | 1000 | Interval in milliseconds between scheduler checks. |
Example
Section titled “Example”import { FleetManager } from '@herdctl/core';
// Minimal configurationconst manager = new FleetManager({ stateDir: './.herdctl',});
// Full configurationconst manager = new FleetManager({ configPath: './config/herdctl.yaml', stateDir: './.herdctl', checkInterval: 5000, // 5 seconds logger: { debug: (msg) => console.debug(`[DEBUG] ${msg}`), info: (msg) => console.info(`[INFO] ${msg}`), warn: (msg) => console.warn(`[WARN] ${msg}`), error: (msg) => console.error(`[ERROR] ${msg}`), },});Lifecycle Methods
Section titled “Lifecycle Methods”initialize()
Section titled “initialize()”Initializes the fleet manager by loading configuration and preparing the state directory.
await manager.initialize(): Promise<void>Description
Section titled “Description”This method:
- Loads and validates the configuration file
- Initializes the state directory structure
- Prepares the scheduler (but does not start it)
After initialization, the fleet manager is ready to start.
Throws
Section titled “Throws”| Error | Condition |
|---|---|
FleetManagerStateError | Already initialized or running |
FleetManagerConfigError | Configuration is invalid or not found |
FleetManagerStateDirError | State directory cannot be created |
Example
Section titled “Example”const manager = new FleetManager({ configPath: './herdctl.yaml', stateDir: './.herdctl',});
try { await manager.initialize(); console.log(`Loaded ${manager.state.agentCount} agents`);} catch (error) { if (error instanceof FleetManagerConfigError) { console.error('Configuration error:', error.message); }}start()
Section titled “start()”Starts the fleet manager scheduler, which begins processing agent schedules.
await manager.start(): Promise<void>Description
Section titled “Description”This begins the scheduler, which will:
- Check agent schedules at the configured interval
- Trigger agents when their schedules are due
- Track schedule state in the state directory
Throws
Section titled “Throws”| Error | Condition |
|---|---|
FleetManagerStateError | Not initialized |
Example
Section titled “Example”await manager.initialize();await manager.start();
// The manager is now running and processing schedulesmanager.on('schedule:triggered', (payload) => { console.log(`Triggered ${payload.agentName}/${payload.scheduleName}`);});stop(options?)
Section titled “stop(options?)”Gracefully stops the fleet manager.
await manager.stop(options?: FleetManagerStopOptions): Promise<void>Parameters
Section titled “Parameters”| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
options.waitForJobs | boolean | No | true | Wait for running jobs to complete before stopping |
options.timeout | number | No | 30000 | Maximum time in ms to wait for jobs to complete |
options.cancelOnTimeout | boolean | No | false | Cancel jobs that don’t complete within timeout |
options.cancelTimeout | number | No | 10000 | Time in ms for each job to respond to SIGTERM before SIGKILL |
Description
Section titled “Description”This will:
- Signal the scheduler to stop accepting new triggers
- Wait for running jobs to complete (with timeout)
- If timeout is reached and
cancelOnTimeoutis true, cancel remaining jobs - Persist all state before shutdown completes
- Emit
'stopped'event when complete
Throws
Section titled “Throws”| Error | Condition |
|---|---|
FleetManagerShutdownError | Shutdown times out and cancelOnTimeout is false |
Example
Section titled “Example”// Normal shutdown - wait for jobs with default 30s timeoutawait manager.stop();
// Shutdown with custom timeoutawait manager.stop({ timeout: 60000 });
// Shutdown without waiting for jobs (not recommended)await manager.stop({ waitForJobs: false });
// Cancel jobs if they don't complete in timeawait manager.stop({ timeout: 30000, cancelOnTimeout: true, cancelTimeout: 10000,});reload()
Section titled “reload()”Hot-reloads configuration without restarting the fleet.
await manager.reload(): Promise<ConfigReloadedPayload>Returns
Section titled “Returns”interface ConfigReloadedPayload { agentCount: number; // Number of agents in new config agentNames: string[]; // Names of agents in new config configPath: string; // Path to reloaded config file changes: ConfigChange[]; // List of detected changes timestamp: string; // ISO timestamp of reload}
interface ConfigChange { type: 'added' | 'removed' | 'modified'; category: 'agent' | 'schedule' | 'defaults'; name: string; // Agent name or "agent/schedule" details?: string; // What changed (for modifications)}Description
Section titled “Description”This method provides hot configuration reload capability:
- Loads and validates the new configuration
- If validation fails, keeps the old configuration (fails gracefully)
- Running jobs continue with their original configuration
- New jobs will use the new configuration
- Updates the scheduler with new agent definitions
- Emits a
'config:reloaded'event with change details
Throws
Section titled “Throws”| Error | Condition |
|---|---|
InvalidStateError | Fleet manager is not initialized |
FleetManagerConfigError | New configuration is invalid |
Example
Section titled “Example”// Reload configurationconst result = await manager.reload();console.log(`Reloaded with ${result.changes.length} changes`);
for (const change of result.changes) { console.log(` ${change.type} ${change.category}: ${change.name}`);}
// Subscribe to reload eventsmanager.on('config:reloaded', (payload) => { console.log(`Config reloaded: ${payload.agentCount} agents`);});Query Methods
Section titled “Query Methods”getFleetStatus()
Section titled “getFleetStatus()”Returns a comprehensive snapshot of fleet state.
await manager.getFleetStatus(): Promise<FleetStatus>Returns
Section titled “Returns”interface FleetStatus { state: FleetManagerStatus; // Current state uptimeSeconds: number | null; // Time since started initializedAt: string | null; // ISO timestamp startedAt: string | null; // ISO timestamp stoppedAt: string | null; // ISO timestamp counts: FleetCounts; // Summary counts scheduler: { status: 'stopped' | 'running' | 'stopping'; checkCount: number; // Total checks performed triggerCount: number; // Total triggers fired lastCheckAt: string | null; // ISO timestamp checkIntervalMs: number; }; lastError: string | null;}
interface FleetCounts { totalAgents: number; idleAgents: number; runningAgents: number; errorAgents: number; totalSchedules: number; runningSchedules: number; runningJobs: number;}Example
Section titled “Example”const status = await manager.getFleetStatus();console.log(`Fleet: ${status.state}`);console.log(`Uptime: ${status.uptimeSeconds}s`);console.log(`Agents: ${status.counts.totalAgents} total, ${status.counts.runningAgents} running`);console.log(`Jobs: ${status.counts.runningJobs} running`);getAgentInfo()
Section titled “getAgentInfo()”Returns information about all configured agents.
await manager.getAgentInfo(): Promise<AgentInfo[]>Returns
Section titled “Returns”interface AgentInfo { name: string; // Agent local name qualifiedName: string; // Dot-separated qualified name (e.g., "myfleet.agent-name") fleetPath: string[]; // Fleet hierarchy path (empty for root agents) description?: string; // From configuration status: 'idle' | 'running' | 'error'; currentJobId: string | null; // Currently running job lastJobId: string | null; // Last completed job maxConcurrent: number; // Max concurrent instances runningCount: number; // Currently running instances errorMessage: string | null; // Error if status is 'error' scheduleCount: number; // Number of schedules schedules: ScheduleInfo[]; // Schedule details model?: string; // Model from config working_directory?: string; // Working directory path chat?: Record<string, AgentChatStatus>; // Chat connector statuses by platform}
interface AgentChatStatus { configured: boolean; connectionStatus?: 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'disconnecting' | 'error'; botUsername?: string; lastError?: string;}Example
Section titled “Example”const agents = await manager.getAgentInfo();for (const agent of agents) { console.log(`${agent.name}: ${agent.status}`); console.log(` Running: ${agent.runningCount}/${agent.maxConcurrent}`); console.log(` Schedules: ${agent.scheduleCount}`);}getAgentInfoByName(name)
Section titled “getAgentInfoByName(name)”Returns information about a specific agent.
await manager.getAgentInfoByName(name: string): Promise<AgentInfo>Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | The agent name to look up |
Throws
Section titled “Throws”| Error | Condition |
|---|---|
AgentNotFoundError | No agent with that name exists |
Example
Section titled “Example”try { const agent = await manager.getAgentInfoByName('my-agent'); console.log(`Status: ${agent.status}`); console.log(`Running: ${agent.runningCount}/${agent.maxConcurrent}`);} catch (error) { if (error instanceof AgentNotFoundError) { console.log(`Agent "${error.agentName}" not found`); console.log(`Available: ${error.availableAgents?.join(', ')}`); }}getSchedules()
Section titled “getSchedules()”Returns all schedules across all agents.
await manager.getSchedules(): Promise<ScheduleInfo[]>Returns
Section titled “Returns”interface ScheduleInfo { name: string; // Schedule name agentName: string; // Owning agent type: string; // 'interval', 'cron', etc. interval?: string; // e.g., "5m", "1h" expression?: string; // Cron expression status: 'idle' | 'running' | 'disabled'; lastRunAt: string | null; // ISO timestamp nextRunAt: string | null; // ISO timestamp lastError: string | null;}Example
Section titled “Example”const schedules = await manager.getSchedules();for (const schedule of schedules) { console.log(`${schedule.agentName}/${schedule.name}: ${schedule.status}`); console.log(` Next run: ${schedule.nextRunAt}`);}getSchedule(agentName, scheduleName)
Section titled “getSchedule(agentName, scheduleName)”Returns a specific schedule by agent and schedule name.
await manager.getSchedule(agentName: string, scheduleName: string): Promise<ScheduleInfo>Throws
Section titled “Throws”| Error | Condition |
|---|---|
AgentNotFoundError | Agent doesn’t exist |
ScheduleNotFoundError | Schedule doesn’t exist for agent |
Example
Section titled “Example”const schedule = await manager.getSchedule('my-agent', 'hourly');console.log(`Status: ${schedule.status}`);console.log(`Last run: ${schedule.lastRunAt}`);console.log(`Next run: ${schedule.nextRunAt}`);enableSchedule(agentName, scheduleName)
Section titled “enableSchedule(agentName, scheduleName)”Enables a previously disabled schedule.
await manager.enableSchedule(agentName: string, scheduleName: string): Promise<ScheduleInfo>Description
Section titled “Description”Enables a schedule that was previously disabled, allowing it to trigger again on its configured interval. The enabled state is persisted and survives restarts.
Example
Section titled “Example”const schedule = await manager.enableSchedule('my-agent', 'hourly');console.log(`Schedule status: ${schedule.status}`); // 'idle'disableSchedule(agentName, scheduleName)
Section titled “disableSchedule(agentName, scheduleName)”Disables a schedule temporarily.
await manager.disableSchedule(agentName: string, scheduleName: string): Promise<ScheduleInfo>Description
Section titled “Description”Disables a schedule, preventing it from triggering on its configured interval. The schedule remains in the configuration but won’t run until re-enabled. The disabled state is persisted and survives restarts.
Example
Section titled “Example”// Disable a schedule temporarilyconst schedule = await manager.disableSchedule('my-agent', 'hourly');console.log(`Schedule status: ${schedule.status}`); // 'disabled'
// Later, re-enable itawait manager.enableSchedule('my-agent', 'hourly');Programmatic Agent Management
Section titled “Programmatic Agent Management”addAgent(agent, options?)
Section titled “addAgent(agent, options?)”Registers an agent at runtime without writing YAML or reloading configuration.
await manager.addAgent( agent: AgentConfig | (Record<string, unknown> & { name: string }), options?: AddAgentOptions): Promise<AgentInfo>Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
agent | AgentConfig | Yes | Agent configuration object |
options.baseDir | string | No | Base directory for resolving relative working_directory paths (defaults to config directory or cwd) |
options.mergeDefaults | boolean | No | Whether to merge fleet defaults into the agent (default: true) |
options.replace | boolean | No | Replace existing agent with same name instead of throwing (default: false) |
Returns
Section titled “Returns”interface AgentInfo { name: string; qualifiedName: string; status: 'idle' | 'running' | 'error'; // ... (see getAgentInfo for full type)}Description
Section titled “Description”This method provides programmatic agent registration without the write-yaml-then-reload round trip. The agent configuration is validated against the agent schema, merged with fleet defaults (unless disabled), and integrated into the in-memory config. The resolved agent is immediately triggerable and visible in fleet status. A config:reloaded event is emitted describing the change.
Programmatic agents are always registered at the root fleet level (fleetPath: []), so their qualified name equals their local name.
Throws
Section titled “Throws”| Error | Condition |
|---|---|
InvalidStateError | Fleet manager not initialized |
ConfigurationError | Validation fails or qualified name collides with existing agent (and replace is not set) |
Example
Section titled “Example”// Register a new agentconst agent = await manager.addAgent({ name: 'my-agent', prompt: 'You are a helpful assistant', model: 'claude-opus-4-5', working_directory: '/path/to/project', schedules: [ { name: 'hourly', interval: '1h', prompt: 'Check for updates', }, ],});
console.log(`Added agent: ${agent.qualifiedName}`);
// Replace an existing agentawait manager.addAgent( { name: 'my-agent', prompt: 'Updated prompt', }, { replace: true });
// Add agent without merging defaultsawait manager.addAgent( { name: 'standalone-agent', prompt: 'I have my own config', }, { mergeDefaults: false });removeAgent(name)
Section titled “removeAgent(name)”Unregisters an agent at runtime.
await manager.removeAgent(name: string): Promise<boolean>Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Agent qualified name or local name to remove |
Returns
Section titled “Returns”Returns true if an agent was removed, false if no match was found.
Description
Section titled “Description”Removes the agent from the in-memory config and the scheduler. Accepts a qualified name (e.g., "sub.agent") or a local name; qualified names are matched first. Running jobs are unaffected — the scheduler simply stops triggering the removed agent’s schedules.
Throws
Section titled “Throws”| Error | Condition |
|---|---|
InvalidStateError | Fleet manager not initialized |
Example
Section titled “Example”// Remove by nameconst removed = await manager.removeAgent('my-agent');if (removed) { console.log('Agent removed');} else { console.log('Agent not found');}
// Remove by qualified nameawait manager.removeAgent('myfleet.my-agent');Session Management Methods
Section titled “Session Management Methods”getAgentSessions(name, options?)
Section titled “getAgentSessions(name, options?)”Lists discovered Claude Code sessions for an agent.
await manager.getAgentSessions( name: string, options?: { limit?: number }): Promise<DiscoveredSession[]>Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Agent qualified name or local name |
options.limit | number | No | Optional limit for top-N enrichment |
Returns
Section titled “Returns”interface DiscoveredSession { sessionId: string; lastModified: string; // ISO timestamp customName?: string; // Custom display name if set firstPrompt?: string; // First user message lastAssistant?: string; // Last assistant response messageCount?: number; // Total messages createdByJobId?: string; // Originating job ID // ... (additional enrichment fields)}Description
Section titled “Description”Derives the agent’s working directory and Docker mode from the loaded config. Sessions are returned sorted by modification time (newest first).
Note: Sessions are keyed by working directory. This method uses the agent’s configured working_directory. If you triggered the agent with a per-trigger workingDirectory override, the resulting sessions live under that override directory and will NOT appear here.
Throws
Section titled “Throws”| Error | Condition |
|---|---|
InvalidStateError | Fleet manager not initialized |
AgentNotFoundError | Agent doesn’t exist |
Example
Section titled “Example”const sessions = await manager.getAgentSessions('my-agent');for (const session of sessions) { console.log(`${session.sessionId}: ${session.customName || session.firstPrompt}`); console.log(` Last modified: ${session.lastModified}`); console.log(` Messages: ${session.messageCount}`);}
// Get top 10 sessionsconst recent = await manager.getAgentSessions('my-agent', { limit: 10 });getAgentSessionMessages(name, sessionId)
Section titled “getAgentSessionMessages(name, sessionId)”Gets the parsed chat messages for one of an agent’s sessions.
await manager.getAgentSessionMessages( name: string, sessionId: string): Promise<ChatMessage[]>Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Agent qualified name or local name |
sessionId | string | Yes | Session ID to read |
Returns
Section titled “Returns”interface ChatMessage { role: 'user' | 'assistant'; content: string; timestamp?: string; // ... (additional fields)}Throws
Section titled “Throws”| Error | Condition |
|---|---|
InvalidStateError | Fleet manager not initialized |
AgentNotFoundError | Agent doesn’t exist |
Example
Section titled “Example”const messages = await manager.getAgentSessionMessages( 'my-agent', 'session-abc123');
for (const msg of messages) { console.log(`[${msg.role}] ${msg.content}`);}getAgentSessionUsage(name, sessionId)
Section titled “getAgentSessionUsage(name, sessionId)”Gets token usage data for one of an agent’s sessions.
await manager.getAgentSessionUsage( name: string, sessionId: string): Promise<SessionUsage>Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Agent qualified name or local name |
sessionId | string | Yes | Session ID to read |
Returns
Section titled “Returns”interface SessionUsage { inputTokens: number; // Last turn's input + cache tokens turnCount: number; // Total turns in session hasData: boolean; // Whether usage data exists}Description
Section titled “Description”Reads the session transcript and returns the most recent context-window fill level (last assistant turn’s input + cache tokens) plus a turn count. This lets a UI show “context used” for a chat loaded from history — before any new turn streams a fresh usage value.
Throws
Section titled “Throws”| Error | Condition |
|---|---|
InvalidStateError | Fleet manager not initialized |
AgentNotFoundError | Agent doesn’t exist |
Example
Section titled “Example”const usage = await manager.getAgentSessionUsage('my-agent', 'session-abc123');if (usage.hasData) { console.log(`Context used: ${usage.inputTokens} tokens`); console.log(`Turn count: ${usage.turnCount}`);}deleteSession(name, sessionId)
Section titled “deleteSession(name, sessionId)”Deletes one of an agent’s Claude Code session transcripts from disk.
await manager.deleteSession( name: string, sessionId: string): Promise<boolean>Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Agent qualified name or local name |
sessionId | string | Yes | Session ID whose transcript should be removed |
Returns
Section titled “Returns”Returns true if a file was removed, false if no transcript existed (or the agent has no working directory).
Description
Section titled “Description”Resolves the agent’s working directory and Docker mode from the loaded config, computes the CLI (or Docker) transcript file path, deletes it, and invalidates the session-discovery cache so a subsequent getAgentSessions() no longer lists it.
The sessionId is validated (only [A-Za-z0-9-] is allowed) to prevent path traversal.
Throws
Section titled “Throws”| Error | Condition |
|---|---|
InvalidStateError | Fleet manager not initialized |
AgentNotFoundError | Agent doesn’t exist |
Error | sessionId contains invalid characters |
Example
Section titled “Example”const deleted = await manager.deleteSession('my-agent', 'session-abc123');if (deleted) { console.log('Session deleted');} else { console.log('Session not found');}setSessionName(name, sessionId, customName)
Section titled “setSessionName(name, sessionId, customName)”Sets (or clears) the custom display name for one of an agent’s sessions.
await manager.setSessionName( name: string, sessionId: string, customName: string | null): Promise<void>Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Agent qualified name or local name |
sessionId | string | Yes | Session ID to (re)name |
customName | string | null | Yes | Custom name to set, or null/empty to clear it |
Description
Section titled “Description”Writes through the fleet’s shared session metadata store so a subsequent getAgentSessions() reflects the new customName immediately. Passing null or an empty/whitespace string clears any existing custom name.
Throws
Section titled “Throws”| Error | Condition |
|---|---|
InvalidStateError | Fleet manager not initialized |
AgentNotFoundError | Agent doesn’t exist |
Example
Section titled “Example”// Set custom nameawait manager.setSessionName('my-agent', 'session-abc123', 'Bug Fix Discussion');
// Clear custom nameawait manager.setSessionName('my-agent', 'session-abc123', null);invalidateSessions(name)
Section titled “invalidateSessions(name)”Drops the cached session listing for an agent so the next getAgentSessions() call rebuilds it from disk.
manager.invalidateSessions(name: string): voidParameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Agent qualified name or local name |
Description
Section titled “Description”The underlying session discovery service caches each working directory’s listing for up to its TTL (default 30s). That cache is now mtime-aware, so newly created transcript files are normally picked up immediately. Call this when you want to force a fresh listing regardless — e.g., after each chat turn, or on filesystems whose directory mtime has coarse (1-second) granularity.
Resolves the agent’s working directory and Docker mode from the loaded config. A no-op when the agent has no working directory.
Throws
Section titled “Throws”| Error | Condition |
|---|---|
InvalidStateError | Fleet manager not initialized |
AgentNotFoundError | Agent doesn’t exist |
Example
Section titled “Example”// Force refresh session listingmanager.invalidateSessions('my-agent');
// Next call will rebuild from diskconst sessions = await manager.getAgentSessions('my-agent');listAgentCommands(agentName, options?)
Section titled “listAgentCommands(agentName, options?)”Lists the slash commands available to an agent — for populating a command palette or autocomplete — in a single call.
await manager.listAgentCommands( agentName: string, options?: ChatSessionOptions): Promise<SlashCommand[]>Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
agentName | string | Yes | Agent qualified name or local name |
options.workingDirectory | string | No | Per-call working-directory override so the list reflects the intended project context |
options.injectedMcpServers | Record<string, InjectedMcpServerDef> | No | MCP servers to inject, so their commands appear in the list |
Returns
Section titled “Returns”interface SlashCommand { name: string; // Command name, without the leading slash description: string; // Human-readable description argumentHint: string; // Hint for arguments (e.g. "<pr>"), "" if none}Returns the full command list — built-ins plus project .claude/commands plus any MCP-provided commands — exactly as the CLI reports them for the resolved session’s cwd and config.
Description
Section titled “Description”A one-shot convenience over openChatSession(): it opens a streaming session, reads its command list, and always closes the session (in a finally, even if the listing throws), so consumers never have to manage the underlying claude subprocess lifecycle themselves.
Accepts the same ChatSessionOptions as openChatSession. The session runs on the SDK runtime regardless of the agent’s configured runtime, so this works for cli-runtime (Claude-subscription) agents too.
Cost: each call spawns and tears down a claude subprocess (~seconds). The command list is essentially static per project, so callers that query it repeatedly should cache the result.
Throws
Section titled “Throws”| Error | Condition |
|---|---|
InvalidStateError | Fleet manager not initialized |
AgentNotFoundError | Agent doesn’t exist |
StreamingSessionUnsupportedError | Agent is Docker-wrapped (surfaced unchanged from openChatSession) |
Example
Section titled “Example”import type { SlashCommand } from '@herdctl/core';
const commands: SlashCommand[] = await manager.listAgentCommands('my-agent');for (const cmd of commands) { console.log(`/${cmd.name} ${cmd.argumentHint} — ${cmd.description}`);}Action Methods
Section titled “Action Methods”trigger(agentName, scheduleName?, options?)
Section titled “trigger(agentName, scheduleName?, options?)”Manually triggers an agent outside its normal schedule.
await manager.trigger( agentName: string, scheduleName?: string, options?: TriggerOptions): Promise<TriggerResult>Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
agentName | string | Yes | Name of the agent to trigger |
scheduleName | string | No | Schedule to use for configuration (prompt, work source) |
options.prompt | string | No | Override the prompt for this trigger |
options.workItems | WorkItem[] | No | Custom work items instead of fetching from work source |
options.bypassConcurrencyLimit | boolean | No | Force trigger even if agent is at capacity (default: false) |
options.workingDirectory | string | No | Override the agent’s configured working directory for this trigger only |
Returns
Section titled “Returns”interface TriggerResult { jobId: string; // Unique job identifier agentName: string; scheduleName: string | null; startedAt: string; // ISO timestamp prompt?: string; // Prompt used for trigger}Throws
Section titled “Throws”| Error | Condition |
|---|---|
InvalidStateError | Fleet manager not initialized |
AgentNotFoundError | Agent doesn’t exist |
ScheduleNotFoundError | Specified schedule doesn’t exist |
ConcurrencyLimitError | Agent at capacity and bypass not enabled |
Example
Section titled “Example”// Trigger with agent defaultsconst job = await manager.trigger('my-agent');console.log(`Created job: ${job.jobId}`);
// Trigger a specific scheduleconst job = await manager.trigger('my-agent', 'hourly');
// Trigger with custom promptconst job = await manager.trigger('my-agent', undefined, { prompt: 'Review the latest security updates',});
// Force trigger even at capacityconst job = await manager.trigger('my-agent', undefined, { bypassConcurrencyLimit: true,});
// Trigger with custom working directoryconst job = await manager.trigger('my-agent', undefined, { workingDirectory: '/path/to/specific/project', prompt: 'Analyze this project',});cancelJob(jobId, options?)
Section titled “cancelJob(jobId, options?)”Cancels a running job gracefully.
await manager.cancelJob( jobId: string, options?: { timeout?: number }): Promise<CancelJobResult>Parameters
Section titled “Parameters”| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
jobId | string | Yes | — | ID of the job to cancel |
options.timeout | number | No | 10000 | Time in ms to wait for graceful shutdown before SIGKILL |
Returns
Section titled “Returns”interface CancelJobResult { jobId: string; success: boolean; terminationType: 'graceful' | 'forced' | 'already_stopped'; canceledAt: string; // ISO timestamp}Description
Section titled “Description”Cancels a running job by first sending SIGTERM to allow graceful shutdown. If the job doesn’t terminate within the timeout, it will be forcefully killed with SIGKILL.
Throws
Section titled “Throws”| Error | Condition |
|---|---|
InvalidStateError | Fleet manager not initialized |
JobNotFoundError | Job doesn’t exist |
Example
Section titled “Example”// Cancel with default timeout (10s)const result = await manager.cancelJob('job-2024-01-15-abc123');console.log(`Cancelled: ${result.terminationType}`);
// Cancel with custom timeoutconst result = await manager.cancelJob('job-2024-01-15-abc123', { timeout: 30000, // 30 seconds});forkJob(jobId, modifications?)
Section titled “forkJob(jobId, modifications?)”Creates a new job based on an existing job’s configuration.
await manager.forkJob( jobId: string, modifications?: JobModifications): Promise<ForkJobResult>Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
jobId | string | Yes | ID of the job to fork |
modifications.prompt | string | No | Override the prompt |
modifications.schedule | string | No | Override the schedule name |
modifications.workItems | WorkItem[] | No | Replace work items |
Returns
Section titled “Returns”interface ForkJobResult { jobId: string; // New job ID forkedFromJobId: string; // Original job ID agentName: string; startedAt: string; // ISO timestamp prompt?: string;}Description
Section titled “Description”Creates a new job based on an existing job’s configuration. The new job will have the same agent and can optionally have modifications applied. If the original job has a session ID, the new job will fork from that session, preserving conversation context.
Throws
Section titled “Throws”| Error | Condition |
|---|---|
InvalidStateError | Fleet manager not initialized |
JobNotFoundError | Original job doesn’t exist |
JobForkError | Job cannot be forked |
Example
Section titled “Example”// Fork with same configurationconst result = await manager.forkJob('job-2024-01-15-abc123');console.log(`Forked to: ${result.jobId}`);
// Fork with modified promptconst result = await manager.forkJob('job-2024-01-15-abc123', { prompt: 'Continue the previous task but focus on testing',});
// Fork with different scheduleconst result = await manager.forkJob('job-2024-01-15-abc123', { schedule: 'nightly',});Log Streaming Methods
Section titled “Log Streaming Methods”streamLogs(options?)
Section titled “streamLogs(options?)”Streams all fleet logs as an async iterable.
manager.streamLogs(options?: LogStreamOptions): AsyncIterable<LogEntry>Parameters
Section titled “Parameters”| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
options.level | LogLevel | No | 'info' | Minimum log level ('debug', 'info', 'warn', 'error') |
options.agentName | string | No | — | Filter to specific agent |
options.jobId | string | No | — | Filter to specific job |
options.includeHistory | boolean | No | true | Replay history before streaming |
options.historyLimit | number | No | 1000 | Max historical entries |
Returns
Section titled “Returns”interface LogEntry { timestamp: string; // ISO timestamp level: 'debug' | 'info' | 'warn' | 'error'; source: 'fleet' | 'agent' | 'job' | 'scheduler'; agentName?: string; jobId?: string; scheduleName?: string; message: string; data?: Record<string, unknown>;}Example
Section titled “Example”// Stream all info+ logsfor await (const log of manager.streamLogs()) { console.log(`[${log.level}] ${log.message}`);}
// Stream only errors for a specific agentfor await (const log of manager.streamLogs({ level: 'error', agentName: 'my-agent',})) { console.error(log.message);}streamJobOutput(jobId)
Section titled “streamJobOutput(jobId)”Streams output from a specific job.
manager.streamJobOutput(jobId: string): AsyncIterable<LogEntry>Description
Section titled “Description”For completed jobs, this will replay the job’s history and then complete. For running jobs, it will continue streaming until the job completes.
Throws
Section titled “Throws”| Error | Condition |
|---|---|
JobNotFoundError | Job doesn’t exist |
Example
Section titled “Example”for await (const log of manager.streamJobOutput('job-2024-01-15-abc123')) { console.log(`[${log.level}] ${log.message}`);}streamAgentLogs(agentName)
Section titled “streamAgentLogs(agentName)”Streams logs for a specific agent.
manager.streamAgentLogs(agentName: string): AsyncIterable<LogEntry>Throws
Section titled “Throws”| Error | Condition |
|---|---|
AgentNotFoundError | Agent doesn’t exist |
Example
Section titled “Example”for await (const log of manager.streamAgentLogs('my-agent')) { console.log(`[${log.jobId}] ${log.message}`);}getJobFinalOutput(jobId)
Section titled “getJobFinalOutput(jobId)”Returns the final output of a completed job as a string.
await manager.getJobFinalOutput(jobId: string): Promise<string>Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
jobId | string | Yes | ID of the job to get output from |
Returns
Section titled “Returns”Returns a Promise<string> containing the job’s final output. Throws JobNotFoundError if the job doesn’t exist.
Example
Section titled “Example”const output = await manager.getJobFinalOutput('job-2024-01-15-abc123');console.log(output);// "I checked the prices for office chairs..."State Accessors
Section titled “State Accessors”Returns the current fleet manager state (read-only property).
manager.state: FleetManagerStateReturns
Section titled “Returns”interface FleetManagerState { status: FleetManagerStatus; initializedAt: string | null; startedAt: string | null; stoppedAt: string | null; agentCount: number; lastError: string | null;}
type FleetManagerStatus = | 'uninitialized' // Initial state | 'initialized' // After initialize() | 'starting' // During start() | 'running' // Scheduler active | 'stopping' // During stop() | 'stopped' // After stop() | 'error'; // Error occurredExample
Section titled “Example”console.log(`Status: ${manager.state.status}`);console.log(`Agents: ${manager.state.agentCount}`);console.log(`Started at: ${manager.state.startedAt}`);getConfig()
Section titled “getConfig()”Returns the loaded configuration.
manager.getConfig(): ResolvedConfig | nullReturns null if not initialized.
getAgents()
Section titled “getAgents()”Returns the loaded agents array.
manager.getAgents(): ResolvedAgent[]Returns empty array if not initialized.
Events
Section titled “Events”The FleetManager extends EventEmitter and provides strongly-typed events.
Event Types
Section titled “Event Types”interface FleetManagerEventMap { // Lifecycle 'initialized': []; 'started': []; 'stopped': []; 'error': [error: Error]; 'config:reloaded': [payload: ConfigReloadedPayload];
// Agent events 'agent:started': [payload: AgentStartedPayload]; 'agent:stopped': [payload: AgentStoppedPayload];
// Schedule events 'schedule:triggered': [payload: ScheduleTriggeredPayload]; 'schedule:skipped': [payload: ScheduleSkippedPayload];
// Job events 'job:created': [payload: JobCreatedPayload]; 'job:output': [payload: JobOutputPayload]; 'job:completed': [payload: JobCompletedPayload]; 'job:failed': [payload: JobFailedPayload]; 'job:cancelled': [payload: JobCancelledPayload]; 'job:forked': [payload: JobForkedPayload];}Event Payloads
Section titled “Event Payloads”interface JobCreatedPayload { job: JobMetadata; agentName: string; scheduleName?: string; timestamp: string;}
interface JobOutputPayload { jobId: string; agentName: string; output: string; outputType: 'stdout' | 'stderr' | 'assistant' | 'tool' | 'system'; timestamp: string;}
interface JobCompletedPayload { job: JobMetadata; agentName: string; exitReason: ExitReason; durationSeconds: number; timestamp: string;}
interface JobFailedPayload { job: JobMetadata; agentName: string; error: Error; exitReason: ExitReason; durationSeconds?: number; timestamp: string;}
interface JobCancelledPayload { job: JobMetadata; agentName: string; terminationType: 'graceful' | 'forced' | 'already_stopped'; durationSeconds?: number; timestamp: string;}
interface JobForkedPayload { job: JobMetadata; originalJob: JobMetadata; agentName: string; timestamp: string;}interface ScheduleTriggeredPayload { agentName: string; scheduleName: string; schedule: Schedule; timestamp: string;}
interface ScheduleSkippedPayload { agentName: string; scheduleName: string; reason: 'already_running' | 'disabled' | 'max_concurrent' | 'work_source_empty'; timestamp: string;}interface ConfigReloadedPayload { agentCount: number; agentNames: string[]; configPath: string; changes: ConfigChange[]; timestamp: string;}
interface ConfigChange { type: 'added' | 'removed' | 'modified'; category: 'agent' | 'schedule' | 'defaults'; name: string; details?: string;}Example: Event Subscription
Section titled “Example: Event Subscription”const manager = new FleetManager({ stateDir: './.herdctl' });
// Lifecycle eventsmanager.on('initialized', () => { console.log('Fleet initialized');});
manager.on('started', () => { console.log('Fleet started');});
manager.on('stopped', () => { console.log('Fleet stopped');});
manager.on('error', (error) => { console.error('Fleet error:', error.message);});
// Job eventsmanager.on('job:created', (payload) => { console.log(`Job ${payload.job.id} created for ${payload.agentName}`);});
manager.on('job:output', (payload) => { process.stdout.write(payload.output);});
manager.on('job:completed', (payload) => { console.log(`Job completed in ${payload.durationSeconds}s`);});
manager.on('job:failed', (payload) => { console.error(`Job failed: ${payload.error.message}`);});
// Schedule eventsmanager.on('schedule:triggered', (payload) => { console.log(`${payload.agentName}/${payload.scheduleName} triggered`);});
manager.on('schedule:skipped', (payload) => { console.log(`${payload.agentName}/${payload.scheduleName} skipped: ${payload.reason}`);});
// Config reloadmanager.on('config:reloaded', (payload) => { console.log(`Config reloaded with ${payload.changes.length} changes`);});Error Classes
Section titled “Error Classes”All errors extend FleetManagerError and include error codes for programmatic handling.
Error Hierarchy
Section titled “Error Hierarchy”FleetManagerError (base)├── ConfigurationError // Config invalid/not found├── AgentNotFoundError // Agent not in config├── JobNotFoundError // Job doesn't exist├── ScheduleNotFoundError // Schedule not configured├── InvalidStateError // Operation invalid for state├── ConcurrencyLimitError // Agent at max capacity├── JobCancelError // Job cancellation failed├── JobForkError // Job fork failed├── FleetManagerConfigError // Legacy config error├── FleetManagerStateError // Legacy state error├── FleetManagerStateDirError // State directory init failed└── FleetManagerShutdownError // Shutdown failedError Codes
Section titled “Error Codes”const FleetManagerErrorCode = { FLEET_MANAGER_ERROR: 'FLEET_MANAGER_ERROR', CONFIGURATION_ERROR: 'CONFIGURATION_ERROR', CONFIG_LOAD_ERROR: 'CONFIG_LOAD_ERROR', AGENT_NOT_FOUND: 'AGENT_NOT_FOUND', JOB_NOT_FOUND: 'JOB_NOT_FOUND', SCHEDULE_NOT_FOUND: 'SCHEDULE_NOT_FOUND', INVALID_STATE: 'INVALID_STATE', STATE_DIR_ERROR: 'STATE_DIR_ERROR', CONCURRENCY_LIMIT: 'CONCURRENCY_LIMIT', SHUTDOWN_ERROR: 'SHUTDOWN_ERROR', JOB_CANCEL_ERROR: 'JOB_CANCEL_ERROR', JOB_FORK_ERROR: 'JOB_FORK_ERROR',};Type Guards
Section titled “Type Guards”import { isFleetManagerError, isConfigurationError, isAgentNotFoundError, isJobNotFoundError, isScheduleNotFoundError, isInvalidStateError, isConcurrencyLimitError, isJobCancelError, isJobForkError,} from '@herdctl/core';
try { await manager.trigger('unknown-agent');} catch (error) { if (isAgentNotFoundError(error)) { console.log(`Agent "${error.agentName}" not found`); console.log(`Available: ${error.availableAgents?.join(', ')}`); } else if (isConcurrencyLimitError(error)) { console.log(`At capacity: ${error.currentJobs}/${error.limit}`); }}Error Properties
Section titled “Error Properties”class AgentNotFoundError extends FleetManagerError { agentName: string; // The agent that wasn't found availableAgents?: string[]; // List of valid agents}class ScheduleNotFoundError extends FleetManagerError { agentName: string; scheduleName: string; // The schedule that wasn't found availableSchedules?: string[]; // List of valid schedules}class ConcurrencyLimitError extends FleetManagerError { agentName: string; currentJobs: number; // Currently running limit: number; // Max allowed
isAtLimit(): boolean; // Helper method}class InvalidStateError extends FleetManagerError { operation: string; // The attempted operation currentState: string; // Current fleet state expectedState: string | string[]; // Required state(s)}Complete Example
Section titled “Complete Example”Here’s a complete example showing typical usage patterns:
import { FleetManager, isAgentNotFoundError } from '@herdctl/core';
async function main() { const manager = new FleetManager({ configPath: './herdctl.yaml', stateDir: './.herdctl', checkInterval: 5000, });
// Set up event handlers manager.on('initialized', () => console.log('Fleet initialized')); manager.on('started', () => console.log('Fleet started')); manager.on('stopped', () => console.log('Fleet stopped'));
manager.on('job:created', (payload) => { console.log(`Job ${payload.job.id} created for ${payload.agentName}`); });
manager.on('job:output', (payload) => { process.stdout.write(payload.output); });
manager.on('job:completed', (payload) => { console.log(`Job ${payload.job.id} completed in ${payload.durationSeconds}s`); });
manager.on('job:failed', (payload) => { console.error(`Job ${payload.job.id} failed: ${payload.error.message}`); });
// Initialize and start await manager.initialize(); await manager.start();
// Get fleet status const status = await manager.getFleetStatus(); console.log(`Running ${status.counts.totalAgents} agents`);
// List all agents const agents = await manager.getAgentInfo(); for (const agent of agents) { console.log(`${agent.name}: ${agent.status}`); }
// Manually trigger an agent try { const result = await manager.trigger('my-agent', 'check-issues', { prompt: 'Check for urgent issues only', }); console.log(`Triggered job: ${result.jobId}`); } catch (error) { if (isAgentNotFoundError(error)) { console.error(`Agent not found: ${error.agentName}`); } }
// Handle shutdown process.on('SIGINT', async () => { console.log('Shutting down...'); await manager.stop({ timeout: 30000, cancelOnTimeout: true, }); process.exit(0); });}
main().catch(console.error);TypeScript Support
Section titled “TypeScript Support”The @herdctl/core package is written in TypeScript and provides full type definitions. All types are exported from the main package:
import type { // Options and configuration FleetManagerOptions, FleetManagerLogger,
// State types FleetManagerState, FleetManagerStatus,
// Query result types FleetStatus, FleetCounts, AgentInfo, ScheduleInfo,
// Action types TriggerOptions, TriggerResult, JobModifications, CancelJobResult, ForkJobResult,
// Stop options FleetManagerStopOptions,
// Log streaming types LogLevel, LogSource, LogEntry, LogStreamOptions,
// Event types FleetManagerEventMap, FleetManagerEventName, ConfigReloadedPayload, ConfigChange, JobCreatedPayload, JobOutputPayload, JobCompletedPayload, JobFailedPayload, JobCancelledPayload, JobForkedPayload, ScheduleTriggeredPayload, ScheduleSkippedPayload,} from '@herdctl/core';