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:
parent
3bef1dfab6
commit
cec35bfbb1
6 changed files with 356 additions and 76 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue