Before spawning an agent (pi/zot), colibri-daemon now runs
/usr/local/bin/clawdie-hw-probe (if present) and passes the
JSON output as CLAWDIE_HW_PROFILE env var. Non-blocking:
probe failure or missing binary logs a warning and the agent
spawns normally without hw profile.
This gives agents immediate host awareness — USB names, GPU
capabilities, RAM, CPU, disks, ZFS pools — without running
probes themselves.
Every crate hardcoded version = "0.0.1" while the root colibri package was
already 0.11.0. Add a [workspace.package] version = "0.11.0" and switch all
crates (and the root package) to version.workspace = true, so the whole
workspace is 0.11.0 and the next bump is one line.
Internal deps are path-only (no version constraints), so nothing else changes.
Verified: cargo metadata resolves; all 13 packages report 0.11.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Treat zot tool_use_start as the canonical tool_execution_start event and skip the later standalone tool_call so Glasspane does not double-fire tool starts. Update the real-key transcript notes to mark the double-fire issue resolved.\n\nValidation: ./scripts/check-format.sh; cargo fmt --check; cargo test -p colibri-glasspane; cargo test -p colibri-daemon glasspane -- --nocapture; cargo test -p colibri-daemon pi_spawn_path_produces_correct_glasspane_state -- --nocapture; cargo clippy -p colibri-glasspane -p colibri-daemon --all-targets -- -D warnings.
The raw stdout shows only tool_use_* + tool_progress + tool_result for the tool
cycle — no standalone {"type":"tool_call"} line. Downgrade the double-fire
note from a 'verified fact' to an open question, and mark the tool_call table
row as mapped-but-not-observed. Keeps the doc's 'observed, not inferred'
section honest. 14/15 types remain validated against real output.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Step 1 of colibri#143 complete. Complete tool call cycle captured
with valid DEEPSEEK_API_KEY: 61 lines, 2 turns, 1 bash tool call.
All 15 event types observed and mapped — no glasspane gaps.
Notable: tool_call and tool_use_start both map to tool_execution_start
(double-fire on real runs). Verified facts replace 'name matches source.'
Full raw transcript at /tmp/zot_transcript_full.txt (OSA).
Pre-existing gate offender (PR #141 slipped check-format.sh). Table-alignment
whitespace only, no content change. Restores a green ./scripts/check-format.sh.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The bare underscored names (tool_call, tool_use_*, text_delta, assistant_*)
were read as markdown emphasis and mangled by prettier; wrap them in code
spans so they render literally and stay prettier-immune.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Step 1 of colibri#143. Captured with zot rpc --provider deepseek.
Wire format: bare event objects (no JSON-RPC envelope), matches
glasspane's zot_event_type parser. All 6 observed types mapped.
Remaining types (tool_call, text_delta, etc.) need a live API key
but type names match zot source.
Verdict: glasspane parser is correct. Steps 2-3 unblocked.
Honor an optional pkg-list-jails path/URL argument, allow comments inside the agent-jail section, and apply Prettier to docs/README.md. This preserves the cross-repo gate for the jq addition.\n\nValidation: ./scripts/check-format.sh; cargo fmt --check; ./packaging/freebsd/port/check-cargo-crates.sh; ./packaging/freebsd/check-agent-jail-pkgs.sh /home/clawdie/ai/clawdie-iso/packages/pkg-list-jails.txt; sh -n packaging/freebsd/agent-jail-bootstrap.sh packaging/freebsd/mother-sync-hive-keys.sh; cargo check -p colibri-daemon -p colibri-client -p colibri-mcp.
Mother side of the vault-mediated hive key exchange (direction B — agents call
mother). Pulls the hive-pubkey-* items agents publish to Vaultwarden and rebuilds
the colibri user's authorized_keys, each entry restricted to the MCP command
(command="colibri-mcp",restrict,no-pty,no-*-forwarding).
- Rebuild, not append: deleting an agent's vault item revokes it next run.
- Fail-safe: a vault/login failure leaves authorized_keys untouched.
- Atomic write (mktemp + mv); colibri-owned 0600.
- Tunable via PROVIDER_ENV / COLIBRI_HOME / COLIBRI_USER / MCP_COMMAND
(mother = osa for now; a dedicated host is a config change).
- Cron-driven (sample line in the header). Uses the bitwarden-cli-vault skill's
session + authorized_keys-rebuild patterns.
sh -n clean; parse/rebuild core tested (filters non-key items, strips key
comments, applies the restriction wrapper).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The MCP tooling parses colibri-mcp / external MCP JSON-RPC output with jq, so
agent jails need it on PATH. Add jq to the pinned PKGS list (host must have it
in the pkg cache, like the other pinned packages). Mirrors the matching entry
in clawdie-iso pkg-list-jails.txt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Run the mandatory markdown format gate (check-format.sh / prettier@3) on the
doc — table column padding only, no content change. Makes the PR pass the gate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completes the CLI surface: all 19 socket commands now have CLI wrappers.
Remote agents can work entirely through the colibri binary instead of raw
Python socket calls.
CLI additions:
- claim-task --task-id UUID --agent-id UUID
- transition-task --task-id UUID --status STATUS
- set-cost-mode MODE
DaemonClient gains claim_task(), transition_task(), set_cost_mode().
Also adds 3 pick_agent unit tests (Phase 1a):
- tie-breaking: equal score → later-in-slice wins (deterministic)
- multi-required-capabilities: agent with both caps beats one
- active-status-eligible: 'active' status eligible same as 'idle'
Plus 6 CLI parse tests (positive + negative) for the new commands.
Gate: fmt clean, clippy clean, 245 tests pass (Sam & Claude)
Workstream B of the next ISO rebuild: the live image should boot with at
least one Pi instance live on the Colibri board without operator action.
On startup, after the control-plane socket is up, the daemon spawns one
DeepSeek-backed Pi when configured. Host-spawn (no jail) — the live image is
single-agent; jails remain for deployed multi-tenant hosts. The Pi inherits
DEEPSEEK_API_KEY from the daemon environment (sourced from provider.env by
the rc.d service).
- Gated by COLIBRI_AUTOSPAWN_PI (YES/1/true/on); no-op otherwise.
- Requires a DEEPSEEK_API_KEY; logs and skips if absent (operator adds it via
Join Hive, then the daemon restart spawns it).
- Idempotent: skips if a Pi subprocess is already running, so the post-creds
restart does not stack duplicates.
- Pi binary and argv are env-tunable (COLIBRI_PI_BINARY default `pi`,
COLIBRI_AUTOSPAWN_PI_ARGS default `--mode json`) so the exact invocation can
be finalized on the FreeBSD image without a rebuild.
Reuses cmd_spawn_agent so glasspane attach, stdout streaming, and board
registration are identical to an operator-issued spawn. Tests for the pure
helpers (basename, env_truthy); full daemon suite green; clippy clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#136 moved staging from /var/run/colibri-stage to
/home/clawdie/.cache/colibri/stage. Bastille creates the jail's
/home/clawdie as root:wheel, so the daemon (running as clawdie)
couldn't create staging directories there. chown after binary
copy ensures the daemon owns its home directory inside the jail.
Closes#135. The daemon stages per-spawn launch.sh/env.sh under the jail root;
the previous location /var/run/colibri-stage is root-owned, so the daemon
(running as clawdie) could not create per-spawn subdirs there — the second
jail-spawn EACCES, worked around in #134 by pre-creating the dir in
agent-jail-bootstrap.sh.
Move the default staging root to the daemon user's home,
/home/clawdie/.cache/colibri/stage, which clawdie owns by construction of the
jail account. create_dir_all now succeeds with no privileged pre-creation step,
and /home is persistent (unlike a tmpfs /var/run). The path is overridable via
COLIBRI_JAIL_STAGE_DIR, matching the daemon's other env-configurable paths.
- spawner.rs: const → staged_jail_run_dir() resolver; updated unit test.
- agent-jail-bootstrap.sh: drop the now-unnecessary install -d staging block
and DAEMON_USER var (the #134 workaround).
- docs: update jailed-spawn design + truss analysis to the new location.
clippy clean; spawner suite green (21 tests); sh -n clean; touched docs pass
the markdown gate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Second root cause of the jail-spawn EACCES (found via truss, docs PR #132):
for staged spawns the daemon writes launch.sh/env.sh under
<jail_root>/var/run/colibri-stage/<stage_id>/, but nothing created
/var/run/colibri-stage. The daemon runs as clawdie and cannot mkdir under
root-owned /var/run, so staging failed with Permission denied.
agent-jail-bootstrap.sh now pre-creates the dir owned by the daemon user
(0700), replacing the runtime `chmod 777` workaround — durable across jail
rebuilds and not world-writable (staged files are sourced as shell, so a
world-writable staging dir would be a privilege footgun). DAEMON_USER is
overridable, defaulting to clawdie.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Second root cause of the jail-spawn EACCES (found via truss, docs PR #132):
for staged spawns the daemon writes launch.sh/env.sh under
<jail_root>/var/run/colibri-stage/<stage_id>/, but nothing created
/var/run/colibri-stage. The daemon runs as clawdie and cannot mkdir under
root-owned /var/run, so staging failed with Permission denied.
agent-jail-bootstrap.sh now pre-creates the dir owned by the daemon user
(0700), replacing the runtime `chmod 777` workaround — durable across jail
rebuilds and not world-writable (staged files are sourced as shell, so a
world-writable staging dir would be a privilege footgun). DAEMON_USER is
overridable, defaulting to clawdie.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two root causes found via truss:
1. Bare command names (sudo, jexec) unresolved under daemon(8) PATH
→ fixed by resolve_program() in PR #131
2. Jail staging directory owned by root, unwritable by clawdie
→ fixed by chmod 777 <jail_root>/var/run/colibri-stage
Trace saved at /tmp/daemon.truss (1964 lines, successful spawn).
The jail spawn path launches its wrapper by bare name (sudo / jexec / mdo)
and relies on execvp + the daemon's inherited PATH. Under daemon(8)/rc the
PATH is often empty or reordered, so execvp either misses the binary (ENOENT)
or hits a non-executable same-named entry first and returns EACCES — the spawn
"Permission denied" seen on FreeBSD even though the identical command runs from
a shell.
- resolve_program() absolutizes a bare program name against a fixed search
list (first regular executable wins), leaving slash-bearing paths untouched
and falling back to the bare name so the OS still reports a real error.
- spawn_prepared_child now logs the resolved program, requested name, full
argv, and PATH before spawning. The previous "attempting spawn" log carried
no spawn-context detail, which is why the failure was opaque.
This removes the PATH-search EACCES as a variable so a truss/ktrace run can
attribute any remaining denial to an actual kernel/MAC policy instead.
Tests: resolve_program pass-through, absolutization, and missing-name fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes#122. Creates packaging/freebsd/clawdie-npm-profile.sh as
the single source for npm PATH + npm config. The agent-jail
bootstrap installs it with NPM_PREFIX baked in, replacing the
inline heredoc. The clawdie-iso build.sh installs the same file.
Before: two divergent heredocs, different filenames, different
prefixes. Now: one file, both environments, parameterized prefix.
Move the backoff spawn operation into a named async helper so older tooling does not trip over || async syntax, and add a jail sudo wrapping unit test. Document sudo as an interim validated-host privilege mode.\n\nValidation: ./scripts/check-format.sh; cargo fmt --check; cargo check -p colibri-daemon; cargo test -p colibri-daemon jail_tests -- --nocapture.
Uses 'sudo -n' to wrap jail commands. Set via
COLIBRI_JAIL_PRIV_MODE=sudo. Requires sudoers entry:
clawdie ALL=(root) NOPASSWD: /usr/sbin/jexec *
The daemon's async spawn closure (edition 2015) may need a
follow-up to fully use this mode — the env var and wrapping
logic are correct, verified via manual jexec test.
Two changes to the clawdie deploy binary:
1. Service user renamed from 'clawdie' to '_clawdie' — follows FreeBSD
daemon convention (underscore prefix). Avoids collision with the
operator's interactive 'clawdie' user on existing hosts like OSA.
2. User creation is now idempotent — exit code 65 (pw: user already
exists) is treated as success via the new allowed_exit_codes field
on Action::Run. Deploy can safely re-run without failing.
Full end-to-end test on OSA file-backed pool: all 7 steps (ZFS
datasets, user, chown, rc.d write, sysrc enable) complete.