Skip to content

Fleet API Reference

The Fleet class is the primary interface for managing Cursor Background Agents. It provides agent lifecycle management, communication, diamond-pattern orchestration, and token-aware GitHub coordination.

Terminal window
npm install @jbcom/agentic
import { Fleet, CursorAPI } from '@jbcom/agentic';
import type {
FleetConfig,
CoordinationConfig,
SpawnContext,
CursorAPIOptions,
} from '@jbcom/agentic';
const fleet = new Fleet(config?: FleetConfig);

FleetConfig extends CursorAPIOptions with fleet-specific options:

OptionTypeDefaultDescription
apiKeystringprocess.env.CURSOR_API_KEYCursor API key
timeoutnumber60000Request timeout in milliseconds
baseUrlstringhttps://api.cursor.com/v0API base URL
maxRetriesnumber3Maximum retries for transient errors
retryDelaynumber1000Initial retry delay (exponential backoff)
archivePathstring./memory-bank/recoveryPath for archived conversations

Example:

import { Fleet } from '@jbcom/agentic';
const fleet = new Fleet({
apiKey: process.env.CURSOR_API_KEY,
timeout: 120000,
maxRetries: 5,
retryDelay: 2000,
archivePath: './archives',
});

If CURSOR_API_KEY is not set, the Fleet instance is created but API methods return { success: false, error: 'Cursor API not available' }.


Check if the Cursor API key is configured and the internal client is initialized.

isApiAvailable(): boolean

Returns: true if CursorAPI was successfully initialized, false otherwise.

if (!fleet.isApiAvailable()) {
console.error('Set CURSOR_API_KEY environment variable');
process.exit(1);
}

List all agents in the fleet.

list(): Promise<Result<Agent[]>>

Returns: Result<Agent[]> containing all agents regardless of status.

const result = await fleet.list();
if (result.success && result.data) {
for (const agent of result.data) {
console.log(`${agent.id}: ${agent.status} - ${agent.source.repository}`);
}
}

Filter agents by a specific status.

listByStatus(status: AgentStatus): Promise<Result<Agent[]>>

Parameters:

ParameterTypeDescription
statusAgentStatusOne of RUNNING, FINISHED, COMPLETED, FAILED, CANCELLED, PENDING, UNKNOWN
const failed = await fleet.listByStatus('FAILED');
if (failed.success && failed.data) {
console.log(`${failed.data.length} agents have failed`);
}

Convenience method to list only running agents. Equivalent to listByStatus('RUNNING').

running(): Promise<Result<Agent[]>>
const result = await fleet.running();
if (result.success && result.data) {
console.log(`${result.data.length} agents currently running`);
}

Find a specific agent by ID.

find(agentId: string): Promise<Result<Agent | undefined>>
const result = await fleet.find('bc-abc123');
if (result.success && result.data) {
console.log(`Found agent: ${result.data.status}`);
}

Get the current status of a specific agent. Makes a direct API call per agent (unlike find() which searches the full list).

status(agentId: string): Promise<Result<Agent>>
const result = await fleet.status('bc-abc123');
if (result.success && result.data) {
console.log(`Agent status: ${result.data.status}`);
if (result.data.target?.prUrl) {
console.log(`PR: ${result.data.target.prUrl}`);
}
}

Spawn a new Cursor Background Agent. This is the primary method for launching agents.

spawn(options: SpawnOptions & { context?: SpawnContext }): Promise<Result<Agent>>

SpawnOptions:

interface SpawnOptions {
/** GitHub repository URL (e.g., https://github.com/org/repo) */
repository: string;
/** Task description for the agent */
task: string;
/** Git ref (branch, tag, commit) - defaults to "main" */
ref?: string;
/** Target configuration */
target?: {
/** Auto-create PR when agent completes */
autoCreatePr?: boolean;
/** Custom branch name */
branchName?: string;
/** Open PR as Cursor GitHub App instead of user */
openAsCursorGithubApp?: boolean;
/** Skip adding user as reviewer */
skipReviewerRequest?: boolean;
};
/** Webhook for status notifications */
webhook?: {
/** URL to receive notifications (must be HTTPS) */
url: string;
/** Secret for payload verification (min 32 chars) */
secret?: string;
};
}

SpawnContext (optional coordination context):

interface SpawnContext {
controlManagerId?: string;
controlCenter?: string;
relatedAgents?: string[];
metadata?: Record<string, unknown>;
}

When context is provided, a --- COORDINATION CONTEXT --- block is appended to the task string with the manager ID, control center URL, related agent IDs, and metadata.

Example — basic spawn:

const result = await fleet.spawn({
repository: 'https://github.com/my-org/my-repo',
task: 'Fix the failing CI workflow in .github/workflows/ci.yml',
ref: 'main',
target: {
autoCreatePr: true,
branchName: 'fix/ci-workflow',
},
});
if (result.success && result.data) {
console.log(`Spawned agent: ${result.data.id}`);
}

Example — spawn with webhook:

const result = await fleet.spawn({
repository: 'https://github.com/my-org/my-repo',
task: 'Refactor the authentication module',
target: { autoCreatePr: true },
webhook: {
url: 'https://hooks.myapp.com/cursor',
secret: 'whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
},
});

Validation rules:

  • repository must contain / (owner/repo or full URL format), max 200 characters
  • task cannot be empty, max 100,000 characters
  • webhook.url must be HTTPS and cannot point to internal/private addresses (SSRF protection)
  • ref must be under 200 characters

Send a follow-up message to a running agent.

followup(agentId: string, message: string): Promise<Result<void>>
const result = await fleet.followup(
'bc-abc123',
'Please focus on the authentication module first, then handle the API routes.'
);
if (result.success) {
console.log('Follow-up message sent');
}

Send the same message to multiple agents concurrently.

broadcast(agentIds: string[], message: string): Promise<Map<string, Result<void>>>

Returns: A Map where each key is an agent ID and the value is the result of sending the message.

const results = await fleet.broadcast(
['bc-abc123', 'bc-def456', 'bc-ghi789'],
'STATUS CHECK: Please provide a progress update in the coordination PR.'
);
for (const [id, result] of results) {
console.log(`${id}: ${result.success ? 'sent' : result.error}`);
}

Retrieve the full conversation history for an agent.

conversation(agentId: string): Promise<Result<Conversation>>

Returns: Result<Conversation> where:

interface Conversation {
agentId: string;
messages: ConversationMessage[];
totalMessages: number;
}
interface ConversationMessage {
type: 'user_message' | 'assistant_message';
text: string;
timestamp?: string;
}
const result = await fleet.conversation('bc-abc123');
if (result.success && result.data) {
console.log(`${result.data.totalMessages} messages`);
for (const msg of result.data.messages) {
console.log(`[${msg.type}] ${msg.text.slice(0, 100)}...`);
}
}

Archive an agent’s conversation to a JSON file on disk.

archive(agentId: string, outputPath?: string): Promise<Result<string>>

Parameters:

ParameterTypeDefaultDescription
agentIdstringAgent ID to archive
outputPathstring{archivePath}/conversation-{agentId}.jsonCustom output path

Returns: Result<string> containing the path to the saved file.

// Default path
const result = await fleet.archive('bc-abc123');
if (result.success) {
console.log(`Saved to: ${result.data}`);
// -> ./memory-bank/recovery/conversation-bc-abc123.json
}
// Custom path
const result2 = await fleet.archive('bc-abc123', './archives/agent.json');

List all repositories available to the Cursor API account.

repositories(): Promise<Result<Repository[]>>
interface Repository {
owner: string;
name: string;
fullName: string;
defaultBranch: string;
isPrivate: boolean;
url: string;
}
const result = await fleet.repositories();
if (result.success && result.data) {
for (const repo of result.data) {
console.log(`${repo.fullName} (${repo.defaultBranch})`);
}
}

List available AI models for agent execution.

listModels(): Promise<Result<string[]>>
const result = await fleet.listModels();
if (result.success && result.data) {
console.log('Available models:', result.data.join(', '));
}

Get aggregate statistics across all agents.

summary(): Promise<Result<FleetSummary>>

Returns:

interface FleetSummary {
total: number;
running: number;
completed: number; // Includes both COMPLETED and FINISHED statuses
failed: number;
agents: Agent[];
}
const result = await fleet.summary();
if (result.success && result.data) {
const { total, running, completed, failed } = result.data;
console.log(`Fleet: ${total} total, ${running} running, ${completed} done, ${failed} failed`);
}

Block until an agent reaches a terminal state (anything other than RUNNING).

waitFor(agentId: string, options?: WaitOptions): Promise<Result<Agent>>

WaitOptions:

OptionTypeDefaultDescription
timeoutnumber300000 (5 min)Maximum wait time in milliseconds
pollIntervalnumber10000 (10 sec)Polling interval in milliseconds
const result = await fleet.waitFor('bc-abc123', {
timeout: 1800000, // 30 minutes
pollInterval: 30000, // 30 seconds
});
if (result.success && result.data) {
console.log(`Agent finished with status: ${result.data.status}`);
} else {
console.error(`Wait failed: ${result.error}`);
// error is "Timeout waiting for agent bc-abc123" on timeout
}

Monitor multiple agents until all reach terminal states. Supports progress callbacks.

monitorAgents(
agentIds: string[],
options?: MonitorOptions
): Promise<Map<string, Agent>>

MonitorOptions:

OptionTypeDefaultDescription
pollIntervalnumber15000 (15 sec)Polling interval
onProgress(status: Map<string, AgentStatus>) => voidCalled each poll cycle

Terminal states are any status other than RUNNING or PENDING.

const results = await fleet.monitorAgents(
['bc-abc123', 'bc-def456'],
{
pollInterval: 30000,
onProgress: (statusMap) => {
for (const [id, status] of statusMap) {
console.log(`${id.slice(0, 12)}: ${status}`);
}
},
}
);
for (const [id, agent] of results) {
console.log(`Final: ${id} -> ${agent.status}`);
}

