-
Notifications
You must be signed in to change notification settings - Fork 135
π€ feat: add token-budget context window rollovers #4097
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ThomasK33
wants to merge
70
commits into
main
Choose a base branch
from
plan-token-budget-combined
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
70 commits
Select commit
Hold shift + click to select a range
2750598
π€ feat: define token-budget context window contracts
ThomasK33 3058044
π€ feat: wire context-budget rollover lifecycle
ThomasK33 0d751d7
π€ feat: present token-budget context windows and document rollover poβ¦
ThomasK33 49cd04a
π€ feat: complete token-budget presentation metadata contracts
ThomasK33 4dda23e
π€ fix: refresh context notes after memory tools and clean budget refuβ¦
ThomasK33 f2419f0
π€ feat: add bounded session history recovery across context windows
ThomasK33 4503008
π€ fix: guard budget continuations against interrupt supersession
ThomasK33 e666e28
π€ docs: describe atomic token-budget history batches accurately
ThomasK33 baee222
π€ feat: reserve context notes and preflight assembled token budgets
ThomasK33 f81098c
π€ fix: preserve structured budget errors and suppress unchanged retries
ThomasK33 3c1adb2
π€ fix: pass effective memory permissions to budget settlement
ThomasK33 eae3d1d
π€ fix: preserve flush opportunity and sync token-budget documentation
ThomasK33 a71068c
π€ tests: cover token-budget lifecycle and settled step stopping
ThomasK33 8c0f945
π€ tests: align rollover fixtures with settled budget contracts
ThomasK33 39c74e0
π€ fix: open collapsed sidebar in token-budget settings story
ThomasK33 e4489c7
π€ fix: preserve explicit compaction recovery with token budgets
ThomasK33 90ae593
π€ fix: align session history inputs and paging with D9
ThomasK33 c90c03e
π€ fix: finalize rollover recovery and token-budget presentation
ThomasK33 c0d737e
π€ fix: enforce malformed reset privacy and rotate published history bβ¦
ThomasK33 3ce1127
π€ fix: preserve policy and request context across token-budget rollovers
ThomasK33 b176141
π€ fix: scale token-budget reserves for small context windows
ThomasK33 546bf05
π€ fix: display the effective token-budget rollover threshold
ThomasK33 32fb9a3
π€ fix: retain accepted rollover inputs and isolate rejected context
ThomasK33 2b18a51
π€ fix: bound history identifiers without stalling recovery cursors
ThomasK33 d8bdae6
π€ fix: recover from terminal budget rejection and damaged preludes
ThomasK33 05c3f4d
π€ fix: require complete rollover validation for history privacy excepβ¦
ThomasK33 ba06f05
π€ fix: atomically reject budget request payloads
ThomasK33 af7406c
π€ fix: preserve reset floors and reject asynchronous budget failures
ThomasK33 d9d600e
π€ fix: sanitize persisted token-budget counters
ThomasK33 9d2051d
π€ merge: preserve routing-aware token budgets on current main
ThomasK33 df8ee64
π€ fix: detect escaped reset markers in bounded history scans
ThomasK33 f3681fb
π€ fix: preserve reset privacy across malformed history fragments
ThomasK33 e06b1a0
π€ fix: keep legacy window IDs out of rollover instructions
ThomasK33 ec29277
π€ fix: require history access only for eligible context rollovers
ThomasK33 23bd88c
π€ fix: preserve reset privacy through corrupt separators and history β¦
ThomasK33 52971d3
π€ fix: recognize reset tokens across arbitrary malformed separators
ThomasK33 b7f2081
π€ fix: normalize persisted request prelude references
ThomasK33 be6e1f0
π€ fix: terminate rejected retries and preserve append-only budget sends
ThomasK33 5d0e8ea
π€ fix: certify history cursor appends with durable cross-process receβ¦
ThomasK33 00a1560
π€ tests: cover terminal rejected-tail replay and document cursor provβ¦
ThomasK33 e153c3c
π€ merge: reconcile current main with token-budget context windows
ThomasK33 a231fd6
π€ fix: reject special-file history receipts without blocking
ThomasK33 96b9245
π€ fix: preserve raw history reset floors and sanitize request preludes
ThomasK33 1cbd1f2
π€ fix: retain unreadable reset floors through partial history truncation
ThomasK33 12ea025
π€ fix: separate unterminated archive rows during truncation
ThomasK33 730d649
π€ fix: recover budget usage and own copied retry skill snapshots
ThomasK33 416b79e
π€ fix: harden history rewrites and truncation marker compatibility
ThomasK33 2828ad5
π€ fix: validate persisted message parts before history operations
ThomasK33 fb2b82a
π€ fix: quarantine rejected context payloads in downgrade-safe capsules
ThomasK33 a5eb5ef
π€ tests: verify copied rejection capsules and document downgrade safety
ThomasK33 c77d0d8
π€ fix: reject ambiguous reset keys and select the newest request idenβ¦
ThomasK33 d3c5840
π€ fix: honor inherited tool policy for session history
ThomasK33 1472e6b
π€ fix: slice durable context boundaries before filtering hidden rows
ThomasK33 1224e85
π€ fix: recognize hex-escaped history reset privacy floors
ThomasK33 255a7fd
π€ fix: clamp provider history to unreadable reset floors
ThomasK33 e99a2f2
π€ tests: preserve replay-boundary coverage after provider clamping
ThomasK33 a575706
π€ fix: stop treating historical input usage as system overhead
ThomasK33 939a1bf
π€ fix: retain active history without verified replay proof
ThomasK33 a01ca86
π€ docs: explain conservative replay retention
ThomasK33 b4fcc8f
π€ fix: tolerate corrupted persisted step boundaries
ThomasK33 d24f321
π€ fix: pin workspace request middleware before context rollover
ThomasK33 b386a77
π€ fix: apply goal safety when manual token-budget input is rejected
ThomasK33 be64254
π€ fix: identify exact history rows and preserve Unicode search offsets
ThomasK33 21be114
π€ fix: bind exact history row references to append epochs
ThomasK33 5e17c08
π€ fix: recover readable rows through exact history references
ThomasK33 4d112b8
π€ fix: enforce token-budget hard ceilings across dense input and settβ¦
ThomasK33 e968cb8
π€ docs: describe real-encoding guards and settled hard stops
ThomasK33 67fa40a
π€ fix: repair rotation retry tails and distinguish reset data from boβ¦
ThomasK33 15baf40
π€ fix: reconcile main delivered-completion lifecycle
ThomasK33 36e6181
π€ fix: preserve surrogate pairs in history character pages
ThomasK33 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| --- | ||
| title: Token-Budget Context Windows | ||
| description: An opt-in automatic reset policy with bounded retrieval and a manual-reset privacy floor | ||
| --- | ||
|
|
||
| # 0005. Automatic Rollover Can Retrieve Earlier Context Windows | ||
|
|
||
| ## Status | ||
|
|
||
| Accepted. Amends only consequence 2 of [ADR 0003](./0003-context-boundaries-for-compaction-and-reset.md) for automatic token-budget rollover. | ||
|
|
||
| ## Context | ||
|
|
||
| Repeated automatic summaries lose detail and consume inference tokens. An opt-in policy can instead start a fresh Active Conversation Context while retaining Transcript History for explicit, bounded retrieval. Manual resets must keep their privacy semantics. | ||
|
|
||
| ## Decision | ||
|
|
||
| Automatic rollover uses a provider-invisible Context Reset Boundary followed by a provider-visible synthetic lead-in. The lead-in identifies the new window and offers `session_history` retrieval; it does not summarize old messages. Earlier windows are retrievable only while the experiment is enabled and never across the newest manual reset. Manual `/clear --soft` remains provider-invisible, adds no lead-in, and establishes that privacy floor. | ||
|
|
||
| Manual `/compact`, idle compaction, continuous compaction, and effective RLM retain their existing behavior and take precedence over rollover. Existing edited-file carryover is unchanged. With automatic handling disabled, no rollover or flush warning is emitted, but hard assembled-request preflight still blocks oversized requests. `session_history` follows ordinary inherited agent and caller tool policy: an explicit tool name or matching wildcard must grant access, and later matching rules can remove it. The experiment does not widen narrow allowlists; built-in Exec and Plan grant access through `.*`, and Explore inherits that grant. If effective policy omits or disables history access, a rollover that would seal existing context is blocked rather than falling back to lossy summaries. A fitting first request in an empty or internal-only window does not require history access. | ||
|
|
||
| Before a rollover can clear context state or append a boundary, request admission reconciles the workspace's lazy plugin hooks and captures an immutable, ordered snapshot of the applicable request-assembly registrations. Unrestricted middleware is uncertified even when it appears benign: it could remove a tool or mutate its implementation/schema in place. Such middleware blocks rollover; it is never overridden by restoring a denied tool. Explicit workspace scopes are enforced during dispatch, so registrations for other workspaces do not block admission. Only the context-only registration adapter certifies toolset preservation: it receives no tool references and writes back system text alone. The sandboxed plugin context adapter uses this path. | ||
|
|
||
| The rollover turn's primary and fallback requests run the admitted snapshot; thinking rebuilds retain that assembled context and toolset. Neither consults later registry changes or constructs the toolset before cleanup. In-process automatic retries retain the same snapshot; it is never serialized into history or send options. Registration/unregistration changes affect subsequent admissions. Plugin disposal and managed-plugin mutation epochs remain live revocation checks, and hook execution reacquires sandbox mounts rather than retaining a disposed kernel. Ordinary non-rollover requests retain live middleware filtering. | ||
|
|
||
| A once-per-window warning offers a settled tool step to write the conventional `workspace/context-notes.md` file (up to 8 KiB, if writable). Its reserved hot-set slot still requires both Memory and Memory Hot Set. Rollover waits for a settled tool step, preserves tool call/result pairs, and allows only one pending rollover to be handled on the next send. Restart stays paused: it does not resurrect a queued continuation; the next message derives context pressure from persisted history. | ||
|
|
||
| The reset, lead-in, and triggering message or continuation are committed as one all-or-nothing batch before continuation. `HistoryService.appendManyToHistory` uses `writeFileAtomic` (temporary file and rename) under the cross-process history lock, rather than `fs.appendFile`; the current writer does not expose a torn batch prefix on crash. Recovery tests must still cover partial prefixes from legacy or externally modified histories without duplicating rollover or resurrecting queued work. A payload estimated not to fit even in a fresh window is rejected before a provider request. | ||
|
|
||
| Fresh-request, assembled-request, and settled-tool-output hard guards use the resolved model/capability encoding, bypassing approximation mode only for those counts. Large strings are counted in codepoint-safe chunks with boundary slack to bound long-run encoding work; encoding failures do not silently fall back to character ratios. Provider-family encodings and media/framing allowances remain estimates, so provider context-overflow handling remains a backstop. At a settled hard ceiling with automatic handling disabled, the turn stops without warning, rollover, continuation, or preflight quarantine; completed sibling tool results remain durable. | ||
|
|
||
| Only context-scoped cache, persisted carryover, and sandbox clearing runs before append. This ordering is deliberately fail-closed: a crash after publication must not reopen a fresh window with stale pre-reset carryover or kernel state. If cleanup succeeds but cancellation or append failure prevents publication, the old transcript remains with that disposable state cleared; it is not restored because a failed acknowledgment may still mean publication succeeded. Cancellation and admission are checked before cleanup and again before append. Branch-summary clearing and epoch notification run after append; cleanup failure must prevent a provider request. When rollover invalidates other sends, its own caller must adopt the updated epoch before continuing. | ||
|
|
||
| ### Rejected request retention across downgrades | ||
|
|
||
| Rejected inputs and their owned snapshots are transcript-only. They are stored as empty, non-partial assistant records, retaining their identity and sequence; the original role, content, and display metadata live inside a new opaque metadata field. No original skill, file, command, or peer control metadata remains active on the outer record. Current display/export code can recover the original transcript projection without restoring it to provider history. | ||
|
|
||
| The preceding request assembler already excludes empty assistant records, so downgrading cannot replay rejected payloads merely because it ignores the new rejection flag. Older builds may not display the quarantined original content, but preserve it for a subsequent upgrade. Partial-truncation transaction markers likewise retain legacy decoded-text digests in their existing fields and add separately versioned byte digests, allowing both versions to recognize an accepted rewrite containing invalid UTF-8. | ||
|
|
||
| ### Append-stable retrieval cursors | ||
|
|
||
| Head/tail hashes alone cannot distinguish an append from an interior rewrite followed by an append. Retrieval therefore uses a constant-size durable append receipt in addition to the bounded scan cursor. This receipt is cursor-safety metadata, not a rollover journal or a second copy of the transcript. | ||
|
|
||
| All cooperative history writers share the existing cross-process history lock. Before changing transcript files, a writer publishes a pending receipt; failure to invalidate the old receipt aborts the mutation. Only positively verified append operations may retain the receipt's epoch; a rewrite, truncation, rotation, recovery, or unexplained file change invalidates it. A stable receipt binds the epoch to the resulting chat and archive file stamps. Failure to finalize the receipt after accepting a history write expires cursors rather than reporting the accepted write as failed. Readers hold the same lock and validate the receipt and stamps before and after each bounded page, without running recovery during the scan. The bounded append scan still checks for newly added manual-reset privacy floors. | ||
|
|
||
| Append stability is guaranteed for tracked `HistoryService` appends, including tool-result appends and appends made by another backend process. Direct filesystem edits or appends observed outside a tracked transaction are untracked: existing cursors fail closed instead of treating file growth as proof of append-only history. Missing, malformed, pending, or mismatched receipts also expire existing cursors. A new query can establish a fresh baseline under the same history lock; it cannot revive an old cursor. Backend restarts continue to expire authenticated cursors. | ||
|
|
||
| The receipt assumes transcript writers honor the history lock during a tracked transaction. It detects an untracked edit between transactions or pages, including an interior rewrite followed by an append; it is not a defense against a process with filesystem write access racing an interior edit inside another writer's append/stat interval. Protecting against that adversary requires filesystem access isolation or verification of the entire prior prefix, not bounded file stamps. | ||
|
|
||
| The receipt does not turn history readers into unbounded prefix verifiers. Transcript scan and result budgets remain unchanged, and the receipt itself has a fixed-size read limit. Raw malformed reset candidates must also survive automatic history rewrites: invalidating an old cursor cannot repair a privacy floor that a writer erased before a new query. | ||
|
|
||
| Archived sequence coverage is not proof that an active row is a replay. Retrieval retains rows with reused sequences so repaired or imported content remains accessible; possible physical replay duplicates may therefore appear in results. | ||
|
|
||
| ## Consequences | ||
|
|
||
| - `session_history` list/search/read is bounded: 16 KiB per tool result, 2 MiB scanned, 500 rows, and a 1 MiB per-line cap. Retrieval is scoped to the calling workspace and the manual-reset privacy floor. | ||
| - Old windows remain on disk and in transcript display/export. The lead-in stays hidden in normal transcript display; warnings render as machine messages, not human prompts. | ||
| - Opting out disables retrieval, not retention. ADR 0003's remaining decisions and consequences are unchanged. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| --- | ||
| title: Token-Budget Context Windows | ||
| description: Start fresh context windows without automatic summaries and retrieve earlier work on demand | ||
| --- | ||
|
|
||
| Enable **Token-budget context windows** in **Settings β Experiments** to replace usage-triggered automatic summaries with fresh context windows. The experiment is off by default. | ||
|
|
||
| ## Threshold and precedence | ||
|
|
||
| Use the existing context-usage slider to choose the per-model threshold. The **Rolls over by N%** label includes the five-percentage-point force buffer: a 70% slider setting displays **Rolls over by 75%**. Automatic rollover is evaluated when sending and after a settled tool step. The displayed percentage is an upper bound; the hard request ceiling takes precedence if reached first. Rollover starts a fresh window without summarizing earlier messages. The transcript shows a **Context window rollover** divider; earlier messages remain on disk, in the UI, and in exports. | ||
|
|
||
| - Manual `/compact` and idle compaction still summarize normally. | ||
| - Continuous compaction and effective RLM take precedence over rollover. | ||
| - Setting the usage threshold to **100%** disables automatic rollover and its warning. Hard request-size checks still apply, including after settled tool steps: the turn can pause without queuing a rollover or discarding completed tool results. | ||
| - `session_history` must be allowed by the agent's inherited tool policy and any caller restrictions. Built-in Exec, Plan, and Explore already allow it. Narrow custom agents can add `session_history` or a matching wildcard to `tools.add`. If access is omitted or disabled, rollover pauses before sealing existing context instead of falling back to a lossy summary. | ||
|
|
||
| Rollover also pauses when applicable request middleware can change the toolset, before clearing context state or saving a boundary. Context-only integrations, including sandboxed plugin context hooks, remain supported. Xum pins the workspace's applicable hook registrations when admitting a rollover and uses that snapshot throughout the turn and its fallback attempts; later registration changes apply to subsequent requests. Plugin revocation still takes effect. Hooks explicitly scoped to another workspace do not block rollover. Ordinary requests and manual `/compact` retain their existing middleware behavior. | ||
|
|
||
| ## Keeping useful context | ||
|
|
||
| Once per window, a machine-authored warning asks the agent to write important context to the conventional `workspace/context-notes.md` file, up to **8 KiB**, if the workspace is writable. This is an opportunity to preserve notes, not a guarantee that the agent writes them. The notes' reserved hot-set slot still requires both **Memory** and **Memory Hot Set**; this experiment does not enable either. | ||
|
|
||
| The next window receives a model-only lead-in, not a summary. While the experiment is enabled, the agent can use `session_history` to list windows, search, or read earlier messages in the same workspace. Results are capped at **16 KiB** per call, with scans bounded to **2 MiB**, **500 rows**, and **1 MiB per line**. Large histories may require further bounded calls. | ||
|
|
||
| The newest manual `/clear --soft` is a privacy floor: the tool cannot retrieve messages before it. Manual reset behavior and edited-file carryover are unchanged. Turning the experiment off removes retrieval access without deleting old windows. | ||
|
|
||
| ## Pauses and size limits | ||
|
|
||
| Rollover stops only after a tool step settles, preserving tool call/result pairs. Only one rollover may be pending; it is handled on the next send. Restart leaves the workspace paused rather than resurrecting a queued continuation, and the next message re-evaluates pressure from history. | ||
|
|
||
| The boundary, lead-in, and triggering message or continuation are saved as one atomic, all-or-nothing batch. Recovery also tolerates incomplete batches in legacy or externally modified histories. Requests estimated to exceed a fresh window are blocked before contacting the provider; rollover cannot make oversized attachments or instructions fit. Text guards use real encodings, but provider-family, media, and framing estimates can still differ from the provider's accounting. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.