Merge pull request 'docs: clarify Herdr as optional Linux display (Sam & Codex)' (#31) from docs/herdr-platform-boundary-cleanup into main
Some checks are pending
CI / rust (push) Waiting to run
CI / markdown (push) Waiting to run

Reviewed-on: #31
This commit is contained in:
clawdie 2026-06-13 12:30:32 +02:00
commit 773f7294c1
16 changed files with 87 additions and 961 deletions

View file

@ -1,8 +1,7 @@
//! Regression test for the bug the 2026-05-27 osa FreeBSD smoke caught:
//! Regression test for the bug a 2026-05-27 osa FreeBSD smoke caught:
//! `main.rs` started the socket server but never spawned `daemon::run_loop`, so
//! an `intake-task` reported `queued` over the socket yet was never drained into
//! the SQLite coordination store. See
//! `docs/internal/sessions/2026-05-27-osa-freebsd-daemon-scheduler-smoke.md`.
//! the SQLite coordination store.
//!
//! This proves the full path: socket `intake-task` → `run_loop` scheduler tick →
//! persisted SQLite task. If `run_loop` is ever dropped from the wiring again,

View file

@ -1,4 +1,4 @@
// colibri-harness — herdr-like supervision TUI built on Colibri primitives.
// colibri-harness — Colibri-native supervision TUI built on Colibri primitives.
//
// Connects to a colibri-daemon Unix socket, polls GlasspaneSnapshot every 2s,
// lets the operator spawn/kill agents, drill into pane details, and cycle

View file

@ -1,10 +1,11 @@
# Clawdie Agent — wiki / mindmap
The **Clawdie agent** is the simplified, operator-friendly face of Colibri: one
small Rust binary (`clawdie`) that ships with exactly two things wired up — a
Telegram bot and a DeepSeek lane — sitting on top of the proven control-plane
core. Everything heavier (cost modes, quotas, multi-provider fallback) is
deliberately **lifted**.
The **Clawdie agent** mini-binary is an experimental, operator-friendly face of
Colibri: one small Rust binary (`clawdie`) that wires a Telegram bot and a
DeepSeek lane on top of the control-plane core. It is not the current live-ISO
service contract; the FreeBSD live USB runs `colibri_daemon` directly, while
`service clawdie` is reserved for a future installed disk/server service once
that implementation is chosen.
This is a mindmap-style wiki page (the format from the Herdr/Colibri capability
graph work — see [`HERDR-VS-COLIBRI-GRAPH.md`](./HERDR-VS-COLIBRI-GRAPH.md) and
@ -59,9 +60,9 @@ Two halves of the same daemon, both kept:
| **Glasspane** | `colibri-glasspane` + daemon socket | The "radar": agent state, panes, supervision snapshot |
| **Coordination** | `colibri-store` + daemon loop | Task board, agent registry, session lifecycle |
The Clawdie agent reuses these as-is over the Herdr Unix-socket API. It adds the
Telegram + DeepSeek front door and **removes** the cost/quota machinery from its
own runtime path.
The Clawdie mini-binary reuses these as-is over the Colibri control-plane socket.
It adds the Telegram + DeepSeek front door and **removes** the cost/quota
machinery from its own runtime path.
## Surface area (the whole product)
@ -72,7 +73,7 @@ graph LR
ds -->|reply| clawdie
clawdie --> gp["glasspane radar"]
clawdie --> co["coordination board"]
clawdie -. Herdr socket .- ui["operator tools (colibri CLI / TUI)"]
clawdie -. Colibri socket .- ui["operator tools (colibri CLI / TUI)"]
```
## Files
@ -95,4 +96,4 @@ graph LR
One DeepSeek key serves both the Telegram chat lane and the daemon's own
provider routing.
See [`CLAWDIE-BUILD.md`](./CLAWDIE-BUILD.md) for the build + ISO instructions.
See [`CLAWDIE-BUILD.md`](./CLAWDIE-BUILD.md) for experimental build notes.

View file

@ -1,8 +1,10 @@
# Clawdie agent — build & ISO instructions
# Experimental `clawdie` mini-binary — build notes
The `clawdie` binary is the simplified Colibri agent (see
[`CLAWDIE-AGENT-WIKI.md`](./CLAWDIE-AGENT-WIKI.md)). It is configured almost
entirely by **build flags**, so a baked ISO ships ready to run.
The `clawdie` binary is an experimental Colibri-side candidate for a future
deployed-system service (see [`CLAWDIE-AGENT-WIKI.md`](./CLAWDIE-AGENT-WIKI.md)).
It is **not** the current FreeBSD live-ISO service contract. The live USB uses
`colibri_daemon`; `service clawdie` remains reserved for an installed disk/server
service once that implementation is chosen.
## 1. Build with baked credentials
@ -29,8 +31,8 @@ Build with **no** flags for a "bring your own key" binary — it reads
## 2. What the binary does out of the box
- Starts the control-plane core: glasspane supervision + Herdr Unix socket +
coordination loop.
- Starts the control-plane core: glasspane supervision + Colibri control-plane
socket + coordination loop.
- If a Telegram token **and** a DeepSeek key are present, runs the Telegram
bridge (long-poll → DeepSeek → reply).
- No token → headless (control plane only). Token but no key → bridge disabled
@ -39,9 +41,10 @@ Build with **no** flags for a "bring your own key" binary — it reads
No cost modes, quotas, context budgets, or provider fallback — those are lifted
from this agent on purpose.
## 3. Run as a service (like Clawdie-AI)
## 3. Run as an experimental service
On FreeBSD, install the rc.d script and enable it:
On FreeBSD, install the rc.d script only on a throwaway test host or an explicit
deployed-service experiment:
```sh
pw groupadd clawdie
@ -65,18 +68,13 @@ chmod 0600 /usr/local/etc/clawdie/clawdie.env
service clawdie restart
```
## 4. ISO integration
## 4. ISO status
The ISO build stages the prebuilt FreeBSD `clawdie` release binary + rc.d (it
never compiles Rust while the image is mounted), the same model as the Colibri
daemon staging. In `clawdie-iso`:
- `build.cfg`: `FEATURE_CLAWDIE`, `CLAWDIE_ENABLE`, and the build-flag creds.
- `scripts/stage-clawdie-iso.sh`: installs binary + rc.d + rc.conf sample.
Build the binary on the FreeBSD/OSA host (not Debian/Linux), then run the ISO
preflight. **Do not `cargo clean` the colibri checkout** until the ISO build has
consumed `target/release/clawdie`.
The current `clawdie-iso` baseline does **not** stage this mini-binary or its
rc.d script. ISO builds stage `colibri-daemon`, `colibri`, `colibri-smoke-agent`,
and preferably `colibri-tui` from this checkout. If a deployed-system
`service clawdie` lane is reintroduced later, it should get fresh packaging and
acceptance criteria instead of silently treating this experiment as final.
## 5. Next ISO build — XFCE operator-USB fixes to carry forward
@ -84,8 +82,8 @@ The next operator-USB image must retain the XFCE/live-GUI fixes already proven
on real hardware (see `clawdie-iso/PLAN-OPERATOR-USB-NEXT.md` and
`doc/AMD-ASUS-XFCE-LIVE-USB-FINDINGS.md`). Load-bearing items:
- **SDDM over LightDM** — LightDM was the silent blocker for live GUI boot on
Intel and AMD. SDDM is part of the operator-USB contract; do **not** revert.
- **SDDM remains the supported live display manager** — keep it unless an
alternative has equivalent real-hardware proof for Intel and AMD GUI boot.
- **`clawdie-live-gpu`** — pre-SDDM rc.d service that does a conservative KMS
pick (Intel iGPU works out of the box; AMD/NVIDIA picked conservatively)
before the display manager starts.

View file

@ -18,8 +18,8 @@ crates to be updated in lockstep.
│ │
│ ┌──────────┐ ┌───────────┐ ┌──────────┐ ┌───────────────┐ │
│ │ Spawner │ │ Sessions │ │ Heartbeat │ │ Socket Server │ │
│ │ (agents) │ │ (JSONL) │ │ (30s) │ │ (Unix domain) │──┼──► Herdr / web
│ └─────┬─────┘ └───────────┘ └─────┬─────┘ └───────┬───────┘ │ board / Zed
│ │ (agents) │ │ (JSONL) │ │ (30s) │ │ (Unix domain) │──┼──► Colibri TUI / web
│ └─────┬─────┘ └───────────┘ └─────┬─────┘ └───────┬───────┘ │ Zed / optional Herdr Linux
│ │ │ │ │
│ │ stdout JSONL │ poll_exit() │ query │
│ ▼ ▼ ▼ │
@ -47,7 +47,7 @@ matching the existing watchdog convention).
### Wire types (defined in `colibri-daemon/src/lib.rs`)
**Inbound: `HerdrCommand`** (tagged by `cmd` field):
**Inbound: `ColibriCommand`** (tagged by `cmd` field):
| `cmd` value | Parameters | Purpose |
| -------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
@ -59,7 +59,7 @@ matching the existing watchdog convention).
| `get-session` | `session_id` | Full session dump (turns + prompt) |
| `compact-session` | `session_id` | Compact oldest turns in a session |
**Outbound: `HerdrResponse`**:
**Outbound: `ColibriResponse`**:
```json
{
@ -104,8 +104,8 @@ Server: {"ok":true,"data":{"agent_id":"a1b2-c3d4","status":"running"}}\n
### Operator CLI smoke helpers
`colibri-client` also ships two small binaries for manual Herdr/SSH smoke tests
without hand-writing socket JSON:
`colibri-client` also ships small binaries for manual display-client and SSH
smoke tests without hand-writing socket JSON:
```sh
# Inspect daemon state
@ -219,19 +219,19 @@ AgentHandle SupervisedPane Session
### Daemon lifecycle events → Glasspane AgentState
| Daemon lifecycle event | Ingested Pi event type | Resulting `AgentState` | Notes |
| ------------------------------ | ------------------------------------------------------- | ---------------------- | ------------------------------------------ |
| Agent subprocess spawned | _(pane attached, state = `Idle`)_ | `Idle` | `SupervisedPane::new` defaults to `Idle` |
| Agent emits `session` header | `session` / `session_started` | `Idle` | Also captures `pi_session_id` and `cwd` |
| Agent emits turn/message/tool | `turn_start`, `message_start`, `tool_execution_*`, etc. | `Working` | Any of 14 event types |
| Agent emits compaction events | `auto_compaction_*`, `compaction_*` | `Working` | Compaction is active work |
| Agent emits retry events | `auto_retry_*` | `Working` | Retry is active work |
| Agent awaits steering/approval | `queue_update` | `Blocked` | Operator attention needed (Herdr headline) |
| Turn/task complete | `turn_end` / `agent_end` | `Done` | Agent reached a completion point |
| Agent emits explicit error | `error` | `Error` | Terminal failure state |
| Agent subprocess exits (0) | _daemon injects `agent_end`_ | `Done` | Heartbeat detected normal exit |
| Agent subprocess exits (!=0) | _daemon injects `error`_ | `Error` | Heartbeat detected crash/error exit |
| Agent killed externally | _daemon injects `error`_ | `Error` | `kill-agent` command |
| Daemon lifecycle event | Ingested Pi event type | Resulting `AgentState` | Notes |
| ------------------------------ | ------------------------------------------------------- | ---------------------- | ---------------------------------------------- |
| Agent subprocess spawned | _(pane attached, state = `Idle`)_ | `Idle` | `SupervisedPane::new` defaults to `Idle` |
| Agent emits `session` header | `session` / `session_started` | `Idle` | Also captures `pi_session_id` and `cwd` |
| Agent emits turn/message/tool | `turn_start`, `message_start`, `tool_execution_*`, etc. | `Working` | Any of 14 event types |
| Agent emits compaction events | `auto_compaction_*`, `compaction_*` | `Working` | Compaction is active work |
| Agent emits retry events | `auto_retry_*` | `Working` | Retry is active work |
| Agent awaits steering/approval | `queue_update` | `Blocked` | Operator attention needed (dashboard headline) |
| Turn/task complete | `turn_end` / `agent_end` | `Done` | Agent reached a completion point |
| Agent emits explicit error | `error` | `Error` | Terminal failure state |
| Agent subprocess exits (0) | _daemon injects `agent_end`_ | `Done` | Heartbeat detected normal exit |
| Agent subprocess exits (!=0) | _daemon injects `error`_ | `Error` | Heartbeat detected crash/error exit |
| Agent killed externally | _daemon injects `error`_ | `Error` | `kill-agent` command |
### State transition diagram
@ -446,11 +446,12 @@ let snapshot = state.glasspane.read().await.snapshot_at(
### Where it is consumed
| Consumer | Transport | Phase | Purpose |
| -------------------- | ------------------ | ----- | -------------------------------- |
| Herdr (Linux) | Unix socket | 4 | Operator dashboard display |
| Zed / web board | HTTP / SSE | 4 | Web-based supervision view |
| colibri-orchestrator | In-memory / socket | 5 | Route/dispatch work across panes |
| Consumer | Transport | Phase | Purpose |
| ----------------------------- | ------------------ | ----- | -------------------------------------------- |
| `colibri` CLI / `colibri-tui` | Unix socket | 4 | Native operator dashboard and smoke surface |
| Herdr (Linux/macOS optional) | Unix socket/bridge | 4 | Optional external display client, not source |
| Zed / web board | HTTP / SSE | 4 | Web-based supervision view |
| colibri-orchestrator | In-memory / socket | 5 | Route/dispatch work across panes |
### Wire shape (JSON)
@ -513,9 +514,10 @@ library crate, not a process — it is compiled into the daemon binary.
4. socket::serve(state, shutdown_rx) ← BLOCKING
├── Binds Unix socket at config.socket_path
├── Accepts connections
└── Dispatches HerdrCommand variants
└── Dispatches ColibriCommand variants
5. External clients (Herdr, web) connect and send commands
5. External clients (colibri CLI/TUI, optional Herdr Linux/macOS, web) connect
and send commands
```
### Clean boot checklist (in sequence)
@ -553,6 +555,6 @@ Unix socket path (`DaemonConfig.socket_path`).
| `docs/HERDR-VS-COLIBRI-GRAPH.md` | Hybrid boundary: Herdr as Linux display client |
| `crates/colibri-daemon/src/socket.rs` | Socket server implementation |
| `crates/colibri-daemon/src/daemon.rs` | Daemon background loop + heartbeat |
| `crates/colibri-daemon/src/lib.rs` | Wire types: `HerdrCommand`, `HerdrResponse` |
| `crates/colibri-daemon/src/lib.rs` | Wire types: `ColibriCommand`, `ColibriResponse` |
| `crates/colibri-daemon/src/spawner.rs` | Agent subprocess spawner |
| `crates/colibri-glasspane/src/lib.rs` | State machine, supervisor, snapshot contract |

View file

@ -105,14 +105,16 @@ graph LR
| `run.manifest` | `build_run_manifest()``clawdie.interagent.run-manifest.v1` | run-manifest emitter |
**Proposed — roadmap, NOT locked** (graph for comparison; names firm up when
code lands): supervision `supervise.attach/list/state` (Herdr socket);
coordination `board.task(queued→claimed→started→done|failed)`, `skills.*`;
code lands): supervision `supervise.attach/list/state` (Colibri source of
truth, with optional Herdr-compatible Linux display); coordination
`board.task(queued→claimed→started→done|failed)`, `skills.*`;
execution `schedule.cron/interval/once`, `remote.*`.
## 5. Drop candidates (gated)
Covered by Herdr (supervision, Linux client) or `colibri-deepseek`
(provider/cache); remove only after proof gates + per-file caller inventory.
Covered by Herdr where a Linux display client is desired, or by
`colibri-deepseek` (provider/cache); remove only after proof gates + per-file
caller inventory.
Snapshot: clawdie-ai `archive/multitenant-claude-pre-divergence`.
- `tmux-screenshot-command.ts` + glass-pane glue → **Herdr** supervision.

View file

@ -27,6 +27,16 @@ operator/display
Herdr is not being ported to FreeBSD. On the ISO, the native answer is Colibri daemon + glasspane/TUI/harness.
## Platform boundary
| Target | Source of truth | Display/control surface | Claim boundary |
| ------------------------- | -------------------------------------- | --------------------------------------- | -------------------------------------------------------------- |
| FreeBSD 15 live ISO | `colibri_daemon` + `colibri-glasspane` | `colibri` CLI / `colibri-tui` | Requires FreeBSD build/boot evidence; GUI needs hardware proof |
| Linux/macOS operator host | Colibri snapshot/API from the daemon | Optional Herdr, terminal panes, web/Zed | Display-client proof only; does not prove FreeBSD runtime |
Herdr remains an optional Linux/macOS display client. It must not become a
FreeBSD ISO dependency or the source of truth for supervision state.
## Current baseline
Already landed and validated:
@ -316,7 +326,7 @@ Required:
- Phase 3: task capability matching assigns work to jail workers.
- Phase 4: per-task ephemeral jail option if needed.
### Lane D — Native dashboard / Herdr replacement
### Lane D — Native dashboard / optional Herdr display
1. **FreeBSD-native dashboard**
- Continue with `colibri-glasspane-tui` / harness rather than porting Herdr.

View file

@ -191,7 +191,8 @@ Failed: 0 ❌
### Benefits
- ✅ Guarantees Linux and FreeBSD behave identically
- ✅ Compares Linux and FreeBSD behavior and surfaces platform drift; FreeBSD
runtime proof still requires FreeBSD validation
- ✅ Catches platform-specific regressions early
- ✅ Provides clear evidence matrix for each gate
- ✅ Reduces manual "is this working on osa?" checks

View file

@ -1,90 +0,0 @@
# Colibri Phase 4 Live Daemon/Client Smoke Report
**Date:** 27.maj.2026
**Host:** osa.smilepowered.org — FreeBSD 15.0-RELEASE-p8 amd64
**Repo:** `Clawdie/Colibri`
**Commit tested:** `fbcb7e6``Add live daemon client smoke with local fake agent`
**Status:** PASS
## Purpose
Prove that Phase 4 is not only unit-level: a real `colibri-daemon` Unix socket
server can run in parallel with existing services, and `colibri-client` can talk
to it over the socket without sudo or production paths.
## Isolation
The smoke uses a temp data directory and socket under the current user's temp
area. It does **not** touch production services, global sockets, or privileged
paths.
No sudo is required.
## What the smoke covers
The new integration test:
```text
crates/colibri-client/tests/live_socket_smoke.rs
```
performs this full path:
1. Creates isolated `DaemonConfig`
2. Starts real `socket::serve(...)`
3. Creates real `DaemonClient`
4. Calls `status`
5. Calls `glasspane_snapshot` before spawn and verifies no panes
6. Writes a fake local Pi JSONL agent script
7. Calls `spawn-agent` with `provider:"local"`
8. Verifies Glasspane state transitions via the daemon socket:
- `Idle`
- `Working`
- `Blocked`
- `Done`
9. Verifies captured `pi_session_id` and `cwd`
10. Calls `kill-agent`
11. Sends daemon shutdown and removes temp data
## Local provider behavior added
`Provider::Local` was added for no-network smoke/local tools:
- no API key required
- no fallback to remote providers
- socket `model` field is treated as executable path
- no `--mode json` args are injected
This keeps live daemon/client testing deterministic and independent of Pi,
DeepSeek, OpenRouter, Anthropic, or existing agent services.
## Validation commands
```sh
cargo fmt --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
cargo build --workspace --release
```
## Result
All gates passed on FreeBSD 15.
```text
50 tests passed, 0 failed
release build OK
```
## Current conclusion
Colibri Phase 4 now has a working typed client plus real socket smoke coverage.
The daemon/client/glasspane path is proven end-to-end with a local fake Pi JSONL
agent, while remaining safe to run in parallel with existing services.
## Next candidates
- Add a small CLI wrapper around `colibri-client` for manual operator smoke.
- Promote `GlasspaneSnapshot` to `colibri-contracts` once a second external
consumer needs standalone deserialization.
- Add HTTP/SSE transport later; Unix socket is sufficient for current Phase 4.

View file

@ -1,80 +0,0 @@
# OSA FreeBSD `colibri` Task CLI Smoke
**Date:** 2026-05-27
**Host:** osa.smilepowered.org
**OS:** FreeBSD 15.0-RELEASE-p8 amd64
**Repo:** `Clawdie/Colibri`
**Commit:** `f434a89``feat: add colibri task commands`
**Status:** PASS
## Scope
Validated the post-refactor operator CLI task commands, using the renamed binary only:
```sh
colibri create-task
colibri list-tasks
colibri intake-task
```
No raw Python Unix-socket client was used for task operations.
## Gates
```sh
cargo fmt --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
cargo build --workspace --release
```
Workspace gates were green at `f434a89`.
## Isolated smoke environment
```text
COLIBRI_DAEMON_DATA_DIR=/tmp/colibri-cli-task-smoke-clawdie-1779913324/data
COLIBRI_DAEMON_SOCKET=/tmp/colibri-cli-task-smoke-clawdie-1779913324/colibri.sock
COLIBRI_DB_PATH=/tmp/colibri-cli-task-smoke-clawdie-1779913324/colibri.sqlite
COLIBRI_HOST=osa-cli-task-smoke
```
## Result
- `colibri status` connected to the daemon socket.
- `colibri create-task --title ... --description ...` created a queued SQLite task.
- `colibri list-tasks --status queued` returned the direct task.
- `colibri intake-task --title ... --description ... --capability freebsd --capabilities sqlite,scheduler` queued intake successfully.
- The daemon scheduler drained intake on the next 30s tick:
```text
FOUND_ON_POLL=31
```
SQLite verification:
```text
tasks 2
journal_mode wal
```
Graceful shutdown:
```text
socket exists after stop? no
process remains? no
```
The daemon log showed the socket server and background loop exiting cleanly.
## Verdict
The renamed `colibri` operator CLI now covers the immediate task workflow:
```sh
colibri create-task --title "..."
colibri list-tasks --status queued
colibri intake-task --title "..." --capability freebsd
```
The OSA `/tmp` smoke passed without using raw socket helper scripts.

View file

@ -1,243 +0,0 @@
# OSA FreeBSD Colibri Daemon + Scheduler Smoke
**Date:** 27.maj.2026
**Host:** osa.smilepowered.org
**OS:** FreeBSD 15.0-RELEASE-p9 amd64
**Rust:** rustc 1.94.0 (4a4ef493e 2026-03-02)
**Repo:** `Clawdie/Colibri`
**Commit tested:** `53028a0``docs: answer Codex handoff questions — colibri-ctl, scheduler, smoke-agent, WAL`
**Status:** PARTIAL PASS — core daemon smoke passed; scheduler runtime wiring gap found
## Scope
Prototype-only `/tmp` smoke. No rc.d install, no service takeover, no production paths.
Test environment:
```sh
COLIBRI_DAEMON_DATA_DIR=/tmp/colibri-osa-smoke-clawdie-1779905501/data
COLIBRI_DAEMON_SOCKET=/tmp/colibri-osa-smoke-clawdie-1779905501/colibri.sock
COLIBRI_DB_PATH=/tmp/colibri-osa-smoke-clawdie-1779905501/colibri.sqlite
COLIBRI_HOST=osa-smoke
```
Build command:
```sh
cargo build --workspace --release
```
Result: release build OK.
## Core daemon result
### Start + files
`colibri-daemon` started successfully with isolated `/tmp` paths.
Created:
```text
colibri.sock
colibri.sqlite
colibri.sqlite-shm
colibri.sqlite-wal
data/sessions/
daemon.log
```
SQLite WAL mode is active on FreeBSD:
```text
journal_mode wal
```
### Status
```json
{
"agent_list": [],
"agents": 0,
"daemon": "colibri-daemon",
"host": "osa-smoke",
"sessions": 0,
"version": "0.0.1"
}
```
### Snapshot before spawn
```json
{
"schema": "clawdie.glasspane.snapshot.v1",
"host": "osa-smoke",
"observed_at": "2026-05-27T18:11:41.890Z",
"panes": []
}
```
### Local fake agent spawn
Command:
```sh
target/release/colibri-ctl --socket "$COLIBRI_DAEMON_SOCKET" \
spawn-local /home/clawdie/colibri/target/release/colibri-smoke-agent \
--session-id osa-smoke-session
```
Response:
```json
{
"agent_id": "d2c5fc74-4085-4731-a0e0-f373df9d007b",
"status": "running"
}
```
Snapshot after fake agent emitted Pi-compatible JSONL:
```json
{
"schema": "clawdie.glasspane.snapshot.v1",
"host": "osa-smoke",
"observed_at": "2026-05-27T18:11:47.450Z",
"panes": [
{
"id": "d2c5fc74-4085-4731-a0e0-f373df9d007b",
"agent": "/home/clawdie/colibri/target/release/colibri-smoke-agent",
"state": "done",
"pi_session_id": "manual-smoke",
"last_event_at": "2026-05-27T18:11:45.897Z",
"cwd": "/home/clawdie/colibri"
}
]
}
```
Kill command succeeded:
```json
{
"agent_id": "d2c5fc74-4085-4731-a0e0-f373df9d007b",
"status": "stopped"
}
```
## Coordination store result
Direct `create-task` over the Unix socket succeeded and persisted to SQLite:
```json
{
"ok": true,
"data": {
"agent_id": null,
"created_at": "2026-05-27T18:11:47.578003651+00:00",
"description": "direct socket create-task",
"id": "288887de-c82e-46d7-9f2d-2701299266e6",
"status": "queued",
"title": "osa smoke direct task",
"updated_at": "2026-05-27T18:11:47.578003651+00:00"
}
}
```
SQLite counts after smoke:
```text
tasks 1
agents 0
skills 0
```
## Intake-task / scheduler result
`intake-task` command over the socket returned success:
```json
{"ok":true,"data":{"status":"queued"}}
```
However, an immediate `list-tasks` returned no task from intake:
```json
{"ok":true,"data":[]}
```
The direct `create-task` path then proved the store itself works, so this is not a SQLite failure.
### Finding
Scheduler runtime processing is not active in the `colibri-daemon` binary at this commit.
Evidence:
- `cmd_intake_task` queues into `state.scheduler` in memory.
- `daemon::run_loop` contains the scheduler tick.
- `crates/colibri-daemon/src/main.rs` starts the socket server but does **not** spawn `daemon::run_loop`.
- Therefore `intake-task` can report `queued`, but queued intake is not drained into SQLite tasks by the running daemon.
This means T1.3 scheduler unit tests pass, and scheduler code exists, but the live daemon does not yet process intake/scheduled jobs.
## Restart / teardown behavior
Graceful interrupt removed the socket:
```text
after stop socket exists? no
process remains? no
```
Restarting the daemon against the same `/tmp` DB/socket path also worked:
```json
{
"agent_list": [],
"agents": 0,
"daemon": "colibri-daemon",
"host": "osa-smoke-restart",
"sessions": 0,
"version": "0.0.1"
}
```
After restart stop:
```text
restart after stop socket exists? no
restart process remains? no
```
The `/tmp` smoke directory was removed after recording this report.
## Verdict
### Passed
- FreeBSD release build
- daemon starts with isolated `/tmp` data/socket/DB paths
- Unix socket is created and removed on graceful shutdown
- status command works
- glasspane snapshot command works
- SQLite DB + WAL files are created on FreeBSD
- `spawn-local colibri-smoke-agent` works
- Pi JSONL ingestion produces `done` pane state with `pi_session_id`
- direct `create-task` / `list-tasks` store path works
- restart against same temp DB/socket path works
- prototype rc.d file remains review-only and was not installed
### Partial / failed
- `intake-task` returns success but is not processed into a SQLite task because the daemon binary does not start `daemon::run_loop`.
- `colibri-ctl` still lacks task/intake helper commands, so task/intake smoke used a small raw Unix-socket Python client.
## Recommended next fix
1. Spawn `daemon::run_loop(state.clone(), DaemonLoopConfig::default(), ...)` from `crates/colibri-daemon/src/main.rs` alongside the socket server.
2. Add a deterministic integration test proving `intake-task` over the socket becomes a queued SQLite task after a short scheduler tick.
3. Add `colibri-ctl` commands for at least:
- `list-tasks`
- `create-task`
- `intake-task`
4. Re-run this same FreeBSD `/tmp` smoke.

View file

@ -1,142 +0,0 @@
# OSA FreeBSD Intake Scheduler Re-smoke
**Date:** 27.maj.2026
**Host:** osa.smilepowered.org
**OS:** FreeBSD 15.0-RELEASE-p9 amd64
**Repo:** `Clawdie/Colibri`
**Base pulled:** `af8c011` — daemon loop wiring landed
**Fix commit:** `d760536``fix: avoid scheduler store deadlock on intake drain`
**Status:** PASS after follow-up fix
## What was checked
Pulled the daemon-loop wiring (`9717ce7`, plus `af8c011`) and ran the full workspace gates:
```sh
cargo fmt --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
cargo build --workspace --release
```
Initial gates were green, but a live `/tmp` FreeBSD re-smoke exposed one more runtime bug.
## Finding during live re-smoke
With `daemon::run_loop` now started, `intake-task` did reach the scheduler tick and inserted a SQLite task, but a concurrent `list-tasks` socket request timed out.
Root cause: `Scheduler::tick` used expressions like:
```rust
match state.store.lock().unwrap().create_task(...) {
Ok(task) => {
state.store.lock().unwrap().list_agents()
}
}
```
The `MutexGuard` temporary from the `match` scrutinee can live through the match arm. Relocking `state.store` inside the arm can deadlock the scheduler thread. On FreeBSD this manifested as:
- SQLite row was created
- socket request blocked on the store lock
- daemon became unresponsive until killed by the smoke harness timeout
## Fix
`d760536` rewrites scheduler store access so every lock is scoped explicitly and dropped before the next lock:
```rust
let create_result = {
let store = state.store.lock().unwrap();
store.create_task(...)
};
```
It also adds a regression test:
```text
scheduler::tests::test_scheduler_tick_drains_intake_without_deadlock
```
## Re-smoke result after fix
Isolated `/tmp` environment:
```text
COLIBRI_DAEMON_DATA_DIR=/tmp/colibri-osa-resmoke-clawdie-1779908417/data
COLIBRI_DAEMON_SOCKET=/tmp/colibri-osa-resmoke-clawdie-1779908417/colibri.sock
COLIBRI_DB_PATH=/tmp/colibri-osa-resmoke-clawdie-1779908417/colibri.sqlite
COLIBRI_HOST=osa-resmoke
```
`intake-task` response:
```json
{"ok":true,"data":{"status":"queued"}}
```
The scheduler drained intake on the 30s tick:
```text
FOUND_ON_POLL=30
```
`list-tasks` then returned the queued task:
```json
{
"ok": true,
"data": [
{
"agent_id": null,
"created_at": "2026-05-27T19:00:47.360062420+00:00",
"description": "prove scheduler loop drains intake",
"id": "c3dab9df-8a37-47b1-854b-d956fd796d41",
"status": "queued",
"title": "osa resmoke intake",
"updated_at": "2026-05-27T19:00:47.360062420+00:00"
}
]
}
```
SQLite verification:
```text
tasks 1
journal_mode wal
```
Graceful shutdown:
```text
socket exists after stop? no
process remains? no
```
Daemon log included both task loops exiting cleanly:
```text
daemon background loop started ... scheduler_secs=30
received interrupt signal, initiating graceful shutdown
socket server received shutdown signal
daemon loop received shutdown signal
daemon background loop exited
Herdr socket API shut down
colibri-daemon shut down cleanly
```
## Final verdict
The daemon loop wiring is valid after `d760536`.
Validated on FreeBSD:
- daemon starts with `/tmp` data/socket/DB paths
- background daemon loop starts beside the socket server
- `intake-task` over Unix socket becomes a queued SQLite task on the scheduler tick
- `list-tasks` remains responsive after the tick
- SQLite WAL works
- graceful shutdown removes socket and exits both socket + loop tasks
The smoke directory was removed after recording this report.

View file

@ -1,65 +0,0 @@
# Colibri Scheduler / FreeBSD Store Isolation Finding
**Date:** 27.maj.2026
**Repo:** `Clawdie/Colibri`
**Finding commit:** `ceaeaee` — scheduler T1.3 landed
**Fix commit:** `a48afa1``fix: harden scheduler tests and FreeBSD store isolation`
**Status:** Fixed and verified
## Finding
After pulling `ceaeaee`, the Linux-side direction was good, but FreeBSD validation exposed a real test-isolation bug:
```text
failed to open coordination store at "/var/db/colibri/colibri.sqlite":
I/O error: Permission denied (os error 13)
```
Root cause: `daemon::tests::test_daemon_state_creation` used `DaemonConfig::from_env()`, which on FreeBSD resolves the default production/service SQLite path:
```text
/var/db/colibri/colibri.sqlite
```
Unit tests must not require production service paths or root permissions.
## Fix
`test_daemon_state_creation` now overrides:
- `data_dir`
- `socket_path`
- `db_path`
with an isolated temp directory before constructing `DaemonState`.
## Additional hardening
While reviewing the scheduler, the follow-up also hardened edge cases:
- cron fields accept leading-zero forms such as `00 12 01 06 01`
- cron schedules fire at most once per matching minute, even with a 30s daemon tick
- `pick_agent` no longer assigns required-capability tasks to zero-match agents
- empty required-capability tasks can still select a general available agent
## Verification
Commands run after the fix:
```sh
cargo fmt --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
cargo build --workspace --release
```
Result:
```text
89 tests passed, 0 failed
release build OK
```
## Conclusion
T1.3 scheduler remains accepted, but `a48afa1` is the correct green baseline after FreeBSD-safe test isolation and scheduler edge-case hardening.

View file

@ -1,115 +0,0 @@
# Colibri Baseline — main @ d360dde
**Date:** 2026-05-31
**Commit:** `d360dde` — Merge PR #4 (T1.4 PR2: trimming + auto-escalation)
**Status:** All 4 merged PRs validated on Linux + FreeBSD
## Merged PRs
| PR | Title | Content | Tests |
| --- | ------------------------------------- | ---------------------------------------------------- | -------- |
| #1 | PromptAssembly + CacheMetrics structs | Structural wrapper, no behavior change | 5 golden |
| #2 | colibri-skills scaffold | Read-only skill consumer crate, 12 tests | 12 |
| #3 | zot runtime event normalization | AgentRuntime enum, zot event mapper, 16 tests | 16 |
| #4 | cost-aware trimming + auto-escalation | trim_to_budget(), EscalationTrigger, auto_escalate() | 11 |
## Validation
### Linux (debby, Debian 13)
```
rustc: 1.94.0 (via rustup)
cargo fmt --check ✓
cargo clippy --workspace --all-targets -- -D warnings ✓
cargo test --workspace ✓
git diff --check HEAD ✓
```
### FreeBSD (osa, FreeBSD 15.0-RELEASE)
```
host: osa.smilepowered.org
rustc: 1.94.0
cargo: 1.94.0
freebsd-version -k: 15.0-RELEASE-p9
freebsd-version -u: 15.0-RELEASE-p9
uname -r: 15.0-RELEASE-p8 (kernel p9 installed, pending reboot)
cargo fmt --check ✓
cargo clippy --workspace --all-targets -- -D warnings ✓
cargo test --workspace ✓
git diff --check HEAD ✓
```
### Test counts
```
colibri-daemon: 51 unit + 7 glasspane integration + 1 scheduler integration
colibri-contracts: golden tests
colibri-glasspane: 33 unit (17 Pi + 16 zot)
colibri-skills: 12 unit
colibri-store: integration
Workspace total: all passed, no failures
```
### Workspace crates (9)
```
colibri-contracts, colibri-deepseek, colibri-runtime,
colibri-glasspane, colibri-daemon, colibri-client,
colibri-glasspane-tui, colibri-store, colibri-skills
```
## Known caveats
- OSA kernel is p8 running but p9 installed — reboot needed for kernel update.
Does not affect Rust validation.
- `colibri-skills` and `zot-runtime-event-adapter` are scaffold-only (Phase 1).
No IO, no SQLite, no daemon integration yet.
- `PromptAssembly::trim_to_budget()` is implemented but not yet wired into
the scheduler or session append path — PR 3 (scheduler injection) pending.
- `CacheMetrics` struct exists but is not populated by any API call path yet.
- API token (`FORGEJO_API_TOKEN`) stored in `~/.hermes/.env` with 0600 perms.
Enables Hermes to create/merge PRs via Forgejo API without web UI.
## Key files added/changed this session
```
docs/T1.4-PROMPT-DISCIPLINE-PLAN.md (new — T1.4 implementation plan)
docs/COLIBRI-CUTOVER-PLAN.md (link to T1.4 plan)
crates/colibri-daemon/src/session.rs (PromptAssembly, CacheMetrics,
trim_to_budget, golden tests)
crates/colibri-daemon/src/cost.rs (EscalationTrigger, auto_escalate)
crates/colibri-glasspane/src/lib.rs (AgentRuntime, zot_event_type,
apply_zot_event, 16 tests)
crates/colibri-skills/ (new crate — structs, 12 tests)
doc/COLIBRI-SKILLS-PLAN.md → docs/ moved (skills plan)
```
## Next recommended steps
In priority order for USB/ISO target:
1. **Real Pi spawn path proof** — validate that Colibri can spawn a Pi agent
and consume its JSONL events on both Linux and FreeBSD.
2. **ISO service hardening** — Colibri as an `rc.d` service on FreeBSD,
status visibility, firstboot integration.
3. **T1.4 PR3 — scheduler prompt injection** — wire trim_to_budget() into
the scheduler's spawn-agent path, cost-aware prompt assembly.
4. **T1.4 PR4 — startup cache warming** — warm DeepSeek prefix cache on
daemon startup, config-gated. Separate from PR3 because of lifecycle/cost
concerns.
## Branch hygiene
```
main: d360dde (baseline, all merged)
t14-pr2-trimming: merged → can delete
t14-pr1-clean: merged → can delete
docs/t14-prompt-discipline-plan: merged → can delete
feat/colibri-skills-scaffold: merged → can delete
feat/zot-runtime-event-adapter: merged → can delete
```

View file

@ -1,156 +0,0 @@
# OSA real Pi binary spawn proof
**Date:** 2026-05-31
**Host:** `osa.smilepowered.org`
**Colibri commit:** `44865f3` — main after PR #8
**Pi binary:** `/home/clawdie/.npm-global/bin/pi`, version `0.76.0`
## Verdict
Real Pi binary spawn path was proven on OSA through `colibri-daemon`.
The daemon spawned a local executable wrapper, the wrapper executed the real
`pi` binary in JSON mode, Colibri streamed Pi JSONL stdout into glasspane, and
the pane reached `done` with a captured Pi session id.
This proves the runtime path:
```text
colibri-daemon spawn-local
→ wrapper executable
→ real pi --mode json --no-tools --no-context-files --no-session -p ...
→ Pi JSONL stdout
→ colibri glasspane ingestion
→ snapshot state=done + pi_session_id captured
```
## Environment
```text
host: osa.smilepowered.org
rustc: rustc 1.94.0 (4a4ef493e 2026-03-02) (built from a source tarball)
cargo: cargo 1.94.0 (85eff7c80 2026-01-15) (built from a source tarball)
freebsd-version -k: 15.0-RELEASE-p9
freebsd-version -u: 15.0-RELEASE-p9
uname: FreeBSD osa.smilepowered.org 15.0-RELEASE-p8 FreeBSD 15.0-RELEASE-p8 releng/15.0-n281036-53054229dcb3 GENERIC amd64
```
Caveat: OSA has p9 installed, but the running kernel is still p8 pending
operator reboot.
## Direct Pi smoke
Before the daemon proof, direct Pi JSON mode was verified:
```sh
pi --version
# 0.76.0
pi --mode json --no-tools --no-context-files --no-session -p 'Reply with exactly: OK'
```
Result: exit status 0, JSONL emitted, including:
- `session`
- `agent_start`
- `turn_start`
- `message_start` / `message_update` / `message_end`
- `turn_end`
- `agent_end`
## Daemon proof setup
A temporary daemon instance was started with temp-only paths:
```sh
COLIBRI_DAEMON_DATA_DIR=/tmp/colibri-real-pi-daemon-...
COLIBRI_DAEMON_SOCKET=/tmp/colibri-real-pi-daemon-.../colibri.sock
COLIBRI_DB_PATH=/tmp/colibri-real-pi-daemon-.../colibri.sqlite
COLIBRI_HOST=osa-real-pi-proof
target/debug/colibri-daemon
```
The local spawn target was a temporary wrapper script:
```sh
#!/bin/sh
set -eu
WORKDIR="${TMPDIR:-/tmp}/colibri-real-pi-work-$$"
mkdir -p "$WORKDIR"
cd "$WORKDIR"
exec pi --mode json --no-tools --no-context-files --no-session -p 'Reply with exactly: OK'
```
Then Colibri CLI spawned it through the daemon:
```sh
target/debug/colibri --socket "$SOCKET" spawn-local "$WRAPPER" --session-id real-pi-osa-proof
```
Spawn response:
```json
{
"agent_id": "f8d07954-b66d-4008-9da2-783818935b22",
"status": "running"
}
```
## Observed state transitions
Polling `colibri snapshot` showed:
```text
poll 1: idle None None
poll 2: idle None None
poll 3: working 019e7e95-cc0b-751a-8c0b-e6fd3ffab08f None
poll 4: working 019e7e95-cc0b-751a-8c0b-e6fd3ffab08f None
poll 5: done 019e7e95-cc0b-751a-8c0b-e6fd3ffab08f None
```
Final snapshot excerpt:
```json
{
"schema": "clawdie.glasspane.snapshot.v1",
"host": "osa-real-pi-proof",
"panes": [
{
"id": "f8d07954-b66d-4008-9da2-783818935b22",
"agent": "/tmp/colibri-real-pi-daemon-.../run-real-pi.sh",
"state": "done",
"pi_session_id": "019e7e95-cc0b-751a-8c0b-e6fd3ffab08f",
"cwd": "/tmp/colibri-real-pi-work-62462"
}
]
}
```
## What this proves
- OSA has a working `pi` binary.
- `pi --mode json` emits JSONL that Colibri glasspane can ingest.
- `colibri-daemon` can spawn an executable that runs real Pi.
- JSONL stdout streaming from real Pi reaches glasspane.
- Glasspane captures Pi session id separately from Colibri pane id.
- The pane reaches `done` after a real Pi turn completes.
## Remaining limitations
- This used a wrapper because `spawn-local` currently treats `model` as a single
executable path and does not pass argv.
- This did not prove a first-class Pi provider command shape in the daemon.
- This did not prove a long-running interactive Pi session, only one noninteractive
JSON-mode turn.
- This did not prove cache warming or scheduler injection behavior with real Pi.
Suggested follow-up if needed:
- add first-class argv support for `spawn-local`, or
- add a dedicated Pi provider path that invokes:
```text
pi --mode json --no-tools --no-context-files --no-session -p <prompt>
```
with deterministic prompt/config handling.

View file

@ -1,6 +1,10 @@
#!/bin/sh
#
# clawdie — FreeBSD rc.d service for the simplified Colibri agent.
# clawdie — experimental FreeBSD rc.d service candidate.
#
# The supported live-ISO service is colibri_daemon. This script is kept for
# explicit deployed-service experiments only; do not treat it as the final
# installed-system service contract without fresh acceptance criteria.
#
# Operator-friendly by design: enable it and start it. The two credentials
# (Telegram bot token + DeepSeek key) are normally baked into the binary at
@ -101,7 +105,7 @@ clawdie_prestart()
clawdie_poststart()
{
# Wait for the Herdr socket to appear (daemon forks, child binds socket).
# Wait for the Colibri control-plane socket to appear (daemon forks, child binds socket).
local timeout=10
local waited=0
while [ ! -S "${clawdie_socket}" ] && [ $waited -lt $timeout ]; do