---
status: done
claimed-by: "019fba91-eabf-76ae-b086-a37ac061d6e8"
claimed-at: "2026-07-31T21:19:20-04:00"
completed-at: "2026-07-31T21:19:53-04:00"
parent: "[[002-pi-subagents-map]]"
blocked-by:
  - "[[020-pi-subagents-pi-runtime-research]]"
tags:
  - ticket/prototype/afk
---

# Pi subagent status UI prototype

## Question

Prototype the Pi-native status surface for headless or background subagents so the implementation spec can decide what users and parent agents can observe without relying on Herdr.

## Prototype

I built a throwaway text-rendering prototype at `/tmp/pi-subagent-status-prototype.js`.
It models three child records and renders three candidate Pi-native status surfaces:

1. A compact footer/widget summary.
2. An expanded table widget.
3. A grouped transcript entry.

I ran it with:

```bash
nix shell nixpkgs#nodejs --command node /tmp/pi-subagent-status-prototype.js
```

The first attempt with plain `node` failed because this host has no `node` on the ordinary PATH.
Using `nix shell nixpkgs#nodejs` resolved the runtime without installing anything permanently.

The prototype was not committed to a throwaway branch because the repository worktree is already dirty with unrelated user changes and this AFK planning ticket only needed an observable design verdict.
The primary source is the script path and transcript captured below.

## Output transcript

```text
=== compact footer/widget ===
subagents 3 active  1 queued
● review fork 01:42 grep modules/agents/pi
◒ sanity independent 01:05 writing final summary
○ adversary independent -- waiting for concurrency slot

=== table widget ===
   id  agent      context      state     elapsed  trust    tools      last event                  
●  c1  review     fork         running   01:42    user     read,grep  grep modules/agents/pi      
◒  c2  sanity     independent  settling  01:05    project  read       writing final summary       
○  c3  adversary  independent  queued    --       user     read,bash  waiting for concurrency slot

=== grouped transcript entry ===
subagents
  ● review (c1)
    state=running context=fork trust=user model=openai-codex/gpt-5.5
    elapsed=01:42 tools=read,grep last=grep modules/agents/pi
  ◒ sanity (c2)
    state=settling context=independent trust=project model=inherit
    elapsed=01:05 tools=read last=writing final summary
  ○ adversary (c3)
    state=queued context=independent trust=user model=inherit
    elapsed=-- tools=read,bash last=waiting for concurrency slot

verdict:
Use compact footer/widget for live awareness, table widget for expanded live details, and grouped transcript entry only for durable milestones.
The table is the best default expanded state because it exposes state, context, trust, tools, elapsed time, and last event without reading like chat content.
```

## Verdict

Use a three-layer Pi-native status surface.

The always-visible layer should be a compact `ctx.ui.setStatus()` footer item and, when space permits, a short `ctx.ui.setWidget()` summary.
It should show active count, queued count, and one-line rows for active children.
This answers the basic question "are child agents running?" without relying on Herdr panes.

The expanded live layer should be a table-like widget rendered above the editor through Pi extension UI.
It should show child id, agent name, context mode, lifecycle state, elapsed time, trust source, effective tool policy, and last event.
The table prototype was the clearest surface because it exposes the fields that matter for supervision without making them look like assistant prose.

The durable history layer should be a custom transcript entry created with `pi.appendEntry()` and rendered with `pi.registerEntryRenderer()`.
It should appear only on durable milestones such as spawn accepted, child completed, child failed, child cancelled, and child timed out.
It should not stream every child event into the parent transcript and should not participate in LLM context.
Pi docs explicitly say custom entries created with `pi.appendEntry()` do not participate in LLM context.
Source: `/nix/store/rg248h9sz8dylm8p6a9w9fj4zrv4sgm5-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/extensions.md`.

## Implications for implementation

Represent each child with this status record shape:

```typescript
interface ChildStatus {
  id: string;
  agent: string;
  context: "fork" | "independent" | string;
  state: "queued" | "starting" | "running" | "settling" | "completed" | "failed" | "cancelled" | "timed_out";
  trust: "user" | "project" | "explicit" | "unknown";
  model: string;
  cwd: string;
  tools: string[];
  startedAt?: string;
  completedAt?: string;
  elapsedMs: number;
  lastEvent: string;
  lastEventAt?: string;
  summary?: string;
  childSession?: string;
}
```

