Watch
0
0
Fork
You've already forked hyperhive
0

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
This commit is contained in:
atlas 2026-09-30 00:41:05 +02:00 • committed by mara
commit cec35bfbb1
6 changed files with 356 additions and 76 deletions

View file

@ -305,15 +305,16 @@ fn compact_percent() -> u8 {
const ACP_SESSION_FILE: &str = "hyperhive-acp-session";
/// The agent's durable session, on the runtime its environment selects
/// (`hive_runtime::RuntimeSpec`). On claude it is the constant-title
/// [`hive_claude::InfiniteSession`] with hyperhive's percent-of-window
/// compaction policy. Built once by the serve loop (see [`make_session`]) and
/// threaded through the turns, rather than rebuilt each time.
/// (`hive_runtime::RuntimeSpec`), with hyperhive's percent-of-window
/// compaction policy. On claude it is the constant-title
/// [`hive_claude::InfiniteSession`]. Built once by the serve loop (see
/// [`make_session`]) and threaded through the turns, rather than rebuilt each
/// time.
pub type AgentSession = AgentRuntime<PercentPolicy>;
/// Construct the agent's durable session. On claude: constant title + on-disk
/// store + a percent-of-window compaction policy that checkpoints
/// (`CHECKPOINT_PROMPT`) before compacting. Called once at serve-loop start.
/// Construct the agent's durable session, with a percent-of-window compaction
/// policy that checkpoints (`CHECKPOINT_PROMPT`) before compacting; on claude
/// also a constant title and the on-disk store. Called once at serve-loop start.
/// `percent` comes from a boot-time env var and `default_window` is only a
/// fallback for turns where the model didn't report a window, so a single
/// build at startup is fine.
@ -322,20 +323,20 @@ pub type AgentSession = AgentRuntime<PercentPolicy>;
///
/// Returns an error if the runtime selection in the environment is invalid.
pub fn make_session(bus: &Bus) -> Result<AgentSession> {
let policy = PercentPolicy {
percent: compact_percent(),
default_window: Some(effective_context_window(bus)),
checkpoint_prompt: Some(CHECKPOINT_PROMPT.to_string()),
};
Ok(match RuntimeSpec::from_env()? {
RuntimeSpec::Claude => AgentRuntime::Claude(ClaudeRuntime::new(
session_title(),
session_store(),
PercentPolicy {
percent: compact_percent(),
default_window: Some(effective_context_window(bus)),
checkpoint_prompt: Some(CHECKPOINT_PROMPT.to_string()),
},
)),
RuntimeSpec::Claude => {
AgentRuntime::Claude(ClaudeRuntime::new(session_title(), session_store(), policy))
}
RuntimeSpec::Acp(command) => AgentRuntime::Acp(Box::new(AcpRuntime::new(
command,
crate::paths::harness_dir().join(ACP_SESSION_FILE),
acp_permission_policy(),
policy,
))),
})
}
@ -460,15 +461,7 @@ pub async fn drive_turn(
// Reflect `Compacting` in the UI like the idle path (`run_pending_compact`)
// does; the serve loop resets to `Idle` once this turn returns.
bus.set_state(crate::events::TurnState::Compacting);
let compacted = match session.compact(&config, &sink).await {
Err(e @ hive_runtime::Error::Unsupported(_)) => {
bus.emit(LiveEvent::Note {
text: format!("/compact skipped: {e}"),
});
false
}
_ => true,
};
let _ = session.compact(&config, &sink).await;
// If the compact call asked to be woken (the agent's own `compact`
// tool with a `wake_prompt`), stash it — the serve loop reads it
// back after this turn returns and drives a synthetic follow-up
@ -477,7 +470,7 @@ pub async fn drive_turn(
if let Some(prompt) = request.wake_prompt {
bus.set_post_compact_wake(prompt);
}
return Ok(compacted);
return Ok(true);
}
outcome
}