Follow-up to #17. Two issues with the daemon(8) `-r` + child-pidfile pattern:
1. Stop semantics (both services): with `-r`, rc.subr's default stop sends
SIGTERM to the *child* pid — and the still-running daemon(8) supervisor
respawns it ~1s later, so `service … stop` never actually stops it. Fix:
add a `-P` supervisor pidfile and a custom stop_cmd that SIGTERMs the
supervisor (which forwards to the child and exits without restarting),
waits up to 30s, SIGKILL fallback, then cleans pidfiles. Child pidfile +
unique procname are kept for accurate start/status.
2. clawdie.in parity: it still carried the pre-#17 pattern (`-P ${pidfile}`
as the only pidfile + procname="/usr/sbin/daemon"), so `service clawdie
status` could match tailscaled/colibri_daemon on a stale pidfile. Brought
it to the same shape as colibri_daemon.in: child pidfile, procname="clawdie",
supervisor pidfile, stop_cmd, socket-ready poststart, socket cleanup
poststop, and a `health` command.
Packaging-only — no Rust touched, no rebuild needed. `sh -n` clean on both;
stop algorithm exercised standalone (kills supervisor, idempotent). FreeBSD
start/stop/status/restart validation still owed on OSA.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fix three bugs identified in live USB diagnostics (COLIBRI-XFCE-HANDOFF-04.JUN.2026):
1. procname collision: 'colibri-daemon' instead of '/usr/sbin/daemon'
so service status finds OUR process, not tailscaled or any other
daemon(8)-managed service.
2. pidfile flag: -p (child pidfile) instead of -P (supervisor pidfile)
so the pidfile holds the colibri-daemon PID, which rc.subr can
match against the unique procname.
3. Cascading bypass: fixing procname prevents rc.subr from skipping
daemon(8) entirely. Process tree should now be:
rc.d → daemon(8) → colibri-daemon (with crash restart)
Run Prettier on the PR #15 tokenomics doc after the clawdie scope and model-name fixes.\n\nChecks: npx --yes prettier@3 --check docs/COLIBRI-TOKENOMICS-TRIFECTA.md; cargo fmt --check; git diff --check.
Per Claude review: the tokenomics doc implied cost-modes/metering as
universal Colibri behaviour, but the clawdie lane deliberately strips
all of it. Added explicit scope block referencing CLAWDIE-AGENT-WIKI.md.
Also aligned example model name deepseek-v4-flash with harness docs.
clawdie.in exported COLIBRI_DAEMON_DATA_DIR/SOCKET/HOST but not COLIBRI_DB_PATH.
On FreeBSD, default_db_path() then falls back to /var/db/colibri/colibri.sqlite
— the full Colibri daemon's DB, owned colibri:colibri (0750). clawdie runs as
the clawdie user, so Store::open() hits EACCES; DaemonState::new() panics
(daemon.rs:35) and daemon(8) -r restart-loops forever. The service would never
serve, despite a correct-looking rc.d.
Fix: add a clawdie_db_path var (default ${clawdie_data_dir}/clawdie.sqlite) and
export COLIBRI_DB_PATH from prestart, keeping clawdie's DB in its own
clawdie-owned dir. No collision with the colibri daemon's DB.
Reproduced + verified on Linux:
- COLIBRI_DB_PATH unwritable → panic "failed to open coordination store … Permission denied", exit 101
- COLIBRI_DB_PATH in data dir → sqlite created, runs clean
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New `clawdie` crate: the operator-friendly face of Colibri. One small Rust
binary that reuses the proven control-plane core (glasspane supervision +
Herdr Unix-socket API + coordination loop — the "split brain") and puts a
DeepSeek-backed Telegram bot in front of it. That is the entire out-of-the-box
surface.
Deliberately lifted vs. the full control plane: cost modes, quota accounting,
context budgets, multi-provider fallback (OpenRouter/Anthropic), per-user
limits. One DeepSeek key serves both the chat lane and the daemon routing.
- crates/clawdie: main (core + bridge wiring), telegram (long-poll bridge),
deepseek (minimal one-key chat), build.rs (bakes CLAWDIE_TG_TOKEN +
CLAWDIE_DEEPSEEK_KEY build flags; runtime env overrides).
- packaging/freebsd/clawdie.in: rc.d service, daemon(8)-supervised, restart on
crash, dedicated clawdie user — starts as a service like Clawdie-AI.
- release profile strips symbols (binary ~7.6 MB stripped).
- docs/CLAWDIE-AGENT-WIKI.md (mindmap), docs/CLAWDIE-BUILD.md (build + ISO +
next-build XFCE USB fixes), README workspace table.
Build/clippy/fmt green; headless start smoke-tested (socket + sessions bind).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds local_args field to HerdrCommand::SpawnAgent, enabling the
Local provider to pass argv to the agent binary without a wrapper
script. Backward-compatible — local_args defaults to None.
Real Pi spawn on FreeBSD is now:
spawn-agent provider=local model=pi local_args=['--mode','json','--no-tools','-p','task']
Previously required a wrapper script because only an executable
path was accepted. This closes the OSA wrapper caveat from PR #9.
Build: pass | Tests: workspace green
When scheduler_prompt_injection is enabled and a session_id is
provided on spawn-agent, the daemon builds a PromptAssembly from
the session, serializes it as COLIBRI_SESSION_CONTEXT env var,
and passes COLIBRI_COST_MODE to the spawned agent process.
Config-gated (default: disabled) via COLIBRI_SCHEDULER_PROMPT_INJECTION.
No cache warming — that's PR3b (separate).
Build: pass | Tests: workspace green | Clippy: clean | Fmt: clean
Hardens the FreeBSD service for production readiness:
- rc.d: post-start socket health check (waits up to 10s), post-stop
socket cleanup, 'health' extra command that probes socket with
a status command via nc.
- newsyslog: log rotation at 1MB, 7 compressed archives,
colibri:colibri ownership.
- staging: copies newsyslog config into image root, updated
staging report to list all installed files.
- docs/ISO-SERVICE-LAYOUT.md: filesystem layout, boot/shutdown
behavior, startup validation commands, config knobs, secrets
policy, log rotation details.
Shell syntax: sh -n clean on both scripts.
Workspace tests: all green.
Validates: Colibri spawns agent process (fake-pi-agent.py) → reads
JSONL stdout → glasspane ingests → snapshot shows Done state with
correct session ID.
Uses scripts/fake-pi-agent.py which emits the colibri-pi-events
JSONL taxonomy (session, agent_start, turn_start, turn_end,
agent_end). Proves the spawn→ingest→glasspane pipeline without
requiring the real pi binary.
The real Pi binary path (when installed) follows the same pattern:
pi --mode json is spawned with identical spawner code.
Build: pass | Tests: 1/1 green | Workspace: all green
Linux + FreeBSD validation green. All 4 PRs merged.
Known caveats: OSA kernel p8 running / p9 pending reboot,
colibri-skills and zot harness are scaffold-only.
Structural only — no behavior change. Introduces:
- PromptAssembly: named 3-region wrapper around build_prompt_messages()
with to_messages(), immutable_prefix, appendable_log, volatile_scratch,
total_bytes, estimated_tokens.
- CacheMetrics: per-session cache-hit tracking with hit_rate() and
record().
- Session::build_prompt_assembly() wraps existing build_prompt_messages()
with no logic change.
- 5 golden tests: assembly structure, empty volatile, hit rate
calculations, record accumulation.
- Linked T1.4-PROMPT-DISCIPLINE-PLAN.md from COLIBRI-CUTOVER-PLAN.md.
No trimming, no escalation, no scheduler changes — PR 2 and 3 follow.
Parked branches (colibri-skills, zot harness) untouched.
Build: pass | Tests: 41/41 green (+5 new) | Clippy: clean | Fmt: clean
Phase 1 of zot agent harness integration. Adds:
- AgentRuntime enum (Pi, Zot, Local) with serde support
- zot_event_type() — parse zot RPC NDJSON lines to normalized
Colibri event types. Permissive: handles tool_use_start,
tool_use_args, tool_use_end in addition to documented events.
- apply_zot_event() / fold_zot_events() — state transitions
reusing the existing apply_pi_event() logic via normalization.
- Critical: assistant_message does NOT end the pane (zot may
emit them during tool loops). Only turn_end/done signals
completion.
- 16 new tests using real captured zot RPC event lines, including
tool call sequences and error/success response handling.
- PiJsonlIngestor and existing tests unchanged (33/33 green).
No spawner, socket, or process changes — this is the event
taxonomy foundation only. Wrapper + daemon integration follow
in Phase 2.
Build: pass | Tests: 33/33 green | Clippy: clean | Fmt: clean
Phase 1: structs + type system + 12 tests. No IO, no SQLite yet.
Compiles against full workspace (9 crates now, up from 8).
The colibri-skills crate is the read-only runtime consumer for
skill artifacts authored in Clawdie-AI. It does NOT store or author
skills — it indexes committed, reviewed skill bundles.
Seeded from the astro-howto artifact (PR #6 in clawdie-ai):
- Skill, SkillManifest, SkillArtifact, SkillChunk structs
- ArtifactType classifier (document, image, script, transcript, etc.)
- ImportSummary + SearchResult types
- SQLite schema documented in doc/COLIBRI-SKILLS-PLAN.md
Build: pass | Tests: 12/12 green | Clippy: pending
"Cene že še češnje je" — š/č/ž are 2-byte UTF-8, same family as
German umlauts. Complements the existing CJK+umlaut test. Ensures
compact_tool_result never panics on Slavic diacritics in FreeBSD
locale output, pkg descriptions, and agent logs.
Proposal for a unified Colibri + Zed experience where the operator
can edit docs/code while simultaneously running and observing agents,
tasks, and scheduler from one visual surface.
Three integration levels:
1. Near-term: Zed as visual shell over colibri-daemon socket
(sidebar extension, one-app experience, not one binary)
2. Mid-term: colibri-mcp MCP bridge (highest leverage)
- Zed already supports MCP natively
- Reuses colibri-client crate directly
- Editor-agnostic (works with any MCP-capable editor)
- 7 Phase-1 tools defined (status, snapshot, tasks, intake, etc.)
3. Long-term: true single binary (not recommended now)
- Colibri must run headless at boot even when editor is closed
Product shape:
colibri-daemon (service) + colibri-mcp (bridge) + colibri-studio
(launcher) + Zed extension. Two new thin binaries, no existing
crates change.
Estimated: ~1 week of focused agent time for MCP Phase 1 + launcher.
cost.rs:124 sliced at a raw byte boundary which panics on multibyte
UTF-8 characters (ä, ö, ü, CJK, etc). FreeBSD tool output and agent
logs regularly contain non-ASCII.
Fix: use str::floor_char_boundary() to round down to the nearest valid
char boundary before slicing. This never panics and produces valid
UTF-8 output at or below the requested byte limit.
Added test: multibyte truncation with CJK + umlaut input.