Use icon-only state markers in dense surfaces:

- `○` for queued.
- `◐` for starting.
- `●` for running.
- `◒` for settling.
- `✓` for completed.
- `✗` for failed.
- `⊘` for cancelled.
- `⏱` for timed out.

Use words as well as icons in expanded and durable surfaces for accessibility and log readability.

Do not expose secrets, full prompts, full tool output, environment variables, or provider credentials in status rows.
Show trust source and tool policy because they are safety-relevant.
Show child session id or file path only in expanded detail, not the compact footer.

## Mapping to Pi APIs

Use `ctx.ui.setStatus()` for the one-line footer status.
Pi docs list `ctx.ui.setStatus("my-ext", "Processing...")` as a footer status API.
Source: `/nix/store/rg248h9sz8dylm8p6a9w9fj4zrv4sgm5-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/extensions.md`.

Use `ctx.ui.setWidget()` for the compact or expanded live widget.
Pi docs list `ctx.ui.setWidget("my-ext", ["Line 1", "Line 2"])` as a widget above the editor.
Source: `/nix/store/rg248h9sz8dylm8p6a9w9fj4zrv4sgm5-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/extensions.md`.

Use `pi.appendEntry()` plus `pi.registerEntryRenderer()` for durable transcript milestones.
Pi docs say custom entries do not participate in LLM context and can render inside the chat transcript in interactive mode when paired with an entry renderer.
Source: `/nix/store/rg248h9sz8dylm8p6a9w9fj4zrv4sgm5-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/extensions.md`.

Use plain text fallback for RPC/headless clients.
In RPC mode, extension UI exists but some TUI-specific methods are no-ops or return defaults, so status must also be represented in durable entries and runner events.
Source: `/nix/store/rg248h9sz8dylm8p6a9w9fj4zrv4sgm5-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/extensions.md` and `/nix/store/rg248h9sz8dylm8p6a9w9fj4zrv4sgm5-pi-coding-agent-0.82.1/lib/node_modules/pi-monorepo/docs/rpc.md`.

## Acceptance criteria for implementation

- A user can tell that child agents are running without opening Herdr.
- A user can see how many children are active and queued.
- A user can inspect each child's context mode, lifecycle state, model, trust source, tool policy, elapsed time, and last event.
- A parent agent can receive a final compact result without inheriting child transcript noise.
- The parent transcript records durable child milestones but not every streaming child token.
- Cancelling or timing out a child updates the live and durable status surfaces.
- The same status model works when Herdr is absent.

## HITL follow-up verdict

A second HITL prototype was created at `/tmp/pi-subagent-status-hitl-tui-prototype.html` after the first HTML prototype targeted a web-facing interface instead of Pi's default TUI.
The replacement artifact mocks Pi's default dark terminal UI and compares three variants:

- A live widget above the editor.
- A durable transcript milestone entry.
- An expanded inspector above the editor.

The user preferred a combined model rather than one variant alone.
The collapsed live display should be a small widget or footer-style summary that shows a quick overview such as `2 running · 1 queued`.
The expanded live display should use the inspector shape from variant C.
The transcript milestone entry from variant B should record what happened, but it should not be the only status surface.

Implementation should therefore model status with three progressive layers:

1. Collapsed live summary by default.
2. Expanded live inspector on demand or by later user configuration.
3. Durable transcript milestone entries for historical record.

The status data model should be separate from the UI that renders it.
The default subagent extension should provide the child status records, status endpoints, durable entries, and neutral update events.
The Pi-native UI should be optional and replaceable by another extension that consumes the same data model.
This preserves the neutral core and lets a later user-specific or Herdr-oriented UI replace the default presentation without forking child lifecycle code.

## Limitations

This prototype tested information layout and state readability, not the actual Pi TUI renderer.
A later implementation spike should verify exact `ctx.ui.setWidget()` rendering width, wrapping, color support, and keyboard affordances inside Pi.
