hive-agent: guarantee a wake after a self-requested /compact
This commit is contained in:
parent
fa658567db
commit
2c7872841a
8 changed files with 227 additions and 48 deletions
|
|
@ -260,6 +260,14 @@ pub enum TurnState {
|
|||
Compacting,
|
||||
}
|
||||
|
||||
/// One pending `/compact` request (see `Bus::request_compact`).
|
||||
/// `wake_prompt` is what to drive as a synthetic follow-up turn once the
|
||||
/// compaction actually finishes, if anything.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CompactRequest {
|
||||
pub wake_prompt: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Bus {
|
||||
tx: Arc<broadcast::Sender<BusEvent>>,
|
||||
|
|
@ -307,7 +315,23 @@ pub struct Bus {
|
|||
/// One-shot: run `/compact` after the next turn ends. Consumed at the end
|
||||
/// of the current/next turn by `turn::drive_turn`. Deferring to the turn
|
||||
/// boundary keeps compaction from racing a live claude process mid-turn.
|
||||
compact_pending: Arc<AtomicBool>,
|
||||
/// `Some(request)` when a compact is pending; `request.wake_prompt` is
|
||||
/// what to drive as a synthetic follow-up turn once the compaction
|
||||
/// actually completes (`None` = pending but no follow-up wake wanted,
|
||||
/// e.g. the operator dashboard's `/compact` button). `None` = no compact
|
||||
/// pending. Wrapped in [`CompactRequest`] rather than
|
||||
/// `Option<Option<String>>` (clippy pedantic's `option_option` lint,
|
||||
/// and the named field reads clearer at call sites than a bare nested
|
||||
/// `Option`) so "pending" and "what to wake with" can never desync.
|
||||
compact_pending: Arc<Mutex<Option<CompactRequest>>>,
|
||||
/// One-shot, written by `turn::drive_turn`/`turn::run_pending_compact`
|
||||
/// right after a compaction they served finishes, when that compact's
|
||||
/// request carried a `wake_prompt`. Read once by the `hive-agent` serve
|
||||
/// loop after either call site to decide whether to drive a synthetic
|
||||
/// follow-up turn. Separate from `compact_pending`: by the time this is
|
||||
/// set, the compact has already run and that flag has already been
|
||||
/// cleared by `take_compact`.
|
||||
post_compact_wake: Arc<Mutex<Option<String>>>,
|
||||
/// Current fresh-claude-session id (FK to `sessions.id`). Set by the
|
||||
/// bin loop after minting a session row on a fresh start; stamped onto
|
||||
/// every `turn_stats` row until the next fresh session. `None` before
|
||||
|
|
@ -397,7 +421,8 @@ impl Bus {
|
|||
last_cost_usage: Arc::new(Mutex::new(None)),
|
||||
rate_limited: Arc::new(AtomicBool::new(was_rate_limited)),
|
||||
session_reset_pending: Arc::new(AtomicBool::new(false)),
|
||||
compact_pending: Arc::new(AtomicBool::new(false)),
|
||||
compact_pending: Arc::new(Mutex::new(None)),
|
||||
post_compact_wake: Arc::new(Mutex::new(None)),
|
||||
session_id: Arc::new(Mutex::new(None)),
|
||||
fresh_session: Arc::new(AtomicBool::new(false)),
|
||||
tool_calls: Arc::new(Mutex::new(std::collections::HashMap::new())),
|
||||
|
|
@ -438,16 +463,40 @@ impl Bus {
|
|||
}
|
||||
|
||||
/// Request a compaction after the next turn ends (deferred to the turn
|
||||
/// boundary). Idempotent.
|
||||
pub fn request_compact(&self) {
|
||||
self.compact_pending.store(true, Ordering::SeqCst);
|
||||
/// boundary). Idempotent — a second request before the first is
|
||||
/// serviced just overwrites `wake_prompt` with the latest ask. `Some
|
||||
/// (wake_prompt)` schedules a synthetic follow-up turn (driven with
|
||||
/// `wake_prompt` as its body) once the compaction actually completes;
|
||||
/// `None` requests a plain compact with no follow-up wake (the operator
|
||||
/// dashboard's `/compact` button).
|
||||
pub fn request_compact(&self, wake_prompt: Option<String>) {
|
||||
*self.compact_pending.lock().unwrap() = Some(CompactRequest { wake_prompt });
|
||||
}
|
||||
|
||||
/// Take + clear the compact one-shot. Returns true iff `drive_turn` should
|
||||
/// compact at the end of this turn.
|
||||
/// Take + clear the compact one-shot. `Some(request)` means
|
||||
/// `drive_turn`/`run_pending_compact` should compact now —
|
||||
/// `request.wake_prompt` is what to pass to `set_post_compact_wake` once
|
||||
/// that compaction finishes. `None` means no compact is pending.
|
||||
#[must_use]
|
||||
pub fn take_compact(&self) -> bool {
|
||||
self.compact_pending.swap(false, Ordering::SeqCst)
|
||||
pub fn take_compact(&self) -> Option<CompactRequest> {
|
||||
self.compact_pending.lock().unwrap().take()
|
||||
}
|
||||
|
||||
/// Record that a just-finished compaction should drive a synthetic
|
||||
/// follow-up turn with `prompt` as its body. Called by
|
||||
/// `turn::drive_turn`/`turn::run_pending_compact` right after the
|
||||
/// compaction they served (whose `take_compact()` returned a request
|
||||
/// with `wake_prompt: Some(prompt)`) completes.
|
||||
pub fn set_post_compact_wake(&self, prompt: String) {
|
||||
*self.post_compact_wake.lock().unwrap() = Some(prompt);
|
||||
}
|
||||
|
||||
/// Take + clear the post-compact wake one-shot. The serve loop calls
|
||||
/// this after either compact call site to decide whether to
|
||||
/// synthesize a follow-up turn.
|
||||
#[must_use]
|
||||
pub fn take_post_compact_wake(&self) -> Option<String> {
|
||||
self.post_compact_wake.lock().unwrap().take()
|
||||
}
|
||||
|
||||
/// Mark that the current turn started a fresh claude session.
|
||||
|
|
|
|||
Loading…
Reference in a new issue