refactor(agent): build the InfiniteSession once in the serve loop, thread it

This commit is contained in:
müde 2026-07-05 20:16:06 +02:00
commit c9de2eeb97
2 changed files with 32 additions and 12 deletions

View file

@ -230,11 +230,20 @@ fn compact_percent() -> u8 {
u8::try_from(pct.min(100)).unwrap_or(DEFAULT_COMPACT_PERCENT)
}
/// Build the agent's durable session: constant title + on-disk store + a
/// The agent's durable session type: the constant-title [`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 — it's effectively stateless, so one instance serves
/// the whole run.
pub type AgentSession = InfiniteSession<PercentPolicy>;
/// Construct the agent's durable session: constant title + on-disk store + a
/// percent-of-window compaction policy that checkpoints (`CHECKPOINT_PROMPT`)
/// before compacting. `default_window` feeds the policy the effective window
/// for turns where the model didn't itself report one.
fn infinite_session(bus: &Bus) -> InfiniteSession<PercentPolicy> {
/// before compacting. 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.
#[must_use]
pub fn make_session(bus: &Bus) -> AgentSession {
InfiniteSession::new(
session_title(),
session_store(),
@ -260,8 +269,14 @@ fn infinite_session(bus: &Bus) -> InfiniteSession<PercentPolicy> {
/// the whole turn is retried a single time before bubbling `AuthFailed` to
/// the serve loop (which parks for re-login).
///
/// Called once per turn by the `hive` serve loop.
pub async fn drive_turn(prompt: &str, files: &TurnFiles, bus: &Bus) -> TurnOutcome {
/// Called once per turn by the `hive` serve loop, which owns the shared
/// `session` ([`make_session`]) and threads it in.
pub async fn drive_turn(
prompt: &str,
files: &TurnFiles,
bus: &Bus,
session: &AgentSession,
) -> TurnOutcome {
if bus.take_session_reset() {
// Operator-requested (deferred from `POST /api/new-session`).
bus.emit(LiveEvent::Note {
@ -274,7 +289,6 @@ pub async fn drive_turn(prompt: &str, files: &TurnFiles, bus: &Bus) -> TurnOutco
}
let config = claude_config(bus, files);
let sink = BusSink::new(bus);
let session = infinite_session(bus);
let mut result = session.run(&config, prompt, &sink).await;
if matches!(result, Err(hive_claude::Error::AuthFailed)) {
bus.emit(LiveEvent::Note {
@ -398,7 +412,7 @@ pub fn emit_turn_end(bus: &Bus, outcome: &TurnOutcome) {
/// case is handled at the end of [`drive_turn`].) Resume-only via
/// [`InfiniteSession::compact`]: a missing session is a harmless no-op. Returns
/// `true` if a compaction ran.
pub async fn run_pending_compact(files: &TurnFiles, bus: &Bus) -> bool {
pub async fn run_pending_compact(files: &TurnFiles, bus: &Bus, session: &AgentSession) -> bool {
if !bus.take_compact() {
return false;
}
@ -408,7 +422,7 @@ pub async fn run_pending_compact(files: &TurnFiles, bus: &Bus) -> bool {
bus.set_state(crate::events::TurnState::Compacting);
let config = claude_config(bus, files);
let sink = BusSink::new(bus);
match infinite_session(bus).compact(&config, &sink).await {
match session.compact(&config, &sink).await {
Ok(()) => bus.emit(LiveEvent::Note {
text: "/compact done".into(),
}),