You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(loop): graduated context trimming, margin calibration, rolling compaction (#102)
* feat(loop): graduated context trimming, margin calibration, rolling compaction
Reworks context-window management in the agent loop:
Correctness fixes:
- estimateToolDefs now counts JSON parameter schemas (the bulk of every
tool definition) instead of ignoring them
- estimateMessages now counts ReasoningContent (echoed back to reasoning
models, often the largest content in a turn)
- trimToSurvival keeps the original task in multi-turn sessions, preserves
the compaction digest, and no longer appends a zero-value message when
no user message exists
Trimming robustness:
- budget re-checked after memory/skill/episode injections, right before
the LLM call — injected blocks no longer escape the budget for a full
iteration
- graduated trimming: old tool results >2 KiB (except the 4 most recent)
are replaced with a marker before any turn group is dropped
- trim warning moved before the most recent user message (cache-stable
head preserved), updated in place with cumulative totals, and names up
to 5 dropped tools; new headLen protects system prompt + memory block +
digest + original task
- disk-cap session trimming persists a [Session storage limit: ...] marker
into the transcript so resumed sessions know history was removed
New features:
- self-calibrating safety margin: tightens 0.75 -> 0.65 when provider-
reported input tokens exceed the local estimate by >15%
(margin_calibrated signal)
- optional rolling compaction (compaction / ODEK_COMPACTION /
--compaction, default off): dropped turn groups are summarized by the
model into a rolling, untrusted-wrapped digest message instead of being
lost; summarizer failures never break trimming
All new/changed loop, session, and config code is at 100% function
coverage. Full unit suite, race detector, golangci-lint, and CLI E2E
tests pass.
* feat(docker): enable rolling compaction in bundled configs
-**Batch approval gate** (`internal/loop/loop.go`) — multiple risky tools shown at once in a single prompt. `classifyToolCall` now classifies every command inside `parallel_shell`, every path inside `batch_patch`, and the modern `browser` tool, shows full (untruncated) commands, and withholds blanket `SetTrustAll` when unclassifiable tools remain in the iteration.
89
89
-**Tool-failure recovery** — systematic recovery from tool call failures: retry transient errors, skip permanently failed tools, and continue the loop without crashing.
90
-
-**Context-limit protection** — `trimToSurvival` drops oldest messages when approaching the model's context window, keeping the agent functional under extended sessions. Tool messages stay grouped with their parent assistant message.
90
+
- **Context-limit protection** — graduated trimming when approaching the model's context window: old, large tool results are first replaced with a marker (the 4 most recent are always kept intact), then the oldest turn groups are dropped atomically (tool messages stay grouped with their parent assistant message). The protected head (system prompt, memory block, compaction digest, original task) is never dropped. A trim warning is injected before the most recent user message and updated in place with cumulative totals (including which tools lost earlier results). The token estimator counts tool-def parameter schemas and reasoning content, and the safety margin self-tightens (0.75 → 0.65) when provider-reported input tokens exceed the estimate by >15% (`margin_calibrated` signal). `trimToSurvival` (on provider context-length errors) keeps the system prompt, digest, original task, last 2 turn groups, and latest user message. Optional **rolling compaction** (`compaction` config / `--compaction` / `ODEK_COMPACTION`) summarizes dropped groups into a rolling digest system message (untrusted-wrapped) instead of losing them — one extra LLM call per trim.
-**Post-response async processing** — skill learning, episode extraction, and per-turn extended-memory extraction run in background goroutines tracked by `MemoryManager.RunBackground`; `Agent.Close` drains them via `WaitForBackground` (bounded, ~15s) so the work survives CLI exit without hanging `odek run`.
94
-
-**Storage maintenance janitor** — `maintenance.Start` (internal/maintenance) runs a periodic sweep of `~/.odek` (expired sessions/audit/plans, oversized-log rotation, media sweep, skill skip-list GC) inside `odek telegram`, `odek serve`, and `odek schedule daemon` when `maintenance.enabled` is set; `odek cleanup [--dry-run]` runs the same sweep on demand. Session files are also trimmed at write time (oldest groups first, system message kept) when they would exceed `MaxSessionFileBytes`, so a session can never grow past the load cap. Operator-only config — project `./odek.json` cannot set it. See docs/MAINTENANCE.md.
94
+
-**Storage maintenance janitor** — `maintenance.Start` (internal/maintenance) runs a periodic sweep of `~/.odek` (expired sessions/audit/plans, oversized-log rotation, media sweep, skill skip-list GC) inside `odek telegram`, `odek serve`, and `odek schedule daemon` when `maintenance.enabled` is set; `odek cleanup [--dry-run]` runs the same sweep on demand. Session files are also trimmed at write time (oldest groups first, system message kept, `[Session storage limit: ...]` marker persisted into the transcript) when they would exceed `MaxSessionFileBytes`, so a session can never grow past the load cap. Operator-only config — project `./odek.json` cannot set it. See docs/MAINTENANCE.md.
-**Semantic session search** — the `session_search` tool uses go-vector RandomProjections + k-NN for semantic similarity search through session content, with a two-tier pipeline: vector index (fast, ~1ms) → deepSearch fallback (exhaustive, slower).
97
97
-**Security-first defaults** — the latest hardening closes the high/medium/low findings tracked in `sec_findings.md`: default `non_interactive` is `deny`, project-level `odek.json` cannot redirect backends or hijack delivery, project-level sandbox knobs require explicit operator approval, `~/.odek` trust anchors are protected, WebSocket upgrades require a per-instance CSRF token, and all untrusted content is wrapped before reaching the model. See Security Architecture below for the full list.
**Approval gate:** When an approver is configured and the LLM returns multiple tool calls, a single batch approval prompt is shown before any tool executes. If approved, all tools run in parallel. If denied, no tools run.
245
246
247
+
## Rolling compaction (`compaction`)
248
+
249
+
When context trimming drops old conversation turns to stay within the model's context window, those turns are normally lost. With `compaction` enabled, the dropped turns are instead summarized by the model into a rolling digest message, preserving a compressed history of the session.
250
+
251
+
| Field | Default | Env var | CLI flag | Description |
|`compaction`|`false`|`ODEK_COMPACTION`|`--compaction`| Enable LLM-based rolling compaction of trimmed context. Each compaction costs one extra LLM call per trim. |
0 commit comments