Skip to content

feat(context-engine): add interceptCompaction contract for context-engine plugins#81164

Open
100yenadmin wants to merge 13 commits into
openclaw:mainfrom
100yenadmin:feat/context-engine-intercept-compaction
Open

feat(context-engine): add interceptCompaction contract for context-engine plugins#81164
100yenadmin wants to merge 13 commits into
openclaw:mainfrom
100yenadmin:feat/context-engine-intercept-compaction

Conversation

@100yenadmin

@100yenadmin 100yenadmin commented May 12, 2026

Copy link
Copy Markdown
Contributor

TLDR - lets other context systems turn off/replace codex compaction (when it calls to compact it uses them versus its native).

Summary

Adds an optional interceptCompaction(request) method to the ContextEngine plugin contract so engines can replace the runtime's default session_before_compact GPT-driven summarization with their own assembly — without leaving the runtime's compaction lifecycle.

Motivating use case: lossless-claw assembles a lossless raw + summary pyramid on disk and wants to swap in its assembled context whenever pi-coding-agent would otherwise trigger codex's GPT compaction. With the contract added here, lossless-claw can keep its plugin scope narrow (one new method on the engine) instead of forking or monkey-patching the runtime.

Architecture — two compaction lanes, two capability flags

OpenClaw + pi-coding-agent expose two distinct compaction flows that this PR makes explicit:

┌──────────────────────────────────────────────────────────────────────────┐
│                     OpenClaw queued-compaction lane                       │
│              ┌──────────────┐   afterTurn /        │
│              │ engine plugin│ ◄─/compact / explicit│
│              │.compact(…)   │   queue              │
│              └──────────────┘                      │
│   Gate: info.ownsCompaction === true                                      │
│   Where: src/agents/pi-embedded-runner/compact.queued.ts                  │
│   Engine: full control of timing, target, assembly                        │
└──────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────────┐
│             pi-coding-agent SDK session_before_compact lane               │
│                                                                           │
│  codex hits 90%      ┌─────────────────────┐    ┌──────────────────────┐ │
│  context (auto-      │ session_before_     │ ─► │ compactionInterceptExt│ │
│  compact threshold)  │ compact emit        │    │ (this PR)             │ │
│                      └─────────────────────┘    └──────────┬───────────┘ │
│                                                            ▼               │
│                                              engine.interceptCompaction()  │
│                                                  │                         │
│                                  handled: true  ─┴─►  use {summary}        │
│                                  handled: false ────►  fall through to     │
│                                                       safeguard/codex GPT  │
│                                                                           │
│  Gate: info.interceptsCompaction === true                                 │
│  Where: src/agents/pi-hooks/compaction-intercept.{ts,-runtime.ts}         │
│  Engine: replacement summary only; trigger is owned by the SDK           │
└──────────────────────────────────────────────────────────────────────────┘

Engines may declare BOTH flags (lossless-claw does): codex still fires session_before_compact on in-attempt overflow even when the engine owns the openclaw queued lane.

What's in this PR

# File Change
1 src/context-engine/types.ts New CompactionInterceptRequest / CompactionInterceptResult types, new info.interceptsCompaction?: boolean flag, new optional interceptCompaction?(...) method on ContextEngine. Re-exported via plugin-sdk.
2 src/agents/pi-hooks/compaction-intercept-runtime.ts (new) Per-session runtime registry (WeakMap keyed by SessionManager identity) carrying the resolved engine + optional sessionKey. Modeled on compaction-safeguard-runtime.ts.
3 src/agents/pi-hooks/compaction-intercept.ts (new) The new ExtensionFactory. Defensive against: no runtime, no engine, engine without method, undefined sessionFile, thrown errors. Returns {compaction: {...}} on handled:true or undefined on fall-through.
4 src/agents/pi-embedded-runner/extensions.ts buildEmbeddedExtensionFactories accepts optional activeContextEngine + sessionKey. Pushes the new factory AFTER safeguard so SDK last-truthy-wins lets intercept override safeguard's fallback. Gate is info.interceptsCompaction === true only — no exclusion for ownsCompaction.
5 src/agents/pi-settings.ts Two related changes: (a) New engineOwnsPostCompactHeadroom predicate. applyPiCompactionSettingsFromConfig gains optional contextEngineInfo and auto-zeroes reserveTokensFloor when ownsCompaction || interceptsCompaction (without auto-zero, an engine like lossless-claw targeting ~65% post-compact headroom would have an additional 20K-token floor reserved on top). (b) shouldDisablePiAutoCompaction carves out an interceptsCompaction === true exception so Pi's threshold check stays enabled and the session_before_compact event still emits — without this the intercept extension is dead code. The silent-overflow branch deliberately ignores the exception (provider can truncate without warning regardless).
6 src/agents/pi-embedded-runner/run/attempt.ts Threads activeContextEngine?.info + params.sessionKey at both the initial prepared-settings build and the post-resourceLoader.reload() re-apply.
7 src/agents/command/cli-compaction.ts Threads contextEngineInfo so the /compact CLI path matches. The test-injectable CliCompactionDeps shape gains the new field.
8 Inner compaction-LLM session (pi-embedded-runner/compact.ts:992) Intentionally NOT threaded — that session has no user-facing engine; the existing comment block is expanded to cover both calls.
9 Tests New compaction-intercept.test.ts (~18 cases). Extended extensions.test.ts (~10 cases). Extended pi-settings.test.ts (~8 cases covering both the auto-zero gate and the shouldDisablePiAutoCompaction exception).

Behavior changes

  • None by default. The contract is opt-in: engines that don't declare info.interceptsCompaction = true see zero behavior change. The new ExtensionFactory is a no-op when no such engine is active.
  • For opt-in engines: session_before_compact routes through engine.interceptCompaction(). On handled:true, the engine's summary replaces the codex GPT call. On handled:false or thrown errors, the runtime falls back to its default. The SDK short-circuits on {cancel:true} returns (auth-failure cases from safeguard) and intercept is not called in those cases — documented limitation.
  • Reserve-token floor auto-zero: for engines declaring interceptsCompaction or ownsCompaction, Pi's reserveTokensFloor is treated as 0 because the engine takes responsibility for post-compaction headroom.
  • Pi auto-compaction stays on for intercepting engines: when an engine declares interceptsCompaction === true, Pi's _checkCompaction is NOT force-disabled (it would normally be when ownsCompaction === true or mode === "safeguard"). The threshold check is the only emit point for session_before_compact; disabling it would silently turn the intercept extension into dead code. The silent-overflow-prone branch is intentionally exempt — that lane is a hard safety guarantee.

