colibri/docs/COLIBRI-GLASSPANE-DESIGN.md
Sam & Claude 78374d0871 chore: adopt markdown formatting gate + one-shot prettier sweep (Sam & Claude)
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>
2026-06-04 20:13:47 +02:00

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?}] }. id is the Colibri-owned pane id; pi_session_id is captured separately from the Pi JSONL session header. Lives in the crate for now; promote to colibri-contracts once 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 raw posix_openpt; tokio for 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

  1. State model (this scaffold)AgentState + apply_pi_event / fold_pi_events + Pane / GlasspaneSnapshot, unit-tested. Pure, no I/O.
  2. Pi-events ingestion (Rust)pi_event_type / agent_state_from_jsonl / pane_from_jsonl fold a real --mode json JSONL stream through the state machine and capture session id/cwd from the header. Lenient (skips malformed lines). Tested against a verbatim pi 0.75.5 header.
  3. 🟡 PTY/pane supervision — started. PaneSupervisor owns Colibri pane ids, separates Pi session ids, ingests streaming JSONL lines, tracks last_event_at as SystemTime, derives stalled from event silence, and has a portable-pty launch seam. run_pane_reader now 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.
  4. 🟡 Client API — started. colibri-client provides a typed Unix-socket client for daemon commands, including glasspane_snapshot() returning a GlasspaneSnapshot. A live daemon/client smoke now starts the real socket server, spawns a no-network provider:"local" fake Pi JSONL agent, and verifies Idle → Working → Blocked → Done through DaemonClient. Operator smoke helpers colibri and colibri-smoke-agent provide the same path from a Herdr/SSH pane without raw nc -U JSON. Herdr-Linux, Zed, and web boards can consume this API; HTTP/SSE remains a later transport.
  5. 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.