Skip to content

feat(agent-runtime): recall, a reversible compaction boundary, and per-model context settings - #721

Open
zhangqingkun976 wants to merge 2 commits into
vastsa:mainfrom
zhangqingkun976:feat/context-and-compaction-set
Open

zhangqingkun976 wants to merge 2 commits into
vastsa:mainfrom
zhangqingkun976:feat/context-and-compaction-set

Conversation

@zhangqingkun976

@zhangqingkun976 zhangqingkun976 commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

The context/compaction line as one change, rebased onto main and gated locally before it was pushed.

This is a superset of the three smaller PRs I opened for the same work — #688 (deterministic failed-compaction recovery), #700 (post-compaction occupancy), #705 (tool-result tiering). Take whichever shape you prefer: the small ones are still accurate and I will keep them updated, or take this one and I close them on request.

The two commits

commit what
feat(agent-runtime): recall, a reversible compaction boundary, and per-model context settings the change itself, 32 files. The comments that cited a private ADR numbering (0249–0256, which means something else in this repository) and private batch codes now cite ADR 0300/0301 — every one of those changed lines is a comment.
docs(adr): record the reversible boundary and the per-model context settings the specification, ADRs, decisions-log entries and E2E scenarios for the above, in both maintained locales — 16 files

(The first revision's red CI was my publisher's fault, not the change's: it uploaded a hand-maintained file list that was missing six files of the occupancy work, so the branch contained a runtime.ts stamping checkpoint.tokensAfter against a ContextCompactionRecord with no such field. The file set is now derived mechanically from the diff, and the Rust job's cargo fmt failure is fixed.)

Specification, ADRs and E2E

  • ADR 0300 — the compaction boundary is reversible: the recall / recall_project tools, the host read path, the ledger, one reminder tier, deterministic degraded recovery.
  • ADR 0301 — per-model context pressure settings (dynamicContext, earlyCompaction, sleepTime), the single gate, tool-result tiering, the silent idle pass and the sleep digest.
  • docs/spec/03-runtime/02-agent-runtime.md §5.1 · 03-runtime/16-tool-result-limits.md §6a · 03-runtime/06-host-rpc-protocol.md §4 Sessions (five methods) · 03-runtime/01-ipc-protocol.md (compaction_end.idle/silent, mark.tokensAfter) · 04-ux/06-settings-ia.md §2 Model configuration · 08-meta/decisions-log.md D604/D605 · all mirrored to docs/zh-CN/….
  • 06-delivery/04-e2e-test-plan.md: E2E-CONTEXT-recall-reads-a-compacted-away-message, E2E-CONTEXT-idle-compaction-is-silent-and-yields, E2E-CONTEXT-tool-results-tier-under-pressure, E2E-SETTINGS-per-model-context-thresholds, each with its traceability row and an honest status line.

Verification (local, the same steps CI runs)

Re-run on the rebased head ada89381, with @pi-desktop/shared rebuilt first.

JS job — pnpm --filter @pi-desktop/agent-runtime typecheck exit 0;
pnpm -r --if-present test:

package result
shared 80 files / 934 tests passed
agent-runtime 823 passed, 1 failed — native-pi-session (pre-existing: the same test fails on pristine main)
desktop (the two touched contract suites) 13/13 context-compaction, 9/9 latest-turn-context
i18n 3 files / 25 tests passed
host-runtime, plugin-sdk, plugin-devkit, agent-host, racp untouched by this PR

Rust job — cargo fmt --all --check 0; cargo test -p host-core --locked →
587 passed, 4 failed, and those four (mcp_servers, user_skills,
scheduled_rpc, scheduled_tools) are the Windows path-separator assertions in
files this change never touches. Seven new Rust tests are added, all passing
(recall ranking, non-ASCII folding, character paging, sleep append + layout,
project search scoping, project read isolation, a pre-tokensAfter checkpoint
still loading).

Docs — pnpm docs:check (79 EN/zh pairs, 501 pages) exit 0.

E2E: partial. pnpm test:e2e (scripts/e2e-smoke.mjs, the headless protocol suite) — 23/23 passed, 2 skipped (the skips need PI_DESKTOP_TEST_API_KEY), run against the host binary built from this branch, so the host surface it covers (RPC, tools, sessions, plugins) is exercised. The four scenarios below are not driven end to end: they need a built candidate plus an Electron profile with a deterministic boundary/summary fixture, and no suite implements them yet. Alternative validation: the runtime's timer, budget and source-contract seams are covered by unit tests, the host read paths by host-core tests, and both the context inspector and the settings pane by desktop contract tests (all green above). Remaining risk: a real checkpoint boundary followed by a model-issued recall, and the idle-pass timing against a live renderer, have not been observed running.

Where to start reading

  1. crates/host-core/src/transcripts.rs — the recall read path, plus its tests.
  2. packages/agent-runtime/src/recall-tools.ts — the two tools and the pointer text.
  3. packages/agent-runtime/src/runtime.ts — the gate, the idle pass, the ledger projection (mostly additive).
  4. docs/adr/0300-*.md / 0301-*.md — the two decisions, if the shape of the change matters more than the diff.
  5. packages/shared/src/types/models.ts + ModelSelectionPanes.tsx — the settings, which are the only user-visible surface.

Open questions

  1. feat(desktop): report crash dumps found on the next launch #674's follow-ups you listed at merge time (crash dumps from renderer processes, spec sync, collectDumpPaths swallowing EACCES, the non-atomic marker write, shared dumps dir across profiles) are still open — happy to take them as a separate PR if useful.
  2. The estimate calibration from fix(agent-runtime): calibrate the context estimate against what requests cost #683 is being reworked to your four points (conservative handling of anomalous usage, ratio vs fixed offset, the four regression areas, full re-validation) and will come as its own PR.

Base and head

base 206085c07 · head ada89381 · 48 files, +5820 / −95. Rebased onto current main (82 commits
further on); it applied cleanly except for one same-point insertion in
ModelSelectionPanes.tsx on the branches that touch it, where both sides were
kept. The published tree is verified to be the same object the gates ran on:

local tree : 86e64005f10b28252223198c6583f34a8c5f03a7
remote tree: 86e64005f10b28252223198c6583f34a8c5f03a7

@zhangqingkun976
zhangqingkun976 force-pushed the feat/context-and-compaction-set branch 2 times, most recently from 35e2be9 to b079451 Compare September 21, 2026 00:03
zhangqingkun976 added a commit to zhangqingkun976/PI-Desktop that referenced this pull request Sep 21, 2026
…under pressure

Rebased onto main (79cd7ae). One conflict, from vastsa#733 extracting the budget
formula: `ContextBudget` and the `retainedUserMessageBudget` doc comment now
live in `context-budget.ts`, so they stay out of `runtime.ts`, and
`narrowToolResultsUnderPressure` sits beside the import.

Standalone shape: the gate is the fixed `TOOL_RESULT_TIER_PRESSURE` share of the
hard limit. The per-model configurable threshold is in vastsa#721, which contains this
change.
zhangqingkun976 added a commit to zhangqingkun976/PI-Desktop that referenced this pull request Sep 21, 2026
…under pressure

Rebased onto main (79cd7ae). One conflict, from vastsa#733 extracting the budget
formula: `ContextBudget` and the `retainedUserMessageBudget` doc comment now
live in `context-budget.ts`, so they stay out of `runtime.ts`, and
`narrowToolResultsUnderPressure` sits beside the import.

Standalone shape: the gate is the fixed `TOOL_RESULT_TIER_PRESSURE` share of the
hard limit. The per-model configurable threshold is in vastsa#721, which contains this
change.

C:\Users\10470\.pi-desktop\scratch\cb9e2d41-8a55-4a18-899d-92ca2791ab51\commit-705-r.txt
@zhangqingkun976
zhangqingkun976 force-pushed the feat/context-and-compaction-set branch from fd75eb8 to cfdbcfb Compare September 21, 2026 04:40
zhangqingkun976 added a commit to zhangqingkun976/PI-Desktop that referenced this pull request Sep 21, 2026
…under pressure

Rebased onto main (79cd7ae). One conflict, from vastsa#733 extracting the budget
formula: `ContextBudget` and the `retainedUserMessageBudget` doc comment now
live in `context-budget.ts`, so they stay out of `runtime.ts`, and
`narrowToolResultsUnderPressure` sits beside the import.

Standalone shape: the gate is the fixed `TOOL_RESULT_TIER_PRESSURE` share of the
hard limit. The per-model configurable threshold is in vastsa#721, which contains this
change.

C:\Users\10470\.pi-desktop\scratch\cb9e2d41-8a55-4a18-899d-92ca2791ab51\commit-705-r2.txt
@zhangqingkun976
zhangqingkun976 force-pushed the feat/context-and-compaction-set branch from 84e63d5 to bf43a61 Compare September 21, 2026 06:01
@zhangqingkun976

Copy link
Copy Markdown
Contributor Author

Rebased onto main (ab39a9b4) so the new Head contains latest base gate passes. Head is now 52041157.

The only conflicts were append-vs-append — the E2E scenario catalog and the decisions log, where main and this branch each added an entry — and both sides are kept. The identifier bands are still free: upstream's newest ADR is 0299 and its newest decisions-log entry is D607 (the plugin crash report, which is this repository's own fix for issue #747 built on my #756).

This one is two commits on the new base: the change itself, then the ADR/spec/decisions-log/E2E text.

zhangqingkun976 added a commit to zhangqingkun976/PI-Desktop that referenced this pull request Sep 21, 2026
…under pressure

Rebased onto main (79cd7ae). One conflict, from vastsa#733 extracting the budget
formula: `ContextBudget` and the `retainedUserMessageBudget` doc comment now
live in `context-budget.ts`, so they stay out of `runtime.ts`, and
`narrowToolResultsUnderPressure` sits beside the import.

Standalone shape: the gate is the fixed `TOOL_RESULT_TIER_PRESSURE` share of the
hard limit. The per-model configurable threshold is in vastsa#721, which contains this
change.

C:\Users\10470\.pi-desktop\scratch\cb9e2d41-8a55-4a18-899d-92ca2791ab51\commit-705-r2.txt
@zhangqingkun976
zhangqingkun976 force-pushed the feat/context-and-compaction-set branch from 5204115 to daf9d33 Compare September 21, 2026 09:30
@zhangqingkun976

Copy link
Copy Markdown
Contributor Author

Rebased onto main (b71fcf05, 39 commits further on) — main moved a long way under this one. New head daf9d33d, still two commits (0822faa2 the change, daf9d33d the docs).

Two conflicts, both append-vs-append, both resolved by keeping both sides:

  1. runtime.ts at the convertToLlm seam — main had just added the tool-call-id dedupe from my fix(agent-runtime): keep tool-call ids unique in every request #780 (since refactored into tool-call-dedupe.ts and shared with subagents). The resolution runs the dedupe first and the tiering pass second, so the outgoing view is deduped before it is narrowed; both passes are inert when they have nothing to do.
  2. The append-only rows in 04-e2e-test-plan.md and 08-meta/decisions-log.md (EN and zh) — main's rows and mine are both kept.

Local gates on the rebased head:

  • JS: agent-runtime typecheck 0, suite 808 passed / 1 failed (the pre-existing native-pi-session case); shared typecheck 0, 78 files / 901 tests; i18n 3 files / 25 tests; apps/desktop/test/context-compaction.test.mjs 13/13; apps/desktop/test/latest-turn-context.test.mjs 9/9; pnpm docs:check 78 EN/zh pairs, 498 pages, exit 0.
  • Rust: cargo fmt --all -- --check 0; cargo test -p host-core --locked 556 passed / 4 failed, the four being the pre-existing Windows path-separator asserts.
  • E2E: pnpm test:e2e (scripts/e2e-smoke.mjs, headless protocol suite) 23/23 passed, 2 skipped, against the host binary built from this branch. The four E2E-CONTEXT-* / E2E-SETTINGS-* scenarios below are still not driven end to end — no suite implements them yet — and the description says so in those words.
  • node scripts/check-pr-base-main.mjs passed.

On the shape: this is still a superset of #688 / #700 / #705, and all three are rebased and green as well — take whichever unit you prefer.

zhangqingkun976 added a commit to zhangqingkun976/PI-Desktop that referenced this pull request Sep 21, 2026
…under pressure

Rebased onto main (79cd7ae). One conflict, from vastsa#733 extracting the budget
formula: `ContextBudget` and the `retainedUserMessageBudget` doc comment now
live in `context-budget.ts`, so they stay out of `runtime.ts`, and
`narrowToolResultsUnderPressure` sits beside the import.

