kaizen-cli 0.1.35

Distributable agent observability: real-time-tailable sessions, agile-style retros, and repo-level improvement (Cursor, Claude Code, Codex). SQLite, redact before any sync you enable.
Documentation
use kaizen::core::event::{Event, EventKind, EventSource, SessionRecord, SessionStatus};
use kaizen::store::Store;
use serde_json::json;
use std::time::{Duration, Instant};

const SPANS: usize = 250;

#[test]
#[ignore = "perf harness prints append p99 and sustained ingest"]
fn projector_phase2_perf_harness() -> anyhow::Result<()> {
    unsafe { std::env::remove_var("KAIZEN_PROJECTOR") };
    let dir = tempfile::tempdir()?;
    let store = Store::open(&dir.path().join("kaizen.db"))?;
    store.upsert_session(&session("perf"))?;
    let events = paired_events("perf", SPANS);
    let start = Instant::now();
    let mut samples = Vec::new();
    for event in &events {
        let t = Instant::now();
        store.append_event(event)?;
        samples.push(t.elapsed());
    }
    samples.sort();
    let p99 = samples[samples.len() * 99 / 100];
    let rate = events.len() as f64 / start.elapsed().as_secs_f64();
    eprintln!("phase2 projector perf:");
    eprintln!("  events: {}", events.len());
    eprintln!("  append p99: {}", ms(p99));
    eprintln!("  sustained ingest: {:.0} evt/s", rate);
    Ok(())
}

fn session(id: &str) -> SessionRecord {
    SessionRecord {
        id: id.to_string(),
        agent: "codex".into(),
        model: Some("gpt".into()),
        workspace: "/ws".into(),
        started_at_ms: 0,
        ended_at_ms: None,
        status: SessionStatus::Running,
        trace_path: String::new(),
        start_commit: None,
        end_commit: None,
        branch: None,
        dirty_start: None,
        dirty_end: None,
        repo_binding_source: None,
        prompt_fingerprint: None,
        parent_session_id: None,
        agent_version: None,
        os: None,
        arch: None,
        repo_file_count: None,
        repo_total_loc: None,
    }
}

fn paired_events(session_id: &str, pairs: usize) -> Vec<Event> {
    let mut out = Vec::new();
    for i in 0..pairs {
        out.push(event(session_id, (i * 2) as u64, EventKind::ToolCall, i));
        out.push(event(
            session_id,
            (i * 2 + 1) as u64,
            EventKind::ToolResult,
            i,
        ));
    }
    out
}

fn event(session_id: &str, seq: u64, kind: EventKind, id: usize) -> Event {
    Event {
        session_id: session_id.into(),
        seq,
        ts_ms: 1_000 + seq,
        ts_exact: true,
        kind,
        source: EventSource::Tail,
        tool: Some("bash".into()),
        tool_call_id: Some(format!("call-{id}")),
        tokens_in: Some(10),
        tokens_out: Some(20),
        reasoning_tokens: Some(5),
        cost_usd_e6: Some(1),
        stop_reason: None,
        latency_ms: None,
        ttft_ms: None,
        retry_count: None,
        context_used_tokens: None,
        context_max_tokens: None,
        cache_creation_tokens: None,
        cache_read_tokens: None,
        system_prompt_tokens: None,
        payload: json!({"path": format!("src/{id}.rs")}),
    }
}

fn ms(duration: Duration) -> String {
    format!("{:.2}ms", duration.as_secs_f64() * 1_000.0)
}