Skip to content

Agent monitor: live canvas of running agents, lanes, and messages - #24

Merged
thisguymartin merged 8 commits into
mainfrom
feat/agent-monitor
Oct 2, 2026
Merged

thisguymartin merged 8 commits into
mainfrom
feat/agent-monitor

Conversation

@thisguymartin

@thisguymartin thisguymartin commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner

Closes #23

What changed

pstack fans work out to many agents, across two harnesses and several providers, and nothing showed that fan-out while it ran. This adds /pstack:monitor: a read-only local server with a live node canvas of the agents on this machine.

  • Canvas. The selected session's agents, wired left to right from parent to child, each with a model node. Running wires flow, each real activity event sends a spark down its wire, and a new agent emerges from its parent. Claude Code sessions are warm terracotta and Codex sessions are blue; each node carries its provider's color.
  • Drill-down. Any agent opens into a panel with its status, model, effort, timing, tokens, parent, and a live timeline of prompts, replies, thinking, and tool calls paired with their outputs.
  • Messages. Dashed, arrowed arcs with counts show which agents messaged which, from Codex agent messages and Claude Code SendMessage calls.
  • External lanes. pstack-runner kept a child CLI's output in memory until exit, so a Codex cross-judge launched from Claude Code was invisible while it ran. The runner now writes an opt-in journal under ~/.pstack-flex/lanes/ (start record, stdout as it arrives, receipt copy) while that directory exists. A new --label flag names a lane by its role and routes nothing. A journal failure never changes a lane's receipt, exit status, or output.
  • Evidence over guesses. Status comes from process records, lifecycle events, parent tool results, and receipts, never file times. What cannot be known shows as unknown. Unrecognized transcript records are counted and surfaced; pstack-monitor doctor prints parse health without content.
  • Local only. Loopback bind, per-start token traded for an HttpOnly, SameSite=Strict cookie, foreign Host and Origin rejected, no writes, strict same-origin CSP, transcript text rendered only as text. No hooks, no harness settings changed, no idle timeout.
  • CLI. pstack-monitor start|status|stop|doctor|journal on|off|status. start reuses a running server of the same build and replaces one from another build. The skill maps "stop / kill / shut down the monitor" to stop.
  • Never starts by itself. You start it with /pstack:monitor, by asking Codex for pstack:monitor, or with pstack-monitor start; it runs in the background until stopped or the machine restarts. docs/USAGE.md gains a "Watching your agents" section (start, stop, terminal paths, options, lane journal, troubleshooting), and the README gets an optional third get-started step.

Reviewing

The commits read in order: core indexing, the server, the canvas, the skill and docs, the runner journal, stop handling, and message arcs. Everything under skills/poteto-mode/scripts/monitor/ and skills/monitor/ is new and fork-owned. Upstream-authored files touched, all additive and recorded in UPSTREAM-FLEX.md:

  • runner/run.ts: an optional stdout callback on the model run, the journal opened after output reservation and finished on both return paths.
  • runner/cli.ts, runner/types.ts: the optional --label.
  • runner/run.test.ts: points the journal at a missing directory so existing tests never write into a real home, plus four journal tests.
  • references/provider-dispatch.md: one pstack-flex paragraph after the invocation block.
  • scripts/package.json: monitor in the test filter and two tsconfigs in typecheck.

Verification

  • Bun tests, strict typecheck, static invariants, and plugin validation pass.
  • The exact candidate is installed in every affected harness.
  • The changed behavior passes from each real user surface.
  • The installed version, action, and observed result appear below.

Local gate on Bun 1.3.11: bun run test 301 pass, bun run typecheck clean, PSTACK_STATIC_ONLY=1 bash tests/skill-collision-repro.sh exit 0, manifest parse ok, claude plugin validate plugins/pstack passed, git diff --check clean.

Checked before install:

  • pstack-monitor doctor against this machine's transcripts (Claude Code 2.1.281 to 2.1.287, Codex 0.155 to 0.160, last 7 days): every line parsed, no malformed records.
  • The running server against this session: the session showed as running from its process record, its four planning subagents as three done and one cancelled, and the event stream delivered this session's own tool calls live.
  • Headless Chrome: both themes, the drill-down, message arcs, phone width, and reduced motion (flowing wires and the sweep ring stop).
  • The real pstack-runner against a fake codex CLI with the journal on: the "arena cross-judge" lane appeared under its parent session while running, streamed its tool calls, and finished done with the receipt's token counts; the journal's receipt equals the runner's.

Live evidence: pending. To record it:

  1. Claude Code. Install this candidate. Run /pstack:monitor: expect a link, the orange theme, and the current session live. Run a small arena with one Codex lane: expect a labeled lane that streams and finishes. Run /pstack:monitor stop: expect pstack-monitor stopped.
  2. Codex. Ask for pstack:monitor: expect the blue theme and the root thread. Spawn a child: expect it linked under the root with its messages drawn.
  3. Regression. pstack-monitor journal off, then one external lane per harness: expect an unchanged receipt and nothing under ~/.pstack-flex/lanes.