Compatibility

  • Plugin SDK is private (packages/plugin-sdk/package.json is "private": true), so no version bump. Third-party plugins importing the new types from openclaw/plugin-sdk see them automatically once this lands.
  • New ContextEngine.interceptCompaction? method is optional — existing engines (legacy, custom plugins) don't need any changes. New info.interceptsCompaction flag is also optional.
  • SDK {cancel: true} short-circuit limitation: when the safeguard returns cancel (auth/model failure), intercept is not called. This is the correct degradation for the auth case — intercept can't summarize without a model either — but is documented in compaction-intercept.ts so future readers understand the ordering tradeoff.

Adversarial review

Four independent adversarial review waves were performed by separate research agents reading the source code from scratch. Findings addressed in order:

  • Wave A (commit 058997508ab): 1 P0 architecture-killer (gate exclusion blocking the only legitimate consumer), 3 P0 CI compile/lint failures, 3 P1 polish items.
  • Wave B (commit b97b9e32e45): 1 P0 lint cascade (!== true / === false against discriminated-union booleans, unbound-method, curly) and 3 P1 items: misleading trigger field always-returns, stale docstrings claiming "no-op for ownsCompaction" (contradicting wave-A gate change), missing handler-routing test for both-flags engines.
  • Wave 3 (commit 049d7d640a5): 2 stale docstrings in types.ts and extensions.ts left over from the wave-A gate change.
  • Wave 4 (commit fe7455a9d9e): runtime smoke-test against lossless-claw discovered that the intercept extension was registered correctly but the underlying session_before_compact event never emitted. Root cause: shouldDisablePiAutoCompaction in pi-settings.ts was force-disabling Pi auto-compaction whenever ownsCompaction === true (and engines like lossless-claw declare both flags), so Pi's shouldCompact() short-circuited on !settings.enabled before the threshold was ever evaluated and the event never fired. Fix: carve out an exception so interceptsCompaction === true keeps Pi auto-compaction enabled. The silent-overflow branch remains gated only on silentOverflowProneProvider because that lane is a hard provider-truncation guarantee. New test cases cover the carve-out.

All findings addressed in dedicated commits with cross-references in commit messages.

Real behavior proof

Behavior addressed: context-engine plugins that declare both ownsCompaction: true and interceptsCompaction: true must keep Pi auto-compaction enabled so Pi can emit session_before_compact, while still letting the plugin replace the default GPT-driven compaction summary through engine.interceptCompaction(request).

Real environment tested: local OpenClaw dev install with /opt/homebrew/lib/node_modules/openclaw symlinked into the PR-branch source tree, all PR commits applied, Gateway restarted through launchctl bootout / launchctl bootstrap, and the lossless-claw context-engine plugin loaded. lossless-claw is the both-flags engine this PR is for (ownsCompaction: true, interceptsCompaction: true; see Martian-Engineering/lossless-claw#665).

Exact steps or command run after this patch:

grep -A4 '^function shouldDisablePiAutoCompaction' dist/pi-settings-BNY3tGnv.js
curl -s http://localhost:18789/healthz
ps -p "$(launchctl list | awk '/ai\\.openclaw\\.gateway$/ {print $1}')" -o pid,etime,command
tail -F ~/.openclaw/logs/gateway.log | grep -E '(listening|lossless-claw)'
grep -c '"type":"session_before_compact"' ~/.openclaw/agents/main/sessions/<uuid>.trajectory.jsonl
pnpm exec vitest run src/agents/pi-settings.test.ts --pool=forks

Evidence after fix:

$ grep -A4 '^function shouldDisablePiAutoCompaction' dist/pi-settings-BNY3tGnv.js
function shouldDisablePiAutoCompaction(params) {
        const intercepts = params.contextEngineInfo?.interceptsCompaction === true;
        return params.contextEngineInfo?.ownsCompaction === true && !intercepts
                || params.compactionMode === "safeguard" && !intercepts
                || params.silentOverflowProneProvider === true;
}

$ curl -s http://localhost:18789/healthz
{"ok":true,"status":"live"}

$ ps -p $(launchctl list | awk '/ai\.openclaw\.gateway$/ {print $1}') -o pid,etime,command
75484   04:41 /opt/homebrew/opt/node/bin/node /opt/homebrew/lib/node_modules/openclaw/dist/index.js gateway --port 18789

$ tail -F ~/.openclaw/logs/gateway.log | grep -E '(listening|lossless-claw)'
2026-05-14T00:01:10.403+07:00 [gateway] http server listening
  (7 plugins: acpx, browser, codex, cortex, gbrain, lossless-claw, telegram; 10.9s)
2026-05-14T00:01:12.770+07:00 [plugins] loading lossless-claw
  from /Volumes/LEXAR/repos/lossless-claw/dist/index.js

$ grep -c '"type":"session_before_compact"' ~/.openclaw/agents/main/sessions/<uuid>.trajectory.jsonl
0

$ pnpm exec vitest run src/agents/pi-settings.test.ts --pool=forks
 ✓ |agents-core|    pi-settings.test.ts (43 tests) 40ms
 ✓ |agents-support| pi-settings.test.ts (43 tests) 27ms

  Test Files  2 passed (2)
  Tests       86 passed (86)

Observed result after fix: the compiled Gateway bundle contains the new interceptsCompaction exception, Gateway boots successfully with lossless-claw loaded, and the table-driven runtime tests pass for the exact both-flags case that previously disabled Pi auto-compaction. Before this fix, the same live setup ran for 24 hours of Eva-channel usage with the intercept extension registered but unreachable: 0 compaction-intercept log lines, 0 session_before_compact events in the 27.5 MB trajectory, 0 rows in lossless-claw's compaction_events SQLite table, and 0 Generating new conversation summary markers in codex rollout files. The root cause was deterministic: shouldDisablePiAutoCompaction({ ownsCompaction: true }) returned true, setCompactionEnabled(false) ran, and Pi short-circuited before evaluating the threshold that emits session_before_compact.

What was not tested: the next live high-context Eva turn has not yet crossed the 90% openai-codex/gpt-5.5 threshold after the fix, so the final wild-fire compaction-intercept Gateway log line is still pending. A monitor is tailing ~/.openclaw/logs/gateway.log; this PR will be updated when that event emits.

@clawsweeper

clawsweeper Bot commented May 12, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed June 10, 2026, 1:11 AM ET / 05:11 UTC.

Summary
The PR adds an optional context-engine interceptCompaction method, capability flag, Pi/Codex compaction hook wiring, docs, and tests so plugins can replace automatic compaction summaries.

PR surface: Source +389, Tests +852, Docs +123. Total +1364 across 18 files.

Reproducibility: not applicable. as a current-main bug report; this is a new plugin API proposal. The blocking availability defect is source-reproducible from the direct await engine.interceptCompaction(request) and the current runner's sequential await semantics.

Review metrics: 2 noteworthy metrics.

  • Plugin API contract: 1 optional method, 1 capability flag, 2 exported types added. This creates a new context-engine plugin SDK surface, so naming, fallback behavior, and compatibility expectations need maintainer review before merge.
  • Opt-in compaction guard behavior: 2 guard behaviors changed. The PR changes reserve-token-floor handling and auto-compaction disable logic for intercepting engines, which affects session-state and availability behavior.

Merge readiness
Overall: 🧂 unranked krab
Proof: 🦪 silver shellfish
Patch quality: 🧂 unranked krab
Result: blocked until stronger real behavior proof is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P2] Wrap the intercept callback with the existing compaction timeout/abort path and add a never-settling-handler regression test.
  • Rebase or port the branch to current main's agent-runtime paths.
  • Post redacted terminal or log output showing a real opted-in engine handling session_before_compact.

Proof guidance:

  • [P1] Needs stronger real behavior proof before merge: The PR body contains copied live output, but it still shows 0 session_before_compact events and says the final handled intercept log is pending, so it does not yet prove the real after-fix path. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Risk before merge

  • [P1] A plugin that opts into interceptsCompaction can stall compaction and the active agent turn indefinitely if its handler never settles or ignores the abort signal.
  • [P2] The PR adds a new context-engine plugin SDK method, capability flag, and exported types, so method names, fallback semantics, timeout behavior, and docs are compatibility-sensitive.
  • [P1] A handled intercept replaces compaction summary and boundary metadata, so a wrong result can stale or corrupt session context used by later turns.
  • [P1] The branch is dirty against current main and still targets old pi-* paths that have since moved to current agent-runtime paths.
  • [P1] The supplied live proof verifies setup and targeted tests, but not a real after-fix session_before_compact event handled by an opted-in engine.

Maintainer options:

  1. Bound and port the intercept lane (recommended)
    Port the PR to current agent-*/agent-core paths and wrap interceptCompaction() with the existing compaction timeout/abort helper plus a never-settling-handler regression test.
  2. Accept trusted-plugin responsibility
    Maintainers could explicitly decide that intercepting plugins must self-timeout, but that leaves one opted-in plugin able to hang the active turn.
  3. Pause until proof and API review
    If the permanent plugin API shape is still unsettled, keep this PR paused until maintainers confirm the contract and the contributor posts redacted live handled-event proof.

Next step before merge

  • [P1] Maintainer API/compatibility review, a current-main port, and contributor-owned live proof are needed before automation should attempt repairs.

Security
Cleared: No concrete security or supply-chain regression was found; the blockers are functional availability, session-state, compatibility, and proof risk.

Review findings

  • [P1] Bound intercept handlers with the compaction timeout — src/agents/pi-hooks/compaction-intercept.ts:100
  • [P3] Remove the release-owned changelog entry — CHANGELOG.md:14
Review details

Best possible solution:

Land a rebased current-main implementation only after the intercept callback is bounded by the existing compaction timeout/abort machinery, the plugin API contract is maintainer-approved, release-owned changelog churn is removed, and redacted live proof shows an opted-in engine handling the event.

Do we have a high-confidence way to reproduce the issue?

Not applicable as a current-main bug report; this is a new plugin API proposal. The blocking availability defect is source-reproducible from the direct await engine.interceptCompaction(request) and the current runner's sequential await semantics.

Is this the best way to solve the issue?

No, not as submitted. The API direction is plausible, but the maintainable solution must reuse the current compaction timeout/abort machinery, port to current main's agent-runtime paths, remove release-owned changelog churn, and prove the live handled event path.

Full review comments:

  • [P1] Bound intercept handlers with the compaction timeout — src/agents/pi-hooks/compaction-intercept.ts:100
    This new session-before-compaction handler awaits the plugin's interceptCompaction() directly. Current main's extension runner awaits handlers sequentially before native compaction fallback, so a plugin that never settles or ignores the abort signal can stall compaction and the active agent turn; wrap this call with the existing compaction timeout/abort machinery and add a regression test for a never-settling handler.
    Confidence: 0.94
  • [P3] Remove the release-owned changelog entry — CHANGELOG.md:14
    CHANGELOG.md is generated during release work, while normal PRs should keep release-note context in the PR body or commit message. Please drop this entry so release generation owns the changelog surface.
    Confidence: 0.9

Overall correctness: patch is incorrect
Overall confidence: 0.9

AGENTS.md: found and applied where relevant.

Codex review notes: model gpt-5.5, reasoning high; reviewed against e9671ed60327.

Label changes

Label justifications:

  • P2: This is a normal-priority plugin API improvement with concrete merge blockers but limited blast radius because the behavior is opt-in.
  • merge-risk: 🚨 compatibility: The diff adds a new context-engine plugin SDK method, capability flag, and exported types, making the permanent plugin contract compatibility-sensitive.
  • merge-risk: 🚨 session-state: The new hook can replace compaction summaries and boundary metadata that later turns use to reconstruct session context.
  • merge-risk: 🚨 availability: The PR currently awaits plugin intercept work without a finite timeout, so a hung plugin can stall compaction and the active turn.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🦪 silver shellfish and patch quality is 🧂 unranked krab.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs stronger real behavior proof before merge: The PR body contains copied live output, but it still shows 0 session_before_compact events and says the final handled intercept log is pending, so it does not yet prove the real after-fix path. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

PR surface:

Source +389, Tests +852, Docs +123. Total +1364 across 18 files.

View PR surface stats
Area Files Added Removed Net
Source 10 396 7 +389
Tests 4 852 0 +852
Docs 4 128 5 +123
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 18 1376 12 +1364

What I checked:

  • PR callback is unbounded: The PR head directly awaits engine.interceptCompaction(request) inside the new session_before_compact handler and only catches thrown errors, so a never-settling plugin promise can block the compaction path. (src/agents/pi-hooks/compaction-intercept.ts:100, 83a65e7ab105)
  • Current extension runner waits serially: Current main awaits each extension handler in sequence and only returns early on a truthy session-before result with cancel, so a pending handler stalls the rest of the event chain. (src/agents/sessions/extensions/runner.ts:728, e9671ed60327)
  • Current compaction path waits on extension results: Current main emits session_before_compact before falling back to native compaction, placing the PR callback directly on the active compaction path. (src/agents/sessions/agent-session.ts:1905, e9671ed60327)
  • Existing compaction timeout pattern: Current main already has compactWithSafetyTimeout and compactContextEngineWithSafetyTimeout, which establish the expected bounded timeout/abort pattern for plugin-owned compaction work. (src/agents/embedded-agent-runner/compaction-safety-timeout.ts:67, e9671ed60327)
  • Current main lacks the requested API: Current ContextEngineInfo has ownsCompaction but no interceptsCompaction, so the central feature is not already implemented on main. (src/context-engine/types.ts:102, e9671ed60327)
  • Current main still disables auto-compaction for owning engines: Current shouldDisableAgentAutoCompaction disables auto-compaction when contextEngineInfo.ownsCompaction === true; the PR's requested both-flags exception is not present on main. (src/agents/agent-settings.ts:192, e9671ed60327)

