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 12345For 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, timeFinishedUsing 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);
}