Watch
0
0
Fork
You've already forked hyperhive
0

hive-agent: let an agent compact from 40% context usage instead of 66%

This commit is contained in:
damocles 2026-11-14 12:35:27 +01:00
commit 183e55dd80
4 changed files with 20 additions and 4 deletions

View file

@ -105,7 +105,7 @@ hive_name?, swarm_name?, matrix_accounts? }`. `matrix_accounts` is a
- `compact` — queues a compaction of the current session; the only way to - `compact` — queues a compaction of the current session; the only way to
request one. No args. Gated server-side on the last completed request one. No args. Gated server-side on the last completed
turn's context usage: refused (with an explanation, no side effect) turn's context usage: refused (with an explanation, no side effect)
unless usage is above 66% of the effective context window. On a unless usage is above 40% of the effective context window. On a
pass, queues the deferred `compact_pending` flag — consumed at the end pass, queues the deferred `compact_pending` flag — consumed at the end
of the current turn, so it never of the current turn, so it never
races a live claude process, and the usual pre-compaction races a live claude process, and the usual pre-compaction

View file

@ -412,7 +412,7 @@ impl AgentServer {
#[tool( #[tool(
description = "Compact the current session's context, mirroring the operator's \ description = "Compact the current session's context, mirroring the operator's \
dashboard `/compact` button. Gated: only honoured when this agent's last \ dashboard `/compact` button. Gated: only honoured when this agent's last \
completed turn used more than 66% of the effective context window — below \ completed turn used more than 40% of the effective context window — below \
that the call is refused with an explanation and has no effect. On a pass, \ that the call is refused with an explanation and has no effect. On a pass, \
queues compaction for the end of the current turn (same deferred mechanism \ queues compaction for the end of the current turn (same deferred mechanism \
the dashboard button uses, so it never races a live claude process); the \ the dashboard button uses, so it never races a live claude process); the \

View file

@ -124,7 +124,7 @@ pub enum Request {
ReminderRollup { since_secs: u64 }, ReminderRollup { since_secs: u64 },
/// Agent self-service request to compact the current session, mirroring /// Agent self-service request to compact the current session, mirroring
/// the `compact` MCP tool. Gated server-side: only /// the `compact` MCP tool. Gated server-side: only
/// honoured when the last completed turn's context usage is above 66% /// honoured when the last completed turn's context usage is above 40%
/// of the effective context window — below that, `Response::Err` /// of the effective context window — below that, `Response::Err`
/// explains why and takes no action. On a pass, queues the same /// explains why and takes no action. On a pass, queues the same
/// deferred `compact_pending` flag (consumed /// deferred `compact_pending` flag (consumed

View file

@ -39,7 +39,7 @@ use crate::todos::{Todo, Todos};
/// effective context window, an agent-initiated compact is refused (nothing /// effective context window, an agent-initiated compact is refused (nothing
/// meaningful to reclaim yet, and it'd just burn a compaction pass on a /// meaningful to reclaim yet, and it'd just burn a compaction pass on a
/// near-empty session). /// near-empty session).
const COMPACT_MIN_USAGE_FRACTION: f64 = 0.66; const COMPACT_MIN_USAGE_FRACTION: f64 = 0.40;
/// Pause after a failed `accept`, so a failure that persists (fd exhaustion) /// Pause after a failed `accept`, so a failure that persists (fd exhaustion)
/// does not turn the accept loop into a busy spin. /// does not turn the accept loop into a busy spin.
@ -982,6 +982,22 @@ mod tests {
assert!(low.take_compact().is_none(), "a refusal queues nothing"); assert!(low.take_compact().is_none(), "a refusal queues nothing");
} }
#[test]
fn the_compact_floor_is_forty_percent_of_the_window() {
let (_dir, todos, _) = stores();
let wake = Notify::new();
let under = bus_with_context(39);
let resp = dispatch(compact_req(None), &todos, &wake, None, &under);
assert!(
matches!(&resp, Response::Err { message } if message.contains("40%")),
"{resp:?}"
);
let at = bus_with_context(40);
let resp = dispatch(compact_req(None), &todos, &wake, None, &at);
assert!(matches!(resp, Response::Ok), "{resp:?}");
assert!(at.take_compact().is_some(), "the floor itself is allowed");
}
#[test] #[test]
fn compact_above_the_floor_queues_the_request_with_its_wake_prompt() { fn compact_above_the_floor_queues_the_request_with_its_wake_prompt() {
let (_dir, todos, _) = stores(); let (_dir, todos, _) = stores();