fix(compaction): reliable triggering — actual input_tokens + tool_calls counting + output headroom + local summarizer#75
Conversation
…ls counting + output headroom + local summarizer Squashed reliable-compaction work. Resolves the chars/4 over-count thrash and the dead cloud-summarizer, on top of arniwesth#70: - token estimate counts tool_calls args and uses calibrated chars/7 (measured ~7.5 chars/tok for AILANG code + DOCX XML; old chars/4 over-counted ~1.9x -> premature compaction -> working-set thrash) - reserve 75k output headroom so input can't crowd the context window - compact_step_actual drives the elision LEVEL off the provider's ACTUAL last input_tokens (ground truth), not a char estimate; LoopTotals.last_input_tokens - wire compaction_ai's summarizer to a local ollama model (registry default points at a cloud model that's dead on a local-only rig) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The system message (AILANG reference) was seeded into the conversation list and flowed through compaction, which could drop/reorder it — so the model lost its syntax reference after compaction and drifted into invalid syntax (writing `module a.b.c` / `use std.xml`, i.e. another language). partition_system_msgs() splits ALL system messages off before BOTH compaction layers and re-prepends them for every provider call, so the reference is structurally immune to compaction. Verified: docx reimplement 0/17 -> 16/17, markdown 3/3. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Added: pin the system message OUT of compaction (the big one)Pushed a host-loop fix that turned out to be the dominant reliability bug for long agentic runs on a local model. Root cause (found via Fix: Result: Paired with the published Still draft for your review of the approach — happy to split this into its own focused PR if you'd prefer it separate from the trigger work. |
|
Interesting finding. Compaction looks like an obvious optimisation target. |
…ation overflow (M-COMPACT-CALIBRATED) v5 (b7dc43d) gated compaction on the PROVIDER's last input_tokens. That count reflects the COMPACTED call, so the step after every compaction read a low number (~104k < the 60% floor of 112k), skipped compaction, and sent the FULL uncompacted history. The full side oscillated up unchecked (246k->254k->261k->267k) until ollama rejected it (267365 > 262144). Compaction was never broken for gradual growth — only this stale-signal feedback loop. Fix: gate on est_send_tokens = (pinned prefix + conversation) chars * provider-true ratio (last_input_tokens / last_sent_chars). This sizes the CURRENT history (right thing) with provider ground-truth accuracy (right number), so it cannot oscillate — every step measures what is actually about to be sent, including the pinned system prefix the old usage_percent wrongly excluded. Emergency re-checks with the same estimate and fails loudly BEFORE sending if it can't get under 95%. - compaction.ail: message_chars + est_send_tokens + compact_step_calibrated + try_emergency_calibrated; removed the buggy compact_step_actual. - agent_loop_v2.ail: track last_sent_chars in LoopTotals; pass pinned_chars + the ratio to the trigger. - Trajectory regression test (the guard the prior 3 iterations lacked): a large history whose previous call was compacted must STILL compact. make check_core 24/24; ailang test 8/8. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Update: fixed an oscillation overflow in the Verifying this PR against the local ollama rig (docx_reimplement, N=5), the The trigger reads the provider's reported tokens from the previous call — but that count reflects the compacted call. Emergency compaction lands at a ~104k floor, just under the 60% trigger (112k of effective 187k), so the next step reads "104k → 55% → no compaction" and sends the full uncompacted history. Stable oscillation, with the uncompacted side creeping up until it overflows: The control signal ( Fix: gate on Added a trajectory regression test ( Verified: Design note: |
|
Verified on the local rig — docx N=5 with the calibrated core:
(Pass rate was 1/5, but that's gated by unrelated type-error / output-correctness walls, not compaction — and within N=5 noise. The compaction fix's job was to stop the overflow, and it does.) Ready to merge pending review. |
|
This will be superseded by #81, which introduces |
Problem
On a local ollama rig, compaction fired at the wrong time and its AI-summarization path was inert:
chars/4over-counts. AILANG code + DOCX XML measured ~7.5 chars/token, sochars/4over-estimated input by ~1.9× (ollama reported 74K input wherechars/4estimated 138K at the same step). Compaction triggered far too early → it elided the working set the model still needed → re-read thrash.tool_callsaccounting, no output headroom. A single large tool result (acat document.xml, ~96K) could push input past the context window in one step → overflow (>262144).compaction_aiextension defaults its summarizer to a cloud model (openrouter/...llama-3.1-8b), which doesn't exist on a local-only rig — so the intelligent-summarization path never ran; only the structural fallback did.Fix (builds on your merged #70)
chars/7estimate (conservative vs the measured 7.5).effective = context_limit − 75000, so input can't crowd the window.compact_step_actualdrives the elision level off the provider's actual lastinput_tokens(ground truth): gentlekeep_last=10at 60%, tighter at 75%, emergency at 85% of effective. Falls back to the estimate at step 0 (no actual yet).LoopTotalsgainslast_input_tokens, populated per-step fromresult.input_tokens.compaction_ai.jsonpointing the summarizer at a local model.Validated
The actual-token gate stopped both failure modes I'd hit on the docx task (premature thrash at
chars/4, overflow at a naivechars/7).compaction.ail+agent_loop_v2.ailtype-check.Why this is draft — the bit that needs your call
The local-summarizer wiring here is a per-profile config that hardcodes our rig's model:
{ "model": "ollama/qwen3.6:35b-a3b-mxfp8", "threshold_pct": 50, "keep_recent": 6 }That's rig-specific. The general fix is probably better placed in the
compaction_aiextension itself: default the summarizer to the agent's own model instead of a hardcoded cloud one, so any local profile works out-of-the-box with no per-rig config. That's an extension-package change (sunholo/motoko_ext_compaction_ai), outside this repo — happy to do it there if you agree that's the right layer.The
compaction.ailestimate/trigger improvements stand on their own regardless of how we resolve the summarizer-default question.🤖 Generated with Claude Code