ds4-context-engine 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,7 +16,9 @@ bounded active context with provenance
16
16
  Pi provider
17
17
  ```
18
18
 
19
- > **Project status:** Stable `0.2.0` includes M0–M20 and frozen 0.2 contracts. Published prerelease `0.3.0-beta.2` carries forward beta.1's bounded Context Manifest storage, cooperative database leases, metadata-only storage diagnostics, and explicit offline inspect/compact/recover maintenance after successful live maintenance validation. Canonical records, SQLite schema 15, and runtime contracts remain unchanged. npm `beta` points to `0.3.0-beta.2`, `alpha` remains `0.3.0-alpha.5`, `latest` remains `0.2.0`, and the maintenance line targets Pi `0.84.3`.
19
+ > **Project status:** The coordinated `0.3.5` release adds bounded compaction optimizations: one validated previous-summary/source update when the complete prompt fits, a dedicated calibrated input budget, up to two concurrent segments, and metadata-only phase timings in `/context compaction`. Canonical history, SQLite schema 15 and runtime contracts remain unchanged; Pi remains pinned to `0.84.3`. See the [0.3.5 release record](docs/releases/0.3.5.md) for validation and publication status.
20
+
21
+ **New compaction defaults:** `compaction.directUpdate=true`, `compaction.inputBudget="summary"`, `compaction.maxConcurrentSegments=2`. Existing compaction/master switches still apply. See [latency controls and compatibility](docs/COMPACTION.md#latency-controls) for the legacy-path settings. No real-provider speedup is claimed from mock tests. The five optional editing/reading/artifact/job features introduced in `0.3.4` remain default-off.
20
22
 
21
23
  ## Why DS4
22
24
 
@@ -87,12 +89,12 @@ Install the latest stable public npm package with:
87
89
  pi install npm:ds4-context-engine
88
90
  ```
89
91
 
90
- The stable `0.2.0` packages (`ds4-context-engine`, `ds4-context-core`, and `ds4-context-reference-adapter`) use the same exact version. Both adapters require the matching core version.
92
+ The three packages (`ds4-context-engine`, `ds4-context-core`, and `ds4-context-reference-adapter`) are released together with the same exact version. Both adapters require the matching core version. See [0.3.5](docs/releases/0.3.5.md) for this release's changes and compatibility.
91
93
 
92
94
  To dogfood the beta without replacing a global stable installation, pin the exact version in a disposable project:
93
95
 
94
96
  ```bash
95
- pi install -l npm:ds4-context-engine@0.3.0-beta.2
97
+ pi install -l npm:ds4-context-engine@0.3.0-beta.3
96
98
  ```
97
99
 
98
100
  Follow the [0.3 beta dogfooding runbook](docs/DOGFOODING_0.3.0_BETA.md); use synthetic data and a dedicated session directory.
@@ -209,7 +211,7 @@ Valid privacy classifications are `normal`, `internal`, `sensitive` and `local-o
209
211
 
210
212
  ### LLM-callable tools
211
213
 
212
- DS4 registers two model-callable tools:
214
+ DS4 registers two model-callable tools by default:
213
215
 
214
216
  | Tool | Purpose |
215
217
  | --- | --- |
@@ -220,6 +222,24 @@ DS4 registers two model-callable tools:
220
222
 
221
223
  Canonical Pin and Memory changes append Pi custom entries and reconcile disposable SQLite projections. Project-memory source include/exclude is derived local SQLite policy and never appends a fake canonical entry. See [`docs/CONTEXT_PERSISTENCE_TOOL.md`](docs/CONTEXT_PERSISTENCE_TOOL.md).
222
224
 
225
+ **Optional anchored editing:** `/context config set editing.anchored true`, then
226
+ `/reload`, enables a same-name `edit` wrapper. Use `head[upto]tail` in `oldText` to
227
+ replace an inclusive range without repeating its intermediate old content. Exact
228
+ anchors are validated inside Pi's native mutation queue; ordinary calls and diff
229
+ feedback remain native. All edits in a batch containing anchors must match exactly.
230
+ Use per-edit `literal: true` for real marker text.
231
+ Disabled by default; no inference-time forcer or provider change is involved.
232
+ See [`docs/ANCHORED_EDITING.md`](docs/ANCHORED_EDITING.md) for semantics and limits.
233
+
234
+ **Other optional agent tools:** `editing.postEditReport` adds bounded old/new line
235
+ ranges and updated context; `reading.adaptive` chooses model-window-aware default
236
+ read limits; `artifacts.adaptiveBudget` lowers inline/excerpt caps under context
237
+ pressure; `jobs.enabled` exposes confirmed, session-owned local `bash_job`
238
+ start/status/stop/list operations. All default off and require session reload after
239
+ configuration changes. Jobs are a separate module, not a replacement for `bash`.
240
+ See [`docs/PORTABLE_AGENT_TOOLS.md`](docs/PORTABLE_AGENT_TOOLS.md) for activation,
241
+ limits, privacy and lifecycle behavior.
242
+
223
243
  Learned-ranking feedback and local training are explicit:
224
244
 
225
245
  ```text
@@ -250,6 +270,16 @@ The following example shows the main configuration groups. Omitted values use th
250
270
  "maxProjectTokens": 20000,
251
271
  "maxSummaryTokens": 12000
252
272
  },
273
+ "editing": {
274
+ "anchored": false,
275
+ "postEditReport": false
276
+ },
277
+ "reading": {
278
+ "adaptive": false
279
+ },
280
+ "jobs": {
281
+ "enabled": false
282
+ },
253
283
  "retrieval": {
254
284
  "exact": true,
255
285
  "fts": true,
@@ -287,6 +317,7 @@ The following example shows the main configuration groups. Omitted values use th
287
317
  },
288
318
  "artifacts": {
289
319
  "enabled": true,
320
+ "adaptiveBudget": false,
290
321
  "maxInlineToolResultChars": 12000,
291
322
  "maxArtifactBytes": 100000000,
292
323
  "maxSearchBytes": 50000000,
@@ -299,7 +330,10 @@ The following example shows the main configuration groups. Omitted values use th
299
330
  "mode": "hierarchical",
300
331
  "validate": true,
301
332
  "segmentTargetTokens": 30000,
302
- "preserveRecentVerbatim": true
333
+ "preserveRecentVerbatim": true,
334
+ "directUpdate": true,
335
+ "inputBudget": "summary",
336
+ "maxConcurrentSegments": 2
303
337
  },
304
338
  "privacy": {
305
339
  "enabled": false,
@@ -477,8 +511,15 @@ scripts package and release-readiness checks
477
511
  - [Roadmap 0.2.0](docs/ROADMAP_0.2.0.md)
478
512
  - [Release process](docs/RELEASING.md)
479
513
  - [0.2.0 release readiness](docs/RELEASE_READINESS_0.2.0.md)
514
+ - [0.3.5 release notes](docs/releases/0.3.5.md)
515
+ - [0.3.4 release notes](docs/releases/0.3.4.md)
516
+ - [0.3.3 release notes](docs/releases/0.3.3.md)
517
+ - [0.3.2 release notes](docs/releases/0.3.2.md)
518
+ - [0.3.1 release notes](docs/releases/0.3.1.md)
519
+ - [0.3.0 release notes](docs/releases/0.3.0.md)
480
520
  - [0.2.0 release notes](docs/releases/0.2.0.md)
481
521
  - [0.2.0-rc.1 release notes](docs/releases/0.2.0-rc.1.md)
522
+ - [0.3.0-beta.3 prerelease notes](docs/releases/0.3.0-beta.3.md)
482
523
  - [0.3.0-beta.2 prerelease notes](docs/releases/0.3.0-beta.2.md)
483
524
  - [0.3.0-beta.1 prerelease notes](docs/releases/0.3.0-beta.1.md)
484
525
  - [0.3.0-alpha.5 prerelease notes](docs/releases/0.3.0-alpha.5.md)
@@ -493,7 +534,7 @@ scripts package and release-readiness checks
493
534
 
494
535
  The original M0–M13 roadmap is complete. `ds4-context-core` contains the compiled runtime-neutral implementation. M14 context-quality metrics, M15 rich symbol indexing, M16 hybrid semantic retrieval, M17 cross-session project memory, M18 learned-ranking shadow evaluation, M19's runtime adapter/conformance kit, and M20 opt-in local KV eligibility/replay are implemented on `main`. Learned active ranking remains promotion-gated, Pi reports local KV as unsupported, and static ranking/native completion stay authoritative on every failure.
495
536
 
496
- The [0.2.0 roadmap](docs/ROADMAP_0.2.0.md) is complete. Published prerelease `0.3.0-beta.2` carries forward the [context persistence tool](docs/CONTEXT_PERSISTENCE_TOOL.md), privacy-safe [compaction](docs/COMPACTION.md) hardening, bounded persisted manifests, per-profile calibration retention, cooperative client leases, storage diagnostics, and recoverable offline maintenance, with successful live maintenance evidence from beta.1. Confirmation, exact targeting, strict summary grounding, Pi fallback, and stable canonical/configuration/SQLite/runtime contracts remain unchanged. The [0.2 readiness record](docs/RELEASE_READINESS_0.2.0.md) remains the compatibility baseline. Sensitive or transport-specific behavior remains opt-in, and the 0.1 lexical planner stays available as the deterministic fallback.
537
+ The [0.2.0 roadmap](docs/ROADMAP_0.2.0.md) is complete. The stable 0.3 line carries forward the [context persistence tool](docs/CONTEXT_PERSISTENCE_TOOL.md), privacy-safe [compaction](docs/COMPACTION.md), bounded persisted manifests, cooperative client leases and recoverable offline maintenance. Version 0.3.5 adds bounded compaction updates, summary input headroom, concurrent segments and phase timings. The opt-in [anchored editing](docs/ANCHORED_EDITING.md) and [portable agent tools](docs/PORTABLE_AGENT_TOOLS.md) from 0.3.4 remain default-off, without backend rewind, forced sampling or operational KV integration. Confirmation, provenance, Pi fallback and canonical/configuration/SQLite/runtime contracts remain unchanged. The [0.2 readiness record](docs/RELEASE_READINESS_0.2.0.md) remains the compatibility baseline; the lexical planner stays available as the deterministic fallback.
497
538
 
498
539
  ## Contributing
499
540
 
@@ -0,0 +1,51 @@
1
+ # ADR 059 — Optional execution-time anchored editing
2
+
3
+ Status: Accepted
4
+
5
+ ## Context
6
+
7
+ DS4's standalone agent supports `head[upto]tail` replacement and an optional
8
+ inference-time marker forcer. The former is portable tool semantics; the latter
9
+ requires backend control over generation state. Pi already provides batch edits,
10
+ mutation serialization, normalization, cancellation and diff feedback.
11
+
12
+ ## Decision
13
+
14
+ - Add the default-off `editing.anchored` configuration key, gated by the DS4 master
15
+ switch and loaded through existing trusted configuration handling.
16
+ - Register a same-name `edit` override only when enabled and native edit is not
17
+ already replaced. Do not introduce an additional tool or force tool activation.
18
+ - Support one marker between nonempty, non-whitespace exact anchors, inclusive
19
+ replacement and suffix-only tail uniqueness. Add per-edit `literal: true` to
20
+ escape real marker text. Preserve native edit behavior without marker syntax.
21
+ - Expand anchors using private copies inside native `EditOperations.readFile`,
22
+ under Pi's own mutation queue, then delegate batch validation and writing to
23
+ `createEditToolDefinition`. Do not copy Pi's editing implementation or expand in
24
+ an unqueued `tool_call` hook.
25
+ - Require exact matching for all edits in a batch containing anchors: native
26
+ fuzzy fallback changes the whole batch's coordinate space and can relocate an
27
+ exact anchored span. Marker-free calls retain native fuzzy behavior.
28
+ - Keep native normalized ambiguity checks, even when stricter than exact anchor
29
+ validation. Invalid anchored edits fail closed, without a fallback to a broader
30
+ match. ADR 008's context-planning fallback is not permission to guess file edits.
31
+ - Preserve native diff results and append compact original-line range metadata.
32
+ Skip marker-unaware native previews until the actual result diff is available.
33
+ - Restore the native definition if the same extension instance later loads
34
+ disabled configuration; a fresh disabled load registers no editing override.
35
+
36
+ ## Consequences
37
+
38
+ No provider change, Pi fork or dependency upgrade is required for execution-time
39
+ anchors. Only the configuration shape enters the portable core; filesystem/tool
40
+ integration stays in the Pi extension. Existing configuration defaults otherwise
41
+ remain unchanged, and the additive compatibility golden is updated.
42
+
43
+ The adapter relies on native edit reading through the supplied operations before
44
+ matching the provided edit array, while holding its shared queue. Tests against
45
+ Pi 0.84.3 pin that ordering and cancellation behavior. Dependency upgrades must
46
+ re-run these contracts. Other edit overrides, external editors/processes, atomic
47
+ writes and backend generation-state control remain outside the guarantee.
48
+
49
+ See [usage and limitations](../ANCHORED_EDITING.md). No automatic marker forcer,
50
+ constrained grammar, live configuration activation or measured provider savings
51
+ are part of this decision.
@@ -0,0 +1,38 @@
1
+ # ADR 060 — Optional portable DS4 agent improvements
2
+
3
+ Status: accepted and implemented; included in the coordinated 0.3.4 release. Backend integration remains excluded.
4
+
5
+ ## Boundary
6
+
7
+ Pi remains the agent runtime and JSONL remains canonical history. These changes add optional tool behavior, not another agent loop. No inference rewind, forced sampling, native KV handles, database migration or dependency upgrade is included.
8
+
9
+ Four independently default-off switches, also gated by `enabled`:
10
+
11
+ - `editing.postEditReport`: bounded report from the actual native unified patch, including changed old/new ranges, line delta and post-edit context. Works independently of `editing.anchored`.
12
+ - `reading.adaptive`: choose the default `read` limit from the execution-time model window (120 / 240 / 500 lines at <=8192 / <=16384 / larger windows). Explicit limits, images, cancellation and native byte caps remain native. No shared `more` cursor.
13
+ - `artifacts.adaptiveBudget`: lower inline/excerpt limits according to the calibrated context budget after output/safety reserves and estimated fixed overhead. Never increase configured limits. Preserve minimum reference metadata, canonical results, provenance, privacy, atomic tool groups and normal planner fallback. This is an estimate, not a guaranteed fit or reentrant compaction trigger.
14
+ - `jobs.enabled`: opt-in `bash_job` module in this repository, separate from the portable core and existing `bash`. A single start/status/stop/list tool manages local jobs through Pi's public local BashOperations.
15
+
16
+ ## Editing and reading
17
+
18
+ Do not take over missing or visibly overridden native tools. Restore native definitions when an already-enabled instance loads disabled configuration. Fresh disabled loads register no override. Render the real native diff; report failures must not turn a successful write into an apparent failure. Limit report regions, context and escaped text; never re-read a potentially changed file for reporting. Retain ADR 059's strict mixed-anchor batch behavior.
19
+
20
+ ## Adaptive artifacts
21
+
22
+ Use provider-facing privacy-sanitized messages only, and the same calibrated model budget as the planner. Keep all configured storage/search caps. Share the available text budget among candidate results, with a metadata floor bounded by the configured maximum. Missing/invalid budget data keeps static behavior. Rebuild uses the conservative adaptive floor so artifacts from earlier small-budget contexts remain reconstructible. No policy values or canonical messages are mutated. Do not replace a result with a larger estimated result.
23
+
24
+ ## Local jobs
25
+
26
+ - Require an active built-in local `bash`, no conflicting `bash_job`, explicit configuration, project trust, and local UI confirmation for every start. No UI means no start. This separate tool does not inherit other extensions' bash-only permission policies or SDK shell options.
27
+ - Use opaque job IDs, not arbitrary PIDs. Owner is the current session plus the originating branch entry. Status/stop/list never cross that boundary.
28
+ - At most 4 concurrent jobs and 16 retained records. Default timeout 300 seconds, hard maximum 3600 seconds. Cap each log at 8 MiB and stop on overflow or log-write failure.
29
+ - Logs are raw local files in a private temporary directory, not SQLite state. Responses contain bounded quoted head/tail excerpts and a local output path. Keep completed jobs until bounded eviction or session cleanup; explicitly report output truncation and stop reason.
30
+ - Cancellation while start is pending stops the new job. Once start returns, the job has an independent lifetime; cancelling status does not stop it. Stop and shutdown use native process-tree cancellation. No promise of rollback, daemon ownership or restart survival.
31
+ - Stop jobs and remove temporary logs on session replacement, reload and shutdown. Stop jobs made invisible by branch navigation. Compaction preserves processes and appends a bounded canonical metadata snapshot without launching another model turn; status must be refreshed before acting on old snapshots.
32
+ - Completion marks project indexing dirty. Never launch automatic follow-up turns.
33
+
34
+ ## Verification
35
+
36
+ Cover independent opt-ins/default-off paths, legacy missing config, argument mutation, native matching and line endings, explicit read limits/images/model changes, estimated budgets/provenance/privacy/rebuild, process caps/timeouts/aborts/ownership/cleanup/compaction, and clean-consumer packaging. Linux process tests do not establish successful Windows execution; the native Pi backend owns platform-specific process handling.
37
+
38
+ Implementation validation: `npm run check` passed 78 files / 485 tests, including real local bash execution, stop and timeout. Quality comparison and persistence schema gates passed. Clean-consumer packaging passed with Pi 0.84.3 and eight real registry scenarios (core 231 files, reference adapter 7, engine 83). Packaging used a staging copy with the canonical root package manifest; pre-existing user `package.json` changes and `.serena/` were excluded. No provider-token/latency benchmark or Windows execution claim is made. That implementation validation preceded release preparation. See the [0.3.4 release record](../releases/0.3.4.md) for final release gates and publication evidence.
@@ -0,0 +1,21 @@
1
+ # ADR-061 — Bounded compaction latency optimizations
2
+
3
+ Status: accepted and implemented; coordinated 0.3.5 publication explicitly authorized by the user on 2026-09-05. See the [release record](../releases/0.3.5.md) for validation and publication evidence.
4
+
5
+ ## Context
6
+
7
+ Pi normally updates previous summary plus discarded messages in one request. DS4 previously always summarized new segments first and then generated an aggregate when a predecessor existed. Independent segments ran sequentially and summary inputs reused the ordinary context fill target. This can add avoidable calls; provider latency and summary quality still require controlled real-provider comparisons.
8
+
9
+ ## Decision
10
+
11
+ - Add `compaction.directUpdate` (default true): estimate the complete sanitized update prompt, including previous summary, cumulative file evidence, custom instructions, split-turn instructions and output contract. If it fits, generate and validate one immutable `task-state` node with the predecessor as a child, canonical source IDs from both spans, and a hash bound to both the new source and predecessor identity/hash/content. With no predecessor, the existing one-segment path remains sufficient. Oversized updates use bounded hierarchical segmentation/aggregation, not truncation.
12
+ - Add `compaction.inputBudget` (`summary` default, or `context`). Summary mode uses the calibrated hard input limit instead of the ordinary active/fill target. Additionally bound input by model context minus safety margin and the actual summary output cap, converted to estimator units. Do not raise model/configured hard limits, remove calibration, reduce safety margins, or reuse ordinary context space reserved for output. Context mode preserves the old active target (also respecting actual summary output headroom).
13
+ - Add `compaction.maxConcurrentSegments` (default 2, range 1–2). Only independent segment requests overlap. Allocate identities and assemble graph/usage in source order, not completion order. On failure stop scheduling, abort siblings, await their settlement before fallback, and persist no partial graph. Cancellation remains cooperative; provider work already accepted may still be billed. Aggregation remains ordered and bounded.
14
+ - Expose process-local metadata-only timings for preparation, segment/direct generation, aggregation, graph preparation/persistence and total DS4 hook duration, plus chosen path, effective provider/model, direct prompt size and concurrency. Timings use a monotonic clock, include retries and local validation within generation, and are wall times (not sums of overlapping calls). They are not canonical evidence, are not persisted in JSONL, and do not measure a subsequent native Pi fallback.
15
+ - Preserve schema-v2 compaction metadata, the existing `task-state` kind, source/classification/validation contracts, Pi cut points, atomic tool exchanges, fresh routing IDs, retry policy, canonical JSONL and all-or-nothing graph preparation. `segmentSummaryId` refers to the update node itself on a direct update; do not invent a segment that was never generated.
16
+
17
+ ## Compatibility and validation
18
+
19
+ Configuration is additive; absent fields use the new defaults. The old scheduling path can be compared using `directUpdate=false`, `inputBudget=context`, and `maxConcurrentSegments=1`. The compaction master switch still delegates to Pi when disabled. The original local implementation excluded versioning and publication; the user subsequently authorized the coordinated 0.3.5 release. No dependency upgrade, live database maintenance or Pi upgrade is part of this change.
20
+
21
+ Cover exact budget boundaries and calibration/output reserve, predecessor-only exact evidence and privacy, native predecessor warnings, source ordering/provenance/hash/rebuild, bounded concurrency and out-of-order completion, cancellation/failure/transport retry drainage, and no graph persistence before full success. Retain hierarchical regression coverage explicitly even where default routing now chooses a direct update. Automated call counts and synchronization tests establish scheduling behavior, not a promise of 40-second compactions or unchanged semantic quality on real provider outputs.
@@ -62,5 +62,8 @@ The initial decisions from the development plan are accepted:
62
62
  | 056 | Keep project-memory source exclusion as disposable derived SQLite policy | Accepted |
63
63
  | 057 | Derive mutation provenance from the active branch and exclude model-supplied source IDs from V1 | Accepted |
64
64
  | [058](058-bounded-manifest-storage.md) | Bound persisted manifests and defer compression to a versioned migration | Accepted |
65
+ | [059](059-optional-anchored-editing.md) | Opt in to anchored edit expansion inside Pi's native mutation queue | Accepted |
66
+ | [060](060-optional-portable-agent-tools.md) | Opt in to edit reports, adaptive reads/results and session-owned local jobs | Accepted |
67
+ | [061](061-compaction-latency.md) | Bound compaction update calls, input budgets, concurrent segments and phase timings | Accepted |
65
68
 
66
69
  Each decision will receive a dedicated record when implementation pressure introduces alternatives or consequences not already covered by the development plan.
@@ -0,0 +1,144 @@
1
+ # Optional anchored editing
2
+
3
+ DS4 can replace Pi's native `edit` definition with a compatible, opt-in wrapper.
4
+ This is execution-time editing, not a generation-time forcer. It works with any
5
+ provider that can call Pi tools; it does not require a custom inference backend.
6
+
7
+ ## Enable or disable
8
+
9
+ Default: `editing.anchored: false`. In a trusted project:
10
+
11
+ ```text
12
+ /context config set editing.anchored true
13
+ /reload
14
+ ```
15
+
16
+ Use `--global` on the config command to target the agent-directory configuration.
17
+ As with other DS4 config writes, the active session configuration is not changed
18
+ by `set`/`unset`; it is loaded on session startup/replacement or reload.
19
+
20
+ When updating a development checkout, rebuild the core (`npm run build:core`)
21
+ and fully restart Pi. Reloading extension TypeScript can retain previously loaded
22
+ compiled core modules. A core configuration without `editing` safely leaves this
23
+ feature disabled, but the updated core must be loaded to recognize the option.
24
+
25
+ ```json
26
+ {
27
+ "editing": { "anchored": true }
28
+ }
29
+ ```
30
+
31
+ `enabled: false` also disables this override. `context.mode` still controls context
32
+ planning, not explicitly opted-in editing. Project configuration is ignored when
33
+ Pi does not trust the project; global configuration continues to apply.
34
+
35
+ On a fresh disabled load DS4 does not register `edit` at all. If an enabled
36
+ extension instance later loads disabled configuration, it restores Pi's native
37
+ edit definition, schema and guidelines. Pi has no public `unregisterTool`; a
38
+ reload while disabled removes DS4's registration entirely.
39
+
40
+ Registration is skipped if native `edit` is unavailable or already owned by
41
+ another extension/SDK tool, with a UI warning when available. DS4 does not enable
42
+ an inactive native edit tool. Only ownership reported by Pi can be checked: SDK
43
+ custom base tools labeled as built-ins cannot be distinguished through this API.
44
+ Do not enable this feature with other edit replacements or remote/sandbox edit
45
+ implementations; the registered wrapper uses local filesystem operations.
46
+
47
+ ## Tool input
48
+
49
+ The tool name remains `edit`, with `path` and `edits[]`:
50
+
51
+ ```json
52
+ {
53
+ "path": "src/example.ts",
54
+ "edits": [{
55
+ "oldText": "function oldImplementation() {\n[upto]\n}\n// end oldImplementation\n",
56
+ "newText": "function oldImplementation() {\n return improved();\n}\n// end oldImplementation\n"
57
+ }]
58
+ }
59
+ ```
60
+
61
+ Each anchored `oldText` must contain exactly one `[upto]` marker:
62
+
63
+ - The **head**, before the marker, must occur exactly once in the original file.
64
+ - The **tail**, after the marker, must occur exactly once after the end of the
65
+ head. The same tail occurring before the head is allowed.
66
+ - Both anchors must contain non-whitespace text. Spaces are significant.
67
+ - Newlines immediately after the marker are separators and are removed from the
68
+ tail needle, matching DS4's marker-line convention. Other whitespace is not
69
+ trimmed. Anchors may be inline; full distinctive lines are usually clearer.
70
+ - Replacement is **inclusive**: head, intermediate content and tail are all
71
+ replaced. Put anchors in `newText` if they should be retained.
72
+ - All edits refer to the original file, not previous replacements in the batch.
73
+ Overlaps (including mixed ordinary/anchored overlaps), missing or ambiguous
74
+ anchors and no-op batches are rejected without a write.
75
+ - No 64-byte/two-line threshold applies to manually supplied anchors; those DS4
76
+ thresholds belong to automatic marker forcing, which is not implemented here.
77
+
78
+ A marker is special only in `oldText`, never in `newText`. To match literal marker
79
+ text, set `literal: true` **on that edit**:
80
+
81
+ ```json
82
+ {
83
+ "path": "notes.md",
84
+ "edits": [{ "oldText": "[upto]", "newText": "range marker", "literal": true }]
85
+ }
86
+ ```
87
+
88
+ Calls without anchored edits use native Pi matching, including its normalized
89
+ fuzzy fallback (also for `literal: true` edits). **Every edit in a batch containing
90
+ anchors must match exactly**, ordinary edits included. Pi's fuzzy fallback changes
91
+ the coordinate space for the entire batch and can relocate an otherwise exact
92
+ anchored span; mixed fuzzy batches therefore fail closed. Re-read and correct the
93
+ ordinary oldText, or perform the ordinary edit separately and re-read before
94
+ anchored edits.
95
+
96
+ Exact matching uses BOM-free, LF-normalized original content. Expanded spans then
97
+ pass through native batch checks, including Pi's stricter normalized-ambiguity
98
+ check. Thus a range can be rejected even if its literal anchors pass, when the
99
+ expanded text is ambiguous under Pi's normalization. Re-read and disambiguate;
100
+ do not silently broaden the range.
101
+
102
+ ## Execution and feedback
103
+
104
+ `src/extension/anchored-edit-tool.ts` uses public Pi `createEditToolDefinition` and
105
+ `EditOperations`. Its per-call read operation expands anchors only after native
106
+ edit has entered `withFileMutationQueue`. Native edit then performs batch matching,
107
+ overlap checks, cancellation checks, BOM/line-ending restoration, writing and
108
+ diff generation. It is deliberately **not** a `tool_call` preflight expansion:
109
+ there is no unqueued read followed by a later queued write.
110
+
111
+ Expansion uses private argument copies: the full old span is not inserted into
112
+ canonical tool-call arguments or sent back as an expanded input. Native results
113
+ and diff/patch details remain available, plus a compact text report of anchored
114
+ ranges in original line numbers. Canonical result details can still contain old
115
+ text through native diffs; this is not an output privacy/filtering feature.
116
+
117
+ The native TUI matcher cannot preview marker syntax. Anchored calls therefore
118
+ skip its pre-execution preview and display the **actual native result diff** after
119
+ execution. Ordinary edits retain their native preview behavior.
120
+
121
+ The shared Pi queue serializes cooperating edits/writes in the same runtime,
122
+ including symlink aliases. It is not a cross-process file lock, transaction with
123
+ external editors, atomic filesystem write or rollback guarantee. Cancellation
124
+ retains native behavior; once a write has begun it may still complete. Existing
125
+ `tool_call`/`tool_result` hooks continue to see the `edit` tool, not a new tool name.
126
+ Hooks that interpret `oldText` themselves must understand the opt-in syntax.
127
+
128
+ ## Scope and verification
129
+
130
+ The implementation targets the project's Pi **0.84.3** dependency. No dependency
131
+ upgrade, provider grammar, streaming interception, KV rewind or forced continuation
132
+ is included. The reference adapter does not implement this Pi editing capability.
133
+
134
+ Tests cover exact matching, suffix-only tail uniqueness, overlapping matches,
135
+ malformed markers, literal escaping, mixed batches, fuzzy-fallback compatibility,
136
+ BOM/CRLF/Unicode, unchanged arguments, result diffs, cancellation, queue ordering,
137
+ symlinks, permissions, working-directory selection and session-bound registration.
138
+ The package smoke check also inspects the real Pi tool registry offline with the
139
+ flag absent, enabled, master-disabled and enabled while native edit is inactive
140
+ or unavailable.
141
+ A deterministic large-block fixture compares serialized request bytes and verifies
142
+ the same output as native editing. Real-provider reliability, token usage and
143
+ latency improvements have **not** been measured; shorter oldText can save generated
144
+ text, while the opt-in tool schema/instructions also add prompt overhead.
@@ -165,6 +165,44 @@ Pin and Memory mutations resolve provenance from the active Pi branch, revalidat
165
165
 
166
166
  Historical tool arguments and results are a provider-egress surface even when general privacy is disabled. A dedicated guard removes content, query, key, reason, paths and raw errors while preserving only action linkage and safe IDs/revisions. Provenance IDs and source paths are runtime-derived; the model cannot supply them in the V1 schema. Post-append failures distinguish an indeterminate append from a known canonical commit with pending projection, so callers are never encouraged to retry blindly.
167
167
 
168
+ ## Optional anchored editing
169
+
170
+ After trusted configuration loads at `session_start`, `editing.anchored` can
171
+ register a same-name native `edit` wrapper (default off, also gated by `enabled`).
172
+ The Pi extension resolves exact `head[upto]tail` ranges inside the native edit read
173
+ operation, under Pi's shared file mutation queue, then delegates batch checks,
174
+ cancellation, BOM/EOL handling, writing and diffs to native edit. Expansion never
175
+ rewrites canonical tool-call arguments. Calls without anchors retain native fuzzy
176
+ matching; every edit in an anchored batch must match exactly to prevent native
177
+ fuzzy rematching from moving its ranges. Invalid anchored edits fail closed.
178
+ No inference-state control is involved.
179
+ See [anchored editing](ANCHORED_EDITING.md) and [ADR 059](ADR/059-optional-anchored-editing.md).
180
+
181
+ ## Other optional portable agent tools
182
+
183
+ `editing.postEditReport` derives bounded old/new coordinates, line deltas and
184
+ updated context from the actual native patch, independently of anchored editing.
185
+ `reading.adaptive` wraps native read with execution-time model-aware default line
186
+ limits; explicit limits and image/byte handling stay native. Both wrappers restore
187
+ native definitions when disabled and do not initially claim other tool overrides.
188
+
189
+ `artifacts.adaptiveBudget` is a pure portable-core policy applied per context to
190
+ privacy-prepared messages. It uses the calibrated planner budget and estimated
191
+ fixed overhead to lower inline/excerpt caps, never raises configured limits, and
192
+ preserves source identities and canonical history. Rebuild uses a conservative
193
+ floor to reconstruct earlier adaptive references. Normal planner fallback remains
194
+ responsible for oversized mandatory context.
195
+
196
+ `src/tools/bash-job-manager.ts` and `src/extension/bash-job-tool.ts` form a separate
197
+ optional local job module. It delegates process execution/cancellation to Pi's
198
+ public BashOperations, limits concurrency/log storage, enforces session/branch
199
+ ownership and requires local confirmation for starts. Jobs survive compaction but
200
+ not session replacement/reload/shutdown; compaction appends bounded metadata only.
201
+ There is no new agent loop, SQLite state, automatic model turn or backend KV control.
202
+
203
+ All new switches are default-off and master-gated. See [portable agent tools](PORTABLE_AGENT_TOOLS.md)
204
+ and [ADR 060](ADR/060-optional-portable-agent-tools.md).
205
+
168
206
  ## Boundaries
169
207
 
170
208
  Dependency direction is one-way:
@@ -6,14 +6,14 @@ DS4 intercepts Pi's `session_before_compact` event but preserves Pi's cut-point
6
6
 
7
7
  1. Pi determines `messagesToSummarize`, optional split-turn prefix, and `firstKeptEntryId`.
8
8
  2. DS4 maps every source message by exact fingerprint to a canonical branch entry ID.
9
- 3. Pi's serializer converts the newly discarded span to bounded conversation text; enabled privacy policy sanitizes conversation, custom instructions, and file paths for the active provider.
10
- 4. DS4 estimates the complete sanitized summary request against the calibrated active-model input budget. If the whole request does not fit, it partitions the source into ordered contiguous segments. Individual messages are indivisible, and every tool call remains in the same atomic group as all matching results; an exceptionally large turn may split only between such groups.
11
- 5. The active model generates and validates each immutable segment summary only against that segment's sanitized evidence, with cache retention disabled and a fresh routing session ID per call. Only failures categorized as `transport` are retried: at most two retries follow bounded 200 ms and 500 ms delays, each with another fresh routing session ID. The delay and the next call both honor Pi's abort signal; all other failure categories fall back immediately.
12
- 6. DS4 recursively aggregates ordered child summaries in bounded fan-in calls until one root remains. The ordered roots include the previous branch summary, when present, followed by every new segment. DS4-generated IDs, hashes, kinds, and graph levels remain outside model-visible evidence. A Pi-native predecessor is imported as an explicitly unverified branch node.
13
- 7. The highest input classification is wrapped around each generated node, then all nodes are persisted atomically as one `prepared` graph batch. Usage is summed across every segment and aggregate request; Pi receives only the final root text and still appends exactly one canonical `CompactionEntry` with `fromHook: true`.
9
+ 3. Pi's serializer converts the newly discarded span to bounded conversation text; enabled privacy policy sanitizes conversation, previous summary, custom instructions, and file paths for the effective compaction provider (dedicated model when configured and eligible).
10
+ 4. DS4 estimates the **complete** sanitized request against a calibrated summary-specific input budget, including framing, instructions, file inventories and output contract. With `compaction.directUpdate` enabled, a previous summary plus new source that fits is updated and validated in **one call**, producing an immutable `task-state` node. No predecessor needs only the existing one-segment call. Oversized updates fall through to hierarchical planning; no additional source is truncated to force a fit.
11
+ 5. The hierarchical path partitions oversized source into ordered contiguous segments. Individual messages are indivisible, and every tool call remains in the same atomic group as all matching results. Up to `compaction.maxConcurrentSegments` independent segment requests run concurrently (default 2). Each summary is validated against only its own sanitized evidence. Identities, source/child order and usage accumulation follow source order, not completion order. Cache retention stays disabled and every attempt has a fresh routing session ID.
12
+ 6. Hierarchical requests recursively aggregate ordered children until one root remains: previous branch summary first, then new segments. Aggregation stays sequential and budget-checked. Direct updates instead link their single node to the predecessor and new canonical source IDs, without generating a synthetic segment or a separate aggregate. DS4-generated IDs, hashes, kinds, and graph levels never become model-visible evidence. A Pi-native predecessor is imported as an explicitly unverified branch node.
13
+ 7. The highest input classification wraps each generated node, then all nodes are persisted atomically as one `prepared` graph batch. Usage includes every returned direct/segment/aggregate request and transport replay. Pi receives only the final root text and still appends exactly one canonical `CompactionEntry` with `fromHook: true`.
14
14
  8. `session_compact` commits all nodes and associates the active root with the Pi entry; failure marks the complete prepared batch `failed`.
15
15
 
16
- Fan-out and fan-in are bounded to 32 segment requests, 64 aggregate requests, and 16 aggregate passes. Transport replay mirrors Pi's assistant retry policy: `compaction.transport.maxAttempts` (default 3) total attempts and `compaction.transport.baseDelayMs` (default 2000 ms, capped at 60 s) backoff, doubling per attempt, abort-aware. Replay never applies to input, usage, rate, authentication, validation, or output-limit failures. A base prompt, individual message, atomic tool exchange, pair of child summaries, or total operation that cannot fit within those limits fails closed. Any mapping, budget, model, output-limit, validation, abort, or storage error returns `undefined` from the hook, allowing Pi's default compaction to run.
16
+ Fan-out and fan-in are bounded to 32 segment requests, 64 aggregate requests, and 16 aggregate passes. The DS4 transport replay policy is: `compaction.transport.maxAttempts` (default 3) total attempts and `compaction.transport.baseDelayMs` (default 2000 ms, capped at 60 s) backoff, doubling per attempt, abort-aware. Replay never applies to input, usage, rate, authentication, validation, or output-limit failures. A base prompt, individual message, atomic tool exchange, pair of child summaries, or total operation that cannot fit within those limits fails closed. Any mapping, budget, model, output-limit, validation, abort, or storage error returns `undefined` from the hook, allowing Pi's default compaction to run. On segment failure or cancellation DS4 stops scheduling, aborts siblings and awaits all started workers before fallback; no partial graph is installed. Cancellation is cooperative: accepted provider work may still cost tokens, and a provider that ignores abort can delay settlement.
17
17
 
18
18
  ## Required summary contract
19
19
 
@@ -32,7 +32,7 @@ Fan-out and fan-in are bounded to 32 segment requests, 64 aggregate requests, an
32
32
  ## Critical Exact Values
33
33
  ```
34
34
 
35
- Every section must occur once, in order, and contain content or `- None`. DS4 replaces each unique `Files Read` and `Files Modified` section with one exact path per bullet from Pi's sanitized file-operation inventory before validation; missing or duplicate sections still fail. Backticked exact values must occur in the serialized segment source, ordered child-summary content, or those known file-operation paths. A bounded unsupported exact-value bullet is removed as a whole and recorded as a validation warning; unsupported prose, more than eight affected bullets, or removal above 25% still fails closed to Pi. The prompt explicitly asks the model to omit a bullet whose complete backticked span cannot be copied verbatim. Every segment is validated independently against only its own sanitized source and deterministic file inventory. Every aggregate is validated independently against only the sanitized content of its ordered children and cumulative deterministic file inventory. Any unrepaired failure prevents the whole graph batch from being installed.
35
+ Every section must occur once, in order, and contain content or `- None`. DS4 replaces each unique `Files Read` and `Files Modified` section with one exact path per bullet from Pi's sanitized file-operation inventory before validation; missing or duplicate sections still fail. Backticked exact values must occur in the serialized segment source, ordered child-summary content, or those known file-operation paths. A bounded unsupported exact-value bullet is removed as a whole and recorded as a validation warning; unsupported prose, more than eight affected bullets, or removal above 25% still fails closed to Pi. The prompt explicitly asks the model to omit a bullet whose complete backticked span cannot be copied verbatim. Every segment is validated independently against only its own sanitized source and deterministic file inventory. Every aggregate is validated independently against only the sanitized content of its ordered children and cumulative deterministic file inventory. A direct update is validated against both the sanitized previous summary and new source, plus cumulative sanitized file evidence; previous-summary-only exact values remain valid evidence. Custom focus instructions are not factual evidence. Any unrepaired failure prevents the whole graph batch from being installed.
36
36
 
37
37
  An unrepaired exact-value failure reports only the stage, issue code, categorical repair status, unsupported-span count, and affected-bullet count. Repair statuses distinguish an unsupported location, more than eight bullets, removal above 25%, and an unexpected invalid second validation. The disputed text is intentionally absent from logs, UI notifications, and diagnostics because it may contain sensitive source material.
38
38
 
@@ -49,7 +49,7 @@ An unrepaired exact-value failure reports only the stage, issue code, categorica
49
49
  - trigger, split-turn flag, source message count;
50
50
  - generation time, provider, and model.
51
51
 
52
- For aggregate compactions, details also embed every non-active node created by that operation. The active node content remains `CompactionEntry.summary`; prior ancestors remain in earlier canonical entries. SQLite stores content, ordered edges, direct/transitive sources, graph level, and lifecycle as a disposable projection. On resume or after deleting the database, DS4 replays Pi entries in append order and recreates the complete graph.
52
+ For aggregate and direct-update compactions, details also embed every non-active node created by that operation. Direct updates reuse the schema-v2 `task-state` kind, set `segmentSummaryId` to their own node ID, increment the predecessor's level, and include the union of previous/new canonical source IDs. Their hash binds the new source hash to predecessor ID, hash, content and level. Prior nodes are never rewritten; no unused segment is fabricated. The active node content remains `CompactionEntry.summary`; prior ancestors remain in earlier canonical entries. SQLite stores content, ordered edges, direct/transitive sources, graph level, and lifecycle as a disposable projection. On resume or after deleting the database, DS4 replays Pi entries in append order and recreates the complete graph.
53
53
 
54
54
  Memory and pin custom entries do not participate directly in Pi's LLM context and are not replaced by summary text. Their append-only mutations remain in the session tree, so durable decisions and explicit classifications replay after any number of compactions without depending exclusively on a summary.
55
55
 
@@ -78,9 +78,31 @@ Semantics:
78
78
  - `compaction.summary.thinking` defaults to `off` and applies only to summary requests: `off` keeps the pre-existing request shape (no thinking fields), while other levels map per API (`thinkingEnabled`/`effort` for `anthropic-messages`, `samplingParams.reasoning_effort` for OpenAI-compatible APIs) and are ignored for unsupported providers;
79
79
  - `context.maxSummaryTokens` remains a session-level limit and does not rise for the dedicated model; the minimum with the model's `maxTokens` still applies.
80
80
 
81
+ ## Latency controls
82
+
83
+ The coordinated `0.3.5` release introduces these additive defaults (absent from `0.3.4`):
84
+
85
+ ```json
86
+ {
87
+ "compaction": {
88
+ "directUpdate": true,
89
+ "inputBudget": "summary",
90
+ "maxConcurrentSegments": 2
91
+ }
92
+ }
93
+ ```
94
+
95
+ - `directUpdate`: one validated previous-summary plus new-source request when the entire prompt fits. Set false to always retain the segment-then-aggregate route. Validation or provider failures still fall back to Pi, not an unvalidated update.
96
+ - `inputBudget`: `summary` uses calibrated `hardInputLimit` rather than the ordinary context fill target (`activeInputBudget`). `context` selects that legacy fill target. Both are additionally capped by `(context window - safety margin - actual summary output cap) / calibration ratio`, rounded down, and never exceed the configured/model hard limit. Existing output reservations remain conservative; ordinary session planning and proactive thresholds are unchanged. This reduces avoidable fragmentation, not a guarantee of provider fit or faster processing for a larger prompt.
97
+ - `maxConcurrentSegments`: integer **1–2**, default 2. Only independent segments overlap, including their retries. Aggregates do not run until their children have completed. Use 1 for sequential execution or providers with restrictive concurrent-request limits. Rate-limit failures are not transport-retried.
98
+
99
+ For an old-path comparison set `directUpdate=false`, `inputBudget=context`, `maxConcurrentSegments=1`. All features remain behind the existing compaction/master switches. Settings are applied on session load; after upgrading the package or rebuilding a development checkout, fully restart Pi to avoid stale compiled-core modules. No schema migration is required.
100
+
101
+ Mock-provider regression tests verify fewer calls, bounded overlap, exact budget boundaries, validation, privacy, immutable provenance and JSONL rebuild. They do **not** establish real-provider wall-time gains, semantic equivalence of generated summaries, or a guaranteed completion time. See [ADR-061](ADR/061-compaction-latency.md).
102
+
81
103
  ## Transport retry policy
82
104
 
83
- Summary requests are replayed only for transport-classified failures (thrown transport errors or `stopReason: "error"` responses whose message matches network/timeout patterns). The replay policy defaults to Pi's assistant retry policy and can be tuned per deployment:
105
+ Summary requests are replayed only for transport-classified failures (thrown transport errors or `stopReason: "error"` responses whose message matches network/timeout patterns). The DS4 replay policy uses **three total attempts**, not three retries after the initial call, and can be tuned per deployment:
84
106
 
85
107
  ```json
86
108
  {
@@ -93,7 +115,7 @@ Summary requests are replayed only for transport-classified failures (thrown tra
93
115
  }
94
116
  ```
95
117
 
96
- - `compaction.transport.maxAttempts`: total attempts per segment or aggregate call, integer 1–10, default 3. With 1, no transport failure is retried.
118
+ - `compaction.transport.maxAttempts`: total attempts per direct update, segment or aggregate call, integer 1–10, default 3. With 1, no transport failure is retried.
97
119
  - `compaction.transport.baseDelayMs`: base backoff before the first replay, integer 0–60000, default 2000. The delay doubles per attempt (2000, 4000, 8000, …) and is capped at 60 s.
98
120
  - Replays use a fresh routing session per attempt; diagnostics expose only stage, failed/next attempt, max attempts, and delay.
99
121
  - Aborts (including during backoff) never trigger replay; non-transport failures are never retried; usage is summed across replayed responses.
@@ -117,4 +139,4 @@ It requests compaction at most once per session leaf. Pi's native threshold and
117
139
  /context summaries
118
140
  ```
119
141
 
120
- The preview reports thresholds and eligibility; Pi remains authoritative for the exact cut point. Runtime diagnostics additionally report the calibrated input budget, estimated whole-source prompt size, generated segment count, aggregate-call count, and completed transport-retry count without source content. Each retry emits only stage, failed/next attempt, maximum attempts, and delay at `debug`. Routine `summary_graph_prepared` and `summary_graph_committed` lifecycle events are also emitted only at `debug`; fallback, failure, persistence, and reconciliation problems remain actionable warnings. Oversized-group and bounded-operation errors expose only numeric budgets/counts, never rejected source text. Provider failures are reduced to metadata-only categories such as `input-limit`, `usage-limit`, `rate-limit`, `authentication`, or `transport`; raw provider error details are not logged or shown. The proactive-threshold TUI notification remains a user-visible `info` notice because it explains why an automatic compaction started.
142
+ The preview reports thresholds and eligibility; Pi remains authoritative for the exact cut point. Runtime diagnostics additionally report the effective provider/model, chosen path (`direct-update` or `hierarchical`), budget mode, calibrated input budget, estimated new-source and full-update prompt sizes, logical summary calls (excluding retries), generated segment count, aggregate-call count, concurrency cap, and completed transport-retry count without source content. Monotonic wall timings cover preparation, generation (direct or parallel segments, including validation and retries), aggregation, graph preparation/persistence, and total DS4 hook time. Parallel generation time is elapsed wall time, not a sum of overlapping requests. Total time excludes Pi's subsequent canonical append or native fallback. Timings survive the in-process commit/fallback notification, but are not persisted in JSONL or reconstructed as fake durations after restart. Metadata-only `compaction.timings` is emitted at `debug` for successful and failed attempts. Each retry emits only stage, failed/next attempt, maximum attempts, and delay at `debug`. Routine `summary_graph_prepared` and `summary_graph_committed` lifecycle events are also emitted only at `debug`; fallback, failure, persistence, and reconciliation problems remain actionable warnings. Oversized-group and bounded-operation errors expose only numeric budgets/counts, never rejected source text. Provider failures are reduced to metadata-only categories such as `input-limit`, `usage-limit`, `rate-limit`, `authentication`, or `transport`; raw provider error details are not logged or shown. The proactive-threshold TUI notification remains a user-visible `info` notice because it explains why an automatic compaction started.