Standalone shape: the gate is the fixed `TOOL_RESULT_TIER_PRESSURE` share of the
hard limit. The per-model configurable threshold is in vastsa#721, which contains this
change.

C:\Users\10470\.pi-desktop\scratch\cb9e2d41-8a55-4a18-899d-92ca2791ab51\commit-705-r2.txt
@zhangqingkun976
zhangqingkun976 force-pushed the feat/context-and-compaction-set branch from daf9d33 to 8f0f2c2 Compare September 21, 2026 12:36
@zhangqingkun976

Copy link
Copy Markdown
Contributor Author

Rebased onto main (43a37373, 24 commits further on). New head 8f0f2c27, two commits, as before.

It applied cleanly, including the convertToLlm seam in runtime.ts that main has since rewritten: your tool-call-dedupe.ts refactor of my #780 now sits there, and git's three-way merge composed it with this change instead of dropping either side. That composition is verified rather than assumed — on the two branches that add a pass at that seam (#705 and #721, a superset of it) the seam now reads this.narrowToolResultsUnderPressure(this.dropDuplicateToolCalls(messages)), so the dedupe runs first and the tiering second. Both passes are identity functions when they have nothing to do, which keeps this PR's original promise that an ordinary request is byte-identical.

Local gates on the rebased head: node scripts/check-pr-base-main.mjs passed; cargo fmt --all -- --check 0 where the branch touches Rust; pnpm docs:check 79 EN/zh pairs, 499 pages on the branches that touch docs. mergeable=true, behind=0.

One thing to know when reading the JS job: main has landed image generation and its new suite is red on main itself. Pristine 43a37373 fails 8 tests across 4 files (native-pi-session, parent-host-proxy, image-generation, openai-images-contract); this branch fails exactly the same 8 and adds its own on top. I have not touched those files.

zhangqingkun976 added a commit to zhangqingkun976/PI-Desktop that referenced this pull request Sep 22, 2026
…under pressure

Rebased onto main (79cd7ae). One conflict, from vastsa#733 extracting the budget
formula: `ContextBudget` and the `retainedUserMessageBudget` doc comment now
live in `context-budget.ts`, so they stay out of `runtime.ts`, and
`narrowToolResultsUnderPressure` sits beside the import.

Standalone shape: the gate is the fixed `TOOL_RESULT_TIER_PRESSURE` share of the
hard limit. The per-model configurable threshold is in vastsa#721, which contains this
change.

C:\Users\10470\.pi-desktop\scratch\cb9e2d41-8a55-4a18-899d-92ca2791ab51\commit-705-r2.txt
…r-model context settings

One change for the context/compaction line, rebased onto current main.

- recall: `recall` and `recall_project` (plus `RECALL_POINTER`, the two tools in
  the core tool set), over byte-exact transcript reads in host-core
  (`recall_transcript`, `read_message_text`, `search_project_messages`,
  `read_project_messages`) and five RPCs (`session.recall`,
  `session.readMessage`, `search.query`, `session.readProject`,
  `session.appendSleep`). A compacted-away message stays readable verbatim, so
  the boundary is reversible rather than final.
- reversible boundary: the checkpoint carries a session ledger and the runtime
  projects it into the session context, so the model can ask for what it lost
  instead of being told it is gone.
- one tier of budget reminder (ADR 0300): the sharper second reminder claimed
  unsummarized detail "will not be available afterwards", which recall made
  false; it is removed rather than rephrased, and the rollover/fallback wording
  is neutral.
- deterministic failed-compaction recovery: a degraded checkpoint describes its
  own range and restores a non-empty context without a model call.
- post-compaction occupancy: `CompactionRecord.tokensAfter` (optional, old
  records read as absent) plus the ring and title that lead with what the
  compaction just freed until the next real request reports usage.
- tool-result tiering: old tool results are narrowed only once the outgoing
  view is under pressure, with the full text kept reachable through the pointer
  the pass embeds.
- per-model settings: `dynamicContext` / `earlyCompaction` / `sleepTime` on
  `ModelBinding`, persisted with the binding, carried on the launch payload,
  surfaced in the settings pane, and translated in all eight catalogs. The gate
  is the single source of the threshold, so the outgoing request, the hard
  boundary and the idle pass all read the same number (ADR 0301).
