- JavaScript 55%
- Python 25.9%
- TypeScript 10.3%
- CSS 7.5%
- HTML 1.2%
- Other 0.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .gitea/workflows | ||
| dashboard | ||
| docs/reports | ||
| server | ||
| .gitignore | ||
| AGENTS.md | ||
| index.ts | ||
| package.json | ||
| preview.png | ||
| README.md | ||
| run-tests.sh | ||
Pi POD Team
A pi extension that turns the agent into the producer of a POD team — a small delivery crew of specialist subagents. Tasks are classified, routed, executed, judged, and verified by Jev (TypeSafe's System One decision model), while a live Bloomberg-terminal-style dashboard shows everything as it happens.
task ──▶ STAFF ──▶ PLAN ──▶ SPLIT ──▶ TRIAGE ──▶ EXECUTE ──▶ JUDGE ──▶ VERIFY ──▶ REPORT
Jev picks 4 planners split-vote Jev picks worker(s) Jev picks reviewer / qa /
the team compete then one single or (double = the winner ux / docs
4 plans archetype double blind A/B) if double follow-ups
per subtask
How it works
- Jev decides, code executes. Every classification, plan comparison, and result judgment is a
single TypeSafe
/v1/systemonecall — typed answers with calibrated probabilities, no text generation to parse. - Team archetypes. Ten specialists (backend, frontend, ui_designer, ux_designer, qa, reviewer, perf, devops, docs, security), each with its own spawn config and brief. Jev picks who owns each subtask.
- Double & judge. Risky subtasks run as two blind, independent workers; Jev compares the reports and keeps the winner (low confidence → the producer merges manually and says so).
- Follow-up checks. After execution Jev flags what else is needed: independent code review, tests, performance measurement, UX critique, documentation — each routed to the right archetype.
- Workers consult Jev too (Jev-first workers). Workers have no
pod_jevtool — they reach the same local brain overcurl(POST 127.0.0.1:8100/predictwith their archetype asactor) for at most two quick checks per task: one approach choice before starting, one done-check against the acceptance criteria before reporting. Busy → retry once after 3s, then decide alone — never block the task. Worker verdicts show on the dashboard JEV DECISIONS feed (fresh and cached answers alike). - Live dashboard. Every Jev verdict, task change, and agent spawn is auto-recorded (pi tool hooks → SQLite) into a dense terminal UI with drag-and-dock panels, resizable edges, column sorting, member drill-downs, and a watchdog that auto-closes finished milestones — including an Event Flow timeline of the whole run (below).
Install — start with /pod-setup
-
Install the plugin:
pi install git:https://git.cyfrowynomada.com/CyfrowyNomada/pi-pod-team.git(or clone into
~/.pi/agent/extensions/pi-pod-team// point theextensionsarray in your pi settings at the cloned folder) -
Start pi in your project and run
/pod-setup— always the first command. It checks the required pi packages (and tells you exactly what to install if something is missing), asks for your TypeSafe API key, starts the local Jev brain, and verifies the dashboard. Idempotent: rerun it after a reboot or whenever something looks off. -
/pod <task>— go.
Usage
/pod toggle producer mode
/pod <task> enable producer mode and start the task
/pod-dashboard open it — up / down / restart control the brain
Then just talk to pi — it plans, staffs, and delivers, announcing every decision with probabilities.
Event Flow panel
A swimlane timeline of the run — time on the horizontal axis, one lane per actor (producer, Jev, each spawned agent), newest events on the right.
- Edges are heuristic: assignment → spawn, start → done, converging spawns → the Jev decision that judged them, decision → the next producer action. Join windows are conservative; the legend says so.
- Dot size = fan-out: how many agents an assignment spawned, or how many starts a decision judged. Big dot = big blast radius.
- Zoom:
15m / 30m / 60m / 180m / 12h / allchips window the timeline; the anchor is the newest event in the data, never the browser clock. - Everything is clickable: decisions open the decision drawer, task events open the task
drawer, anything else opens the raw event — keyboard included (
Tab+Enter). - Active dots breathe: events from the last 5 minutes pulse gently (
prefers-reduced-motionturns it off). - Producer notes are white squares — see Producer notes.
Producer notes
Producers should emit a note for important events and progress milestones — a milestone done, an important decision, a blocker, a handoff. Two ways:
pod_emittool (inside pi): kind"note"withdata:{detail:"<what happened>"}.- Raw HTTP:
POST /pod/eventwith JSON{"project":"<name>","kind":"note","data":{"detail":"..."}}. Thedataobject is stored verbatim as the event detail (JSON, truncated at 400 chars).
Notes show up in the dashboard's Event Flow panel as white squares on the producer lane — click one to inspect the raw event.
Durable reports (F0): milestones whose subject says report/audit/investigation must leave
their report as a repo file and emit pod_emit kind "report" with data {task_id, path}
before completion — the extension blocks report-class completions without that evidence, so
reports survive the session that wrote them.
Backlog
The BACKLOG tab is an ordered queue of waiting milestones for the project — name, description, age — with promote/move/remove controls and an add form. The producer pops the top entry when the current run's milestones are all done.
Producers (and humans) manage it over HTTP. Successful mutations return the fresh array:
# add an entry (name <= 120 chars, desc <= 2000)
curl -X POST http://127.0.0.1:8100/pod/backlog -d '{"project":"my-app","op":"add",
"name":"Add OAuth login","desc":"Google + GitHub providers, session reuse"}'
# move an entry to rank 1 (to is 1-based and clamped); op "remove" takes the id
curl -X POST http://127.0.0.1:8100/pod/backlog -d '{"project":"my-app","op":"move",
"id":"5ecb471d","to":1}'
The list also rides along in GET /pod/state as "backlog". Storage is one JSON file per
project under POD_BACKLOG_DIR (default ~/.pi/agent/pod/backlog/) — point the env var at a
path inside the project's repo if you want the backlog versioned with the code.
Plan notes
The PLAN tab is a question line to the producer: post what you want investigated (optionally attaching FILES-tab ids), and the producer answers it before its final report. The tab badge counts open — unanswered — notes; REMOVE drops one.
Producers (and humans) manage them over HTTP. Successful mutations return the fresh array:
# ask a question (text <= 5000 chars; optional files = up to 10 upload F-ids)
curl -X POST http://127.0.0.1:8100/pod/notes -d '{"project":"my-app","op":"add",
"text":"Why did checkout latency jump after the M12 change?","files":["F3"]}'
# answer one (<= 20000 chars, re-answering overwrites) — the producer's half of the contract
curl -X POST http://127.0.0.1:8100/pod/notes -d '{"project":"my-app","op":"answer",
"id":"N1a2b3c4d","answer":"Cache stampede on price lookup — fixed, profile attached",
"answer_files":["F7"]}'
The list also rides along in GET /pod/state as "notes" — that is how the producer sees open
questions mid-run. Answered status is derived (answer non-empty or answer_files set), never
stored. Storage is one JSON file per project under POD_NOTES_DIR (default
~/.pi/agent/pod/notes/).
Threads — every note accepts nested replies (reply-to-reply, server depth cap 6):
# reply to a note or to another reply (text 1..2000 chars)
curl -X POST http://127.0.0.1:8100/pod/notes -d '{"project":"my-app","op":"reply",
"parent":"N1a2b3c4d","text":"Reproduced on staging — same cache stampede"}'
A note or reply with children cannot be removed (409 has-replies — remove leaves first).
Replies ride in /pod/notes responses as "replies" and in GET /pod/state as
"note_replies"; on the PLAN tab each note expands to its thread with an inline reply composer.
Promote to milestone — the PROMOTE button on a note (or the API below) turns a discussion
into actionable work. The server mints the next free M-token (same vocabulary as the F1
dedupe) and is idempotent: task_id = plan:<noteId>, re-promoting returns the existing target.
curl -X POST http://127.0.0.1:8100/pod/plan/promote -d '{"project":"my-app",
"noteId":"N1a2b3c4d","dest":"board"}' # or "dest":"backlog" to queue it
Board promotes create a pending task titled <M-token>: <note text>; backlog promotes append
an entry whose description folds in the note's reply thread (≤2000 chars). The note keeps a
promoted {dest, id} marker — the badge links to the task drawer or the BACKLOG tab.
Files
The FILES tab uploads images and text files per project — pick files or drag-and-drop them onto
the panel. They are stored under .uploads/<project>/ (env POD_UPLOADS_DIR overrides the root)
with stable F1..FN ids that are never reused after a remove, so you can say "check F3" to the
agent and both sides mean the same file. REMOVE deletes a file from the list; images larger than
2048×2048 are shrunk client-side before upload (PNG stays PNG, everything else becomes JPEG), and
files are served back through a strict content-type allowlist — uploaded HTML/SVG is never
renderable. The list also rides along in GET /pod/state as "uploads".
The brain (server/)
The decision endpoint and the dashboard backend live in server/ — a small stdlib HTTP
service in front of TypeSafe, plus the SQLite tracker the dashboard reads. You normally never
touch it: /pod-setup configures and starts it. Manual run:
cd server
export TYPESAFE_API_KEY=<your TypeSafe key>
python3 api.py # http://127.0.0.1:8100 (dashboard at /dashboard/)
Run it under systemd for a always-on setup:
systemd-run --user --unit=jev-api --property=Restart=on-failure \
--setenv=TYPESAFE_API_KEY=<key> --working-directory=<this repo>/server .venv-path/python api.py
watchdog.sh / watchdog.py (optional, also as a user service) pings the dashboard when a run
stalls, auto-closes milestones on honest evidence, and autopops the backlog: when no producer
is running, no milestone tasks are pending, and the backlog is non-empty, it pops the top entry
and launches a fresh producer run on it (env POD_AUTOPOP=0 to disable). Add work remotely from
the BACKLOG tab and the machine picks it up within 5 minutes. Each loop it also prunes dashboard
events older than POD_RETENTION_DAYS (default 30; 0 disables) from the events table —
decisions and task rows are never touched.
Two honesty contracts keep the watchdog from lying:
- Producer liveness is a lock file, not a process scan. The pi extension writes
~/.pi/agent/pod/producer.lock— one-line JSON{pid, run, cwd, ts}— whenever producer mode is on, heartbeating it at most every 60s. This covers interactive operator sessions and headless autopop runs alike. The watchdog calls a producer alive only if the lock is fresh (<30 min), itscwdmatches the workspace, and thepidis alive and not a zombie. Stale locks age out; a clean shutdown removes only its own lock. - Auto-close is marker-based. A pending milestone
M<id>is completed only when its run log contains the machine-readable linePOD-FINAL-REPORT: <id> done— the producer appends it as the last action of a delivered milestone. A crashed run leaves no marker: it stays pending and the watchdog posts a one-time "no final report — crashed?" warning instead of falsely completing it.
Remote access (tailscale serve)
The dashboard is exposed to your tailnet at https://*.tailXXXX.ts.net/dashboard/
(tailnet-only, path-scoped via tailscale serve; /pod is mounted the same way).
Invariant: serve must stay path-scoped to /dashboard + /pod — never whole-port
(tailscale serve --bg 8100). A whole-port mount would also expose /shutdown (remote kill)
and /predict (the API-key proxy) to every device on the tailnet.
Reboot chain: tailscaled is a system service (enabled) and restores the serve config;
jev-api / pod-watchdog are user services, so login linger must be on — it is (loginctl enable-linger kj).
API token (POD_API_TOKEN)
When POD_API_TOKEN is set (non-empty), the mutating POST /pod/* endpoints (/pod/event,
/pod/remove, /pod/backlog) require header X-Pod-Token matching it (403 otherwise).
GETs stay open, and /predict + /shutdown stay open too — remote access to those is already
covered by the path-scoped tailscale serve mount above. The dashboard gets the token injected
into the served index.html (window.POD_TOKEN) when token mode is on, so the UI keeps working;
with the variable unset, served bytes are exactly as before.
To enable for the systemd user units, add the line to ~/.pi/agent/pod/typesafe.env, ensure
both units reference that file via EnvironmentFile= (they live outside this repo), then
systemctl --user restart jev-api pod-watchdog — a watchdog without the token would drop its backlog ops (it prints a rejection
line instead of failing silently).
Repository layout
index.ts the pi extension: pod_jev tool, /pod command, producer workflow, event hooks
package.json pi package manifest
dashboard/ the live dashboard (vanilla HTML/CSS/JS, no build step)
server/api.py Jev bridge + decision cache + POD tracker + dashboard backend
server/watchdog.* stall/idle watchdog + milestone auto-close
run-tests.sh python server smoke + dashboard suites (CI: .gitea/workflows/check.yaml)
Requirements
- pi with the subagents package (workers spawn as real subagents)
- A TypeSafe API key (Jev) — decisions are online
- Python 3 with
requestsfor the brain; anything that can serve a file for the dashboard
Status badges on the dashboard
pending muted · in_progress amber · completed green · thinking amber · idle dashed
MIT. Built by a POD team using itself — every milestone in the commit history was planned, judged, and verified by the team this plugin manages.
