colibri had no Prettier config or gate, so its markdown drifted freely (22/31 files failed Prettier). Mirror the clawdie-iso gate so docs stay consistent: - .prettierrc: same as clawdie-iso — proseWrap=preserve, printWidth=80, and embeddedLanguageFormatting=off for *.md so fenced code (JSON/mermaid/shell in the graph + design docs) is left exactly as written. - .prettierignore: target/, scratch dirs, CHANGELOG. - scripts/check-format.sh: `prettier@3 --check '**/*.md'` (run before pushing). - AGENTS.md: "Markdown Formatting Gate" section documenting the workflow. - One-shot `prettier --write` across all markdown. Pure formatting — only emphasis-marker (*x* -> _x_), list-bullet, table-padding, and blank-line normalization; no prose/command/code-fence content changed. Gate now green (`./scripts/check-format.sh` → all matched files pass). Docs-only + tooling — no Rust touched, no rebuild. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.9 KiB
colibri-glasspane — design
FreeBSD-native agent supervision ("the radar"): sessions, panes, and a
semantic agent-state per pane. This is Herdr's glasspane capability
reimplemented as our own Rust crate — Herdr is AGPL and Linux/macOS-only, so
on FreeBSD we re-implement the API/state model, not the code. Herdr stays an
optional Linux display client behind the same socket/contract (hybrid
boundary from docs/HERDR-VS-COLIBRI-GRAPH.md).
The key bet: state from structured events, not screen-scraping
Herdr infers agent state by parsing terminal output (heuristic, fragile).
Colibri already has the Pi --mode json event taxonomy (colibri-pi-events
in clawdie-ai), so glasspane derives agent state deterministically from those
events — no scraping. That's the FreeBSD-native, robust advantage, and it
reuses a contract we already proved.
Capability graph
graph LR
pi["Pi agents (--mode json)"] -->|JSONL events| GP
subgraph GP["colibri-glasspane (FreeBSD-native, source of truth)"]
st["agent-state machine<br/>(Pi events → 5 states)"]
pn["panes / sessions<br/>(PTY slots, Phase 3)"]
snap["GlasspaneSnapshot<br/>clawdie.glasspane.snapshot.v1"]
st --> pn --> snap
end
snap -->|socket / SSE / HTTP| herdr["Herdr (Linux display client)"]
snap --> zed["Zed / web board"]
snap --> orch["colibri-orchestrator (Phase 5)"]
GP -. reuses .-> ev["colibri-pi-events taxonomy"]
{
"nodes": [
{"id":"pi","type":"agent_runtime","emits":"pi --mode json events"},
{"id":"glasspane","type":"rust_crate","role":"supervision source of truth","status":"scaffold"},
{"id":"snapshot","type":"contract","schema":"clawdie.glasspane.snapshot.v1"},
{"id":"herdr","type":"linux_client","role":"display only"},
{"id":"orchestrator","type":"future_crate","role":"dispatch/route"}
],
"edges": [
{"from":"pi","to":"glasspane","rel":"events"},
{"from":"glasspane","to":"snapshot","rel":"produces"},
{"from":"snapshot","to":"herdr","rel":"display_over_socket"},
{"from":"glasspane","to":"orchestrator","rel":"feeds (phase 5)"}
]
}
Agent-state model (Herdr's 5 states, event-derived)
| State | Meaning | Pi event types that set it |
|---|---|---|
idle |
ready, between turns | session, session_started |
working |
actively producing | agent_start, turn_start, message_start, message_update, message_end, tool_execution_*, auto_compaction_* (and legacy compaction_*), auto_retry_* |
blocked |
waiting (steering/approval/input) | queue_update (pending) |
done |
turn/agent finished | turn_end, agent_end |
error |
failed / retries exhausted | error |
Unknown event types preserve the current state (forward-compatible).
"Stalled" (blocked for too long — Herdr's headline value) is derived later from
last_event_at + a threshold, in the snapshot layer.
Unified API vocabulary
Glasspane (supervision) names, aligned across Rust core + display clients:
glasspane.snapshot()→GlasspaneSnapshot(all panes + states)glasspane.attach(pane)/glasspane.list()/glasspane.state(pane)- internal:
apply_pi_event(state, type)/fold_pi_events(types)(the state machine)
Provider/cost names stay in colibri-deepseek; coordination stays in the TS
control plane. Glasspane owns supervision only.
Contracts
clawdie.glasspane.snapshot.v1—{ schema, host, observed_at, panes:[{id, agent, state, pi_session_id?, last_event_at?, cwd?, stalled?}] }.idis the Colibri-owned pane id;pi_session_idis captured separately from the Pi JSONL session header. Lives in the crate for now; promote tocolibri-contractsonce a second consumer (a client) needs it.- Reuses the colibri-pi-events taxonomy as the event source (Rust ingestion is Phase 2 — port or consume the JSONL).
FreeBSD implementation notes
- Phase-3 PTY/pane multiplexing:
portable-pty(supports FreeBSD) or rawposix_openpt;tokiofor the socket server (UnixStream, loopback HTTP/SSE). - No Herdr code in the FreeBSD core (AGPL + Linux-only). Reimplement the API.
- Single small binary, no Node/Electron — consistent with the Colibri footprint.
Phases
- State model (this scaffold) —
AgentState+apply_pi_event/fold_pi_events+Pane/GlasspaneSnapshot, unit-tested. Pure, no I/O. - ✅ Pi-events ingestion (Rust) —
pi_event_type/agent_state_from_jsonl/pane_from_jsonlfold a real--mode jsonJSONL stream through the state machine and capture sessionid/cwdfrom the header. Lenient (skips malformed lines). Tested against a verbatim pi 0.75.5 header. - 🟡 PTY/pane supervision — started.
PaneSupervisorowns Colibri pane ids, separates Pi session ids, ingests streaming JSONL lines, trackslast_event_atasSystemTime, derivesstalledfrom event silence, and has aportable-ptylaunch seam.run_pane_readernow drains fake or PTY-backed JSONL readers into the same streaming ingestion path. Tests use fake JSONL readers first; live FreeBSD PTY validation is a later osa lane. - 🟡 Client API — started.
colibri-clientprovides a typed Unix-socket client for daemon commands, includingglasspane_snapshot()returning aGlasspaneSnapshot. A live daemon/client smoke now starts the real socket server, spawns a no-networkprovider:"local"fake Pi JSONL agent, and verifies Idle → Working → Blocked → Done throughDaemonClient. Operator smoke helperscolibriandcolibri-smoke-agentprovide the same path from a Herdr/SSH pane without rawnc -UJSON. Herdr-Linux, Zed, and web boards can consume this API; HTTP/SSE remains a later transport. - Orchestrator — route/dispatch work across panes (separate
colibri-orchestrator).
Non-goals
- No Herdr vendoring into the FreeBSD core; no Herdr-on-FreeBSD.
- No provider/cost logic (that's
colibri-deepseek); no scheduling/task-ownership in display clients — glasspane/daemon is the source of truth.