refactor(agent): build the InfiniteSession once in the serve loop, thread it
This commit is contained in:
parent
f6977a961a
commit
c9de2eeb97
2 changed files with 32 additions and 12 deletions
|
|
@ -503,6 +503,9 @@ async fn serve_loop<S: Surface>(
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
tracing::info!(socket = %socket.display(), "harness serve");
|
tracing::info!(socket = %socket.display(), "harness serve");
|
||||||
S::requeue_inflight(socket).await;
|
S::requeue_inflight(socket).await;
|
||||||
|
// The durable claude session, built once and reused for every turn +
|
||||||
|
// idle compaction below (it's effectively stateless).
|
||||||
|
let session = turn::make_session(&bus);
|
||||||
// Set when a turn calls `request_next_turn` and no real work is
|
// Set when a turn calls `request_next_turn` and no real work is
|
||||||
// pending — the next iteration drives this synthetic message
|
// pending — the next iteration drives this synthetic message
|
||||||
// in-process instead of long-polling the broker. Never
|
// in-process instead of long-polling the broker. Never
|
||||||
|
|
@ -519,7 +522,7 @@ async fn serve_loop<S: Surface>(
|
||||||
// (the in-flight case is handled at the end of drive_turn).
|
// (the in-flight case is handled at the end of drive_turn).
|
||||||
let compacted = {
|
let compacted = {
|
||||||
let _guard = turn_lock.lock().await;
|
let _guard = turn_lock.lock().await;
|
||||||
turn::run_pending_compact(files, &bus).await
|
turn::run_pending_compact(files, &bus, &session).await
|
||||||
};
|
};
|
||||||
if !compacted {
|
if !compacted {
|
||||||
tokio::time::sleep(interval).await;
|
tokio::time::sleep(interval).await;
|
||||||
|
|
@ -545,6 +548,7 @@ async fn serve_loop<S: Surface>(
|
||||||
stats.as_ref(),
|
stats.as_ref(),
|
||||||
files,
|
files,
|
||||||
&turn_lock,
|
&turn_lock,
|
||||||
|
&session,
|
||||||
graceful_stop_message(),
|
graceful_stop_message(),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
@ -553,7 +557,8 @@ async fn serve_loop<S: Surface>(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let ctrl = handle_turn::<S>(socket, &bus, stats.as_ref(), files, &turn_lock, next).await;
|
let ctrl =
|
||||||
|
handle_turn::<S>(socket, &bus, stats.as_ref(), files, &turn_lock, &session, next).await;
|
||||||
if ctrl.auth_failed {
|
if ctrl.auth_failed {
|
||||||
*login_state.lock().unwrap() = LoginState::NeedsLogin;
|
*login_state.lock().unwrap() = LoginState::NeedsLogin;
|
||||||
login::wait_for_login(
|
login::wait_for_login(
|
||||||
|
|
@ -582,6 +587,7 @@ async fn handle_turn<S: Surface>(
|
||||||
stats: Option<&TurnStats>,
|
stats: Option<&TurnStats>,
|
||||||
files: &turn::TurnFiles,
|
files: &turn::TurnFiles,
|
||||||
turn_lock: &TurnLock,
|
turn_lock: &TurnLock,
|
||||||
|
session: &turn::AgentSession,
|
||||||
first: hive_sh4re::DeliveredMessage,
|
first: hive_sh4re::DeliveredMessage,
|
||||||
) -> TurnControl {
|
) -> TurnControl {
|
||||||
let from = first.from;
|
let from = first.from;
|
||||||
|
|
@ -603,7 +609,7 @@ async fn handle_turn<S: Surface>(
|
||||||
let prompt = serve_common::format_wake_prompt(msg_id, &from, &body, unread, redelivered);
|
let prompt = serve_common::format_wake_prompt(msg_id, &from, &body, unread, redelivered);
|
||||||
let outcome = {
|
let outcome = {
|
||||||
let _guard = turn_lock.lock().await;
|
let _guard = turn_lock.lock().await;
|
||||||
turn::drive_turn(&prompt, files, bus).await
|
turn::drive_turn(&prompt, files, bus, session).await
|
||||||
};
|
};
|
||||||
turn::emit_turn_end(bus, &outcome);
|
turn::emit_turn_end(bus, &outcome);
|
||||||
bus.set_state(TurnState::Idle);
|
bus.set_state(TurnState::Idle);
|
||||||
|
|
|
||||||
|
|
@ -230,11 +230,20 @@ fn compact_percent() -> u8 {
|
||||||
u8::try_from(pct.min(100)).unwrap_or(DEFAULT_COMPACT_PERCENT)
|
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`)
|
/// percent-of-window compaction policy that checkpoints (`CHECKPOINT_PROMPT`)
|
||||||
/// before compacting. `default_window` feeds the policy the effective window
|
/// before compacting. Called once at serve-loop start. `percent` comes from a
|
||||||
/// for turns where the model didn't itself report one.
|
/// boot-time env var and `default_window` is only a fallback for turns where
|
||||||
fn infinite_session(bus: &Bus) -> InfiniteSession<PercentPolicy> {
|
/// 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(
|
InfiniteSession::new(
|
||||||
session_title(),
|
session_title(),
|
||||||
session_store(),
|
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 whole turn is retried a single time before bubbling `AuthFailed` to
|
||||||
/// the serve loop (which parks for re-login).
|
/// the serve loop (which parks for re-login).
|
||||||
///
|
///
|
||||||
/// Called once per turn by the `hive` serve loop.
|
/// Called once per turn by the `hive` serve loop, which owns the shared
|
||||||
pub async fn drive_turn(prompt: &str, files: &TurnFiles, bus: &Bus) -> TurnOutcome {
|
/// `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() {
|
if bus.take_session_reset() {
|
||||||
// Operator-requested (deferred from `POST /api/new-session`).
|
// Operator-requested (deferred from `POST /api/new-session`).
|
||||||
bus.emit(LiveEvent::Note {
|
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 config = claude_config(bus, files);
|
||||||
let sink = BusSink::new(bus);
|
let sink = BusSink::new(bus);
|
||||||
let session = infinite_session(bus);
|
|
||||||
let mut result = session.run(&config, prompt, &sink).await;
|
let mut result = session.run(&config, prompt, &sink).await;
|
||||||
if matches!(result, Err(hive_claude::Error::AuthFailed)) {
|
if matches!(result, Err(hive_claude::Error::AuthFailed)) {
|
||||||
bus.emit(LiveEvent::Note {
|
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
|
/// case is handled at the end of [`drive_turn`].) Resume-only via
|
||||||
/// [`InfiniteSession::compact`]: a missing session is a harmless no-op. Returns
|
/// [`InfiniteSession::compact`]: a missing session is a harmless no-op. Returns
|
||||||
/// `true` if a compaction ran.
|
/// `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() {
|
if !bus.take_compact() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -408,7 +422,7 @@ pub async fn run_pending_compact(files: &TurnFiles, bus: &Bus) -> bool {
|
||||||
bus.set_state(crate::events::TurnState::Compacting);
|
bus.set_state(crate::events::TurnState::Compacting);
|
||||||
let config = claude_config(bus, files);
|
let config = claude_config(bus, files);
|
||||||
let sink = BusSink::new(bus);
|
let sink = BusSink::new(bus);
|
||||||
match infinite_session(bus).compact(&config, &sink).await {
|
match session.compact(&config, &sink).await {
|
||||||
Ok(()) => bus.emit(LiveEvent::Note {
|
Ok(()) => bus.emit(LiveEvent::Note {
|
||||||
text: "/compact done".into(),
|
text: "/compact done".into(),
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue