Skip to content

Observability

Swarmlord captures every message, tool call, and timing for every session. You can watch agents live and read the full history of any past session at any time — the foundation for retrospectives and self-improvement loops.

What's captured

For every session, automatically:

  • Every user and assistant message, with timestamps.
  • Every tool call — input, output, start time, end time, duration.
  • Every step — tokens (input, output, reasoning, cache read/write), cost, finish reason.
  • Session outcome (completed / error / cancelled) and the reason.
  • Workspace file snapshots.
  • A durable event stream you can replay from any point.

Nothing you do in the SDK or CLI turns this on — it's always on, and retention is indefinite for the canonical archive.

Reading any past session

Every session is readable at any time — recent, old, finished, paused, or resumed later.

typescript
const session = client.session("past-session-id");
const messages = await session.getMessages();
// [{ info: MessageInfo, parts: Part[] }, ...]

Timings on every tool call

Every tool call part carries a start and end timestamp. Assistant messages carry time.created and time.completed. Reasoning and text parts carry time.start / time.end.

The SDK exports partTimings(part) which returns { start, end, durationMs } uniformly across part types, so you don't have to narrow by type:

typescript
import { partTimings } from "swarmlord";

const messages = await session.getMessages();
for (const { parts } of messages) {
    for (const part of parts) {
        const { durationMs } = partTimings(part);
        if (part.type === "tool" && durationMs !== undefined) {
            console.log(`${part.tool} took ${durationMs}ms`);
        }
    }
}

From the CLI: swarmlord tail

swarmlord tail is the single command for watching or inspecting any session — live or historical.

bash
# Watch an in-progress session
swarmlord tail <sessionId>

# Replay the full event history of a finished session
swarmlord tail <sessionId> --since 0

# Resume from a specific event id (dropped connection, partial replay)
swarmlord tail <sessionId> --since 12345

For programmatic use — building your own dashboards, feeding retro analyses, piping into jq — add --raw to emit newline-delimited JSON events. Every message, tool call (with timings), step finish (with tokens and cost), status change, and error is a single JSON line:

bash
swarmlord tail <sessionId> --since 0 --raw | jq 'select(.type == "tool-complete")'

This is the recommended way to export a session's full history from the command line. Find session IDs with swarmlord logs.

Live tail via SDK

SessionHandle.stream(input) and TaskHandle.stream() both return an AsyncIterable<AgentEvent>:

typescript
const task = await agent.run({ prompt: "..." });
for await (const event of task.stream()) {
    if (event.type === "tool-complete") {
        console.log(`tool ${event.part.tool} done`);
    }
}

Both handles support resuming from a given event id to replay earlier activity.

Per-session rollups

Aggregate metrics for any session:

typescript
const metrics = await session.getMetrics();
// { tokens, cost, messages, steps, toolCalls, toolUsage, costBreakdown, containerUsage }

Account-wide recent sessions:

typescript
const sessions = await client.listSessions({ limit: 50 });
// each row includes totalTokens, totalCost, outcome, messageCount, timeFinished

Using observability for self-improvement

The data model is designed for retrospective analysis of agent behaviour:

  • Every tool call has a start, end, and duration.
  • Every step records tokens, cost, and finish reason.
  • Every session records its outcome and the reason.
  • The full conversation is preserved indefinitely and readable any time.

A common pattern is to list recent sessions for an agent, pull each conversation, and analyse failure modes, slow tool calls, or expensive steps programmatically — then feed the findings back into the agent's instructions.

typescript
const sessions = await client.listSessions({ limit: 50 });
for (const s of sessions.filter(s => s.agent === "my-agent")) {
    const { parts } = (await client.session(s.id).getMessages()).at(-1) ?? { parts: [] };
    const slow = parts.flatMap(p => {
        const { durationMs } = partTimings(p);
        return p.type === "tool" && durationMs && durationMs > 5_000 ? [{ tool: p.tool, durationMs }] : [];
    });
    if (slow.length) console.log(s.id, slow);
}