Skip to content

TypeScript SDK

Deploy agents with the CLI, then use the SDK to create sessions, run tasks, and stream responses from your app.

Install

bash
npm install swarmlord

The SDK resolves the API key from SWARMLORD_API_KEY automatically. You can also pass it explicitly:

typescript
import { createClient } from "swarmlord";

const client = createClient({ apiKey: process.env.SWARMLORD_API_KEY });

TIP

The SDK does not read the auth file from swarmlord auth login — use the env var or pass apiKey explicitly.

Agents

client.agent(name) references a deployed agent by the name field in its swarmlord.jsonc. All configuration — model, tools, instructions, skills, workspace files — comes from the deployment.

typescript
const bot = client.agent("support-bot");

Override settings per handle:

typescript
const reviewer = client.agent({
    name: "code-reviewer",
    model: "anthropic/claude-sonnet-4.6",
});

Resume an existing session by ID:

typescript
const session = client.agent("my-agent").session("existing-session-id");
await session.send("Continue where we left off");

Sessions — Interactive Conversations

Sessions are stateful, multi-turn, and persistent. The agent's sandbox is seeded with deployed workspace files automatically.

typescript
const session = await client.agent("my-agent").createSession({
    title: "Build Todo App",
});

const result = await session.send("Create a REST API with Hono", {
    onText: delta => process.stdout.write(delta),
});

// Continue the conversation
await session.send("Now add authentication");

await session.end();

Streaming Callbacks

session.send() accepts callback functions for real-time events:

typescript
const result = await session.send("Analyze this data", {
    onConnect: () => console.log("Connected"),
    onDisconnect: () => console.log("Reconnecting..."),
    onText: delta => process.stdout.write(delta),
    onReasoning: delta => {
        /* reasoning tokens */
    },
    onToolStart: part => console.log(`[tool] ${part.tool}`),
    onToolComplete: part => console.log(`[done] ${part.tool}`),
    onToolError: (part, error) => console.error(`[error] ${part.tool}: ${error}`),
    onStepFinish: (tokens, cost) => console.log(`$${cost.toFixed(4)}`),
    onAttachment: file => console.log(`File: ${file.filename}`),
    onPermission: async req => "once", // "once" | "always" | "reject"
    onStatus: status => console.log(`Status: ${status}`),
    onSessionIdle: sessionId => console.log(`Idle: ${sessionId}`),
    onError: err => console.error(err),

    // Memory events (when the agent has `memory: { enabled: true }`)
    onMemoryRecalled: e => console.log(`recalled ${e.entryCount} entries`),
    onMemoryExtracted: e => console.log(`extracted ${e.opsApplied}/${e.opsProposed} ops`),
    onMemoryWrote: e => console.log(`wrote [${e.category}] id=${e.id}`),
    onMemoryBlocked: e => console.warn(`blocked: ${e.reasons.join(",")}`),

    // Universal firehose — fires once per event, BEFORE specific callbacks.
    // Useful for custom logging or handling event types without a specific callback.
    onEvent: e => log.debug(e.type, e),
});

Every specific callback still fires; onEvent is an escape hatch that sees every AgentEvent variant including any future ones.

Async Iterator

For full control, use session.stream(). Note that stream() does not automatically reply to permission requests — use send() with onPermission for that, or handle permission-asked events and call replyToPermission() manually.

typescript
for await (const event of session.stream("Add tests")) {
    switch (event.type) {
        case "connect":
            console.log("Connected");
            break;
        case "text-delta":
            process.stdout.write(event.delta);
            break;
        case "tool-start":
            console.log(`\n[${event.part.tool}]`);
            break;
        case "step-finish":
            console.log(`\nStep: $${event.cost.toFixed(4)}`);
            break;
    }
}

File Attachments

Send files alongside messages using the parts-based input:

typescript
import { readFileSync } from "node:fs";

const csvBase64 = readFileSync("data.csv").toString("base64");

await session.send({
    parts: [
        { type: "text", text: "Analyze this CSV and produce a chart." },
        {
            type: "file",
            url: `data:text/csv;base64,${csvBase64}`,
            mime: "text/csv",
            filename: "data.csv",
        },
    ],
});

Sandbox Access

Run shell commands and read/write files directly without going through the LLM:

typescript
const result = await session.shell("npm test");
console.log(result.stdout);
console.log(`Exit code: ${result.exitCode}`);

const file = await session.getFile("/workspace/output.json");
const image = await session.getFileBuffer("/workspace/chart.png");

await session.putFile("/workspace/config.json", JSON.stringify({ key: "value" }));
await session.putFileBuffer("/workspace/input.pdf", readFileSync("doc.pdf"));

Verify Outputs

Check whether expected files exist in the workspace after a send:

typescript
const missing = await session.verifyOutputs(["/workspace/report.md", "/workspace/chart.png"]);
if (missing.length) console.warn("Missing:", missing);

Commands

Run a named command defined in the agent's config:

typescript
const output = await session.runCommand("lint", { path: "./src" });

Permissions

Reply to a permission request outside of the onPermission callback (useful with stream() or events()):

typescript
await session.replyToPermission(request.id, "once", "Approved by automation");

Raw Event Stream

Get the underlying SSE stream for custom consumption. Supports Last-Event-ID replay:

typescript
const stream = await session.events(lastEventId);

Session Lifecycle

typescript
await session.summarize(); // Compact context (free up token budget)
await session.revert(messageId); // Undo to a specific message
await session.abort(); // Cancel the current turn
await session.end(); // Clean up the session

Session Inspection

typescript
const info = await session.getInfo();
const messages = await session.getMessages();
const metrics = await session.getMetrics();
const todos = await session.getTodos();
const artifact = await session.getArtifact("report");
const artifactBuf = await session.getArtifactBuffer("chart");

Session Metadata

typescript
await session.update({ title: "New Title", archived: true });

Resume or Fork

Resume a session from anywhere using client.session(id) — no agent reference needed:

typescript
// Resume from another process
const session = client.session("existing-session-id");
await session.send("Continue where we left off");

// Fork to try an alternative approach
const fork = await client.agent("my-agent").forkSession("session-to-fork");
await fork.send("Try a different approach");

Tasks — Fire-and-Forget

Launch a task, walk away. The agent runs in the cloud and you poll or wait for the result.

typescript
const task = await client
    .agent("code-reviewer")
    .run("Review all TypeScript files in the project", { title: "Code Review" });

// Poll manually
const status = await task.poll(); // "accepted" | "busy" | "compacting" | "idle" | "error"

// Or block until done (default timeout: 600_000ms / 10 minutes)
const result = await task.result({ timeoutMs: 300_000 });
console.log(result.text);
console.log(`Cost: $${result.cost?.toFixed(4)}`);

// Cancel if needed
await task.cancel();

Live Stream

Tasks can also be tailed live via SSE, same as session.stream():

typescript
const task = await client.agent("reviewer").run("Analyze this PR");
for await (const event of task.stream()) {
    if (event.type === "tool-complete") console.log(`tool ${event.part.tool} done`);
    if (event.type === "session-idle") break;
}

Pass { since: <eventId> } to replay from a specific point in the durable event log. See Observability for the full model.

Webhooks

Get notified when a task completes:

typescript
const task = await client.agent("reviewer").run("Analyze this PR", { webhook: "https://my-app.com/hooks/done" });

Resume a Task

typescript
const task = client.task(sessionId);
const result = await task.result();

SendResult

Every session.send() resolves to a SendResult:

typescript
interface SendResult {
    messageId: string;
    text: string; // Full text output
    parts: Part[]; // All message parts (text, tools, files, etc.)
    tokens?: Tokens; // Token usage
    cost?: number; // Cost in USD
    error?: { name: string; data: Record<string, unknown> };
    incomplete?: boolean; // True if SSE stream dropped before completion
    outputErrors?: string[]; // Paths from expectedOutputs that were not found
}

Listing Resources

typescript
const sessions = await client.listSessions({ limit: 20, archived: false });
const agents = await client.listDeployedAgents();
const schedules = await client.listSchedules();
const triggers = await client.listTriggers();
const { data: models } = await client.listModels();

Schedules

Manage deployed scheduled agents:

typescript
const schedule = client.schedule("schedule-id");

await schedule.trigger(); // Manually fire a scheduled run
await schedule.pause(); // Pause the cron
await schedule.resume(); // Resume the cron
await schedule.update({ cron: "0 10 * * 1-5" });
await schedule.delete();

Triggers

Manage deployed webhook triggers:

typescript
const trigger = client.trigger("trigger-id");

const info = await trigger.getInfo(); // Get webhook URL, secret, status
await trigger.pause();
await trigger.resume();
await trigger.update({ events: ["app_mention"] });
await trigger.delete();

Memory

Cross-session structured recall, scoped to a deployed agent. Requires memory: { enabled: true } in the agent's swarmlord.jsonc. See the Memory concept page for the full picture.

typescript
const agent = client.agent({ name: "support-bot" });

await agent.memory.add({ category: "preference", content: "User prefers concise replies" });

