Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/hive-runtime/src/lib.rs
atlas cec35bfbb1 hive-runtime: compact ACP sessions through the agent's compact command
An ACP agent's session is now compacted like a claude one: proactively
once a turn crosses the percent-of-window watermark, and on the
operator's /compact or the agent's compact tool. Before, the ACP
backend's compact returned Unsupported and no watermark applied to it.

- AcpRuntime takes the same CompactionPolicy as ClaudeRuntime;
  make_session builds one PercentPolicy (with CHECKPOINT_PROMPT) and hands
  it to whichever backend runs.
- The runtime keeps the commands each session advertises in
  available_commands_update. If `compact` is among them, compaction sends
  the prompt `/compact` on the same session, which is how the ACP spec
  runs an advertised command. A proactive compaction runs the checkpoint
  turn first, as InfiniteSession does.
- With no compact command, the checkpoint turn runs, the session is
  archived, and the next turn starts a new one with the system prompt.
- Error::Unsupported had no producer left, so it and drive_turn's
  "/compact skipped" arm are gone.

Refs #4391
2026-09-30 07:41:47 +02:00

112 lines
3.8 KiB
Rust

//! `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, Canceller, PermissionAsk, 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. With no session there is nothing to
/// compact, and this succeeds without doing anything.
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>>;
/// A handle that stops the turn in flight from outside [`Self::run`].
/// `None` for a runtime without one: a claude turn is stopped by
/// signalling its `claude` process.
fn canceller(&self) -> Option<Canceller>;
}
/// The runtime an agent was configured with, chosen at startup from a
/// [`RuntimeSpec`].
pub enum AgentRuntime<P: CompactionPolicy> {
Claude(ClaudeRuntime<P>),
Acp(Box<AcpRuntime<P>>),
}
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(),
}
}
fn canceller(&self) -> Option<Canceller> {
match self {
Self::Claude(r) => r.canceller(),
Self::Acp(r) => r.canceller(),
}
}
}
/// 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),
}
/// Result alias for this crate.
pub type Result<T> = std::result::Result<T, Error>;