Known limits: a Codex thread killed mid-turn stays "in turn" because Codex writes nothing on a crash; Claude Desktop and SDK sessions show "unknown" because they keep no process record; Claude, DeepSeek, and MiniMax lanes show their output only at exit because those CLIs print one result.

A pull request without live evidence remains a draft. Do not merge, tag, release, or roll it out.

Add the core of the agent monitor: a domain model, one adapter per
transcript format, and a reducer that derives each agent's status from
evidence (process records, lifecycle events, parent tool results) rather
than file modification times. A byte-level tailer consumes only complete
JSONL lines, and an index discovers, tails, and pages transcript files.

`pstack-monitor doctor` indexes the recent window and prints per-source
parse health, record counts, and CLI versions, never content. Unknown
record types and malformed records are counted and surfaced instead of
dropped.

Refs #23
`pstack-monitor start` launches a detached server on 127.0.0.1 and
prints a link carrying a per-start token; a second start reuses the
running server, and a server from another build is replaced. `status`
and `stop` complete the lifecycle. Nothing times the server out.

The server keeps the index current by polling, with recursive file
events only as a hint, probes recorded processes by pid and start time,
and pushes snapshots, deltas, and the watched agent's new timeline items
over server-sent events. Every route but the health check requires the
token; foreign Host and Origin headers are rejected to stop DNS
rebinding, and the API is read-only.

Refs #23
The server now bundles a page in memory at start: a node canvas of the
selected session's agents, a session rail, live counters, and a detail
panel that streams one agent's timeline. Spawn links run left to right
from each parent's output port; every agent with a known model carries a
model node on a dashed tether.

Motion reports work rather than decorating it: running wires flow,
running cards sweep, each activity event sends a spark down its wire,
and a new agent emerges from its parent. Reduced motion keeps the state
colors and drops the movement. The theme follows the session's harness,
warm terracotta for Claude Code and blue for Codex, and the first paint
takes it from the link so it never flashes the wrong one.

The page has no framework or new dependency, renders transcript text
only through textContent, and runs under a strict same-origin content
security policy.

Refs #23
`/pstack:monitor` starts the monitor and hands the user its link. The
parent passes `--parent` for the harness it is, the same rule the
external runner follows; the flag sets the page theme and first
selection and routes nothing.

Document the sources, the evidence each status rests on, the lifecycle,
and the security model in the reference, and record the new fork-owned
files in UPSTREAM-FLEX.md and NOTICE.md.

Refs #23
`pstack-runner` keeps a child CLI's output in memory until it exits, so
an external lane such as a Codex cross-judge launched from Claude Code
was invisible while it ran. The runner now writes an opt-in journal per
lane under ~/.pstack-flex/lanes/: the start record, the child's stdout
as it arrives, and a copy of the receipt. It writes only when that
directory exists; `pstack-monitor start` creates it, and
`pstack-monitor journal off` deletes it with everything it recorded.
The server prunes lanes older than seven days.

The tap is a synchronous callback inside the existing read loop, passed
only to the model run, so it adds nothing to the cancellation and
deadline races. Every journal write is guarded: a failure stops the
journal and never changes the lane's receipt, exit status, or output.
The existing runner tests point the journal at a missing directory so
they never write into a real home.

`--label` names a lane by the role it fills and routes nothing. The
monitor's lanes adapter turns the journal into canvas nodes under the
session that launched each lane, streaming Codex and Grok lanes live and
showing Claude-style lanes' single result when they exit.

Refs #23
The skill now maps the request to one command: open (start), stop,
kill, or shut down (stop), and check or relink (status). It states that
stop ends the server only and never touches the agents the page shows,
and says where to stop a running agent instead.

Refs #23
Agents talk as well as spawn: Codex parents and children exchange agent
messages, and Claude Code agents use SendMessage. The canvas now draws
each sender-to-recipient pair as a dashed, arrowed arc with a message
count, bowing to the left of travel so a reply takes the other side, and
each new message sends a spark along its arc. The bar counts the
session's messages and the panel shows what an agent sent and received.

Codex records each message once, in the recipient's rollout, addressed
by agent path; the store resolves the path within that tree. Claude Code
targets resolve by agent id, name, or `main` within the session.
Messages are keyed by their own id, so one seen twice counts once, and a
message whose ends are not both indexed, or that goes to another
session's socket, is left out rather than guessed.

Refs #23
The monitor never starts by itself, which the docs did not say. Add a
"Watching your agents" section to USAGE.md: starting it from Claude
Code, Codex, or a terminal (with the installed launcher's path), the
options, stopping it and the other commands, the lane journal, what it
reads and writes, and a troubleshooting table. The README gains an
optional third get-started step that points there.

Refs #23
@thisguymartin
thisguymartin marked this pull request as ready for review October 2, 2026 16:25
@thisguymartin
thisguymartin merged commit 9348ba4 into main Oct 2, 2026
1 check passed
@thisguymartin
thisguymartin deleted the feat/agent-monitor branch October 3, 2026 19:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Agent monitor: local server and live canvas for pstack agent fan-out

1 participant