mirror

Source: src/mirror.ts

The mirror: a live subagent’s session, on a unix socket.

A herdr pane cannot host an in-process subagent, so the pane hosts a client of this instead. The client attaches by id and gets the transcript so far, then pi’s own session events as they happen, which is what pi’s chat components are written against. It may speak back: a word for the turn in flight, or a stop.

This is a port of the core, beside ask.ts and verify.ts, not a reporter: a reporter only reads the stream, and the keyboard reaches the session. It is still opt-in by the act that opens a pane - a subagent nobody attached to is a subagent nobody spoke to, and the workflow runs identically.

One server per process, listening only once something asks where it is, and gone with the last subagent. Registration itself costs a map entry, so every subagent registers and a test suite never touches a socket.

Attach

type

export type Attach = {
	/** The id of the subagent to watch. */
	attach: string;
};

The first line a client sends.

Mirrored

type

export type Mirrored = {
	/** The id a client attaches by, e.g. `scout#1`. */
	id: string;
	/** The definition's name, for the pane's header. */
	agent: string;
	/** The model pi resolved, when it has. */
	model?: string;
	/** Where its tools run - pi's tool renderers want it to shorten paths. */
	cwd: string;
	/** The session itself: its transcript, its events, its steer. */
	session: SessionPort;
	/** Its run's bus: a steer is emitted there, so the record holds it. */
	bus: EventBus;
	/** Its stop switch, the one thing that is safe at any moment. */
	stop(): void;
};

What the mirror needs from a live subagent. Registered by spawn.

MirrorIn

type

export type MirrorIn = { type: "steer"; text: string } | { type: "abort" };

What a client may say after attaching.

MirrorOut

type

export type MirrorOut =
	| { type: "attached"; id: string; agent: string; model?: string; cwd: string; pi: string }
	| { type: "message"; message: unknown }
	| { type: "refused"; reason: string }
	| { type: "error"; message: string }
	| SessionEvent
	| SubagentEvent;

What a client receives, one JSON object per line.

attached comes first and names the pi package this process runs, so a client draws with the same components that produced the events. Then one message per transcript entry, then pi’s own events as they are (their type is pi’s), interleaved with status, usage, steer and close from the run’s stream.