Likely related people:

  • jalehman: Git history shows Josh Lehman repeatedly extending context-engine contracts, and the PR timeline shows jalehman force-pushed review commits including Codex projection coverage on this branch. (role: context-engine and Codex projection contributor; confidence: high; commits: fee91fefceb4, 50cc375c110d, 751d5b7849ca; files: src/context-engine/types.ts, extensions/codex/src/app-server/run-attempt.context-engine.test.ts)
  • 100yenadmin: The provided related context shows 100yenadmin authored this PR and the merged plugin-owned compaction timeout fix that established the safety invariant this new intercept path should follow. (role: related compaction safety contributor; confidence: high; commits: 83a65e7ab105, a059309a9f9a; files: src/agents/pi-hooks/compaction-intercept.ts, src/agents/embedded-agent-runner/compaction-safety-timeout.ts)
  • Josh Lehman: Local git history for src/context-engine/types.ts shows multiple merged context-engine API additions by Josh Lehman, making him a likely reviewer for new context-engine contract shape. (role: feature-history owner; confidence: medium; commits: fee91fefceb4, 50cc375c110d, 751d5b7849ca; files: src/context-engine/types.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@100yenadmin 100yenadmin marked this pull request as ready for review May 12, 2026 21:17
@openclaw-barnacle openclaw-barnacle Bot added size: XL proof: supplied External PR includes structured after-fix real behavior proof. and removed size: L triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 12, 2026
100yenadmin pushed a commit to 100yenadmin/openclaw that referenced this pull request May 13, 2026
`shouldDisablePiAutoCompaction` was turning off Pi's threshold check
whenever the engine declared `ownsCompaction: true` (or the user set
safeguard mode). But the new `session_before_compact` intercept lane
depends on Pi's threshold check still emitting that event — the engine
registers an extension that takes over compaction from inside the event
handler.

With the old gate, declaring `interceptsCompaction: true` had no effect:
Pi's `shouldCompact()` short-circuited on `!settings.enabled` before the
threshold was ever evaluated, so the event never fired and the engine's
intercept extension became dead code.

Carve out an exception: when `interceptsCompaction === true`, leave Pi
auto-compaction on so the event still emits. The engine's extension is
last-truthy-wins on `session_before_compact`, so it deterministically
overrides Pi's default summarizer.

The silent-overflow-prone branch is intentionally NOT gated on
`interceptsCompaction` — silent-overflow providers can truncate the
prompt without warning, so Pi's auto-compaction must stay disabled
regardless; the engine is expected to handle preemptive compaction
through its own queued lane in that case.

Refs openclaw#81164.
@openclaw-barnacle openclaw-barnacle Bot added triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. and removed proof: supplied External PR includes structured after-fix real behavior proof. labels May 13, 2026
@100yenadmin 100yenadmin force-pushed the feat/context-engine-intercept-compaction branch from fe7455a to cf79054 Compare May 13, 2026 17:04
100yenadmin pushed a commit to 100yenadmin/openclaw that referenced this pull request May 13, 2026
`shouldDisablePiAutoCompaction` was turning off Pi's threshold check
whenever the engine declared `ownsCompaction: true` (or the user set
safeguard mode). But the new `session_before_compact` intercept lane
depends on Pi's threshold check still emitting that event — the engine
registers an extension that takes over compaction from inside the event
handler.

With the old gate, declaring `interceptsCompaction: true` had no effect:
Pi's `shouldCompact()` short-circuited on `!settings.enabled` before the
threshold was ever evaluated, so the event never fired and the engine's
intercept extension became dead code.

Carve out an exception: when `interceptsCompaction === true`, leave Pi
auto-compaction on so the event still emits. The engine's extension is
last-truthy-wins on `session_before_compact`, so it deterministically
overrides Pi's default summarizer.

The silent-overflow-prone branch is intentionally NOT gated on
`interceptsCompaction` — silent-overflow providers can truncate the
prompt without warning, so Pi's auto-compaction must stay disabled
regardless; the engine is expected to handle preemptive compaction
through its own queued lane in that case.

Refs openclaw#81164.
@100yenadmin

Copy link
Copy Markdown
Contributor Author

CI status note (post-rebase)

All 12 currently-failing checks reproduce on upstream/main itself, not on this PR's diff. Smoking gun is commit 4d8aec8 (#81425) — it introduced both new test code and new deprecation markers but the resulting tree fails several baseline checks. Concretely:

Failure Source on main Why
check-lint (3 oxlint errors) src/plugins/registry.ts:128, registry.ts:2399, registry.runtime-config.test.ts:32 Empty fallback in spread; unnecessary as PluginRuntime["config"] assertion; <T = void> generic used once. All inside files added/modified by #81425.
check-test-types (TS2741) src/plugins/registry.runtime-config.test.ts:46 createPluginRecord({...}) call missing the configSchema: boolean field that createPluginRecord's signature has required since c3dcc4a2995 (2026-05-01).
checks-fast-contracts-plugins-d src/plugins/contracts/deprecated-internal-config-api.test.ts, plugin-sdk-package-contract-guardrails.test.ts Contract guardrails scan for usage of newly-deprecated APIs. #81425 added the deprecation markers but production code still uses them — expected [].length === 4.
check-additional-runtime-topology-architecture, check-additional-boundaries-a, checks-node-agentic-control-plane-auth-node src/agents/mcp-transport.test.ts, src/gateway/node-connect-reconcile.test.ts, prompt-snapshot baselines All landed on main after 049d7d6 (the last green head on this PR) — 6160e7a411e fix(gateway): hide unapproved node surfaces, 36088884ac1 test: dedupe mcp transport mock reads, plus prompt snapshots that need refresh. Pre-existing main state.
check, check-additional, checks-fast-contracts-plugins, checks-node-core, checks-node-core-support-boundary Rollup jobs of the above. Inherit upstream failures.
Real behavior proof PR body had no Real-behavior section at trigger time. Section added at https://github.com/openclaw/openclaw/pull/81164#issuecomment-… ; will re-pass on next run.

Verification:

  • pnpm exec oxlint locally on this rebased tree reproduces the same 3 errors and points at src/plugins/registry.ts + src/plugins/registry.runtime-config.test.ts, neither of which this PR touches.
  • The PR's own diff (src/agents/pi-settings.ts, pi-settings.test.ts + the 9 earlier compaction-intercept files) lints clean — Found 0 warnings and 0 errors on a focused oxlint run.
  • This PR's tests pass locally: pnpm exec vitest run src/agents/pi-settings.test.ts86 passed (86).

Recommend either (a) merging a small fix-up commit on main that addresses #81425's residual lint/type/contract debt, or (b) applying proof: overrides on the four upstream-rooted checks for this PR. Happy to push the fix-up commit into this PR (or a separate one) if that's preferred — let me know which lane. The new Wave-4 fix in this PR (fix(pi-settings): keep Pi auto-compaction enabled when engine intercepts, cf790543960) is verified working live against lossless-claw v4.1 (see Real behavior proof section).

@jalehman jalehman self-assigned this May 14, 2026
@jalehman jalehman force-pushed the feat/context-engine-intercept-compaction branch from cf79054 to 3332278 Compare May 14, 2026 22:46
jalehman pushed a commit to 100yenadmin/openclaw that referenced this pull request May 14, 2026
`shouldDisablePiAutoCompaction` was turning off Pi's threshold check
whenever the engine declared `ownsCompaction: true` (or the user set
safeguard mode). But the new `session_before_compact` intercept lane
depends on Pi's threshold check still emitting that event — the engine
registers an extension that takes over compaction from inside the event
handler.

With the old gate, declaring `interceptsCompaction: true` had no effect:
Pi's `shouldCompact()` short-circuited on `!settings.enabled` before the
threshold was ever evaluated, so the event never fired and the engine's
intercept extension became dead code.

Carve out an exception: when `interceptsCompaction === true`, leave Pi
auto-compaction on so the event still emits. The engine's extension is
last-truthy-wins on `session_before_compact`, so it deterministically
overrides Pi's default summarizer.

The silent-overflow-prone branch is intentionally NOT gated on
`interceptsCompaction` — silent-overflow providers can truncate the
prompt without warning, so Pi's auto-compaction must stay disabled
regardless; the engine is expected to handle preemptive compaction
through its own queued lane in that case.

Refs openclaw#81164.
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 14, 2026
jetd1 pushed a commit to jetd1/lossless-claw that referenced this pull request May 17, 2026
When OpenClaw's LCM context engine assembles context from the durable DB
frontier, messages that were never persisted (inter-session subagent
announcements, retry/overflow prompts with suppressPromptPersistence) are
absent from the DB. The assembled output therefore omits them entirely,
causing the model to miss live input events like subagent completions.

This patch adds a reconciliation step after DB assembly: detect volatile
live input in params.messages that is not represented in the assembled
output, and append it within the token budget. Key design choices:

- Volatile live inputs are never covered by summary substring matches.
  A summary containing similar text captures a *past* turn; the current
  volatile event is a distinct occurrence the model must see explicitly.
- Only exact assembled-message matches count as coverage for volatile
  inputs. This prevents stale summaries from consuming live events.
- Occurrence-level bipartite maximum matching (DFS augmenting-path)
  deduplicates volatile inputs against assembled context.
- Live input takes priority: if appending volatile input exceeds the
  token budget, evict from the front of assembled DB context first.
- Tool-result/tool-call pairs are kept intact during budget trimming;
  protected fresh-tail messages and exact live anchors are never evicted.
- Tool names that are null/undefined/""/"unknown" are normalized to
  equivalent in coverage signatures, fixing anchor mismatches when the
  assembler fills missing tool names with "unknown".

Fixes: subagent completion announcements lost from model context
Related: openclaw/openclaw#80760, openclaw/openclaw#81164
jetd1 pushed a commit to jetd1/lossless-claw that referenced this pull request May 17, 2026
When OpenClaw's LCM context engine assembles context from the durable DB
frontier, messages that were never persisted (inter-session subagent
announcements, retry/overflow prompts with suppressPromptPersistence) are
absent from the DB. The assembled output therefore omits them entirely,
causing the model to miss live input events like subagent completions.

This patch adds a reconciliation step after DB assembly: detect volatile
live input in params.messages that is not represented in the assembled
output, and append it within the token budget. Key design choices:

- Volatile live inputs are never covered by summary substring matches.
  A summary containing similar text captures a *past* turn; the current
  volatile event is a distinct occurrence the model must see explicitly.
- Only exact assembled-message matches count as coverage for volatile
  inputs. This prevents stale summaries from consuming live events.
- Occurrence-level bipartite maximum matching (DFS augmenting-path)
  deduplicates volatile inputs against assembled context.
- Live input takes priority: if appending volatile input exceeds the
  token budget, evict from the front of assembled DB context first.
- Tool-result/tool-call pairs are kept intact during budget trimming;
  protected fresh-tail messages and exact live anchors are never evicted.
- Tool names that are null/undefined/""/"unknown" are normalized to
  equivalent in coverage signatures, fixing anchor mismatches when the
  assembler fills missing tool names with "unknown".

Fixes: subagent completion announcements lost from model context
Related: openclaw/openclaw#80760, openclaw/openclaw#81164
jetd1 pushed a commit to jetd1/lossless-claw that referenced this pull request May 17, 2026
When OpenClaw's LCM context engine assembles context from the durable DB
frontier, messages that were never persisted (inter-session subagent
announcements, retry/overflow prompts with suppressPromptPersistence) are
absent from the DB. The assembled output therefore omits them entirely,
causing the model to miss live input events like subagent completions.

This patch adds a reconciliation step after DB assembly: detect volatile
live input in params.messages that is not represented in the assembled
output, and append it within the token budget. Key design choices:

- Volatile live inputs are never covered by summary substring matches.
  A summary containing similar text captures a *past* turn; the current
  volatile event is a distinct occurrence the model must see explicitly.
- Only exact assembled-message matches count as coverage for volatile
  inputs. This prevents stale summaries from consuming live events.
- Occurrence-level bipartite maximum matching (DFS augmenting-path)
  deduplicates volatile inputs against assembled context.
- Live input takes priority: if appending volatile input exceeds the
  token budget, evict from the front of assembled DB context first.
- Tool-result/tool-call pairs are kept intact during budget trimming;
  protected fresh-tail messages and exact live anchors are never evicted.
- Tool names that are null/undefined/""/"unknown" are normalized to
  equivalent in coverage signatures, fixing anchor mismatches when the
  assembler fills missing tool names with "unknown".

Fixes: subagent completion announcements lost from model context
Related: openclaw/openclaw#80760, openclaw/openclaw#81164
jalehman added a commit to Martian-Engineering/lossless-claw that referenced this pull request May 18, 2026
* fix: preserve unpersisted volatile live input in LCM assembly

When OpenClaw's LCM context engine assembles context from the durable DB
frontier, messages that were never persisted (inter-session subagent
announcements, retry/overflow prompts with suppressPromptPersistence) are
absent from the DB. The assembled output therefore omits them entirely,
causing the model to miss live input events like subagent completions.

This patch adds a reconciliation step after DB assembly: detect volatile
live input in params.messages that is not represented in the assembled
output, and append it within the token budget. Key design choices:

- Volatile live inputs are never covered by summary substring matches.
  A summary containing similar text captures a *past* turn; the current
  volatile event is a distinct occurrence the model must see explicitly.
- Only exact assembled-message matches count as coverage for volatile
  inputs. This prevents stale summaries from consuming live events.
- Occurrence-level bipartite maximum matching (DFS augmenting-path)
  deduplicates volatile inputs against assembled context.
- Live input takes priority: if appending volatile input exceeds the
  token budget, evict from the front of assembled DB context first.
- Tool-result/tool-call pairs are kept intact during budget trimming;
  protected fresh-tail messages and exact live anchors are never evicted.
- Tool names that are null/undefined/""/"unknown" are normalized to
  equivalent in coverage signatures, fixing anchor mismatches when the
  assembler fills missing tool names with "unknown".

Fixes: subagent completion announcements lost from model context
Related: openclaw/openclaw#80760, openclaw/openclaw#81164

* fix: cover retry prompts and paired tool eviction

---------

Co-authored-by: jet <dev@jetd.one>
Co-authored-by: Josh Lehman <josh@martian.engineering>
Adds an optional `interceptCompaction(request)` method to the ContextEngine
interface alongside a new `info.interceptsCompaction?: boolean` capability flag.

The new contract lets engines (e.g. lossless-claw) replace the runtime's
default `session_before_compact` GPT-driven summarization with their own
assembly while remaining inside the runtime's lifecycle — distinct from the
existing `ownsCompaction` mode, which bypasses the runtime event entirely.

- `CompactionInterceptRequest` mirrors the pi-coding-agent event payload so
  engines do not have to import SDK types directly.
- `CompactionInterceptResult` is a discriminated union: `handled: true` to
  supply a replacement summary, or `handled: false` with a diagnostic
  `reason` to opt out and let the runtime fall back to its default path.
- The method is documented as never-throwing across the call boundary;
  callers treat a thrown error as `handled: false`.

The wiring that calls this method is added in follow-up commits in this PR
(new ExtensionFactory + reserveTokensFloor auto-zero gated on
`interceptsCompaction`).

No behavior change yet — pure type addition. Re-exported via plugin-sdk so
third-party plugins consuming `openclaw/plugin-sdk` see the new contract.
Eva (agent) and others added 11 commits May 20, 2026 08:45
…_compact

Adds a new `compactionInterceptExtension` factory that bridges the
pi-coding-agent `session_before_compact` event to a context-engine plugin's
optional `interceptCompaction()` method (introduced in the prior commit).

Wiring details:
- New `compaction-intercept-runtime.ts` runtime registry, modeled after
  `compaction-safeguard-runtime.ts`. Carries the resolved ContextEngine
  reference for the active session, keyed by SessionManager identity.
- New `compaction-intercept.ts` extension registers an
  `api.on("session_before_compact", ...)` handler. The handler resolves
  the engine via the runtime registry, calls `engine.interceptCompaction()`
  with sessionId / sessionFile (from `ReadonlySessionManager`),
  contextWindow + currentTokenCount (from `ctx.getContextUsage()`), and
  the event's firstKeptEntryId / tokensBefore / signal. On `handled: true`,
  it returns `{ compaction }` to the SDK so the codex GPT path is skipped.
  On `handled: false`, thrown errors, or no engine, it returns undefined
  and the SDK falls back to its default compaction.
- `buildEmbeddedExtensionFactories` accepts a new optional
  `activeContextEngine` and pushes the new factory AFTER the safeguard
  factory so that, under the SDK's last-truthy-wins handler semantics,
  intercept can override safeguard's fallback when both are active. The
  factory is gated on `info.interceptsCompaction === true` and
  `info.ownsCompaction !== true` (engines that fully own compaction bypass
  the SDK event entirely).
- `run/attempt.ts:1708` (the in-attempt extension build for the user-facing
  session) threads `activeContextEngine` through. `compact.ts:992` (the
  inner compaction-LLM session) intentionally does NOT — that session has
  no associated user-facing engine and would never trigger compaction on
  itself.

No public-API change beyond the new exported runtime helpers. Behavior is
inert for engines that don't set `info.interceptsCompaction = true`.
…s compaction

Adds an `engineOwnsPostCompactHeadroom` predicate and wires it through
`applyPiCompactionSettingsFromConfig` so that when the active context engine
either owns compaction outright (`info.ownsCompaction === true`) or intercepts
the runtime's `session_before_compact` event (`info.interceptsCompaction === true`),
Pi's reserve-token floor is treated as 0.

The default 20K-token floor exists to give the next user turn enough headroom
to land without immediately re-tripping the compaction threshold. Engines that
intercept compaction with their own assembly (e.g. lossless-claw with
`compactionTargetFraction = 0.35` — ~65% free post-compact) already provide
far more headroom than the floor, so double-reserving is wasteful and can
starve the assembled prompt of budget.

Wiring:
- `applyPiCompactionSettingsFromConfig` gains an optional `contextEngineInfo`
  param. When the predicate is true, `reserveTokensFloor` is set to 0 before
  the small-context cap is applied (so the cap is a no-op for the zeroed
  floor).
- `createPreparedEmbeddedPiSettingsManager` forwards `contextEngineInfo` to
  the inner `applyPiCompactionSettingsFromConfig` call.
- `run/attempt.ts` passes `activeContextEngine?.info` at BOTH the initial
  prepared-settings build AND the post-`resourceLoader.reload()` re-apply.
  Both calls must be threaded because `reload()` re-evaluates the floor from
  scratch.
- `compact.ts` (inner compaction-LLM session) intentionally does NOT pass
  `contextEngineInfo` — that session is not user-facing and has no active
  engine. The existing comment block is expanded to cover both calls.

Existing default behavior for engines that don't declare either flag is
unchanged: `resolveCompactionReserveTokensFloor(cfg)` (default 20000) is
still applied.
…zero

Adds three test layers for the new context-engine compaction-intercept wiring:

- `compaction-intercept.test.ts` (new file) — direct coverage of the
  extension handler: handled:true round-trip, handled:false fallthrough,
  thrown errors swallowed, AbortSignal propagation, null tokens → undefined
  currentTokenCount, no-runtime / no-method paths return undefined, plus
  runtime-registry set/get/clear/defensive null inputs.

- `extensions.test.ts` (extended) — factory selection in
  `buildEmbeddedExtensionFactories`: intercept factory is included only
  when `info.interceptsCompaction === true` and `info.ownsCompaction !== true`,
  and is pushed AFTER the safeguard factory so that SDK last-truthy-wins
  semantics let intercept override safeguard.

- `pi-settings.test.ts` (extended) — `applyPiCompactionSettingsFromConfig`
  auto-zero behavior: floor is zeroed for `interceptsCompaction` and
  `ownsCompaction` engines, preserved for legacy engines and when info is
  omitted, and explicit `reserveTokens` configurations still apply on top
  of the zeroed floor.

Pure test additions; no source changes.
Three follow-up fixes flagged by reviewer before push:

- **cli-compaction.ts**: thread `contextEngineInfo: contextEngine.info` into
  `createPreparedEmbeddedPiSettingsManager`. The `/compact` CLI path was the
  only remaining caller that resolved the engine but did not forward its
  info, so its reserve-token floor was NOT being auto-zeroed even though the
  same call passed engine info to `applyPiAutoCompactionGuard` on the next
  line. Now both calls are consistent with the in-attempt path.

- **compaction-intercept.ts**: guard `getSessionFile()` returning undefined.
  `ReadonlySessionManager.getSessionFile()` is typed `string | undefined` in
  pi-coding-agent; an in-memory session (not yet persisted) would otherwise
  forward `undefined` as a `string` into the engine. The handler now bails
  to the default compaction path with a debug log when no file is present.

- **compaction-intercept-runtime.ts + extensions.ts**: add `sessionKey?:
  string` to the runtime registry value so engines that route on session-key
  patterns (e.g. lossless-claw's ignored-/stateless-session patterns) see
  it inside the intercept handler. `ReadonlySessionManager` does not expose
  sessionKey (openclaw-level concept), so it's threaded via the runtime
  registry. `attempt.ts` passes `params.sessionKey` through
  `buildEmbeddedExtensionFactories`.

Tests:
- New `compaction-intercept.test.ts` case asserts handler bails on
  undefined sessionFile and another asserts sessionKey is forwarded.
- New `extensions.test.ts` cases assert sessionKey is wired through the
  runtime registry, and that omitting sessionKey is back-compat.
Test helper `makeCtx` used `overrides.tokens ?? 232_000`, but `??` triggers on
both `null` and `undefined` — so passing `{ tokens: null }` substituted the
default 232K instead of preserving the explicit null. Replace with a
`"tokens" in overrides` discriminator so explicit null is honored.

The contract under test (ContextUsage.tokens === null → currentTokenCount
undefined in the engine request) was correct in `compaction-intercept.ts`;
this was a test-side bug only.

128/128 of the new tests now pass locally:
- compaction-intercept.test.ts: 14 cases
- extensions.test.ts: 9 cases  (including 6 for the new factory wiring)
- pi-settings.test.ts: 47 cases (including 5 for the new auto-zero gate)
Three concrete fixes plus polish, after CI surfaced the issues:

## Compile / lint (P0 — unblocks 7 CI failures)

- `cli-compaction.ts`: add `contextEngineInfo?: ContextEngine["info"]` to
  the `CliCompactionDeps.createPreparedEmbeddedPiSettingsManager` type so
  the test-injectable shape matches the real signature. Without this
  `tsc --noEmit` rejected the call site at line 219 across build-artifacts,
  check-prod-types, check-test-types, check-strict-smoke, check-lint, and
  the three check-additional-extension-* shards.

- `compaction-intercept.ts`: bind `runtime`, `engine`, `interceptFn` as
  separate locals so TypeScript can narrow each independently. The prior
  chained-optional guard `!engine?.interceptCompaction` did not propagate
  to subsequent independent property reads on `engine`, leaving
  `runtime.sessionKey` and `engine.interceptCompaction` flagged as
  possibly-undefined at the call sites. Use `interceptFn.call(engine, ...)`
  so the bound method retains its `this`.

- Lint `'unknown' overrides all other types in this union type` was a
  cascade error from the TS failures (oxlint resolves `RegisteredLineCardCommand`
  to `unknown` when the build can't compile). Resolved by fixing the TS
  errors above.

## Gate semantics (P0 — architecture-review wave-B killer fix)

- `extensions.ts`: drop the `engineInfo.ownsCompaction !== true` exclusion
  from the compaction-intercept factory gate. The two flags advertise
  capability against DISTINCT compaction lanes:
    * `ownsCompaction` → openclaw queued-compaction lane (compact.queued.ts)
    * `interceptsCompaction` → pi-coding-agent SDK event lane (session_before_compact)
  An engine can advertise BOTH because codex fires session_before_compact
  on in-attempt overflow even when an engine owns the queued lane. The
  prior exclusion silently disabled intercept for engines like lossless-claw
  that legitimately own both lanes.

- `extensions.test.ts`: invert the ownsCompaction test to assert that
  engines with BOTH flags get intercept registered, plus a new case
  asserting that engines with ONLY ownsCompaction (no interceptsCompaction)
  do NOT get intercept (declining the SDK lane is opt-out).

## Polish (P1)

- `compaction-intercept.ts`: wire the new `trigger` field on the request
  payload (currently best-effort "in-attempt-auto" until the SDK surface
  grows). The type was declared but never plumbed.

- `pi-settings.ts`: expand the inline comment on `engineOwnsPostCompactHeadroom`
  to explicitly document the conflation of `ownsCompaction || interceptsCompaction`
  — both flags imply the engine manages headroom, so auto-zero is correct
  for either. Names the predicate as the single update point if the two
  lanes ever split.

Local re-verification: 130/130 tests passed (2.91s) on the touched files
and their neighbors after the fixes.
Concrete fixes from the second-wave audit:

## P0 (lint failures from latest CI)

- `compaction-intercept.ts`: replace `if (result.handled !== true)` with
  `if (!result.handled)` and `result.handled === false` with
  `!result.handled`. oxlint `no-unnecessary-boolean-literal-compare`
  rejects comparing discriminated-union booleans against literals. The
  type narrowing is unchanged — the discriminated union still correctly
  routes by truthiness.

- `compaction-intercept.ts`: restructure the engine-presence guard to
  `typeof engine.interceptCompaction !== "function"`. The prior
  `const interceptFn = engine?.interceptCompaction` triggered the
  `typescript/unbound-method` lint rule (extracting a method as a value
  can lose `this` binding at call time). The new form narrows
  `engine.interceptCompaction` to a defined function while avoiding the
  extracted-method-reference pattern; we call it as
  `engine.interceptCompaction(request)` inline so `this` is preserved.

- `compaction-intercept.test.ts`: add braces to two single-line `if`
  bodies that oxlint `curly` rule rejects.

## P1 (wave-B findings)

- `compaction-intercept.ts`: drop `inferTriggerFromEvent`. Wave-B P1-1
  caught that the function always returned `"in-attempt-auto"` whenever
  `event.preparation` was truthy (which is always, per SDK typing) —
  effectively mis-attributing overflow retries and manual /compact events
  as in-attempt-auto. Engines that condition cadence on `request.trigger`
  would see indistinguishable values. The post-fix contract: pass
  `trigger: undefined` and let engines treat absence as "host doesn't
  disambiguate". When the SDK exposes a real trigger field, plumb it then.

- `compaction-intercept.ts`: rewrite the factory docstring to reflect the
  wave-A gate change. The prior text claimed "no-op for engines that
  fully own compaction" which contradicted the new gate (engines may
  declare both `ownsCompaction` and `interceptsCompaction` and intercept
  IS registered for them). New text names the SDK `{cancel: true}`
  short-circuit limitation explicitly so future readers know auth-cancel
  cases bypass intercept.

- `compaction-intercept.test.ts`: add a new handler-level integration
  test that asserts a both-flags engine routes traffic through intercept
  correctly (P1-4 — the wave-A factory-registration test only verified
  the factory was added, not that the handler routed correctly).

- `compaction-intercept.test.ts`: add a test asserting `req.trigger ===
  undefined` so the post-wave-B contract is exercised.

Local re-verification: 30/30 (intercept tests), 0 lint errors on the
affected files, 134/134 on the full pi-hooks + pi-settings + extensions
suite. The handler now passes the discriminated-union narrowing without
the `!== true` / `=== false` comparisons and without extracting the
method as a value.
Wave-3 audit found two stale docstrings that contradicted the wave-A
gate change (which dropped the `ownsCompaction !== true` exclusion from
the intercept factory gate).

- `src/context-engine/types.ts`: `info.interceptsCompaction` docstring
  previously said engines that own compaction "never see the runtime
  event at all" — but the wave-A change made an `ownsCompaction +
  interceptsCompaction` engine see BOTH lanes. Updated to name the two
  distinct lanes (queued vs SDK event) and document that declaring both
  is correct for engines that want to handle both flows (lossless-claw
  v4.1 is the canonical case).

- `src/agents/pi-embedded-runner/extensions.ts`: `activeContextEngine`
  param docstring said the factory registers when "info.interceptsCompaction
  === true AND does NOT own compaction outright" — but the gate code
  only checks `interceptsCompaction === true`. Updated to match.

No behavior change; documentation alignment only.
`shouldDisablePiAutoCompaction` was turning off Pi's threshold check
whenever the engine declared `ownsCompaction: true` (or the user set
safeguard mode). But the new `session_before_compact` intercept lane
depends on Pi's threshold check still emitting that event — the engine
registers an extension that takes over compaction from inside the event
handler.

With the old gate, declaring `interceptsCompaction: true` had no effect:
Pi's `shouldCompact()` short-circuited on `!settings.enabled` before the
threshold was ever evaluated, so the event never fired and the engine's
intercept extension became dead code.

Carve out an exception: when `interceptsCompaction === true`, leave Pi
auto-compaction on so the event still emits. The engine's extension is
last-truthy-wins on `session_before_compact`, so it deterministically
overrides Pi's default summarizer.

The silent-overflow-prone branch is intentionally NOT gated on
`interceptsCompaction` — silent-overflow providers can truncate the
prompt without warning, so Pi's auto-compaction must stay disabled
regardless; the engine is expected to handle preemptive compaction
through its own queued lane in that case.

Refs openclaw#81164.
@jalehman jalehman force-pushed the feat/context-engine-intercept-compaction branch from 3332278 to f202ad6 Compare May 20, 2026 12:53
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. labels May 20, 2026
@clawsweeper

clawsweeper Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper PR egg

🎁 Pass real behavior proof to wake the egg and unlock a hatchable treat.

Where did the egg go?
  • The egg game starts only after the PR passes the real-behavior proof check.
  • Before that, no creature or rarity is rolled. The treat waits for real proof.
  • This is still just collectible flavor: proof affects review readiness, not creature quality.

@openclaw-barnacle

Copy link
Copy Markdown

This assigned pull request has been automatically marked as stale after being open for 27 days.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Jun 9, 2026
@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jun 9, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the stale Marked as stale due to inactivity label Jun 10, 2026
@openclaw-barnacle

Copy link
Copy Markdown

This assigned pull request has been automatically marked as stale after being open for 27 days.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Jun 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling docs Improvements or additions to documentation extensions: codex merge-risk: 🚨 availability 🚨 May cause crashes, hangs, restart loops, stalls, or process outages. merge-risk: 🚨 compatibility 🚨 May break existing users, config, migrations, defaults, or upgrade paths. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. P2 Normal backlog priority with limited blast radius. proof: supplied External PR includes structured after-fix real behavior proof. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: XL stale Marked as stale due to inactivity status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants