track compacted turns separately in stats
This commit is contained in:
parent
fceab658f4
commit
5951758b35
3 changed files with 23 additions and 11 deletions
|
|
@ -220,7 +220,7 @@ async fn serve(
|
||||||
// compaction so it shouldn't reach here, but if it
|
// compaction so it shouldn't reach here, but if it
|
||||||
// does we also skip the ack (safer to redeliver than
|
// does we also skip the ack (safer to redeliver than
|
||||||
// to lose the message).
|
// to lose the message).
|
||||||
if matches!(outcome, turn::TurnOutcome::Ok) {
|
if matches!(outcome, turn::TurnOutcome::Ok | turn::TurnOutcome::Compacted) {
|
||||||
ack_turn(socket).await;
|
ack_turn(socket).await;
|
||||||
}
|
}
|
||||||
// Rate-limited: park until the quota resets, then requeue
|
// Rate-limited: park until the quota resets, then requeue
|
||||||
|
|
@ -458,6 +458,7 @@ fn build_row(
|
||||||
};
|
};
|
||||||
let (result_kind, note) = match outcome {
|
let (result_kind, note) = match outcome {
|
||||||
turn::TurnOutcome::Ok => ("ok", None),
|
turn::TurnOutcome::Ok => ("ok", None),
|
||||||
|
turn::TurnOutcome::Compacted => ("compacted", None),
|
||||||
turn::TurnOutcome::PromptTooLong => ("prompt_too_long", None),
|
turn::TurnOutcome::PromptTooLong => ("prompt_too_long", None),
|
||||||
turn::TurnOutcome::RateLimited => ("rate_limited", None),
|
turn::TurnOutcome::RateLimited => ("rate_limited", None),
|
||||||
turn::TurnOutcome::Failed(e) => ("failed", Some(format!("{e:#}"))),
|
turn::TurnOutcome::Failed(e) => ("failed", Some(format!("{e:#}"))),
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,7 @@ async fn serve(
|
||||||
// Ack only on a clean turn-end; Failed / RateLimited leave
|
// Ack only on a clean turn-end; Failed / RateLimited leave
|
||||||
// the popped ids in-flight for the next boot's requeue.
|
// the popped ids in-flight for the next boot's requeue.
|
||||||
// Mirrors hive-ag3nt; see that loop for full rationale.
|
// Mirrors hive-ag3nt; see that loop for full rationale.
|
||||||
if matches!(outcome, turn::TurnOutcome::Ok) {
|
if matches!(outcome, turn::TurnOutcome::Ok | turn::TurnOutcome::Compacted) {
|
||||||
ack_turn(socket).await;
|
ack_turn(socket).await;
|
||||||
}
|
}
|
||||||
// Rate-limited: park until the quota resets, then requeue
|
// Rate-limited: park until the quota resets, then requeue
|
||||||
|
|
@ -378,6 +378,7 @@ fn build_row(
|
||||||
};
|
};
|
||||||
let (result_kind, note) = match outcome {
|
let (result_kind, note) = match outcome {
|
||||||
turn::TurnOutcome::Ok => ("ok", None),
|
turn::TurnOutcome::Ok => ("ok", None),
|
||||||
|
turn::TurnOutcome::Compacted => ("compacted", None),
|
||||||
turn::TurnOutcome::PromptTooLong => ("prompt_too_long", None),
|
turn::TurnOutcome::PromptTooLong => ("prompt_too_long", None),
|
||||||
turn::TurnOutcome::RateLimited => ("rate_limited", None),
|
turn::TurnOutcome::RateLimited => ("rate_limited", None),
|
||||||
turn::TurnOutcome::Failed(e) => ("failed", Some(format!("{e:#}"))),
|
turn::TurnOutcome::Failed(e) => ("failed", Some(format!("{e:#}"))),
|
||||||
|
|
|
||||||
|
|
@ -168,6 +168,11 @@ pub async fn write_system_prompt(
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum TurnOutcome {
|
pub enum TurnOutcome {
|
||||||
Ok,
|
Ok,
|
||||||
|
/// Turn completed and proactive context-size compaction fired afterwards.
|
||||||
|
/// Treated like `Ok` for ack and failure-notification purposes; recorded
|
||||||
|
/// as `result_kind = "compacted"` in turn stats so the stats page can
|
||||||
|
/// distinguish normal turns from turns that triggered a compaction.
|
||||||
|
Compacted,
|
||||||
/// claude saw "Prompt is too long" — the session needs compacting.
|
/// claude saw "Prompt is too long" — the session needs compacting.
|
||||||
/// Run `compact_session()` then retry the same wake-up prompt.
|
/// Run `compact_session()` then retry the same wake-up prompt.
|
||||||
PromptTooLong,
|
PromptTooLong,
|
||||||
|
|
@ -265,9 +270,12 @@ pub async fn drive_turn(prompt: &str, files: &TurnFiles, bus: &Bus) -> TurnOutco
|
||||||
// Proactive: a turn just completed on a still-healthy session. If its
|
// Proactive: a turn just completed on a still-healthy session. If its
|
||||||
// context crossed the watermark, checkpoint + compact before a later
|
// context crossed the watermark, checkpoint + compact before a later
|
||||||
// turn overflows into the reactive path. Best-effort — never changes
|
// turn overflows into the reactive path. Best-effort — never changes
|
||||||
// the outcome of the turn that already succeeded.
|
// the outcome of the turn that already succeeded, but records it as
|
||||||
|
// `Compacted` so turn stats can distinguish it from a plain `Ok`.
|
||||||
if matches!(outcome, TurnOutcome::Ok) {
|
if matches!(outcome, TurnOutcome::Ok) {
|
||||||
maybe_checkpoint_and_compact(files, bus).await;
|
if maybe_checkpoint_and_compact(files, bus).await {
|
||||||
|
return TurnOutcome::Compacted;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
outcome
|
outcome
|
||||||
}
|
}
|
||||||
|
|
@ -276,17 +284,18 @@ pub async fn drive_turn(prompt: &str, files: &TurnFiles, bus: &Bus) -> TurnOutco
|
||||||
/// has crossed the watermark, run one notes-checkpoint turn so the agent
|
/// has crossed the watermark, run one notes-checkpoint turn so the agent
|
||||||
/// can persist durable state, then `/compact`. Best-effort: a failed
|
/// can persist durable state, then `/compact`. Best-effort: a failed
|
||||||
/// checkpoint or compaction is logged + surfaced as a Note but never
|
/// checkpoint or compaction is logged + surfaced as a Note but never
|
||||||
/// fails the turn that already succeeded.
|
/// fails the turn that already succeeded. Returns `true` if compaction
|
||||||
async fn maybe_checkpoint_and_compact(files: &TurnFiles, bus: &Bus) {
|
/// was attempted (watermark crossed), `false` if skipped.
|
||||||
|
async fn maybe_checkpoint_and_compact(files: &TurnFiles, bus: &Bus) -> bool {
|
||||||
let watermark = compact_watermark_tokens(bus);
|
let watermark = compact_watermark_tokens(bus);
|
||||||
if watermark == 0 {
|
if watermark == 0 {
|
||||||
return; // proactive compaction disabled
|
return false; // proactive compaction disabled
|
||||||
}
|
}
|
||||||
let Some(used) = bus.last_ctx_usage().map(|u| u.context_tokens()) else {
|
let Some(used) = bus.last_ctx_usage().map(|u| u.context_tokens()) else {
|
||||||
return; // no usage reading yet — nothing to compare against
|
return false; // no usage reading yet — nothing to compare against
|
||||||
};
|
};
|
||||||
if used < watermark {
|
if used < watermark {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
bus.emit(LiveEvent::Note {
|
bus.emit(LiveEvent::Note {
|
||||||
text: format!(
|
text: format!(
|
||||||
|
|
@ -298,7 +307,7 @@ async fn maybe_checkpoint_and_compact(files: &TurnFiles, bus: &Bus) {
|
||||||
// session is somehow already too far gone to run even this, fall
|
// session is somehow already too far gone to run even this, fall
|
||||||
// through to compaction anyway — the checkpoint is best-effort.
|
// through to compaction anyway — the checkpoint is best-effort.
|
||||||
match run_turn(CHECKPOINT_PROMPT, files, bus).await {
|
match run_turn(CHECKPOINT_PROMPT, files, bus).await {
|
||||||
TurnOutcome::Ok => {}
|
TurnOutcome::Ok | TurnOutcome::Compacted => {}
|
||||||
TurnOutcome::PromptTooLong => bus.emit(LiveEvent::Note {
|
TurnOutcome::PromptTooLong => bus.emit(LiveEvent::Note {
|
||||||
text: "checkpoint turn overflowed the window — compacting without it".into(),
|
text: "checkpoint turn overflowed the window — compacting without it".into(),
|
||||||
}),
|
}),
|
||||||
|
|
@ -315,6 +324,7 @@ async fn maybe_checkpoint_and_compact(files: &TurnFiles, bus: &Bus) {
|
||||||
text: format!("/compact after checkpoint failed: {e:#}"),
|
text: format!("/compact after checkpoint failed: {e:#}"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pre-turn auto-reset check. If context is large AND the prompt cache has
|
/// Pre-turn auto-reset check. If context is large AND the prompt cache has
|
||||||
|
|
@ -360,7 +370,7 @@ fn maybe_auto_reset(bus: &Bus) {
|
||||||
/// agent and manager loops agree on outcome semantics.
|
/// agent and manager loops agree on outcome semantics.
|
||||||
pub fn emit_turn_end(bus: &Bus, outcome: &TurnOutcome) {
|
pub fn emit_turn_end(bus: &Bus, outcome: &TurnOutcome) {
|
||||||
match outcome {
|
match outcome {
|
||||||
TurnOutcome::Ok | TurnOutcome::PromptTooLong => {
|
TurnOutcome::Ok | TurnOutcome::Compacted | TurnOutcome::PromptTooLong => {
|
||||||
bus.emit(LiveEvent::TurnEnd {
|
bus.emit(LiveEvent::TurnEnd {
|
||||||
ok: true,
|
ok: true,
|
||||||
note: None,
|
note: None,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue