75 lines
3.6 KiB
Markdown
75 lines
3.6 KiB
Markdown
# hive-claude
|
|
|
|
A small, reusable async driver for headless `claude --print` (Claude Code CLI)
|
|
sessions. It spawns the CLI, streams and classifies the `stream-json` output,
|
|
and reports the result. It knows only about the Claude Code CLI — **no
|
|
hyperhive types, policy, watermarks, or logging.** Compaction, retry, and reset
|
|
policy belong to the caller.
|
|
|
|
## When to use it
|
|
|
|
Reach for this crate whenever you need to run the `claude` CLI from Rust and
|
|
react to how a turn ended. It is the shared substrate under
|
|
`hive-ag3nt`'s turn loop; new callers (tools, tests, other agents) should build
|
|
on it rather than shelling out to `claude` by hand.
|
|
|
|
## Shape
|
|
|
|
Two layers — reach for the high-level one:
|
|
|
|
- **`InfiniteSession { name, store, policy }`** — a durable session that keeps
|
|
itself alive across the context window. `.run(&config, prompt, &sink)` →
|
|
`Result<Progress, Error>` does resume-or-create, compacts **reactively** on
|
|
overflow (compact + retry once), and **proactively** after a clean turn when
|
|
the policy says so (optional checkpoint turn, then compact). `.compact(…)`
|
|
forces one. `Progress { created, compacted, telemetry }` reports what happened
|
|
— including the turn's parsed `Telemetry`, finalised at the `result` event.
|
|
- **`Claude::run(&config, &attach, prompt, &sink)`** → `Result<(), Error>` —
|
|
the low-level driver: one turn, one `Attach` target (`Resume` / `Create` /
|
|
`Continue` / `OneOff`). A clean turn is `Ok(())`; every other state is an
|
|
`Error` variant.
|
|
|
|
Supporting pieces:
|
|
|
|
- **`CompactionPolicy`** — decides *when* to compact. **`PercentPolicy`**
|
|
(`percent`, `default_window`, `checkpoint_prompt`) compacts at a percent of
|
|
the model window; **`NeverCompact`** never does.
|
|
- **`Config`** — the invocation (model, effort, cwd, prompt/MCP files, tools,
|
|
extra args).
|
|
- **`Sink`** — a trait with no-op defaults; implement what you care about to
|
|
observe stream events, non-JSON stdout, and stderr. `NoopSink` ignores all.
|
|
- **`SessionStore`** — locate and archive on-disk sessions by title.
|
|
- **`Telemetry`** (`context`, `cost`, `context_window`, `model`) — everything
|
|
the driver parses from a turn's stream, returned in `Progress`. **`Usage`** is
|
|
the minimal slice (`context_tokens`, `context_window`) the policy sees.
|
|
|
|
`Error` unifies the two things that can stop a turn: recognized **sentinels**
|
|
(`PromptTooLong`, `RateLimited`, `AuthFailed`, `SessionNotFound`) and **hard
|
|
failures** (`Spawn`, `Stdin`, `Wait`, `Exit`, `Io`). Sentinels are expected
|
|
control-flow, not crashes — the caller compacts, parks, re-auths, or creates a
|
|
session in response.
|
|
|
|
```rust
|
|
use hive_claude::{Claude, Config, Error, NoopSink, Session};
|
|
|
|
let config = Config { model: "haiku".into(), ..Default::default() };
|
|
match Claude::run(&config, &Session::Resume("my-session".into()), "hello", &NoopSink).await {
|
|
Ok(()) => {}
|
|
Err(Error::PromptTooLong) => { /* compact + retry */ }
|
|
Err(Error::RateLimited) => { /* park + retry */ }
|
|
Err(other) => eprintln!("claude: {other}"),
|
|
}
|
|
```
|
|
|
|
## `thiserror` here, `anyhow` in the apps
|
|
|
|
This is a **library**, so it returns a concrete, matchable `Error` enum built
|
|
with `thiserror`: callers can tell a rate-limit from a spawn failure and act
|
|
accordingly. Libraries should never force their callers into `anyhow`'s
|
|
type-erased error.
|
|
|
|
The **applications** (the `hive-*` binaries) use `anyhow` instead — at the top
|
|
level you usually only want to add context and bubble a failure up, not match
|
|
on it. A `hive_claude::Error` converts into an `anyhow::Error` for free at the
|
|
`?` boundary. Rule of thumb: **libraries return `thiserror` enums, binaries
|
|
consume them with `anyhow`.**
|