const hits = await agent.memory.search("reply style", { limit: 5 });
const all = await agent.memory.list({ category: "preference", limit: 50 });
await agent.memory.forget(hits[0].id);

Categories: fact, preference, instruction, relationship, skill_observation (any string is accepted).

Entries are soft-deleted and kept as audit trail (deletedAt, supersededBy). Injection into the system prompt happens automatically at the start of each session turn loop, wrapped in a <memory-context> fence so it never overrides your agent's instructions.

Configuration

Read and update user-level configuration:

typescript
const config = await client.getConfig();
await client.updateConfig({
    model: "anthropic/claude-sonnet-4.6",
    compaction: { auto: true },
});

Workflows

Workflows are durable multi-step programs that coordinate sessions, ask humans for approval, and wait for timers or external events — surviving deploys and resuming exactly where they paused. See the Workflows guide for node types and semantics.

typescript
import { createClient, defineWorkflow } from "swarmlord";

const client = createClient();

const morningBrief = defineWorkflow({
    startNodeId: "research",
    sharedSession: { title: "morning brief" },
    nodes: {
        research: {
            id: "research",
            type: "task",
            prompt: "Read my inbox and list the 3 most important emails.",
            saveAs: "emails",
            next: "confirm",
        },
        confirm: {
            id: "confirm",
            type: "approval",
            question: "Send the top 3 emails as an SMS summary now?",
            options: ["yes", "no"],
            next: { yes: "send", no: "done" },
        },
        send: {
            id: "send",
            type: "task",
            prompt: "Compose a concise SMS with: {{emails}}",
            next: "done",
        },
        done: { id: "done", type: "end", result: "emails" },
    },
});

const handle = await client.startWorkflow({
    definition: morningBrief,
    title: "Morning brief",
});

const info = await handle.result();
console.log(info.status); // "completed" | "failed" | "cancelled"

WorkflowHandle methods:

MethodReturnsDescription
status()Promise<WorkflowStatus>Current lifecycle status.
get()Promise<WorkflowInfo>Full workflow state including vars and last step.
history()Promise<WorkflowStepInfo[]>Ordered step history with timings and outcomes.
result()Promise<WorkflowInfo>Polls until the workflow reaches a terminal state.
stream()Promise<ReadableStream>SSE stream of workflow events (node transitions, task progress).
cancel()Promise<void>Cancels the workflow at its next checkpoint.

Reconnect to an existing workflow with client.workflow(id).

Metrics and Cost

Every session tracks token usage and cost across all services:

typescript
const metrics = await session.getMetrics();
console.log(`Total cost: $${metrics.cost.toFixed(4)}`);
console.log(`LLM: $${metrics.costBreakdown.llm.toFixed(4)}`);
console.log(`Steps: ${metrics.steps}, Tool calls: ${metrics.toolCalls}`);

SessionMetrics includes:

FieldTypeDescription
sessionIdstringSession identifier
messagesnumberTotal messages in the conversation
stepsnumberLLM inference steps
toolCallsnumberTotal tool invocations
toolUsageRecord<string, number>Per-tool call counts
tokensTokensAggregate token usage (input, output, reasoning, cache)
persistBytesnumberPersistent storage size in bytes
costnumberTotal cost in USD
costBreakdownCostBreakdownPer-service breakdown (LLM, container, DO, R2, Exa, egress, service fee)

Per-step cost is also available via the onStepFinish callback and the step-finish event.

Shell Result

session.shell() returns a ShellResult:

typescript
interface ShellResult {
    stdout: string;
    stderr: string;
    exitCode: number;
}

Task Result

task.result() resolves to a TaskResult:

typescript
interface TaskResult {
    sessionId: string;
    status: "completed" | "error";
    text: string;
    messages: WithParts[];
    tokens?: Tokens;
    cost?: number;
}

Configuration Fields

client.getConfig() returns the user-level Config:

FieldTypeDescription
modelstringDefault model for new sessions
smallModelstringModel used for background tasks (titles, summaries)
defaultAgentstringDefault agent entry name
instructionsstring[]Additional system instructions prepended to all sessions
permissionPermissionConfigGlobal permission rules
toolsToolsConfigGlobal tool enable/disable defaults
commandobjectNamed commands — each has template, optional description, agent, model, subtask
compactionobjectContext compaction settings: auto (boolean), prune (boolean), reserved (number)

Types

All types are exported from "swarmlord" — including AgentHandle, SessionHandle, TaskHandle, WorkflowHandle, SendResult, TaskResult, StreamCallbacks, WorkflowDefinition, StartWorkflowInput, WorkflowInfo, and others. Custom tools use a separate import path: "swarmlord/tool".