Scaffold Colibri Phase 1: colibri-probe DeepSeek cache smoke (Sam & Claude)
Greenfield cross-platform (FreeBSD/Linux) Rust crate per clawdie-ai doc/COLIBRI-CONTROLPLANE-PLAN.md. colibri-probe sends a byte-stable DeepSeek prefix twice and reports prompt_cache_hit_tokens as a clawdie.provider-smoke.result.v1 manifest; build-only/skipped without DEEPSEEK_API_KEY. Stack: tokio + reqwest(rustls-tls, no OpenSSL) + serde + chrono. Builds clean on Linux (cargo build --release, 1m16s); rust-toolchain pinned to 1.95.0. FreeBSD (osa) build is the next lane. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
commit
cf7d25e83a
6 changed files with 1745 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
1483
Cargo.lock
generated
Normal file
1483
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
18
Cargo.toml
Normal file
18
Cargo.toml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "colibri"
|
||||
version = "0.0.1"
|
||||
edition = "2021"
|
||||
description = "Clawdie Colibri control plane — cross-platform (FreeBSD/Linux) Rust core"
|
||||
license = "AGPL-3.0-only"
|
||||
|
||||
[[bin]]
|
||||
name = "colibri-probe"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
|
||||
# rustls (not OpenSSL) keeps the TLS stack pure-Rust and FreeBSD-portable.
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
chrono = { version = "0.4", default-features = false, features = ["clock"] }
|
||||
47
README.md
Normal file
47
README.md
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
# Colibri
|
||||
|
||||
The Clawdie control plane core — a small, cross-platform (FreeBSD + Linux) Rust
|
||||
daemon. It unifies a coordination model (agents-as-teammates, task board, team
|
||||
skills) with a cache-first cost discipline (byte-stable prompt prefixes,
|
||||
cache-hit metering), sitting on top of the existing Pi engine, watchdog, hostd
|
||||
and Postgres.
|
||||
|
||||
Design + implementation path: see `doc/COLIBRI-CONTROLPLANE-PLAN.md` in
|
||||
`clawdie-ai`.
|
||||
|
||||
## Phase 1 — `colibri-probe`
|
||||
|
||||
A falsifiable first build that proves three things at once:
|
||||
|
||||
1. Rust + `rustls` + `tokio` build cross-platform (Linux first, FreeBSD next).
|
||||
2. A raw DeepSeek HTTPS call works.
|
||||
3. DeepSeek **prefix caching** is real on our infra: send a byte-stable prefix
|
||||
twice and observe `prompt_cache_hit_tokens > 0` on the second request.
|
||||
|
||||
It prints a `clawdie.provider-smoke.result.v1` manifest on stdout.
|
||||
|
||||
### Build (no key needed)
|
||||
|
||||
```sh
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
```sh
|
||||
# Build-only / skipped mode (no key): verifies the binary runs.
|
||||
./target/release/colibri-probe
|
||||
|
||||
# Live cache probe:
|
||||
DEEPSEEK_API_KEY=sk-... ./target/release/colibri-probe
|
||||
```
|
||||
|
||||
Env overrides: `DEEPSEEK_MODEL` (default `deepseek-chat`, the DeepSeek API model
|
||||
string — distinct from our internal `deepseek-v4-flash` alias),
|
||||
`DEEPSEEK_ENDPOINT`, `COLIBRI_HOST`.
|
||||
|
||||
## FreeBSD note
|
||||
|
||||
Target `x86_64-unknown-freebsd` (Rust Tier-2). Install via `pkg install rust` or
|
||||
rustup; the `rust-toolchain.toml` pins the channel for cross-host
|
||||
reproducibility. TLS is `rustls` to avoid `openssl-sys` linking on FreeBSD.
|
||||
2
rust-toolchain.toml
Normal file
2
rust-toolchain.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[toolchain]
|
||||
channel = "1.95.0"
|
||||
194
src/main.rs
Normal file
194
src/main.rs
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
//! colibri-probe — Phase 1 of the Colibri control plane.
|
||||
//! See clawdie-ai `doc/COLIBRI-CONTROLPLANE-PLAN.md`.
|
||||
//!
|
||||
//! Proves three things at once:
|
||||
//! 1. Rust + rustls + tokio build cross-platform (Linux now, FreeBSD next).
|
||||
//! 2. A raw DeepSeek HTTPS call works.
|
||||
//! 3. DeepSeek prefix caching is real on our infra: send a byte-stable prefix
|
||||
//! twice and observe `prompt_cache_hit_tokens > 0` on the second request.
|
||||
//!
|
||||
//! Emits a `clawdie.provider-smoke.result.v1` manifest on stdout. Runs in
|
||||
//! build-only/"skipped" mode when `DEEPSEEK_API_KEY` is unset.
|
||||
|
||||
use std::env;
|
||||
|
||||
use chrono::Utc;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const RESULT_SCHEMA: &str = "clawdie.provider-smoke.result.v1";
|
||||
const DEFAULT_ENDPOINT: &str = "https://api.deepseek.com/chat/completions";
|
||||
// DeepSeek API model string (cache-capable). Distinct from our internal
|
||||
// `deepseek-v4-flash` alias; override with DEEPSEEK_MODEL.
|
||||
const DEFAULT_MODEL: &str = "deepseek-chat";
|
||||
|
||||
// Deliberately long and byte-stable. Reasonix discipline: the immutable region
|
||||
// must not change between turns or the cache will not hit.
|
||||
const STABLE_SYSTEM_PREFIX: &str = "You are Colibri, the Clawdie control-plane probe. \
|
||||
Answer tersely. This system prefix is intentionally fixed byte-for-byte so that \
|
||||
DeepSeek automatic prefix caching can engage across repeated requests. The Clawdie \
|
||||
control plane partitions context into an immutable prefix, an append-only log, and \
|
||||
volatile scratch; this probe exercises only the immutable prefix to measure \
|
||||
cache-hit economics.";
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct ChatMessage<'a> {
|
||||
role: &'a str,
|
||||
content: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct ChatRequest<'a> {
|
||||
model: &'a str,
|
||||
messages: Vec<ChatMessage<'a>>,
|
||||
stream: bool,
|
||||
max_tokens: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
struct Usage {
|
||||
#[serde(default)]
|
||||
prompt_tokens: u64,
|
||||
#[serde(default)]
|
||||
completion_tokens: u64,
|
||||
#[serde(default)]
|
||||
total_tokens: u64,
|
||||
#[serde(default)]
|
||||
prompt_cache_hit_tokens: u64,
|
||||
#[serde(default)]
|
||||
prompt_cache_miss_tokens: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ChatResponse {
|
||||
#[serde(default)]
|
||||
model: Option<String>,
|
||||
usage: Option<Usage>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct SmokeResult {
|
||||
schema: &'static str,
|
||||
test_id: String,
|
||||
host: String,
|
||||
agent: String,
|
||||
provider: String,
|
||||
model: String,
|
||||
started_at: String,
|
||||
ended_at: String,
|
||||
status: String,
|
||||
warm_usage: Option<Usage>,
|
||||
probe_usage: Option<Usage>,
|
||||
cache_hit_tokens: u64,
|
||||
cache_hit_observed: bool,
|
||||
notes: Vec<String>,
|
||||
}
|
||||
|
||||
async fn one_call(
|
||||
client: &reqwest::Client,
|
||||
endpoint: &str,
|
||||
key: &str,
|
||||
model: &str,
|
||||
) -> Result<(Option<String>, Usage), Box<dyn std::error::Error>> {
|
||||
let body = ChatRequest {
|
||||
model,
|
||||
messages: vec![
|
||||
ChatMessage {
|
||||
role: "system",
|
||||
content: STABLE_SYSTEM_PREFIX,
|
||||
},
|
||||
ChatMessage {
|
||||
role: "user",
|
||||
content: "Reply with the single word: ok.",
|
||||
},
|
||||
],
|
||||
stream: false,
|
||||
max_tokens: 16,
|
||||
};
|
||||
let resp = client
|
||||
.post(endpoint)
|
||||
.bearer_auth(key)
|
||||
.json(&body)
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?;
|
||||
let parsed: ChatResponse = resp.json().await?;
|
||||
Ok((parsed.model, parsed.usage.unwrap_or_default()))
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let host = env::var("COLIBRI_HOST")
|
||||
.or_else(|_| env::var("HOSTNAME"))
|
||||
.unwrap_or_else(|_| "domedog".to_string());
|
||||
let endpoint =
|
||||
env::var("DEEPSEEK_ENDPOINT").unwrap_or_else(|_| DEFAULT_ENDPOINT.to_string());
|
||||
let model = env::var("DEEPSEEK_MODEL").unwrap_or_else(|_| DEFAULT_MODEL.to_string());
|
||||
|
||||
let mut result = SmokeResult {
|
||||
schema: RESULT_SCHEMA,
|
||||
test_id: format!("colibri-probe-{}", Utc::now().format("%Y%m%dT%H%M%SZ")),
|
||||
host,
|
||||
agent: "claude-domedog".to_string(),
|
||||
provider: "deepseek".to_string(),
|
||||
model: model.clone(),
|
||||
started_at: Utc::now().to_rfc3339(),
|
||||
ended_at: String::new(),
|
||||
status: String::new(),
|
||||
warm_usage: None,
|
||||
probe_usage: None,
|
||||
cache_hit_tokens: 0,
|
||||
cache_hit_observed: false,
|
||||
notes: Vec::new(),
|
||||
};
|
||||
|
||||
match env::var("DEEPSEEK_API_KEY") {
|
||||
Ok(key) if !key.trim().is_empty() => {
|
||||
let client = reqwest::Client::builder()
|
||||
.user_agent("colibri-probe/0.0.1")
|
||||
.build()?;
|
||||
// Warm the cache, then probe with a byte-identical prefix.
|
||||
match one_call(&client, &endpoint, &key, &model).await {
|
||||
Ok((m, warm)) => {
|
||||
if let Some(m) = m {
|
||||
result.model = m;
|
||||
}
|
||||
result.warm_usage = Some(warm);
|
||||
match one_call(&client, &endpoint, &key, &model).await {
|
||||
Ok((_, probe)) => {
|
||||
result.cache_hit_tokens = probe.prompt_cache_hit_tokens;
|
||||
result.cache_hit_observed = probe.prompt_cache_hit_tokens > 0;
|
||||
result.probe_usage = Some(probe);
|
||||
result.status = "ok".to_string();
|
||||
result.notes.push(if result.cache_hit_observed {
|
||||
"prefix cache HIT on second request".to_string()
|
||||
} else {
|
||||
"no cache hit observed — check prefix byte-stability / provider cache state".to_string()
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
result.status = "error".to_string();
|
||||
result.notes.push(format!("probe call failed: {e}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
result.status = "error".to_string();
|
||||
result.notes.push(format!("warm call failed: {e}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
result.status = "skipped".to_string();
|
||||
result
|
||||
.notes
|
||||
.push("DEEPSEEK_API_KEY unset/empty — build verified, live cache probe skipped".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
result.ended_at = Utc::now().to_rfc3339();
|
||||
println!("{}", serde_json::to_string_pretty(&result)?);
|
||||
if result.status == "error" {
|
||||
std::process::exit(1);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue