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.
The operator CLI was already a subcommand dispatcher; drop the -ctl suffix so
it reads `colibri status` / `colibri snapshot` / `colibri spawn-local …` — one
`colibri` entrypoint (same single-binary-with-subcommands shape as just, but a
control plane). Renames the bin + its source file, updates usage strings, and
points the forward-looking docs at `colibri`. Dated session/handoff records
are left as historical. (Task-board subcommands intake/create/list are the
follow-up T1.3c slice.)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
colibri-daemon runs in the foreground and writes no pidfile, so the previous
`command=/usr/local/bin/colibri-daemon` would hang `service start` and leave
status/stop unable to track it. Run it under daemon(8): -P supervisor pidfile,
-r restart on crash, -u colibri privilege drop, -o logfile for the tracing
stdout. start_precmd recreates the colibri-owned /var/run/colibri (tmpfs),
/var/db/colibri, and the log dir each start. Still review-only / not installed;
needs an on-FreeBSD `service` test (osa-smoke rec #4).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Regression guard for the scheduler store deadlock fixed upstream in d760536:
scheduler.tick held a non-reentrant std::sync::Mutex (state.store) across the
match scrutinee, so relocking inside the arm deadlocked the first time any
intake/scheduled task fired.
This test (independently authored on domedog) submits an intake-task over the
real Unix socket, runs run_loop with a fast scheduler tick, and asserts the
task is drained into SQLite. It hangs (>8min) against the pre-fix code and
passes in 0.09s against d760536 — so it cross-validates that fix and guards the
regression. Closes osa-smoke rec #2.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two improvements to Hermes' run_loop wiring (9717ce7):
1. Add scheduler_secs to the daemon loop startup log — the most
important interval for the OSA re-smoke was missing from the
heartbeat/rotation/handoff log line.
2. Replace tokio::select! with tokio::join! in main.rs — select!
returned when the first task finished, leaving the other dangling.
join! waits for both the socket server and the daemon loop to
complete before proceeding to shutdown.
89 tests pass, clippy clean.
The scheduler tick was wired into daemon::run_loop but main.rs only
started the socket server, never the background loop. intake-task
commands queued into the scheduler's in-memory queue were never
processed. Fix: spawn daemon::run_loop as a second tokio task
alongside the socket server.