fix(workspace-context): deduplicate baseline on resume
This commit is contained in:
@@ -6,7 +6,7 @@ Per-session workspace instruction loading for `AGENTS.md`-compatible files. The
|
||||
|
||||
## Lifecycle
|
||||
|
||||
The baseline is injected at the first `agent/step` of each live session. It reads `$DSH_HOME/AGENTS.md` followed by, in each directory from the project root to `agent.session.header.cwd`, every existing base candidate and then every existing local-overlay candidate. Within one directory, candidates whose content is byte-identical after trimming leading and trailing whitespace collapse to the earliest candidate in configured order, so a `CLAUDE.md` that merely duplicates its sibling `AGENTS.md` is rendered once. The durable sourced `user/message` enters the same request as the claimed prompt. If a later surface replacement such as compaction shadows that baseline, a model-request `system-prompt/assemble` recomposes and injects the current chain before the loop snapshots that request; inspection-only assemblies do not mutate the session.
|
||||
A complete baseline is injected at the first `agent/step` of a fresh session. It reads `$DSH_HOME/AGENTS.md` followed by, in each directory from the project root to `agent.session.header.cwd`, every existing base candidate and then every existing local-overlay candidate. Within one directory, candidates whose content is byte-identical after trimming leading and trailing whitespace collapse to the earliest candidate in configured order, so a `CLAUDE.md` that merely duplicates its sibling `AGENTS.md` is rendered once. The durable sourced `user/message` enters the same request as the claimed prompt. A resumed loop retains that baseline while it remains visible and appends only current-file transitions. If a later surface replacement such as compaction shadows the baseline, a model-request `system-prompt/assemble` recomposes and injects the current chain before the loop snapshots that request; inspection-only assemblies do not mutate the session.
|
||||
|
||||
The plugin also listens on `tools/post-execute` for successful first-party `read`, `write`, and `edit` calls. Each touch checks newly reached descendant scopes and every previously loaded scope. Each configured candidate name is an independent scope in its directory: a newly present file is attached through the result's `additionalContexts`; a changed file appends a replacement; a file that disappears or becomes a per-directory duplicate of an earlier candidate appends a removal notice. Native calls and Code Mode sub-dispatches share this path: `run_code` defers each nested context until its outer result, so the loop still appends updates after tool-call/result adjacency is complete. This follows structured filesystem activity rather than shell `cd`, because each local bash call starts a fresh shell and parsing arbitrary shell syntax would be unreliable.
|
||||
|
||||
@@ -48,11 +48,11 @@ The plugin owns the complete `<system-reminder>` framing, and every injected `us
|
||||
|
||||
## State And Refresh
|
||||
|
||||
Model-visible text contains no hidden state markers. Each baseline or dynamic context event instead carries a typed `workspace-instructions` source with a list of `{ action, scope, path, digest? }` changes; the complete startup or resume baseline also carries `baseline: true`. On every relevant tool touch, the plugin reconstructs loaded state from its visible session events and overlays a short in-memory pending window for context present on the immutable top-level `tools/result` but not yet appended by the loop. A matching durable `user/message` confirms the pending transition. If the owning `step/end` arrives before a matching context reaches the log, the plugin clears the pending transition and its version fast path so the next successful touch can load it again. Nested Code Mode results stage pending changes under the outer execution token for same-run duplicate suppression; the outer result rolls that state back and recommits only contexts that survived outer policy.
|
||||
Model-visible text contains no hidden state markers. Each baseline or dynamic context event instead carries a typed `workspace-instructions` source with a list of `{ action, scope, path, digest? }` changes; a complete baseline also carries `baseline: true`. On every relevant tool touch, the plugin reconstructs loaded state from its visible session events and overlays a short in-memory pending window for context present on the immutable top-level `tools/result` but not yet appended by the loop. A matching durable `user/message` confirms the pending transition. If the owning `step/end` arrives before a matching context reaches the log, the plugin clears the pending transition and its version fast path so the next successful touch can load it again. Nested Code Mode results stage pending changes under the outer execution token for same-run duplicate suppression; the outer result rolls that state back and recommits only contexts that survived outer policy.
|
||||
|
||||
An unchanged path and SHA-1 content digest is not injected again. A per-session, per-scope provider cache stores only `{ path, version, digest, trimmedDigest }`: when the provider's opaque `FsVersion` and the effective visible state both match, reconciliation skips the content read; a changed version triggers a bounded read and SHA-1 confirmation before any model-visible update. The `trimmedDigest` — SHA-1 over the whitespace-trimmed content — is the per-directory duplicate key, so an unchanged file can still be removed when an earlier candidate converges on its content. Resume works because SHA-1 state is persisted in the typed source, while an empty in-memory version cache merely causes one confirming read. Compaction re-arms a scope after its context event leaves the visible surface even when the cached version is unchanged. A removal is a tombstone, so a later candidate reappearance is loaded again. Only model-visible changes actually rendered within the byte budget enter the source, pending state, and version cache; an omitted change remains eligible for a later touch, while a same-digest version refresh updates only the provider cache.
|
||||
|
||||
The initial baseline event itself is not rewritten. Its typed changes remain authoritative only while that event is in the visible session surface. After a surface replacement removes it, model-request prompt assembly recomposes the current baseline and rechecks cancellation, visibility, and the current replacement generation immediately before injecting it. Concurrent preparations can read in parallel, but only the first commit queues a baseline; inspection-only assemblies never restore one. A successful filesystem touch can still append later replacements or removals. The in-memory scope marker and provider-version cache only select and accelerate probes. A hot plugin remount retains a baseline only when its typed event remains visible, while rebuilding current scope and version tracking; otherwise it injects a current baseline. A resumed loop always recomposes the current baseline and also reconciles still-visible dynamic scopes before its first request. There is no file watcher, so an on-disk change becomes visible at the next successful `read`, `write`, or `edit` touch, when a model request restores a shadowed baseline, or when a resumed loop prepares its baseline.
|
||||
The initial baseline event itself is not rewritten. Its typed changes remain authoritative only while that event is in the visible session surface. A resumed loop or hot plugin remount retains that one visible baseline and reconciles its baseline and dynamic scopes against current files before the first request: unchanged files append nothing, while offline additions, edits, and removals append typed `set`, `replace`, and `remove` transitions. If no typed baseline remains visible, as after a surface replacement, model-request prompt assembly recomposes the complete current baseline and rechecks cancellation, visibility, and the current replacement generation immediately before injecting it. Concurrent preparations can read in parallel, but only the first commit queues a baseline; inspection-only assemblies never restore one. The in-memory scope marker and provider-version cache only select and accelerate probes. There is no file watcher, so an on-disk change becomes visible at the next successful `read`, `write`, or `edit` touch, when a model request restores a shadowed baseline, or when a resumed loop prepares its baseline.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -83,7 +83,7 @@ Instruction content is read through `streamText()` under `maxSourceBytes`, even
|
||||
|
||||
#### What the model sees
|
||||
|
||||
At the first request of each loop instance, and again on the first request after a surface replacement shadows it, the model receives one durable user-role message containing the bounded user-global and project instruction chain in broad-to-specific order.
|
||||
A fresh session's first request contains one durable user-role message with the bounded user-global and project instruction chain in broad-to-specific order. A resumed request retains that message while it remains visible and adds only detected transitions; the first request after a surface replacement shadows it receives one recomposed complete baseline.
|
||||
|
||||
##### Baseline instruction template
|
||||
|
||||
@@ -107,7 +107,7 @@ The rendered baseline remains in derived history until a surface replacement sha
|
||||
|
||||
#### KV Cache effect
|
||||
|
||||
Append-only after the existing reusable prefix. A new, resumed, or post-compaction request may append a recomposed baseline, so instruction, precedence, cwd, candidate, or byte-budget changes affect cache reuse from that history position.
|
||||
Append-only after the existing reusable prefix. A fresh or post-compaction request may append a complete baseline; a resumed request retains its visible baseline and appends only detected transitions, so instruction, precedence, cwd, candidate, or byte-budget changes affect cache reuse from that history position.
|
||||
|
||||
### Newly discovered scope context
|
||||
|
||||
|
||||
Reference in New Issue
Block a user