Watch
0
0
Fork
You've already forked hyperhive
0

hive-runtime: shared runtime crate with claude and acp backends

A `Runtime` trait (run / compact / archive) with two backends:

- claude: a pass-through to hive_claude's InfiniteSession and
  SessionStore, so a claude turn is the same spawn, session handling
  and errors as before.
- acp: a generic Agent Client Protocol client. It spawns the command,
  args and env from RuntimeSpec (HIVE_RUNTIME / HIVE_ACP_COMMAND /
  HIVE_ACP_ARGS / HIVE_ACP_ENV), refuses an agent whose
  mcpCapabilities.http is not true, passes the claude --mcp-config
  servers as ACP mcpServers, keeps one session id in a file
  (session/load after a restart, session/new otherwise), and maps
  session/update into claude stream-json events plus usage_update into
  Telemetry. Permission requests are answered by a caller-supplied
  policy on the ACP tool kind. compact returns Unsupported for now.

The crate depends on no hyperhive binary crate, so the subagent
daemon can move onto it without pulling in hive-agent.

Refs #4391
This commit is contained in:
atlas 2026-09-29 21:22:41 +02:00 • committed by mara
commit b0e26e7e44
10 changed files with 1583 additions and 0 deletions

103
hive-runtime/src/lib.rs Normal file
View file

@ -0,0 +1,103 @@
//! `hive-runtime` — the layer an agent's turns are driven through, with one
//! backend per runtime:
//!
//! - [`ClaudeRuntime`] — `claude --print`, via `hive_claude`'s
//! [`hive_claude::InfiniteSession`];
//! - [`AcpRuntime`] — any Agent Client Protocol agent, spawned from the
//! command, args and env it is handed (see [`RuntimeSpec`]). It knows no
//! agent by name.
//!
//! Both report a turn through the same [`Sink`], in claude's `stream-json`
//! shape: the ACP backend translates its `session/update` notifications into
//! it, so a consumer of that stream reads either backend unchanged.
//!
//! Depends on no hyperhive binary crate, so the harness and the subagent
//! daemon can both drive turns through it.
use std::future::Future;
use std::path::PathBuf;
mod acp;
mod claude;
mod spec;
pub use acp::{AcpError, AcpRuntime, PermissionPolicy};
pub use claude::ClaudeRuntime;
pub use hive_claude::{
CompactionPolicy, Config, PercentPolicy, Progress, SessionStore, Sink, Telemetry, TokenUsage,
};
pub use spec::{
ACP_ARGS_ENV, ACP_COMMAND_ENV, ACP_ENV_ENV, AcpCommand, RUNTIME_ENV, RuntimeSpec, SpecError,
};
/// The operations every runtime backend offers.
///
/// Each takes the same [`Config`] the claude backend spawns from; a backend
/// reads the fields that mean something to it and ignores the rest.
pub trait Runtime {
/// Run one turn on the agent's durable session, creating it if there is
/// none, and stream it to `sink`.
fn run(
&self,
config: &Config,
prompt: &str,
sink: &impl Sink,
) -> impl Future<Output = Result<Progress>>;
/// Compact the durable session now. A backend that cannot returns
/// [`Error::Unsupported`].
fn compact(&self, config: &Config, sink: &impl Sink) -> impl Future<Output = Result<()>>;
/// Set the durable session aside so the next turn starts a fresh one.
/// Returns the file the session was moved to, or `None` if there was no
/// session to archive. Only call it between turns.
fn archive(&self) -> Result<Option<PathBuf>>;
}
/// The runtime an agent was configured with, chosen at startup from a
/// [`RuntimeSpec`].
pub enum AgentRuntime<P: CompactionPolicy> {
Claude(ClaudeRuntime<P>),
Acp(Box<AcpRuntime>),
}
impl<P: CompactionPolicy> Runtime for AgentRuntime<P> {
async fn run(&self, config: &Config, prompt: &str, sink: &impl Sink) -> Result<Progress> {
match self {
Self::Claude(r) => r.run(config, prompt, sink).await,
Self::Acp(r) => r.run(config, prompt, sink).await,
}
}
async fn compact(&self, config: &Config, sink: &impl Sink) -> Result<()> {
match self {
Self::Claude(r) => r.compact(config, sink).await,
Self::Acp(r) => r.compact(config, sink).await,
}
}
fn archive(&self) -> Result<Option<PathBuf>> {
match self {
Self::Claude(r) => r.archive(),
Self::Acp(r) => r.archive(),
}
}
}
/// Why a runtime operation did not complete.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
/// From the claude backend, unchanged.
#[error(transparent)]
Claude(#[from] hive_claude::Error),
/// From the ACP backend.
#[error(transparent)]
Acp(#[from] AcpError),
/// The backend does not implement this operation.
#[error("{0} is not supported by this runtime")]
Unsupported(&'static str),
}
/// Result alias for this crate.
pub type Result<T> = std::result::Result<T, Error>;