When multiple Pi agents are running across repositories, branches, worktrees, and machines, the user needs a lightweight way to notice and answer only the decisions that require human input without streaming every chat transcript into a dashboard.
Existing dashboard-style approaches tend to mirror full agent conversations. That is too noisy for the desired workflow. The user wants a focused “postbox” for agent attention: each Pi session registers its presence, reports useful session metadata, and sends structured decision cards when it needs input. The user can open the web interface from a phone or laptop over Tailscale/lizardtail, answer the card, and let the blocked Pi session continue.
The long-term vision is richer than a form UI: write_question is a durable decision handoff. Each new Question must state the ambiguity it aims to resolve and may preserve description and impact on each answer option. Question Chat can help the user reach a decision from an exact private fork of the originating Pi Session without polluting the coding agent’s main context.
Build Pi Postbox, consisting of:
- A standalone
pi-postbox-serverweb service. - A Pi extension exposing a
write_questiontool. - A reactive web UI optimized around pending attention cards, not streamed chat logs.
Each Pi extension instance connects outbound to the standalone server, registers the active Pi session, sends heartbeats and semantic state, and posts structured question requests. The server persists sessions, machines, projects, pending requests, resolved answers, and history in SQLite. Browser clients receive reactive state via SSE and submit answers via HTTP actions.
When an agent calls write_question with action: "create" or action: "create_batch", the tool returns after the server durably persists the Question. The agent continues independent work and receives a lightweight notification when an Answer is available; it does not poll. If the human decision is the sole remaining blocker, the agent calls wait_for_postbox once to enter explicit idle/blocked mode until an actionable event. The extension also observes ask_user tool calls so local pi-ask prompts can still appear as attention states. Explicit Postbox waits emit Herdr-compatible blocked events so Herdr sidebars can reflect the same state when Pi runs inside Herdr.
V1 does not include native push notifications or a conversational interviewer. It reserves architecture for both through server-side notification hooks and an optional exact source-session/leaf reference; it does not persist a separate top-level handoff-context object.
- As a user running multiple Pi agents, I want to see which sessions are live, so that I know what work is currently active.
- As a user, I want Pi sessions to register automatically when they start, so that I do not manually add sessions to the dashboard.
- As a user, I want each session to show its project, branch, worktree, and machine, so that I can identify what feature or task needs attention.
- As a user, I want machine names to be editable in the web UI, so that I can recognize devices by friendly names.
- As a user, I want project names and icons to be auto-detected but overrideable, so that the UI is visually scannable without mandatory configuration.
- As a user, I want project icons to work even when the Pi session runs on another machine, so that the server does not require shared filesystem access.
- As a user, I want pending questions shown as cards, so that I can answer decisions without reading full chat streams.
- As a user on mobile, I want the attention inbox to prioritize pending questions, so that I can quickly unblock agents.
- As a user with the dashboard open on multiple devices, I want an answer submitted on one device to immediately resolve the card everywhere else, so that state stays consistent.
- As a Pi agent, I want to call
write_questionwith structured options and continue independent work after persistence, so that I do not guess or waste a runnable turn. - As a Pi agent, I want an Answer notification plus a compact
get_answerresult with normalized machine-readable values, so that I can continue deterministically without polling or reloading Question context. - As a Pi agent, I want to include why the question matters, so that the user understands the decision context.
- As a Pi agent, I want to describe the impact of the decision, so that the user can answer with awareness of downstream consequences.
- As a Pi agent, I want to explain each answer option with a description and impact, so that the user understands what it means and its consequences.
- As a Pi agent, I want to state the exact ambiguity behind the Question, so that the human understands which uncertainty must be resolved.
- As a user, I want only final machine-readable answers and an optional note returned to the coding agent, so that Question details do not pollute the main coding session.
- As a future interviewer agent, I want an exact private fork of the originating Pi Session, so that I can use its real decision context without relying on a reconstructed summary.
- As a future tool, I want each request to record the originating Pi session path/id and leaf id, so that a temporary forked Pi session can be created from the exact decision point later.
- As a user, I want Pi sessions to show
working,blocked/waiting, andidlestates, so that I can distinguish active work from input waits. - As a user, I want local
ask_userwaits to appear as blocked/attention states too, so that pi-ask prompts are not invisible. - As a user running Pi inside Herdr, I want explicit
wait_for_postboxcalls to mark Herdr blocked, so that Herdr and Postbox agree without treating every persisted Question as a blocked agent. - As a user, I want resolved and expired questions retained for a limited history window, so that I can audit decisions later.
- As a user, I want the server to survive restarts without losing names or pending/history records, so that Postbox can be trusted as infrastructure.
- As a Pi user, I want Pi startup not to block if Postbox is unavailable, so that Pi remains usable without the server.
- As a Pi user, I want the extension to reconnect in the background, so that the dashboard recovers automatically after network/server interruptions.
- As a Pi user, I want
write_questioncreation requests to be idempotent across reconnects, so that duplicate cards are not created after connection drops. - As a Pi user, I want a local fallback command while a request is pending, so that I can answer/cancel from the terminal if the web UI is unavailable.
- As a server operator, I want Postbox to run as a normal local HTTP service, so that local operation remains reliable and Tailscale exposure can proxy to the actual bound port.
- As a server operator with Tailscale installed, I want Postbox startup to automatically expose the dashboard over Tailnet-private Tailscale Serve when safe, so that I can open the printed URL from a phone or laptop without manually wrapping the server.
- As a server operator, I want
pi-postbox-server statusto show the local URL, Tailnet URL, and copy-pastePI_POSTBOX_URLconfiguration for other machines, so that I can connect remote Pi sessions to the right Postbox instance. - As a developer, I want the extension and server packaged through npm/Pi conventions, so that installation across machines is straightforward.
- As a developer, I want schemas validated consistently, so that extension, server, and browser clients agree on request and answer shapes.
-
Product/tool naming:
- Tool:
write_question. - Server CLI/package identity:
pi-postbox-server. - The name “postbox” is intentional: it signals queued attention/decision handoffs, not streamed chat dashboards, and avoids “inbox” terminology collisions.
- Tool:
-
Architecture boundary:
- The server is standalone.
- The Pi extension is a thin client.
- The extension must not own or start the web server in v1.
-
Transport:
- Pi extension to server uses one outbound WebSocket connection per extension runtime/process.
- Browser clients use SSE for reactive state and HTTP endpoints for actions.
- Answer submissions must broadcast state changes to all connected browser clients.
-
Persistence:
- Server uses SQLite from day one.
- Persist machine aliases, project aliases, session registry/history, pending/resolved/expired requests, answers, timestamps, and icon cache metadata.
- Presence is derived from connection/heartbeat state, not treated as permanently persisted live state.
-
Server stack:
- TypeScript workspace.
- Fastify server.
- Zod for schema validation.
- Vite + Svelte + TypeScript + Tailwind for the web UI.
-
Extension configuration:
- Support environment variable configuration for the server URL.
- Maintain a small extension config file for persistent generated machine identity and defaults.
- Add a status command for connection health.
- V1 uses Tailscale as the trust boundary and does not require app-level auth.
-
Machine identity:
- Use hostname plus a generated persistent machine id.
- Allow dashboard-side renaming persisted by machine id.
- Do not use MAC address or IP address as the primary identity.
-
Project/session metadata:
- Extension sends cwd, git root, repo name, branch, head sha, dirty state, and worktree path when available.
- Primary display title is Pi session name when available; otherwise repo/worktree plus branch.
- Project metadata is auto-detected with optional repo-local override for display name, icon, and description.
- Icons are uploaded by the extension as small cached blobs/hashes because the server may not share the Pi machine filesystem.
-
Presence/state model:
- Adapt Herdr’s semantic state model for Pi lifecycle:
- agent start → working.
- explicit
wait_for_postbox→ blocked/waiting. ask_usertool call observed → locally blocked.- agent end → debounced idle.
- session shutdown → release/offline.
- Add dashboard WebSocket heartbeat for remote presence/offline detection.
- Mark sessions offline/stale when heartbeat/connection is lost beyond the configured threshold.
- Adapt Herdr’s semantic state model for Pi lifecycle:
-
Herdr interoperability:
wait_for_postboxemits Herdr-compatible blocked events while waiting and clears them afterward.- Postbox state remains independent and must not depend on Herdr being installed.
-
Question write behavior:
write_questioncreation actions return after durable persistence, not after human resolution.- Every accepted create returns a reusable handle with the Question ID, current content and ownership revisions, current lifecycle status, and create/idempotent disposition.
- Postbox sends the owning session a lightweight Answer-available notification; agents do not poll bounded read/list tools.
wait_for_postboxis called once only when a human decision is the sole remaining blocker.- Single-Question and batch inputs are mutually strict. Batch idempotency is per item through each stable
requestId; there is no ignored top-level batch key. - Once persistence is acknowledged, aborting or compacting the originating tool turn does not cancel the Question.
- Requests are idempotent by request id across reconnects.
- Reconnect with exponential backoff while keeping the request pending until timeout/expiry.
- Default expiry should be long enough for remote/asynchronous attention, not a short interactive timeout.
- Unresolved bounded Answer reads return a structured pending result.
-
Local fallback:
- While a request is pending, show compact local status.
- Provide local commands to answer or cancel the active pending request from the terminal.
- Do not automatically open local prompts in v1.
-
write_questioncontract:- Use one explicit action enum for creation (
create,create_batch) and owned updates (revise,cancel,supersede,reparent,transfer,takeover); never infer a destructive action from object shape. - Keep compatibility with the core ask-user pattern: single/multi/preview-style options and normalized machine-readable values.
- Require a Question prompt and a non-blank ambiguity for creation.
- Allow bounded per-answer description and impact.
- Do not accept or persist a top-level handoff-context object or per-option context.
- Use one explicit action enum for creation (
-
Coding-agent context hygiene:
- The coding agent supplies only the structured Question and option details needed for the decision.
- The extension adds objective source-session metadata only.
- Do not automatically crawl or summarize the repo in v1.
- Return compact write handles for mutations, and only the Question ID, Answer ID, final selected option values, and optional user note for Answer reads.
- Do not return Question Chat transcripts to the main coding session by default.
-
Future fork reference:
- Store originating Pi session path/id and current leaf id on each ask request.
- This supports a later separate feature where a conversational interviewer can start a temporary forked Pi session from the exact decision point.
- This future backchannel is not part of the v1
write_questionschema.
-
Dashboard UX:
- Primary view is an attention inbox sorted by urgency/age.
- Cards show project, branch, machine, session title, age, and current status.
- Hierarchy metadata is visible, but machine/project/branch tree navigation is not the primary v1 workflow.
- Answer UX supports options, optional note, submit, and cancel.
- First answer wins; after one device submits, all other clients update and disable/resolve the card.
-
Notifications:
- Native push notifications are out of scope for v1.
- V1 may expose server-side event hooks so notifications can be added later.
- Manual dashboard usage is acceptable for MVP.
-
Deployment/Tailscale:
- Server runs as a normal local HTTP service and binds locally by default.
- When Tailscale is installed and usable, startup best-effort exposes the actual dashboard port through Tailnet-private Tailscale Serve and prints the Tailnet URL.
- Automatic Tailscale exposure must not clobber existing non-Postbox Serve mappings, must not enable Funnel/public exposure, must not block local startup if Tailscale is unavailable, and must be disableable for CI/operators who do not want CLI-managed Serve state.
- lizardtail or manual Tailscale Serve remains an alternative wrapper path.
- The app should expose health/status endpoints useful for wrapping and monitoring, and
pi-postbox-server statusshould report local/Tailnet access URLs.
-
Test external behavior and protocol outcomes, not implementation details.
-
Highest-value test seams:
write_questionpersistence receipt → Answer notification/explicit wait → compact normalizedget_answerresult.- Extension state transitions for working/blocked/idle/offline.
- Observation of
ask_usertool calls causing local blocked state. - Herdr-compatible blocked event emission around explicit
wait_for_postboxcalls. - WebSocket reconnect/idempotent request behavior.
- Server persistence across restart for machines, aliases, requests, answers, and history.
- SSE client state updates after HTTP answer submission.
- First-answer-wins behavior across multiple browser clients.
- Metadata collection for git branch/worktree/session/machine.
- Question ambiguity and per-option metadata rendering without returning it wholesale to the coding agent.
-
Extension tests should use mocked Pi extension context/events where possible.
-
Server tests should exercise Fastify routes/WebSocket/SSE contracts with an isolated temporary SQLite database.
-
UI tests should focus on card lifecycle and answer submission behavior rather than visual internals.
-
End-to-end smoke tests should run a fake extension client and browser client against the server to verify registration, pending card creation, answer resolution, and persisted history.
- Streaming full Pi chat transcripts to the dashboard.
- Native push notifications for phone/laptop.
- Conversational AI interviewer.
- Interviewer-to-coding-agent backchannel.
- Starting temporary forked Pi sessions from the dashboard.
- Full pi-ask UI parity such as per-option notes, review tabs, and elaborate flows.
- Full multi-user accounts or app-level authentication.
- Tailscale Funnel/public internet exposure.
- Pushing Postbox configuration to other machines automatically.
- Automatic codebase crawling or summarization by the extension/server.
- Docker deployment as the primary v1 distribution.
Herdr’s Pi integration is important prior art. It reports semantic state from Pi lifecycle hooks, includes native session references, debounces idle, handles retryable provider errors, and uses herdr:blocked events for integrations that need to mark blocked state. Postbox should borrow the lifecycle/state ideas but add dashboard-specific heartbeat, persistence, WebSocket registration, and structured ask/answer workflows.
The current project directory is empty and not a git repository, so this PRD is written locally rather than published to an issue tracker.