reporters/picture

Source: src/reporters/picture.ts

The picture of a run: the event stream folded, once, into what every reader wants to know - who is alive, under whom, doing what, at what cost.

The TUI widget, the console, usage.json, an experiment’s cell and the tool’s own result all read this. None of them folds the stream itself: a fix to how the picture is built reaches every one of them, and a reader that kept its own copy of a fact would be the copy that drifts.

Nothing here draws. Formatting is tui.ts, and it takes a snapshot in; putting a child under its parent is tree.ts.

createRunPicture

function

export function createRunPicture(): RunPicture { /* … */ }

Folds subagent events into a picture.

A fan-out reads top to bottom in the order the branches were launched, not in the order they finish and not in the order their sessions came up. The last one is why this sorts rather than trusting arrival: spawn cannot be emitted before the session exists, since it carries the model pi resolved, and three scouts launched together drew as scout#2, scout#1, scout#3.

RunPicture

type

export type RunPicture = {
	/** Subscribe this to the event bus. */
	reporter: EventListener;
	/** The current picture. Cheap enough to call on every frame. */
	snapshot(): RunSnapshot;
	/** One subagent, or nothing if the picture never saw it spawn. */
	of(id: string): SubagentSnapshot | undefined;
	/** Called on every event, so a display knows when to redraw. */
	onChange(listener: () => void): void;
};

The live picture of a run: subscribe it, then read it whenever you draw.

RunSnapshot

type

export type RunSnapshot = {
	/** In launch order, so a fan-out reads top to bottom as it was launched. */
	subagents: SubagentSnapshot[];
	/** Finished, whatever the outcome. */
	done: number;
	/** Currently working. */
	running: number;
	/** Finished with `ok: false`. Counted apart: `2/3 done` hides a crash. */
	failed: number;
	/** Spawned so far, which is what `done` and `running` are counted against. */
	total: number;
	/** Sum over every subagent. `wallMs` is filled in by the caller. */
	usage: Usage;
};

The whole picture: every subagent, plus what it adds up to.

snapshotFrom

function

export function snapshotFrom(subagents: readonly SubagentSnapshot[]): RunSnapshot { /* … */ }

What a list of subagents adds up to, in the order it was given.

The one place the counts and the total are derived: the live picture calls it on every frame, and a reader handed the subagents alone - the tool’s result, once pi has serialised it - calls it to get the same picture back.

wallMs stays 0: a list of subagents cannot know the elapsed time, and the caller passes the real one to formatUsage/usageReport when it has one.

SubagentSnapshot

type

export type SubagentSnapshot = {
	/** The subagent, e.g. `scout#1`. Unique for the life of the process. */
	id: string;
	/** The agent it came from. Several subagents may share one agent. */
	agent: string;
	/** The lifetime it is running with - the row says whether it will remember. */
	lifetime: string;
	/** What it is doing right now. `"done"` covers success and failure alike. */
	status: SubagentStatus;
	/** The task it was given. Empty until the first `ask`. */
	task: string;
	/** Every tool call so far, in order. The last one is what the collapsed row shows. */
	tools: ToolCall[];
	/** Assistant text, accumulated from the deltas. */
	output: string;
	/** Cumulative since spawn - for a persistent subagent, that is several turns. */
	usage: Usage;
	/**
	 * How far under a root it sits: `0` for a root, one more per level of
	 * delegation. Decided when it spawns, from the parent the picture had seen
	 * by then - a parent it never saw leaves the child a root, never lost.
	 */
	depth: number;
	/** `provider/id` as pi resolved it, when it could. */
	model?: string;
	/** The subagent that had this one spawned. Absent on a root. */
	parentId?: string;
	/**
	 * Monotonic instant the current turn began, while one is running.
	 *
	 * `usage.busyMs` only lands when the turn ends, so without this the widget
	 * would read `0.0s` for the whole wait and then jump straight to the total.
	 */
	startedAt?: number;
	/** Whether its last turn succeeded. Absent until it has finished one. */
	ok?: boolean;
	/** The failure, when there was one - shown on the row rather than swallowed. */
	error?: string;
};

Everything known about one subagent, at one instant.

ToolCall

type

export type ToolCall = {
	/** The tool pi ran, e.g. `read` or `bash`. */
	name: string;
	/** Its arguments, untouched: the expanded view formats them, we only keep them. */
	args: unknown;
	/** pi's id for the call, when it gave one. */
	call?: string;
	/** What pi said when the call came back an error: refused, or failed while it ran. */
	error?: string;
};

A tool call as it happened, kept for the expanded view.