From 1f2377d4dd957f9795fe94bf67467eb736c15d01 Mon Sep 17 00:00:00 2001 From: Sam & Claude Date: Sat, 13 Jun 2026 19:19:07 +0200 Subject: [PATCH] cleanup: drop the experimental clawdie mini-binary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `clawdie` crate (Telegram+DeepSeek mini-agent over the control-plane core) was an experimental operator-lane candidate. Per the agent-harness consolidation, the live USB runs colibri_daemon + the zot agent, and the deployed `service clawdie` is a reserved name, not this binary — so the mini-binary is dead weight. Remove it and its now-orphaned docs. - delete crates/clawdie (leaf crate; nothing depended on it) - delete packaging/freebsd/clawdie.in (its rc.d candidate) - delete docs/CLAWDIE-AGENT-WIKI.md + docs/CLAWDIE-BUILD.md (only described it) - drop it from workspace members + Cargo.lock; tidy the strip-profile comment - README: 11 → 10 crates, remove the clawdie row - COLIBRI-TOKENOMICS-TRIFECTA: drop the stale clawdie-lane scope note No "relay" existed in this repo (already gone). zot is untouched. The Clawdie brand, the clawdie operator user, and the reserved deployed `service clawdie` name are unaffected — this only removes the experimental Rust mini-binary. Co-Authored-By: Claude Opus 4.8 --- Cargo.lock | 14 --- Cargo.toml | 6 +- README.md | 3 +- crates/clawdie/Cargo.toml | 22 ---- crates/clawdie/build.rs | 29 ----- crates/clawdie/src/deepseek.rs | 114 ------------------ crates/clawdie/src/main.rs | 170 -------------------------- crates/clawdie/src/telegram.rs | 133 -------------------- docs/CLAWDIE-AGENT-WIKI.md | 99 --------------- docs/CLAWDIE-BUILD.md | 98 --------------- docs/COLIBRI-TOKENOMICS-TRIFECTA.md | 4 +- packaging/freebsd/clawdie.in | 181 ---------------------------- 12 files changed, 5 insertions(+), 868 deletions(-) delete mode 100644 crates/clawdie/Cargo.toml delete mode 100644 crates/clawdie/build.rs delete mode 100644 crates/clawdie/src/deepseek.rs delete mode 100644 crates/clawdie/src/main.rs delete mode 100644 crates/clawdie/src/telegram.rs delete mode 100644 docs/CLAWDIE-AGENT-WIKI.md delete mode 100644 docs/CLAWDIE-BUILD.md delete mode 100644 packaging/freebsd/clawdie.in diff --git a/Cargo.lock b/Cargo.lock index 768a9f0..ae156b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -277,20 +277,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" -[[package]] -name = "clawdie" -version = "0.0.1" -dependencies = [ - "colibri-daemon", - "colibri-glasspane", - "reqwest", - "serde", - "serde_json", - "tokio", - "tracing", - "tracing-subscriber", -] - [[package]] name = "colibri" version = "0.0.1" diff --git a/Cargo.toml b/Cargo.toml index 8c69e7a..780d8a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["crates/colibri-contracts", "crates/colibri-deepseek", "crates/colibri-runtime", "crates/colibri-glasspane", "crates/colibri-daemon", "crates/colibri-client", "crates/colibri-glasspane-tui", "crates/colibri-store", "crates/colibri-skills", "crates/colibri-mcp", "crates/clawdie"] +members = ["crates/colibri-contracts", "crates/colibri-deepseek", "crates/colibri-runtime", "crates/colibri-glasspane", "crates/colibri-daemon", "crates/colibri-client", "crates/colibri-glasspane-tui", "crates/colibri-store", "crates/colibri-skills", "crates/colibri-mcp"] [package] name = "colibri" @@ -28,7 +28,7 @@ tokio = { version = "1", features = ["macros", "rt-multi-thread"] } serde = { version = "1", features = ["derive"] } serde_json = "1" -# Lean release artifacts (notably the `clawdie` operator binary): strip symbols -# so the staged ISO binaries stay small without a manual strip step. +# Lean release artifacts: strip symbols so the staged ISO binaries stay small +# without a manual strip step. [profile.release] strip = true \ No newline at end of file diff --git a/README.md b/README.md index 79354df..f964289 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,11 @@ Next ISO integration plan: `docs/ISO-INTEGRATION-PLAN.md`. ISO acceptance runbook: `docs/ISO-ACCEPTANCE-RUNBOOK.md`. Clawdie Studio/Zed proposal: `docs/CLAWDIE-STUDIO-PROPOSAL.md`. -## Workspace — 11 crates +## Workspace — 10 crates | Crate | Role | | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | `colibri-mcp` | MCP bridge for editor integration (Zed, Claude Code) via stdio JSON-RPC | -| `clawdie` | Simplified operator agent: glasspane + DeepSeek/Telegram in one small binary (build-flag configured). See `docs/CLAWDIE-AGENT-WIKI.md`. | | `colibri-contracts` | JSON schema contracts (golden tests) | | `colibri-deepseek` | DeepSeek cache-hit probe, prefix metering | | `colibri-runtime` | Host status ingestion, runtime inventory | diff --git a/crates/clawdie/Cargo.toml b/crates/clawdie/Cargo.toml deleted file mode 100644 index 4218225..0000000 --- a/crates/clawdie/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "clawdie" -version = "0.0.1" -edition = "2021" -license = "AGPL-3.0-only" -description = "Clawdie — the simplified, operator-friendly Colibri agent (glasspane + supervision + DeepSeek/Telegram) as one small binary" - -[[bin]] -name = "clawdie" -path = "src/main.rs" - -[dependencies] -# Reuse the proven control-plane core: glasspane (supervision radar), -# the Colibri control-plane socket, the coordination loop, and session lifecycle. -colibri-daemon = { path = "../colibri-daemon" } -colibri-glasspane = { path = "../colibri-glasspane" } -tokio = { version = "1", features = ["full"] } -reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } -serde = { version = "1", features = ["derive"] } -serde_json = "1" -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/crates/clawdie/build.rs b/crates/clawdie/build.rs deleted file mode 100644 index 63eb2a3..0000000 --- a/crates/clawdie/build.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! Build-time configuration for the `clawdie` binary. -//! -//! The whole product surface is two credentials, and we want them bakeable at -//! compile time so an operator ISO ships ready-to-run with no config files: -//! -//! CLAWDIE_TG_TOKEN — Telegram bot token (the chat front door) -//! CLAWDIE_DEEPSEEK_KEY — DeepSeek API key (the one key for everything) -//! -//! Pass them as build flags, e.g.: -//! -//! CLAWDIE_TG_TOKEN=123:abc CLAWDIE_DEEPSEEK_KEY=sk-... cargo build --release -//! -//! Whatever is present at build time is re-exported into the compiled binary -//! via `cargo:rustc-env`, where `option_env!` can read it. Runtime environment -//! variables always win over baked-in values (see `src/main.rs`), so the same -//! binary works for both "baked" ISO builds and "bring your own key" operators. - -fn main() { - // Re-run if the build-flag inputs change. - println!("cargo:rerun-if-env-changed=CLAWDIE_TG_TOKEN"); - println!("cargo:rerun-if-env-changed=CLAWDIE_DEEPSEEK_KEY"); - - // Bake whatever was provided (empty string when unset — main.rs treats - // empty as absent). We never print the secret values to the build log. - let tg = std::env::var("CLAWDIE_TG_TOKEN").unwrap_or_default(); - let ds = std::env::var("CLAWDIE_DEEPSEEK_KEY").unwrap_or_default(); - println!("cargo:rustc-env=CLAWDIE_BUILTIN_TG_TOKEN={tg}"); - println!("cargo:rustc-env=CLAWDIE_BUILTIN_DEEPSEEK_KEY={ds}"); -} diff --git a/crates/clawdie/src/deepseek.rs b/crates/clawdie/src/deepseek.rs deleted file mode 100644 index 8d9e219..0000000 --- a/crates/clawdie/src/deepseek.rs +++ /dev/null @@ -1,114 +0,0 @@ -//! Minimal DeepSeek chat lane for the Clawdie agent. -//! -//! Deliberately tiny: one model, one key, one request shape, no cost modes, no -//! quota accounting, no provider fallback. That heavier discipline lives in -//! `colibri-deepseek` / `colibri-daemon::cost` for the full control plane; the -//! simplified Clawdie agent intentionally does **not** carry it. - -use serde::{Deserialize, Serialize}; - -/// DeepSeek's OpenAI-compatible chat-completions endpoint. -pub const DEFAULT_ENDPOINT: &str = "https://api.deepseek.com/chat/completions"; -/// Default chat model. Override at runtime with `DEEPSEEK_MODEL`. -pub const DEFAULT_MODEL: &str = "deepseek-chat"; - -/// One DeepSeek chat lane, configured once at startup. -#[derive(Clone)] -pub struct DeepSeek { - client: reqwest::Client, - endpoint: String, - model: String, - api_key: String, - system_prompt: String, -} - -#[derive(Serialize)] -struct ChatMessage<'a> { - role: &'a str, - content: &'a str, -} - -#[derive(Serialize)] -struct ChatRequest<'a> { - model: &'a str, - messages: Vec>, - stream: bool, -} - -#[derive(Deserialize)] -struct Choice { - message: ResponseMessage, -} - -#[derive(Deserialize)] -struct ResponseMessage { - #[serde(default)] - content: String, -} - -#[derive(Deserialize)] -struct ChatResponse { - #[serde(default)] - choices: Vec, -} - -impl DeepSeek { - /// Build a lane. `api_key` must be non-empty (callers gate on this). - pub fn new(api_key: String) -> Result { - let endpoint = - std::env::var("DEEPSEEK_ENDPOINT").unwrap_or_else(|_| DEFAULT_ENDPOINT.to_string()); - let model = std::env::var("DEEPSEEK_MODEL").unwrap_or_else(|_| DEFAULT_MODEL.to_string()); - let system_prompt = std::env::var("CLAWDIE_SYSTEM_PROMPT").unwrap_or_else(|_| { - "You are Clawdie, a friendly, concise operator assistant running on a \ - FreeBSD live system. Answer directly and helpfully." - .to_string() - }); - let client = reqwest::Client::builder() - .user_agent(concat!("clawdie/", env!("CARGO_PKG_VERSION"))) - .build()?; - Ok(Self { - client, - endpoint, - model, - api_key, - system_prompt, - }) - } - - /// Send one user message, return the assistant's reply text. - pub async fn reply( - &self, - user_text: &str, - ) -> Result> { - let body = ChatRequest { - model: &self.model, - messages: vec![ - ChatMessage { - role: "system", - content: &self.system_prompt, - }, - ChatMessage { - role: "user", - content: user_text, - }, - ], - stream: false, - }; - let resp = self - .client - .post(&self.endpoint) - .bearer_auth(&self.api_key) - .json(&body) - .send() - .await? - .error_for_status()?; - let parsed: ChatResponse = resp.json().await?; - let text = parsed - .choices - .into_iter() - .next() - .map(|c| c.message.content) - .unwrap_or_default(); - Ok(text) - } -} diff --git a/crates/clawdie/src/main.rs b/crates/clawdie/src/main.rs deleted file mode 100644 index 1ae268f..0000000 --- a/crates/clawdie/src/main.rs +++ /dev/null @@ -1,170 +0,0 @@ -//! clawdie — the simplified, operator-friendly Colibri agent. -//! -//! One small binary, two credentials, zero ceremony. It bundles the proven -//! control-plane core (glasspane supervision radar + the Colibri control-plane socket + -//! the 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. -//! -//! What was deliberately **lifted** vs. the full Colibri control plane: -//! - no cost modes / quota accounting / context-budget enforcement -//! - no multi-provider fallback (OpenRouter / Anthropic) — one DeepSeek key -//! - no per-user limits, no admin gating -//! -//! Credentials come from build flags (see `build.rs`) and are overridable at -//! runtime by environment variables, so the same binary serves both a baked -//! ISO and a bring-your-own-key operator. - -mod deepseek; -mod telegram; - -use std::sync::Arc; - -use colibri_daemon::{daemon, session, socket, DaemonConfig, DaemonState, SharedState}; -use tracing::{info, warn}; - -/// Resolve a credential: runtime env wins, then the build-flag baked value, -/// then nothing. Empty strings count as "not set". -fn resolve(env_names: &[&str], baked: Option<&str>) -> Option { - for name in env_names { - if let Ok(v) = std::env::var(name) { - if !v.trim().is_empty() { - return Some(v); - } - } - } - baked - .map(str::trim) - .filter(|v| !v.is_empty()) - .map(str::to_string) -} - -#[tokio::main] -async fn main() -> Result<(), Box> { - tracing_subscriber::fmt() - .with_env_filter( - tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), - ) - .init(); - - info!("clawdie {} starting", env!("CARGO_PKG_VERSION")); - - // ── Resolve the two and only credentials ──────────────────────────────── - let tg_token = resolve( - &["CLAWDIE_TG_TOKEN", "TELEGRAM_BOT_TOKEN"], - option_env!("CLAWDIE_BUILTIN_TG_TOKEN"), - ); - let deepseek_key = resolve( - &["CLAWDIE_DEEPSEEK_KEY", "DEEPSEEK_API_KEY"], - option_env!("CLAWDIE_BUILTIN_DEEPSEEK_KEY"), - ); - - // One key for everything: make the daemon's own provider routing see it too. - if let Some(key) = &deepseek_key { - std::env::set_var("DEEPSEEK_API_KEY", key); - } - - info!( - telegram = tg_token.is_some(), - deepseek = deepseek_key.is_some(), - "credentials resolved (build flags + runtime env)" - ); - - // ── Bring up the control-plane core (glasspane + control-plane socket + loop) ──── - let config = DaemonConfig::from_env(); - info!( - host = %config.host, - data_dir = %config.data_dir.display(), - socket_path = %config.socket_path.display(), - "control plane configured" - ); - - let state: SharedState = Arc::new(DaemonState::new(config.clone())); - - tokio::fs::create_dir_all(&config.data_dir).await?; - tokio::fs::create_dir_all(config.data_dir.join("sessions")).await?; - - // Reload any persisted sessions so supervision picks up where it left off. - if let Ok(mut entries) = tokio::fs::read_dir(config.data_dir.join("sessions")).await { - while let Ok(Some(entry)) = entries.next_entry().await { - let path = entry.path(); - if path.extension().and_then(|e| e.to_str()) == Some("jsonl") { - if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) { - let session_id = stem.to_string(); - let cfg = Arc::new(config.clone()); - match session::Session::load(session_id.clone(), cfg) { - Ok(sess) => { - state.sessions.insert(session_id, sess); - } - Err(e) => warn!(error = %e, "failed to load session file"), - } - } - } - } - } - - // Colibri control-plane socket. - let socket_state = state.clone(); - let socket_shutdown = state.shutdown_rx.resubscribe(); - let socket_handle = tokio::spawn(async move { - socket::serve(socket_state, socket_shutdown).await; - }); - - // Coordination / heartbeat / glasspane loop. - let loop_state = state.clone(); - let loop_shutdown = state.shutdown_rx.resubscribe(); - let loop_handle = tokio::spawn(async move { - daemon::run_loop( - loop_state, - daemon::DaemonLoopConfig::default(), - loop_shutdown, - ) - .await; - }); - - // ── Telegram front door (only if we have both a token and a key) ───────── - let telegram_handle = match (tg_token, &deepseek_key) { - (Some(token), Some(key)) => match deepseek::DeepSeek::new(key.clone()) { - Ok(ds) => { - let tg_shutdown = state.shutdown_rx.resubscribe(); - Some(tokio::spawn(async move { - telegram::run(token, ds, tg_shutdown).await; - })) - } - Err(e) => { - warn!(error = %e, "could not start DeepSeek lane; Telegram bridge disabled"); - None - } - }, - (Some(_), None) => { - warn!("Telegram token present but no DeepSeek key — bridge disabled"); - None - } - (None, _) => { - info!("no Telegram token — running headless (control plane only)"); - None - } - }; - - // Graceful shutdown on Ctrl-C / SIGTERM. - let shutdown_state = state.clone(); - tokio::spawn(async move { - tokio::signal::ctrl_c().await.ok(); - info!("interrupt received, shutting down"); - let _ = shutdown_state.shutdown_tx.send(()); - tokio::time::sleep(std::time::Duration::from_secs(2)).await; - for entry in shutdown_state.agents.iter() { - let _ = entry.value().kill().await; - } - }); - - let (socket_result, loop_result) = tokio::join!(socket_handle, loop_handle); - socket_result?; - loop_result?; - if let Some(h) = telegram_handle { - let _ = h.await; - } - - info!("clawdie shut down cleanly"); - Ok(()) -} diff --git a/crates/clawdie/src/telegram.rs b/crates/clawdie/src/telegram.rs deleted file mode 100644 index 67c4101..0000000 --- a/crates/clawdie/src/telegram.rs +++ /dev/null @@ -1,133 +0,0 @@ -//! Minimal Telegram bridge: long-poll `getUpdates`, hand each text message to -//! the DeepSeek lane, and `sendMessage` the reply back. -//! -//! No webhooks, no command framework, no per-user quota — the simplest thing -//! that gives an operator a working chat bot out of the box. Shuts down cleanly -//! when the daemon broadcasts shutdown. - -use serde::Deserialize; -use tokio::sync::broadcast; -use tracing::{info, warn}; - -use crate::deepseek::DeepSeek; - -const API_BASE: &str = "https://api.telegram.org"; - -#[derive(Deserialize)] -struct UpdatesResponse { - #[serde(default)] - ok: bool, - #[serde(default)] - result: Vec, -} - -#[derive(Deserialize)] -struct Update { - update_id: i64, - #[serde(default)] - message: Option, -} - -#[derive(Deserialize)] -struct Message { - #[serde(default)] - text: Option, - chat: Chat, -} - -#[derive(Deserialize)] -struct Chat { - id: i64, -} - -/// Run the bridge until `shutdown` fires. Network errors are logged and retried -/// rather than fatal — the bot should survive flaky links on a live USB. -pub async fn run(token: String, deepseek: DeepSeek, mut shutdown: broadcast::Receiver<()>) { - let client = match reqwest::Client::builder() - .user_agent(concat!("clawdie/", env!("CARGO_PKG_VERSION"))) - .build() - { - Ok(c) => c, - Err(e) => { - warn!(error = %e, "telegram: failed to build HTTP client; bridge disabled"); - return; - } - }; - let base = format!("{API_BASE}/bot{token}"); - let mut offset: i64 = 0; - info!("telegram bridge online (long-poll)"); - - loop { - tokio::select! { - _ = shutdown.recv() => { - info!("telegram bridge shutting down"); - return; - } - updates = poll_once(&client, &base, offset) => { - match updates { - Ok(list) => { - for upd in list { - offset = offset.max(upd.update_id + 1); - if let Some(msg) = upd.message { - if let Some(text) = msg.text { - handle_message(&client, &base, &deepseek, msg.chat.id, &text) - .await; - } - } - } - } - Err(e) => { - warn!(error = %e, "telegram: getUpdates failed; retrying"); - tokio::time::sleep(std::time::Duration::from_secs(3)).await; - } - } - } - } - } -} - -async fn poll_once( - client: &reqwest::Client, - base: &str, - offset: i64, -) -> Result, Box> { - // 25s long-poll; keep below the client's default timeout. - let resp: UpdatesResponse = client - .get(format!("{base}/getUpdates")) - .query(&[("timeout", "25"), ("offset", &offset.to_string())]) - .timeout(std::time::Duration::from_secs(35)) - .send() - .await? - .error_for_status()? - .json() - .await?; - if !resp.ok { - return Err("telegram getUpdates returned ok=false".into()); - } - Ok(resp.result) -} - -async fn handle_message( - client: &reqwest::Client, - base: &str, - deepseek: &DeepSeek, - chat_id: i64, - text: &str, -) { - let reply = match deepseek.reply(text).await { - Ok(r) if !r.trim().is_empty() => r, - Ok(_) => "(no reply)".to_string(), - Err(e) => { - warn!(error = %e, "telegram: deepseek reply failed"); - "Sorry — I couldn't reach the model just now.".to_string() - } - }; - if let Err(e) = client - .post(format!("{base}/sendMessage")) - .json(&serde_json::json!({ "chat_id": chat_id, "text": reply })) - .send() - .await - { - warn!(error = %e, "telegram: sendMessage failed"); - } -} diff --git a/docs/CLAWDIE-AGENT-WIKI.md b/docs/CLAWDIE-AGENT-WIKI.md deleted file mode 100644 index dd52751..0000000 --- a/docs/CLAWDIE-AGENT-WIKI.md +++ /dev/null @@ -1,99 +0,0 @@ -# Clawdie Agent — wiki / mindmap - -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 -[`COLIBRI-GLASSPANE-DESIGN.md`](./COLIBRI-GLASSPANE-DESIGN.md)). - -## Mindmap - -```mermaid -mindmap - root((clawdie
one binary)) - Split brain - Glasspane radar - 5-state machine - idle/working/blocked/done/error - derived from Pi JSON events - Coordination - task board - agent registry - session JSONL - Front door - Telegram bot - long-poll getUpdates - sendMessage reply - DeepSeek lane - one model - one key - no fallback - Build flags - CLAWDIE_TG_TOKEN - CLAWDIE_DEEPSEEK_KEY - baked at compile time - runtime env overrides - Lifted (NOT shipped) - cost modes - quota accounting - context budgets - OpenRouter / Anthropic - per-user limits - Runs as a service - rc.d clawdie - daemon(8) supervised - restart on crash - like Clawdie-AI -``` - -## What "split brain" means here - -Two halves of the same daemon, both kept: - -| Half | Crate / module | Job | -| ---------------- | ----------------------------------- | ----------------------------------------------------- | -| **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 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) - -```mermaid -graph LR - tg["Telegram
(CLAWDIE_TG_TOKEN)"] -->|text| clawdie - clawdie -->|chat| ds["DeepSeek
(CLAWDIE_DEEPSEEK_KEY)"] - ds -->|reply| clawdie - clawdie --> gp["glasspane radar"] - clawdie --> co["coordination board"] - clawdie -. Colibri socket .- ui["operator tools (colibri CLI / TUI)"] -``` - -## Files - -| Path | Role | -| -------------------------------- | ------------------------------------------------------- | -| `crates/clawdie/src/main.rs` | Entrypoint: resolve creds, start core + Telegram bridge | -| `crates/clawdie/src/telegram.rs` | Long-poll bridge | -| `crates/clawdie/src/deepseek.rs` | Minimal one-key chat lane (no cost/quota) | -| `crates/clawdie/build.rs` | Bakes the two credentials from build flags | -| `packaging/freebsd/clawdie.in` | rc.d service (daemon(8)-supervised, like Clawdie-AI) | - -## Credential resolution order - -1. Runtime env (`CLAWDIE_TG_TOKEN` / `TELEGRAM_BOT_TOKEN`, - `CLAWDIE_DEEPSEEK_KEY` / `DEEPSEEK_API_KEY`) -2. Build-flag baked value (`option_env!` from `build.rs`) -3. Absent → that half stays off (no token = headless control plane only) - -One DeepSeek key serves both the Telegram chat lane and the daemon's own -provider routing. - -See [`CLAWDIE-BUILD.md`](./CLAWDIE-BUILD.md) for experimental build notes. diff --git a/docs/CLAWDIE-BUILD.md b/docs/CLAWDIE-BUILD.md deleted file mode 100644 index 45e2d8a..0000000 --- a/docs/CLAWDIE-BUILD.md +++ /dev/null @@ -1,98 +0,0 @@ -# Experimental `clawdie` mini-binary — build notes - -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 - -Pass the two credentials as environment variables at build time. `build.rs` -bakes whatever is present into the binary; runtime env still overrides. - -```sh -cd ~/colibri -CLAWDIE_TG_TOKEN="123456:telegram-bot-token" \ -CLAWDIE_DEEPSEEK_KEY="sk-deepseek-key" \ - cargo build --release -p clawdie - -ls -lh target/release/clawdie # release profile strips symbols automatically -target/release/clawdie --version 2>/dev/null || true -``` - -Build with **no** flags for a "bring your own key" binary — it reads -`CLAWDIE_TG_TOKEN` / `TELEGRAM_BOT_TOKEN` and `CLAWDIE_DEEPSEEK_KEY` / -`DEEPSEEK_API_KEY` from the runtime environment (or the rc.d env file) instead. - -> Build credentials are secrets. Pass them inline on the build command (so they -> are not persisted), never commit them, and prefer the runtime env file for -> rotation. They are never printed to the build log. - -## 2. What the binary does out of the box - -- 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 - with a warning. - -No cost modes, quotas, context budgets, or provider fallback — those are lifted -from this agent on purpose. - -## 3. Run as an experimental service - -On FreeBSD, install the rc.d script only on a throwaway test host or an explicit -deployed-service experiment: - -```sh -pw groupadd clawdie -pw useradd clawdie -g clawdie -d /var/db/clawdie -s /usr/sbin/nologin -install -m 0555 packaging/freebsd/clawdie.in /usr/local/etc/rc.d/clawdie -install -m 0555 target/release/clawdie /usr/local/bin/clawdie -sysrc clawdie_enable=YES -service clawdie start -service clawdie status -``` - -Per-host credential override (optional; binary already has baked defaults): - -```sh -install -d -m 0750 /usr/local/etc/clawdie -cat > /usr/local/etc/clawdie/clawdie.env <<'EOF' -CLAWDIE_TG_TOKEN=123456:telegram-bot-token -CLAWDIE_DEEPSEEK_KEY=sk-deepseek-key -EOF -chmod 0600 /usr/local/etc/clawdie/clawdie.env -service clawdie restart -``` - -## 4. ISO status - -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 - -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 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. -- **Hardened USB-root power policy** (commit `b9290ab`): `powerdxx`, C3 C-state - default, `hw.usb.no_suspend=1`, and a boot-time `power_profile` — keeps - USB-root laptops from stalling/suspending. -- **Base runtime fixes already landed**: resolver bootstrap, internal-audio - preference, touchpad XInput guards, loader branding, hardware-report capture, - public probe-URL reporting, `hw-probe` upload dependency. - -Final XFCE/session/input/audio behavior is only proven on **real hardware** — -bhyve/nested VMs/static image inspection are build gates, not final proof. diff --git a/docs/COLIBRI-TOKENOMICS-TRIFECTA.md b/docs/COLIBRI-TOKENOMICS-TRIFECTA.md index a31d4f9..4542218 100644 --- a/docs/COLIBRI-TOKENOMICS-TRIFECTA.md +++ b/docs/COLIBRI-TOKENOMICS-TRIFECTA.md @@ -5,9 +5,7 @@ **Date:** 2026-06-01 **Status:** Strategic vision — maps to existing T1.4/T1.5 work -> **Scope:** This applies to the full Colibri control plane. The simplified -> `clawdie` operator lane intentionally ships none of this (no cost modes, -> quotas, or metering) — see `docs/CLAWDIE-AGENT-WIKI.md`. +> **Scope:** This applies to the full Colibri control plane. ## Core Thesis diff --git a/packaging/freebsd/clawdie.in b/packaging/freebsd/clawdie.in deleted file mode 100644 index 13e8e57..0000000 --- a/packaging/freebsd/clawdie.in +++ /dev/null @@ -1,181 +0,0 @@ -#!/bin/sh -# -# 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 -# build time (see crates/clawdie/build.rs), so a baked ISO needs no config. -# To override per-host, drop them in the env file (default -# /usr/local/etc/clawdie/clawdie.env) or set them in rc.conf below. -# -# clawdie runs in the FOREGROUND (no self-daemonize, no pidfile), so rc.d runs -# it under daemon(8), which backgrounds it, restarts on crash, drops privileges -# to the clawdie user, and redirects stdout/stderr (tracing) to a logfile. -# -# Install: -# pw groupadd clawdie -# pw useradd clawdie -g clawdie -d /var/db/clawdie -s /usr/sbin/nologin -# cp packaging/freebsd/clawdie.in /usr/local/etc/rc.d/clawdie -# chmod 555 /usr/local/etc/rc.d/clawdie -# sysrc clawdie_enable=YES -# service clawdie start -# -# Requires the clawdie binary at /usr/local/bin/clawdie. - -# PROVIDE: clawdie -# REQUIRE: LOGIN NETWORKING cleanvar -# KEYWORD: shutdown - -. /etc/rc.subr - -name="clawdie" -rcvar="clawdie_enable" - -load_rc_config $name - -: ${clawdie_enable:="NO"} -: ${clawdie_user:="clawdie"} -: ${clawdie_group:="clawdie"} -: ${clawdie_program:="/usr/local/bin/clawdie"} -: ${clawdie_data_dir:="/var/db/clawdie"} -: ${clawdie_run_dir:="/var/run/clawdie"} -: ${clawdie_socket:="${clawdie_run_dir}/clawdie.sock"} -: ${clawdie_db_path:="${clawdie_data_dir}/clawdie.sqlite"} -: ${clawdie_logfile:="/var/log/clawdie/clawdie.log"} -: ${clawdie_host:="$(/bin/hostname)"} -: ${clawdie_env_file:="/usr/local/etc/clawdie/clawdie.env"} - -pidfile="${clawdie_run_dir}/clawdie.pid" -# Supervisor pidfile (the daemon(8) parent). Kept distinct from the child -# pidfile so `stop` can target the supervisor — see clawdie_stop. -supervisor_pidfile="${clawdie_run_dir}/clawdie-supervisor.pid" - -# Run clawdie under daemon(8): -# -P supervisor pidfile (the daemon(8) parent — used by stop) -# -p child pidfile (writes the clawdie PID — used by start/status) -# -r restart on crash, -t process title, -u drop to the clawdie user, -# -o append stdout/stderr to log. -command="/usr/sbin/daemon" -command_args="-P ${supervisor_pidfile} -p ${pidfile} -r -t ${name} -u ${clawdie_user} \ - -o ${clawdie_logfile} ${clawdie_program}" - -# Match the child binary so `service clawdie status` finds OUR process via the -# child pidfile, not the generic /usr/sbin/daemon supervisor (which would -# collide with tailscaled, colibri_daemon, and other daemon(8) services). -procname="clawdie" - -start_precmd="clawdie_prestart" -start_postcmd="clawdie_poststart" -stop_cmd="clawdie_stop" -stop_postcmd="clawdie_poststop" -extra_commands="health" - -clawdie_prestart() -{ - # /var/run is tmpfs on FreeBSD (wiped each boot) — recreate every start. - install -d -o "${clawdie_user}" -g "${clawdie_group}" -m 0750 "${clawdie_run_dir}" - install -d -o "${clawdie_user}" -g "${clawdie_group}" -m 0750 "${clawdie_data_dir}" - install -d -o "${clawdie_user}" -g "${clawdie_group}" -m 0750 \ - "$(/usr/bin/dirname "${clawdie_logfile}")" - - # Control-plane config passed to the child via the environment. - # COLIBRI_DB_PATH is REQUIRED: without it the daemon falls back to - # /var/db/colibri/colibri.sqlite (the full Colibri daemon's path, owned by - # the colibri user), which the clawdie user cannot open — Store::open then - # panics and daemon(8) -r restart-loops. Keep clawdie's DB in its own dir. - export COLIBRI_DAEMON_DATA_DIR="${clawdie_data_dir}" - export COLIBRI_DAEMON_SOCKET="${clawdie_socket}" - export COLIBRI_DB_PATH="${clawdie_db_path}" - export COLIBRI_HOST="${clawdie_host}" - - # Optional per-host credential overrides (binary already has baked defaults). - # File format: simple KEY=VALUE lines, e.g. - # CLAWDIE_TG_TOKEN=123456:abc - # CLAWDIE_DEEPSEEK_KEY=sk-... - if [ -r "${clawdie_env_file}" ]; then - set -a - . "${clawdie_env_file}" - set +a - fi -} - -clawdie_poststart() -{ - # 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 - sleep 1 - waited=$((waited + 1)) - done - - if [ -S "${clawdie_socket}" ]; then - echo "clawdie socket ready after ${waited}s" - else - echo "WARNING: clawdie socket not ready after ${timeout}s" - fi -} - -clawdie_stop() -{ - # daemon(8) -r restarts the child if it is killed directly, so a plain - # SIGTERM to the child pidfile would just be undone. Stop the supervisor - # instead: on SIGTERM it forwards the signal to the child and exits without - # restarting it. - local _sup="" - [ -f "${supervisor_pidfile}" ] && _sup=$(cat "${supervisor_pidfile}" 2>/dev/null) - if [ -n "${_sup}" ] && kill -0 "${_sup}" 2>/dev/null; then - echo "Stopping ${name} (daemon(8) supervisor pid ${_sup})." - kill -TERM "${_sup}" 2>/dev/null - local _n=0 - while kill -0 "${_sup}" 2>/dev/null && [ ${_n} -lt 30 ]; do - sleep 1 - _n=$((_n + 1)) - done - if kill -0 "${_sup}" 2>/dev/null; then - echo "Supervisor did not exit in time; sending SIGKILL." - kill -KILL "${_sup}" 2>/dev/null - fi - else - echo "${name} is not running." - fi - # Belt-and-suspenders: terminate the child if it somehow outlived the - # supervisor (e.g. supervisor SIGKILLed before it could clean up). - local _ch="" - [ -f "${pidfile}" ] && _ch=$(cat "${pidfile}" 2>/dev/null) - if [ -n "${_ch}" ] && kill -0 "${_ch}" 2>/dev/null; then - kill -TERM "${_ch}" 2>/dev/null - fi - rm -f "${supervisor_pidfile}" "${pidfile}" -} - -clawdie_poststop() -{ - # Clean up tmpfs artifacts on graceful shutdown. - if [ -S "${clawdie_socket}" ]; then - rm -f "${clawdie_socket}" - fi -} - -health_cmd="clawdie_health" -clawdie_health() -{ - if [ -S "${clawdie_socket}" ]; then - if printf '{"cmd":"status"}\n' | nc -U "${clawdie_socket}" -w 2 >/dev/null 2>&1; then - echo "clawdie is healthy (socket responding)" - return 0 - else - echo "clawdie socket exists but not responding" - return 1 - fi - else - echo "clawdie socket not found" - return 1 - fi -} - -run_rc_command "$1"