Create a diamond orchestration pattern: spawn multiple target agents in parallel, then a counterparty agent that knows about all targets.

createDiamond(config: DiamondConfig): Promise<Result<DiamondResult>>

DiamondConfig:

interface DiamondConfig {
/** Parallel target agents */
targetRepos: SpawnOptions[];
/** Aggregator/verifier agent */
counterparty: SpawnOptions;
/** Control center repository URL */
controlCenter: string;
}

DiamondResult:

interface DiamondResult {
targetAgents: Agent[];
counterpartyAgent: Agent;
}

The diamond pattern works as follows:

  1. Identifies the current control manager agent ID
  2. Spawns all targetRepos agents with coordination context (control manager ID, control center)
  3. Spawns the counterparty agent with knowledge of all target agent IDs and the diamond pattern metadata
  4. Sends follow-up messages to all target agents informing them of the counterparty agent
const result = await fleet.createDiamond({
targetRepos: [
{
repository: 'https://github.com/org/frontend',
task: 'Update the UI components for the new API',
target: { autoCreatePr: true },
},
{
repository: 'https://github.com/org/backend',
task: 'Add the new REST endpoints',
target: { autoCreatePr: true },
},
],
counterparty: {
repository: 'https://github.com/org/integration-tests',
task: 'Verify end-to-end integration between frontend and backend changes',
target: { autoCreatePr: true },
},
controlCenter: 'https://github.com/org/control',
});
if (result.success && result.data) {
console.log(`Spawned ${result.data.targetAgents.length} target agents`);
console.log(`Counterparty: ${result.data.counterpartyAgent.id}`);
}

Run a bidirectional coordination loop that bridges PR comments with agent follow-up messages. This method runs indefinitely.

coordinate(config: CoordinationConfig): Promise<void>

CoordinationConfig:

interface CoordinationConfig {
/** PR number for coordination channel */
coordinationPr: number;
/** Repository in owner/repo format */
repo: string;
/** Outbound poll interval (ms) - check agents and send status requests */
outboundInterval?: number; // default: 60000
/** Inbound poll interval (ms) - check PR comments for @cursor mentions */
inboundInterval?: number; // default: 15000
/** Agent IDs to monitor */
agentIds?: string[];
}

The coordination loop runs two concurrent processes:

  • Outbound loop: Periodically checks each agent’s status. If running, sends a follow-up requesting a progress update. If finished, removes the agent from the monitoring set.
  • Inbound loop: Watches for new PR comments containing @cursor. Processes structured coordination messages like DONE: <agent-id> <summary> and BLOCKED: <agent-id> <issue>.
await fleet.coordinate({
coordinationPr: 42,
repo: 'my-org/control-center',
agentIds: ['bc-abc123', 'bc-def456'],
outboundInterval: 60000,
inboundInterval: 15000,
});

Token routing is automatic — the GitHubClient uses the organization extracted from repo to select the correct GitHub token via the Token Management system.


interface Agent {
id: string;
name?: string;
status: AgentStatus;
source: AgentSource;
target?: AgentTarget;
createdAt?: string;
updatedAt?: string;
summary?: string;
error?: string;
}
interface AgentSource {
repository: string;
ref?: string;
commitSha?: string;
}
interface AgentTarget {
branchName?: string;
url?: string;
prUrl?: string;
prNumber?: number;
}
type AgentStatus =
| 'RUNNING' // Agent is actively working
| 'FINISHED' // Agent completed work (Cursor API term)
| 'COMPLETED' // Agent completed work (alias)
| 'FAILED' // Agent encountered an error
| 'CANCELLED' // Agent was cancelled
| 'PENDING' // Agent is queued but not started
| 'UNKNOWN'; // Status could not be determined

All Fleet methods return a Result<T> wrapper:

interface Result<T> {
success: boolean;
data?: T;
error?: string;
}

The Fleet class never throws exceptions from API operations. All errors are captured in the Result wrapper. The underlying CursorAPI client handles:

  • Retryable errors: HTTP 429, 500, 502, 503, 504 are retried with exponential backoff (up to maxRetries times)
  • Timeout errors: Retried automatically
  • Network errors: TypeError, connection failures are retried
  • Input validation: Invalid agent IDs, repository formats, or webhook URLs throw synchronously before the API call
  • Sensitive data: Error messages are sanitized to redact API keys and tokens
const result = await fleet.spawn({
repository: 'https://github.com/org/repo',
task: 'Fix the bug',
});
if (!result.success) {
// result.error contains a safe, redacted error message
console.error('Spawn failed:', result.error);
// e.g., "API Error 401: Unauthorized"
// e.g., "Cursor API not available"
// e.g., "Request timeout after 60000ms"
}

For direct API access without Fleet’s high-level features, use CursorAPI:

import { CursorAPI } from '@jbcom/agentic';
const api = new CursorAPI({
apiKey: process.env.CURSOR_API_KEY,
});
// Check availability without instantiating
if (CursorAPI.isAvailable()) {
const agents = await api.listAgents();
const models = await api.listModels();
}