- silent early compaction: armed when a run settles, stood down by any new
  prompt, and `compaction_end` carries `idle`/`silent`; only the toast channel
  is silenced - the transcript row, the inspector and recall all remain.
- sleep-time digest: a deterministic digest taken before the summary attempt
  (`session.appendSleep`), so a digest exists even when the attempt never
  returns.

Rebased onto 6f24ff2 (main) for the PR-base gate. The comment-level fixes that
cited a private ADR numbering are folded into this commit, so every comment now
cites ADR 0300/0301; the specification, ADR and E2E text follows in the next
commit. The merge was clean: main's new work in `runtime.ts` (custom SYSTEM.md,
the retry-budget reset, delegate budgeting) sits in different regions.
…ettings

ADR 0300 and ADR 0301, the specification text they amend, the decisions-log
entries for the changed defaults, and the E2E scenarios with their traceability
rows - in both maintained locales.

- `03-runtime/02-agent-runtime.md` §5.1: the boundary is reversible (recall, the
  ledger, one reminder tier), the idle pass, and the configurable pressure gate
  with tool-result tiering.
- `03-runtime/16-tool-result-limits.md` §6a: the pressure gate and tiering, plus
  its acceptance line.
- `03-runtime/06-host-rpc-protocol.md` §4 Sessions: the five methods
  (`session.recall`, `session.readMessage`, `search.query`,
  `session.readProject`, `session.appendSleep`). `03-runtime/01-ipc-protocol.md`:
  `compaction_end.idle`/`silent` and `mark.tokensAfter`.
- `04-ux/06-settings-ia.md`: the three per-binding context controls behind the
  Advanced disclosure.
- `08-meta/decisions-log.md`: D604 (reversible boundary, one reminder tier) and
  D605 (per-model pressure settings).
- `06-delivery/04-e2e-test-plan.md`: E2E-CONTEXT-recall-reads-a-compacted-away-message,
  E2E-CONTEXT-idle-compaction-is-silent-and-yields,
  E2E-CONTEXT-tool-results-tier-under-pressure and
  E2E-SETTINGS-per-model-context-thresholds, with their matrix rows.

Rebased onto f6dd5e2 (main) for the new PR-base gate; the only conflicts were
append-vs-append in the E2E plan, resolved by keeping both scenarios.

C:\Users\10470\.pi-desktop\scratch\cb9e2d41-8a55-4a18-899d-92ca2791ab51\commit-721-docs-r2.txt
@zhangqingkun976
zhangqingkun976 force-pushed the feat/context-and-compaction-set branch from 8f0f2c2 to ada8938 Compare September 22, 2026 01:38
@zhangqingkun976

Copy link
Copy Markdown
Contributor Author

Correction to my previous comment on this PR. I wrote that pristine main failed 8 tests across 4 files (native-pi-session, parent-host-proxy, image-generation, openai-images-contract) and that the image-generation failures were main's own. That was wrong, and the fault was mine, not main's.

What actually happened: my local packages/shared/dist was stale. main had landed image generation, which added exports to @pi-desktop/shared, and my built dist predated them — so agent-runtime imported names that did not exist in the artifact it was type-checking and running against. Six of the eight failures, and the three typecheck errors I saw, were produced by my environment.

After rebuilding @pi-desktop/shared and re-measuring on pristine main (206085c07):

  • pnpm --filter @pi-desktop/agent-runtime typecheck → clean, 0 errors
  • pnpm --filter @pi-desktop/agent-runtime test → 763 passed, 2 failed

The two real baseline failures are:

  1. src/native-pi-session.test.ts > native fork children > never deletes a foreign publication and classifies the failure path-free — a long-standing Windows path assertion, unrelated to this change.
  2. src/hosted-search-contract.test.ts > forwards hosted_search_update as message_update — a 5 s timeout that is intermittent: it passes standalone and on reruns.

So main is not red, and image generation is not broken. I should have rebuilt the workspace dependency before drawing a conclusion from a failure I did not recognise; the fact that the failures appeared only after main gained a new feature should have pointed at my stale artifact first.

This head ada89381 was measured the same way, with the dependency rebuilt: the suite fails exactly the single native-pi-session case above, and nothing else. No file in the image-generation or hosted-search area is touched by this PR.

Apologies for the noise — and for stating a baseline I had not verified. The CI panel on this head is the authoritative record, and it is green.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant