pi-continual-harness 0.19.0 → 0.21.0
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/CHANGELOG.md +1010 -0
- package/README.md +112 -115
- package/dist/auto-refine.d.ts +9 -3
- package/dist/auto-refine.js +38 -7
- package/dist/classify.js +8 -2
- package/dist/config.d.ts +24 -6
- package/dist/config.js +88 -18
- package/dist/corpus.d.ts +31 -0
- package/dist/corpus.js +69 -32
- package/dist/durable.d.ts +13 -2
- package/dist/durable.js +48 -17
- package/dist/entry.d.ts +15 -1
- package/dist/entry.js +35 -13
- package/dist/harness.d.ts +4 -5
- package/dist/harness.js +287 -94
- package/dist/index.d.ts +5 -2
- package/dist/index.js +7 -4
- package/dist/inject.d.ts +7 -1
- package/dist/inject.js +35 -15
- package/dist/lessons.d.ts +65 -0
- package/dist/lessons.js +184 -0
- package/dist/lifecycle.js +10 -1
- package/dist/list.d.ts +14 -1
- package/dist/list.js +40 -12
- package/dist/outcome.js +7 -5
- package/dist/proposer.d.ts +16 -3
- package/dist/proposer.js +37 -11
- package/dist/refine.d.ts +1 -1
- package/dist/refine.js +110 -72
- package/dist/remind.js +2 -2
- package/dist/schemas.d.ts +28 -6
- package/dist/schemas.js +54 -12
- package/dist/select.d.ts +8 -5
- package/dist/select.js +65 -10
- package/dist/setup.js +80 -47
- package/dist/status.js +34 -32
- package/dist/store.d.ts +76 -14
- package/dist/store.js +430 -154
- package/dist/tools.d.ts +3 -0
- package/dist/tools.js +136 -16
- package/dist/types.d.ts +89 -1
- package/dist/types.js +24 -0
- package/harness.schema.json +156 -0
- package/package.json +14 -6
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,1010 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to **pi-continual-harness** are documented here. The format
|
|
4
|
+
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the
|
|
5
|
+
project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
Releases are tag-driven (`vX.Y.Z`) and published by GitHub Actions via npm
|
|
8
|
+
Trusted Publishing. This file begins at 0.7.0; earlier releases are recorded in
|
|
9
|
+
the git tags (`git tag -l`) and the [GitHub release history](https://github.com/pungggi/pi-continual-harness/releases).
|
|
10
|
+
|
|
11
|
+
## [Unreleased]
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **CI on every push/PR** (`.github/workflows/ci.yml`): typecheck + lint +
|
|
16
|
+
tests with coverage + build, so breakage is caught before a release tag —
|
|
17
|
+
previously verification only ran at publish time in `release.yml`.
|
|
18
|
+
- **Biome** for lint + format (`biome.json`, `npm run lint` / `npm run
|
|
19
|
+
format`), with `noExplicitAny` and unused-code checks on; the codebase is
|
|
20
|
+
clean against it (the one `Model<any>` in `refine.ts` became `Model<Api>`).
|
|
21
|
+
- **Coverage measurement** (`npm run test:coverage`, `vitest.config.ts`):
|
|
22
|
+
v8 provider with thresholds — 95% statements package-wide, and 100% for the
|
|
23
|
+
pure policy modules (`select.ts`, `lessons.ts`, `cadence.ts`, `vmodel.ts`)
|
|
24
|
+
whose "pure, deterministic, unit-tested" property the README advertises.
|
|
25
|
+
- **Provenance trust boundary**: every item now records who last wrote its
|
|
26
|
+
content. `harness_mutate` creates and content rewrites (plus all `/refine`
|
|
27
|
+
proposer writes) are stamped `provenance: "agent"` server-side; the model
|
|
28
|
+
can never set the field (smuggled values are stripped at the tool boundary,
|
|
29
|
+
and agent writes can only ever lose trust, never gain it). Absent provenance
|
|
30
|
+
means trusted, so owner-authored items, AGENTS.md imports, and existing
|
|
31
|
+
stores need no migration. Two on-by-default gates under
|
|
32
|
+
`harness.json { "provenance": { "label": true, "priority": true } }`:
|
|
33
|
+
**label** renders agent items as `(unverified: agent-authored)` in the
|
|
34
|
+
injected block (plus one defining intro line, paid only when such an item is
|
|
35
|
+
surfaced) and `(unverified)` chips in `/harness list` (both hidden by
|
|
36
|
+
`label: false`); **priority** fills each kind's item slots and the injection
|
|
37
|
+
budget with trusted items first, so an agent note can never crowd
|
|
38
|
+
an owner note out of the block (stores without agent items queue identically
|
|
39
|
+
to before). The durable file carries a `- provenance: agent` sub-line
|
|
40
|
+
(durable wins on import: hand-deleting the line trusts the item); legacy
|
|
41
|
+
files stay byte-identical. Stamping always happens — the gates only affect
|
|
42
|
+
presentation. Inspired by Protect-Context's forced agent trust level.
|
|
43
|
+
New tests: `test/provenance.test.ts` (22).
|
|
44
|
+
- **`makePersist(pi)`** (store.ts): the one factory for the
|
|
45
|
+
`appendEntry("harness-state", …)` snapshot callback every mutation path
|
|
46
|
+
uses. The entry-type literal and persist closure were previously
|
|
47
|
+
copy-pasted at ~10 call sites — a typo in any one silently broke `/tree`
|
|
48
|
+
restore for that path.
|
|
49
|
+
- **`validateDeltas`** boundary guard (tools.ts) plus `maxLength` caps on
|
|
50
|
+
`content`/`evidence`/`reason` in the tool schemas: a model could previously
|
|
51
|
+
create unbounded-size items that flowed verbatim into the durable files.
|
|
52
|
+
- `test/entry.test.ts` pins the **turn_end registration order** (durable
|
|
53
|
+
sync's export before outcome/auto-refine) via the new exported
|
|
54
|
+
`TURN_END_CHAIN`; the invariant used to live only in a comment.
|
|
55
|
+
- `test/hardening.test.ts` covers the new hardening behaviors (corrupt
|
|
56
|
+
snapshot coercion, ENOENT vs real read errors, flag validation, the
|
|
57
|
+
boundary guard).
|
|
58
|
+
- **pi 1.5 conformance tests** (`test/schemas.test.ts`): pi 1.5 moved
|
|
59
|
+
tool-argument validation to `@earendil-works/pi-schema` (breaking change —
|
|
60
|
+
new coercion rules for JSON-Schema tools, new failure-message format). The
|
|
61
|
+
shipped hand-written schemas are now run through the same
|
|
62
|
+
`compile({ coerce: true })` path pi applies to tool arguments: well-formed
|
|
63
|
+
batches pass, loosely typed args coerce (`importance: "0.8"` → `0.8`,
|
|
64
|
+
`active: "true"` → `true`, a bare delta wraps into a one-item array), and
|
|
65
|
+
schema violations still reject after coercion (range/required/enum/
|
|
66
|
+
minItems/maxItems asserted by path+keyword, not message text).
|
|
67
|
+
- **`CHANGELOG.md` is published** (added to npm `files`): pi ≥ 1.5 shows
|
|
68
|
+
users what changed after an extension update, reading the installed
|
|
69
|
+
package's changelog — which was previously never shipped. Timely, because
|
|
70
|
+
1.5 also fixed `-e npm:` extensions to actually update on start.
|
|
71
|
+
- **`.gitattributes` pins LF** (`* text=auto eol=lf`): with
|
|
72
|
+
`core.autocrlf=true` on Windows, a fresh clone checks out CRLF while
|
|
73
|
+
biome.json enforces `lf`, so `npm run check` failed locally (50 format
|
|
74
|
+
errors) while Linux CI stayed green. New clones now check out LF.
|
|
75
|
+
|
|
76
|
+
### Fixed
|
|
77
|
+
|
|
78
|
+
- **Explicit-path `/harness import` now honors the path's layer scope**: the
|
|
79
|
+
`defaultScope` from `defaultScopeForPath` was computed and then never
|
|
80
|
+
threaded through `reconstructFromDurable`, so importing a project-dir file
|
|
81
|
+
by path silently defaulted untagged items to the global scope (contradicting
|
|
82
|
+
the documented behavior; surfaced by the new unused-variable lint).
|
|
83
|
+
- Only `ENOENT` counts as "no durable file": a real read error (EACCES,
|
|
84
|
+
EISDIR, …) now surfaces as the import failure it is instead of the
|
|
85
|
+
misleading "No durable files — run /refine --commit" advice.
|
|
86
|
+
- **Corrupt-snapshot field types are coerced at `reconstruct`** instead of
|
|
87
|
+
crashing far-away renders later (`importance: "high"` died at `toFixed(2)`,
|
|
88
|
+
an unknown `kind` at per-kind tallies).
|
|
89
|
+
- The `session_start` durable auto-import is wrapped in the same soft-fail
|
|
90
|
+
try/catch as its `turn_end` counterpart — a throw can no longer escape the
|
|
91
|
+
session_start handler.
|
|
92
|
+
- `/harness push-mem`, `prune`, `adopt`, `keep|drop`, `move`, `split`,
|
|
93
|
+
`lessons`, `import|export|export-corpus`, and `setup` now **warn on
|
|
94
|
+
invalid/extra arguments** instead of silently falling back (the rule
|
|
95
|
+
`/harness list` already documented): an invalid `--kind` used to quietly
|
|
96
|
+
push memories, a bad `--decay` to prune without decay — and, per the PR
|
|
97
|
+
review, a *misspelled* flag (`prune --decya 30`, `push-mem --knd skill`)
|
|
98
|
+
was still silently ignored. A grammar gate (`ARG_SPECS`/`checkRest`) now
|
|
99
|
+
rejects unknown flags, valueless value-flags, and extra positionals for
|
|
100
|
+
every subcommand before dispatch.
|
|
101
|
+
- **Durable sync survives an unreadable layer** (PR review): a layer that
|
|
102
|
+
fails to read (EACCES, EISDIR, …) no longer crashes `syncDurableOnStart`
|
|
103
|
+
or blocks the healthy ones — it is skipped and reported **once per
|
|
104
|
+
session**, the readable layers import, and auto-export stays paused until
|
|
105
|
+
every layer reads again (an unread layer must never be overwritten by a
|
|
106
|
+
store that never saw it). The turn_end catch-up retries quietly and the
|
|
107
|
+
sync self-heals; previously every turn re-warned and even the healthy
|
|
108
|
+
global layer never exported. Manual `/harness import` still surfaces the
|
|
109
|
+
real error (unchanged).
|
|
110
|
+
- `validateDeltas`' create branch now checks `importance` (number in [0,1])
|
|
111
|
+
and `scope` like the update branch (PR review: a non-pi host could send
|
|
112
|
+
`importance: "high"` past the guard); `clamp` in the store is NaN-safe as
|
|
113
|
+
defense in depth for direct-apply proposers; and a smuggled `ownerModel`
|
|
114
|
+
on a model-facing `harness_mutate` delta is stripped at the boundary
|
|
115
|
+
(same rule as `support` — creates always stamp to the active model).
|
|
116
|
+
- Under `virtualBinding: "physical"`, a mid-run route change re-caches the
|
|
117
|
+
binding key at `context_with_system`, so `harness_mutate` creates stamp
|
|
118
|
+
with the same key injection renders (previously they could diverge until
|
|
119
|
+
the next agent start).
|
|
120
|
+
- `loadConfig` is single-flight (concurrent cold callers share one read)
|
|
121
|
+
with an epoch guard, so a read that raced a cache reset can no longer
|
|
122
|
+
repopulate the cache with a stale result.
|
|
123
|
+
|
|
124
|
+
### Changed
|
|
125
|
+
|
|
126
|
+
- Internal consolidation, no behavior change: shared constants in
|
|
127
|
+
`types.ts` (`KIND_VALUES`/`KIND_ORDER`/`KIND_LABEL`, `KEEP_DROP_BUMP`) and
|
|
128
|
+
`schemas.ts` (`MAX_DELTAS`, which `ADOPT_BATCH_CAP` now derives from instead
|
|
129
|
+
of a hand-synced copy); `tally`/`AnyEntry`/`preview` deduplicated across
|
|
130
|
+
modules; the audit-trail walk extracted into one `walkAuditTrail` shared by
|
|
131
|
+
`status.ts` and `corpus.ts`; `layerPaths` exported as `defaultLayerPaths`
|
|
132
|
+
and reused; `runRefine`, `mergeParsed`, `handleSetup`, and the `/harness`
|
|
133
|
+
completions split into focused helpers; dead exports `registerHarness` /
|
|
134
|
+
`registerAutoRefine` removed.
|
|
135
|
+
- README: the long per-key Configuration reference moved to
|
|
136
|
+
**[docs/CONFIG.md](docs/CONFIG.md)**; the README keeps the shape, a key
|
|
137
|
+
table, and the link. Status section updated to 0.20.x. ROADMAP phases
|
|
138
|
+
reordered chronologically (8 → 13).
|
|
139
|
+
- `ProposeInput`'s optional fields are now explicitly `| undefined`-tolerant
|
|
140
|
+
(matches the package's `exactOptionalPropertyTypes` convention; type-level
|
|
141
|
+
only).
|
|
142
|
+
- **Adopted pi 1.5 in the toolchain**: devDependencies
|
|
143
|
+
`@earendil-works/pi-ai` / `pi-coding-agent` `^1.3.0` → `^1.5.0` (+ explicit
|
|
144
|
+
`@earendil-works/pi-schema` devDependency for the conformance tests), so CI
|
|
145
|
+
runs against the validator pi 1.5 actually applies to tool arguments. No
|
|
146
|
+
runtime code changes were needed — the tool schemas have been plain JSON
|
|
147
|
+
Schema since the issue-#36 startup refactor, which is exactly what 1.5's
|
|
148
|
+
pi-schema migration validates — and `peerDependencies` stay `>=1.2.0`
|
|
149
|
+
because nothing 1.5-only is imported at runtime. README Status notes
|
|
150
|
+
tested-against-pi-1.5.
|
|
151
|
+
- **Typed tool factories** (schemas.ts): `harnessListTool` /
|
|
152
|
+
`harnessMutateTool` now type their `execute` argument with the hand-written
|
|
153
|
+
`Static<>` params (a `ToolExecute<P>` derived from pi's own `execute`
|
|
154
|
+
signature, so rest/return types cannot drift), and the one cast back to
|
|
155
|
+
pi's `Static<TSchema>` slot lives inside the factories. The four
|
|
156
|
+
`params as Harness*Params` casts at the registration sites (entry.ts,
|
|
157
|
+
tools.ts) are gone. Type-level only — the emitted dist is byte-identical
|
|
158
|
+
(verified: the #36 startup graph and timings are unchanged).
|
|
159
|
+
|
|
160
|
+
## [0.20.0] — 2026-10-21
|
|
161
|
+
|
|
162
|
+
### Added
|
|
163
|
+
|
|
164
|
+
- **Harness²-inspired harnessing-level recursion** (arXiv
|
|
165
|
+
[2610.14224](https://arxiv.org/abs/2610.14224)): the improvement process now
|
|
166
|
+
learns from what past improvement yielded, using only signals already on the
|
|
167
|
+
branch — no extra model calls.
|
|
168
|
+
- **Support counters** on every item: `citations` (the outcome loop saw the
|
|
169
|
+
agent cite `[h_xxxx]`), `keeps` / `drops` (`/harness keep|drop`). Dedupe
|
|
170
|
+
merges carry them onto the keeper; durable export/import round-trips them
|
|
171
|
+
via a `support:` sub-line; `/harness list -f` and the `harness_list` tool
|
|
172
|
+
show them.
|
|
173
|
+
- **Lessons digest** injected into the `/refine` steering prompt:
|
|
174
|
+
evidence-gated sections for items proven useful (cited/kept), rejected
|
|
175
|
+
signals (drops strictly outweighing later use — a drop followed by
|
|
176
|
+
re-citations is contested, not rejected), and recent refine-run
|
|
177
|
+
productivity. Never-cited items are never named (no signal ≠ negative
|
|
178
|
+
signal); the digest is scoped to the **active model's items** (per-model
|
|
179
|
+
isolation applies to what the improver is told). Visible
|
|
180
|
+
any time via the new **`/harness lessons`** command.
|
|
181
|
+
- **Scoped refine proposals**: the steering prompt classifies every create
|
|
182
|
+
as invariant (cross-domain agentic behavior → `scope: "global"`) or
|
|
183
|
+
domain-specific (project knowledge → `scope: "project"`).
|
|
184
|
+
- **History gate** for auto-refine: `autoRefine.refineNoOpSkip` (default 3,
|
|
185
|
+
`0` disables) skips a cadenced refine when the last N refine runs all
|
|
186
|
+
produced nothing durable. Runs before the classifier gate and costs
|
|
187
|
+
nothing; **bounded to one consecutive skip** so the gate can never latch
|
|
188
|
+
shut against later trajectory evidence; manual `/refine` bypasses it.
|
|
189
|
+
- New exports for companion packages: `buildLessonsDigest`, `classifyRuns`,
|
|
190
|
+
`isEffectiveRun`, `recentIneffectiveRuns`, `mergeSupport`, `renderSupport`,
|
|
191
|
+
`parseSupport`, `buildSteeringPrompt`, `DEFAULT_REFINE_NOOP_SKIP`,
|
|
192
|
+
`HISTORY_GATE_MAX_CONSECUTIVE_SKIPS`, and the
|
|
193
|
+
`SupportInfo` type.
|
|
194
|
+
- **`harness.schema.json`** — published JSON Schema for `harness.json`
|
|
195
|
+
(same pattern as pi ≥ 1.2's published config schemas). Both doc examples
|
|
196
|
+
now start with a `$schema` line pointing at the raw GitHub URL; the schema
|
|
197
|
+
also ships inside the npm package (`files`). The loader already ignores
|
|
198
|
+
unknown keys — `$schema` included — and `/harness setup` rewrites preserve
|
|
199
|
+
it; a new test pins both behaviors.
|
|
200
|
+
|
|
201
|
+
### Changed
|
|
202
|
+
|
|
203
|
+
- README: noted that codemode (pi ≥ 1.2) renders the harness tools' input
|
|
204
|
+
constraints as `@` tags on property comments (`@minimum 0 @maximum 1` on
|
|
205
|
+
`importance`, `@maxItems 20` on the batch cap) — already declared by the
|
|
206
|
+
tool schemas, so scripts see the limits for free.
|
|
207
|
+
|
|
208
|
+
## [0.19.0] — 2026-10-16
|
|
209
|
+
|
|
210
|
+
### Changed
|
|
211
|
+
|
|
212
|
+
- **pi 1.2 adoption** — the peer floor moves from `>=0.99.0` to `>=1.2.0`.
|
|
213
|
+
- Every `pi.sendUserMessage` steering send now passes an explicit
|
|
214
|
+
`deliverAs`: `"steer"` for `/refine` (mid-run injection when busy) and
|
|
215
|
+
`"followUp"` for the auto-refine boundary fallback and the `/harness`
|
|
216
|
+
split/adopt/push-mem steering prompts (queued instead of throwing). Pi
|
|
217
|
+
1.2 counts the session as busy from run start, and a bare send throws
|
|
218
|
+
"Agent is already processing" in that window.
|
|
219
|
+
- Extension-triggered runs (`pi.sendMessage(..., { triggerTurn: true })`,
|
|
220
|
+
e.g. scheduled tasks) now fire `before_agent_start` with
|
|
221
|
+
`messageType: "custom"`, so the model-binding bridge — binding key,
|
|
222
|
+
orphan adoption, injection — covers them. Before pi 1.2 those runs ran
|
|
223
|
+
with an empty binding cache.
|
|
224
|
+
|
|
225
|
+
## [0.18.0] — 2026-10-11
|
|
226
|
+
|
|
227
|
+
### Changed
|
|
228
|
+
|
|
229
|
+
- **Near-zero startup cost: thin entry + precompiled dist** (issue #36). Pi
|
|
230
|
+
loads extension entries sequentially and awaits every factory before
|
|
231
|
+
startup continues, so the old all-static TypeScript entry paid its whole
|
|
232
|
+
import graph (~230KB, jiti-transpiled) on every launch: **~0.8s marginal**
|
|
233
|
+
in the 2026-10-11 measurement (9.4s total with all 16 packages installed).
|
|
234
|
+
Measured now: **~30ms marginal** (bogus-provider fast-fail, issue's
|
|
235
|
+
acceptance target was ≤150ms).
|
|
236
|
+
- **`pi.extensions` now points at `./dist/entry.js`** — a thin factory that
|
|
237
|
+
only registers hooks, commands, and tool schemas. Every feature module is
|
|
238
|
+
dynamic-imported inside its own handler (`session_start`, `turn_end`,
|
|
239
|
+
`before_agent_start`, `context_with_system`, command handlers, tool
|
|
240
|
+
`execute`), so nothing heavy evaluates until the relevant event fires.
|
|
241
|
+
- **The package ships precompiled `dist/`** (`main: dist/index.js`, types
|
|
242
|
+
included): no per-startup jiti transpile, and companion packages get real
|
|
243
|
+
JavaScript + `.d.ts` instead of raw TS via `main`. `src/` is no longer part
|
|
244
|
+
of the npm tarball; `prepack` builds `dist/` and CI verifies it exists.
|
|
245
|
+
- **Tool schemas are hand-written plain JSON Schema objects** (module
|
|
246
|
+
`src/schemas.ts`) instead of TypeBox builders: pi does not preload `typebox`
|
|
247
|
+
or `pi-ai` before extensions in the one-shot path, and importing either
|
|
248
|
+
costs ~0.4–1.0s cold. `test/schemas.test.ts` guards that the emitted JSON
|
|
249
|
+
is identical to the historical TypeBox output.
|
|
250
|
+
- **No behavior change**: commands, tools, hooks, registration order, and the
|
|
251
|
+
public API are identical. The feature modules' `register*` functions remain
|
|
252
|
+
(tests use them); they now delegate to the same extracted handlers the thin
|
|
253
|
+
entry calls.
|
|
254
|
+
|
|
255
|
+
### Notes for companion authors
|
|
256
|
+
|
|
257
|
+
- `getArgumentCompletions` for `/harness` may now return a Promise (pi's API
|
|
258
|
+
allows `AutocompleteItem[] | null | Promise<AutocompleteItem[] | null>`);
|
|
259
|
+
sync values still work. Only relevant if you wrap the command.
|
|
260
|
+
- Importing `pi-continual-harness` from a companion is now cheaper too: the
|
|
261
|
+
package root is precompiled JS, not jiti-transpiled TS.
|
|
262
|
+
|
|
263
|
+
## [0.17.0] — 2026-10-11
|
|
264
|
+
|
|
265
|
+
### Fixed
|
|
266
|
+
|
|
267
|
+
- **Companion proposer registrations were silently invisible under pi's
|
|
268
|
+
extension loader** — pi gives every extension a fresh, module-cache-disabled
|
|
269
|
+
jiti instance, so a companion package's
|
|
270
|
+
`import { registerProposer } from "pi-continual-harness"` resolved to a
|
|
271
|
+
DIFFERENT copy of the module than the one whose `/refine` reads the
|
|
272
|
+
registry. `registerProposer` wrote to a private map, `getProposer` never saw
|
|
273
|
+
it, and `/refine --proposer <name>` silently fell back to `steering`.
|
|
274
|
+
Verified against the real loader (pi 1.1.0): after running the companion's
|
|
275
|
+
factory, `listProposers()` on the harness copy still showed only the
|
|
276
|
+
built-ins.
|
|
277
|
+
- The registry is now backed by a **process-wide global**
|
|
278
|
+
(`Symbol.for("pi-continual-harness.proposers")`), which is shared across all
|
|
279
|
+
of pi's isolated module instances. `registerProposer` writes to it;
|
|
280
|
+
`getProposer` reads it first and falls back to the module-local built-ins
|
|
281
|
+
(`steering`, `dedupe`), so two harness copies never hand each other's
|
|
282
|
+
closures to `/refine`. Foreign entries without a `propose` function are
|
|
283
|
+
ignored defensively.
|
|
284
|
+
- **No API change**: same `registerProposer` / `getProposer` / `listProposers`
|
|
285
|
+
signatures. Upgrading the harness alone also fixes companion packages that
|
|
286
|
+
were built against older versions (their private copy of
|
|
287
|
+
`registerProposer` now writes to the same shared registry).
|
|
288
|
+
|
|
289
|
+
### Notes for companion authors
|
|
290
|
+
|
|
291
|
+
- Companions that want a zero-import startup entry can skip importing this
|
|
292
|
+
package entirely and push a `DeltaProposer` (typically a lazy delegate that
|
|
293
|
+
dynamic-imports its implementation on first `propose()`) straight into
|
|
294
|
+
`globalThis[Symbol.for("pi-continual-harness.proposers")]`. The string key
|
|
295
|
+
`"pi-continual-harness.proposers"` is the stable public contract.
|
|
296
|
+
|
|
297
|
+
## [0.16.1] — 2026-10-11
|
|
298
|
+
|
|
299
|
+
Deep-review release: six correctness fixes (each found by a full code review,
|
|
300
|
+
`review findings #1–#7` below) plus the simplifications that remove the drift
|
|
301
|
+
surfaces the bugs grew on.
|
|
302
|
+
|
|
303
|
+
### Fixed
|
|
304
|
+
|
|
305
|
+
- **`/harness export-corpus` dropped the first snapshot's `created` events**
|
|
306
|
+
(review finding #1) — `buildCorpus`'s snapshot walk skipped the branch's
|
|
307
|
+
FIRST `harness-state` entry, while `/harness status` had the implicit
|
|
308
|
+
empty-baseline fix since review #27. A fresh session's entire first create
|
|
309
|
+
batch (often the auto-import) never landed in `lifecycle.jsonl`, and the
|
|
310
|
+
two reports disagreed on `created` counts for the same branch. The corpus
|
|
311
|
+
walk now diffs against `{ items: [] }` exactly like the status aggregator.
|
|
312
|
+
- **Completions crashed on prototype-collision subcommands** (review finding
|
|
313
|
+
#2) — typing `/harness constructor --` (or `toString`, `valueOf`, …) hit
|
|
314
|
+
`sub in FLAGS` / `FLAGS[sub] ?? []`, which resolve `Function` through the
|
|
315
|
+
prototype chain: not nullish, so `.filter` threw. Both the condition and
|
|
316
|
+
the lookup now go through `Object.hasOwn` (same class as the review #26
|
|
317
|
+
`expandPreset` fix).
|
|
318
|
+
- **No-op `/harness prune` appended a `harness-state` entry** (review finding
|
|
319
|
+
#3) — `decayAndPrune` persisted unconditionally. A prune that decayed and
|
|
320
|
+
removed nothing now persists nothing, matching the `mergeParsed` /
|
|
321
|
+
`adoptOrphans` idempotency contract (no `/tree` noise).
|
|
322
|
+
- **Re-importing a durable file without an export in between duplicated
|
|
323
|
+
items** (review finding #4) — id-less bullets and foreign (non-`h_`) ids
|
|
324
|
+
never hit the id map, so every import created a fresh copy. Import now
|
|
325
|
+
falls back to a kind+content match: the re-import folds into the item the
|
|
326
|
+
first import created (and under `--prune`, content-matched items count as
|
|
327
|
+
"present in the file" — no delete+recreate id churn).
|
|
328
|
+
- **One corrupt `harness-state` entry could break `session_start`** (review
|
|
329
|
+
finding #5) — `reconstruct` called `.map` on whatever `state.items`
|
|
330
|
+
happened to be. Non-array `items` and non-object entries are now filtered
|
|
331
|
+
out instead of throwing; a fully-corrupt snapshot degrades to an empty
|
|
332
|
+
store.
|
|
333
|
+
- **Malformed `harness.json` silently reset every setting** (review finding
|
|
334
|
+
#6) — the loader fell back to defaults with zero feedback. `loadConfig`
|
|
335
|
+
now flags the parse failure (`configMalformed()`) and `session_start`
|
|
336
|
+
warns once per session: "not valid JSON — every setting is on its DEFAULT
|
|
337
|
+
until the file parses."
|
|
338
|
+
- **Typo'd `/refine` flags and proposer names vanished silently** (review
|
|
339
|
+
finding #7) — `--comit` ran a plain no-commit refine; `--proposer dedupee`
|
|
340
|
+
silently fell back to steering. Unknown `--flags` and unknown proposer
|
|
341
|
+
names now produce a warning (the refine still runs).
|
|
342
|
+
|
|
343
|
+
### Changed
|
|
344
|
+
|
|
345
|
+
- **One shared turn-cadence counter** (new `src/cadence.ts`) backs
|
|
346
|
+
auto-refine, the `/refine` reminder, and the import-awareness reminder —
|
|
347
|
+
three hand-rolled copies of seed-on-first-completed-turn / count-completed
|
|
348
|
+
/ reset-on-fire had already drifted once (review #26's turnIndex fix, and
|
|
349
|
+
issue #29 grew on that drift). `turnEndedBadly` moved to `cadence.ts`
|
|
350
|
+
(still re-exported from `auto-refine.ts`). Behavior is identical for every
|
|
351
|
+
consumer except one normalization: the import reminder now also treats
|
|
352
|
+
`everyTurns <= 0` as disabled (previously it fired every completed turn
|
|
353
|
+
until the reminder cap).
|
|
354
|
+
- `durableLayer` is exported from `proposer.ts` and shared with `corpus.ts`
|
|
355
|
+
(the dedupe-pair layer key had to stay in sync by hand).
|
|
356
|
+
- The split/adopt steering prompts render their item listings through one
|
|
357
|
+
shared `formatItemLines()` (adopt keeps full-precision importance — the
|
|
358
|
+
agent copies it verbatim; split rounds). The two formats had already
|
|
359
|
+
drifted once.
|
|
360
|
+
- Status aggregation counts lifecycle events with an exhaustive `switch` —
|
|
361
|
+
a future `LifecycleEvent` falls to an explicit `default` instead of being
|
|
362
|
+
silently counted as `deleted`.
|
|
363
|
+
## [0.16.0] — 2026-10-11
|
|
364
|
+
|
|
365
|
+
### Added
|
|
366
|
+
|
|
367
|
+
- **`/harness list [-f] [--kind <kind>] [--scope <global|project|all>]`**
|
|
368
|
+
(issue #31): the read view of the store in the TUI. What `/harness status`
|
|
369
|
+
counts, but with the actual item text: no agent turn and no trip to the
|
|
370
|
+
layer files required.
|
|
371
|
+
- **Compact rows by default**: id, kind, importance, scope, ~60-char
|
|
372
|
+
preview, one line per item, ranked importance-first (ties keep store
|
|
373
|
+
order, the same stable policy as injection selection). Inactive items
|
|
374
|
+
carry an `(inactive)` marker.
|
|
375
|
+
- **`-f`**: full text plus the evidence line, harness_list-style, extended
|
|
376
|
+
with owner model and scope in the parenthetical.
|
|
377
|
+
- **Scope filters mirror the durable layers**: default = global + the
|
|
378
|
+
active project; foreign-project items are hidden and counted in a
|
|
379
|
+
trailing note (empty results included) so they look hidden, never
|
|
380
|
+
missing. `--scope project` lists every project's items with the slug on
|
|
381
|
+
each row; `--scope all` lists everything.
|
|
382
|
+
- **Guidance over bare zeros**: an empty store gets zero-state first steps,
|
|
383
|
+
an empty filter result gets kind tallies of what the store holds, and
|
|
384
|
+
invalid `--kind`/`--scope` values or unexpected arguments warn instead of
|
|
385
|
+
silently listing unfiltered.
|
|
386
|
+
- **Autocomplete**: `list` in the subcommand menu, `-f`/`--kind`/`--scope`
|
|
387
|
+
flag rows (flag matching now also catches short `-` prefixes), and value
|
|
388
|
+
menus for both filters.
|
|
389
|
+
- **Read-only**: no store mutation, no durable writes, one notify. The
|
|
390
|
+
selection/rendering core is pure in `src/list.ts`
|
|
391
|
+
(`selectListItems`/`renderListReport`) and re-exported from the package
|
|
392
|
+
entry; `preview()` consolidated there.
|
|
393
|
+
## [0.15.1] — 2026-10-11
|
|
394
|
+
|
|
395
|
+
### Fixed
|
|
396
|
+
|
|
397
|
+
- **`/refine` crash on plain-string message content** — pi messages carry
|
|
398
|
+
`content` as a plain string OR an array of content blocks depending on the
|
|
399
|
+
message source; both trajectory readers (`gatherEvidence` behind `/refine`,
|
|
400
|
+
and the opt-in `outcomeImportance` loop's `turn_end` scan) assumed blocks and
|
|
401
|
+
threw `((intermediate value) ?? []).map is not a function` the moment a
|
|
402
|
+
branch contained a string-content message. Both now flatten through one
|
|
403
|
+
shared pure helper, `messageText()` (re-exported from the package entry):
|
|
404
|
+
strings pass through, block arrays join on spaces, and any other shape
|
|
405
|
+
degrades to `""` instead of throwing. No behavior change otherwise —
|
|
406
|
+
citations, evidence lines, and cadences are untouched.
|
|
407
|
+
|
|
408
|
+
## [0.15.0] — 2026-10-11
|
|
409
|
+
|
|
410
|
+
The activation, visibility, and onboarding release: issues **#19–#22**
|
|
411
|
+
(activation nudges, rich status, cross-model adoption, setup wizard) plus
|
|
412
|
+
the pending 0.14.0 outcome-guard work.
|
|
413
|
+
|
|
414
|
+
> **Fold note:** releases 0.11.0–0.14.0 shipped from this same accumulated
|
|
415
|
+
> section without header cuts (see git tags / npm history), so their entries
|
|
416
|
+
> are retained below verbatim. Per-release headers resume here.
|
|
417
|
+
|
|
418
|
+
### Changed
|
|
419
|
+
|
|
420
|
+
- **`remindRefine` defaults flipped: on, every 25 turns** (issue #19). Fresh
|
|
421
|
+
installs were silent, so the cheapest activation lever (a notify-only
|
|
422
|
+
nudge) never fired. A reminder mutates nothing, which keeps the "manual by
|
|
423
|
+
default" safety posture intact while the `/refine` habit gets built for
|
|
424
|
+
you. **Migration note for existing silent-by-default users:** if your
|
|
425
|
+
`harness.json` has no `remindRefine` key you will start seeing nudges
|
|
426
|
+
(first one at 25 completed turns). Restore the old silence with
|
|
427
|
+
`{ "remindRefine": { "enabled": false } }`. Explicit config keeps winning
|
|
428
|
+
in both directions, and the cadence still counts completed turns per the
|
|
429
|
+
aborted/error-inert semantics (seed on the first observed completed
|
|
430
|
+
`turn_end`, reset per fire and per session).
|
|
431
|
+
|
|
432
|
+
### Added
|
|
433
|
+
|
|
434
|
+
- **Tiered, rotating reminder content (`remindRefine.detailEvery`)** — every
|
|
435
|
+
4th fired reminder (default) escalates from the one-line nudge to a
|
|
436
|
+
detailed educational guide, rotating through 7 slots so repeat viewers
|
|
437
|
+
never see the same wall of text: how the improvement loop works (3
|
|
438
|
+
alternating descriptions), the `/refine` workflow, `keep`/`drop`/
|
|
439
|
+
`prune --decay <days>` hygiene, `push-mem` (pi-mem composition), `split`
|
|
440
|
+
(global vs project scopes), `export-corpus` (pi-reflex contract), and a
|
|
441
|
+
recommended starter config whose guidance is honest about autonomy (review
|
|
442
|
+
#28): with `autoImport` on, durable layers are already written on every
|
|
443
|
+
store change — `commit` only adds a refine-time flush when `autoImport` is
|
|
444
|
+
off — so the conservative start is `autoImport` off + manual
|
|
445
|
+
`/harness export`. Config: `"remindRefine": { "everyTurns": 25,
|
|
446
|
+
"detailEvery": 4 }` with `1` = every reminder detailed; non-integer/sub-1
|
|
447
|
+
values coerce to the default (spam guard). The short nudge is now
|
|
448
|
+
data-aware ("12 active item(s), 4 never cited since creation"; an empty
|
|
449
|
+
store gets first-run guidance instead), derived from `getState()` with no
|
|
450
|
+
new persisted state. Copy builders are pure and exported (`buildNudge`,
|
|
451
|
+
`buildDetailGuide`, `reminderState`, `ReminderState`) so the richer
|
|
452
|
+
`/harness status` (companion issue) can share the same copy source.
|
|
453
|
+
|
|
454
|
+
- **Awareness reminders for durable sync (`importRemind`)** — with
|
|
455
|
+
`"autoImport": true`, silently-imported instructions now announce
|
|
456
|
+
themselves. The session-start import lists **what arrived** (item id, kind,
|
|
457
|
+
first words of content — bounded to 5 lines; an id present in both layers
|
|
458
|
+
is listed once, with its post-merge content), and a one-line `turn_end`
|
|
459
|
+
reminder re-surfaces the imported batch every `importRemind.everyTurns`
|
|
460
|
+
**completed** turns (default 25; aborted/error `turn_end`s are inert — they
|
|
461
|
+
neither seed nor fire, so they never consume one of the bounded notices,
|
|
462
|
+
and the count cannot be defeated by pi resetting `turnIndex` on every
|
|
463
|
+
`agent_start`). Both pointers are truthful (review #28): the overflow line
|
|
464
|
+
and the reminder point at the agent's `harness_list` tool (which actually
|
|
465
|
+
lists ids/content), not `/harness status` (counts only), and the advertised
|
|
466
|
+
kill-switch is valid, paste-able JSON. Bounded by design: at most 3
|
|
467
|
+
reminders per imported batch, then quiet until the next session — awareness
|
|
468
|
+
without nagging.
|
|
469
|
+
Config: `"importRemind": { "enabled": true, "everyTurns": 25 }`
|
|
470
|
+
(effective only with `autoImport`; kill-switch `enabled: false` keeps the
|
|
471
|
+
start-of-session listing). `DurableImportResult` now carries
|
|
472
|
+
`createdIds`/`updatedIds` (additive) so the sync knows exactly which items
|
|
473
|
+
to re-surface, and `imported` counts DISTINCT items — an id folded across
|
|
474
|
+
layers is one item, not one per copy (so the "imported N item(s)" notify
|
|
475
|
+
no longer double-counts collisions);
|
|
476
|
+
`evaluateImportReminder`/`registerImportReminder`/
|
|
477
|
+
`getImportedThisSession`/`summarizeImported` exported for tests and
|
|
478
|
+
companions.
|
|
479
|
+
|
|
480
|
+
### Removed
|
|
481
|
+
|
|
482
|
+
- **`durableScope` config key removed** (deprecated since 0.9.0 as a no-op):
|
|
483
|
+
items carry their own scope and durable I/O is always layered, so the key
|
|
484
|
+
switched nothing. The dead `resolveDurablePath()` helper is gone too (no
|
|
485
|
+
callers). Config files that still set `durableScope` load unchanged —
|
|
486
|
+
unknown keys are ignored; migration is the documented one-time
|
|
487
|
+
`/harness import` + `/harness move`/`split`.
|
|
488
|
+
|
|
489
|
+
### Fixed
|
|
490
|
+
|
|
491
|
+
- **Review #27 fixes (all unreleased features, amended in place)** — impact's
|
|
492
|
+
implicit empty baseline (first-snapshot `created 0`), per-model
|
|
493
|
+
update/merge attribution, status keying under `virtualBinding: "physical"`,
|
|
494
|
+
honest classifier-gate hint (names `classifier.model` AND `autoRefine.gate`),
|
|
495
|
+
and imported refs reconciled against the live store every `turn_end` (an
|
|
496
|
+
item deleted or deactivated after the import drops out of the reminders;
|
|
497
|
+
review pointers now target `harness_list`, which lists ids/content — status
|
|
498
|
+
intentionally stays aggregate-only).
|
|
499
|
+
|
|
500
|
+
- **`harness_list` empty results are now diagnostic** — the bare
|
|
501
|
+
`"Harness state is empty."` collapsed three very different situations into
|
|
502
|
+
one opaque line, which users read as "the feature is broken": (1) a
|
|
503
|
+
genuinely empty store (a new session starts empty — durable sync is
|
|
504
|
+
opt-in), (2) everything owned by another model (per-model isolation hides
|
|
505
|
+
it from the default view), (3) a `kind` filter that matched nothing. Each
|
|
506
|
+
case now gets its own actionable text: zero-state guidance (`/refine`,
|
|
507
|
+
`harness_mutate`, `"autoImport": true`), an owner tally with a
|
|
508
|
+
`model:"*"` pointer, and a kind tally respectively. The diagnosis is
|
|
509
|
+
attributed from the store's actual contents (review #23): a kind miss for a
|
|
510
|
+
model that owns items is never reported as model isolation, and the
|
|
511
|
+
`model:"*"` pointer only appears when the requested kind actually exists
|
|
512
|
+
for another model. `structuredContent` is unchanged (stable schema); only
|
|
513
|
+
the model-facing text changed.
|
|
514
|
+
|
|
515
|
+
### Added
|
|
516
|
+
|
|
517
|
+
- **Rich `/harness status` (issue #20)** — the status report is now a
|
|
518
|
+
three-band, still read-only notify. **Impact**: derived from the session
|
|
519
|
+
branch's audit trail by reusing the corpus lifecycle classification
|
|
520
|
+
(`classifyTransition` now exported with item attribution): refine runs
|
|
521
|
+
(manual vs auto), items created / updated (direct-apply, a lower bound) /
|
|
522
|
+
kept / cited (config-aware bump) / dropped / pruned / deleted / merged
|
|
523
|
+
(dedupe merges), whole-store plus a per-active-model line (lifecycle events
|
|
524
|
+
AND direct-apply update/merge ops are owner-attributed; the key follows the
|
|
525
|
+
configured `virtualBinding` policy). The branch's first snapshot diffs
|
|
526
|
+
against an implicit empty baseline, so a fresh session's first create batch
|
|
527
|
+
counts its items. **Injection
|
|
528
|
+
stats**: renders that served items, items surfaced, last block's estimated
|
|
529
|
+
tokens vs `injection.maxTokens`, and selection-omitted counts
|
|
530
|
+
(`InjectionStats`, recorded at render time in `inject.ts`, reset on
|
|
531
|
+
`session_start`). **Zero-state guidance**: 0 active items → actionable
|
|
532
|
+
first steps (`/refine`, MANUAL Quick start) instead of bare zeros. **Hint**:
|
|
533
|
+
one rotating contextual one-liner per call (classifier gate, outcome loop,
|
|
534
|
+
`push-mem`, `remindRefine`, `split`), never repeating the previous call's
|
|
535
|
+
hint (`src/hints.ts`, shared copy source for #19's content library). The
|
|
536
|
+
report builder is pure and exported — `buildStatusReport` /
|
|
537
|
+
`renderStatusReport` — and `handleStatus` stays thin glue. No new persisted
|
|
538
|
+
state; no mutation.
|
|
539
|
+
|
|
540
|
+
- **Cross-model adoption: `/harness adopt --from <provider/id> [--all]`**
|
|
541
|
+
(issue #21) — the audited, agent-filtered path for carrying **owned** items
|
|
542
|
+
across models (per-model isolation starts every new model id on a blank
|
|
543
|
+
harness; orphan adoption only covered *unowned* items). Steers the agent to
|
|
544
|
+
classify the source model's active items **generalizes** (model-agnostic:
|
|
545
|
+
engineering practices, user preferences, project facts) vs **stays** (that
|
|
546
|
+
model's quirks, formatting preferences, failure modes), then apply ONE
|
|
547
|
+
`harness_mutate` batch of create deltas — **copy, not move**: the source
|
|
548
|
+
harness is never touched, content is copied verbatim, evidence is annotated
|
|
549
|
+
`adopted from <provider/id>`, and because `ownerModel` is never
|
|
550
|
+
model-settable the creates stamp to the **active** model via the existing
|
|
551
|
+
create-stamping path (audited, `/tree`-rollback-able). `--all` adopts
|
|
552
|
+
everything without the filter. The batch is capped at 20 items (matching
|
|
553
|
+
`harness_mutate`'s per-call delta limit so the whole adoption fits one
|
|
554
|
+
batch), keeping the highest-importance items first and announcing any
|
|
555
|
+
truncation. Guards: missing `--from`, empty store, unknown model (warning
|
|
556
|
+
lists the owners present, with counts), self-adopt. `--from` values
|
|
557
|
+
complete from the model keys in the store. Overlaps afterwards are the
|
|
558
|
+
dedupe proposer's job: `/refine --proposer dedupe`. No automatic adoption
|
|
559
|
+
exists — explicit, manual, reviewable only. Review #25 hardening: the
|
|
560
|
+
self-adopt guard compares against the **resolved binding key**
|
|
561
|
+
(`virtualBinding`-aware, the same key create-stamping uses — a router
|
|
562
|
+
selection under `physical` adopts as the routed model); foreign-project
|
|
563
|
+
source items are skipped rather than re-stamped into the current
|
|
564
|
+
session's project layer (adopt them from a session in that project);
|
|
565
|
+
content the adopter already owns is skipped **before** the cap so repeated
|
|
566
|
+
adoption of a >20-item source progresses instead of re-selecting the same
|
|
567
|
+
top items; and importance is listed at **full precision** so an adopted
|
|
568
|
+
copy keeps its exact ranking. `buildAdoptPrompt`,
|
|
569
|
+
`selectAdoptSource`, and `ADOPT_BATCH_CAP` re-exported from the package
|
|
570
|
+
entry.
|
|
571
|
+
|
|
572
|
+
- **`/harness setup` — guided configuration wizard (presets, reviewed
|
|
573
|
+
harness.json write)** — every opt-in feature used to require hand-editing
|
|
574
|
+
`~/.pi/agent/harness.json` with exact key spellings; the wizard replaces
|
|
575
|
+
that with presets, per-feature confirms, and one safe write. Flow: show
|
|
576
|
+
the current effective config (file merged over defaults) + durable-layer
|
|
577
|
+
paths; choose `quiet` (pin the shipped defaults), `recommended`
|
|
578
|
+
(`autoImport` + `importRemind` on, with a gate-readiness note), or `power`
|
|
579
|
+
(`recommended` + `autoRefine`); confirm each implied delta (autonomous
|
|
580
|
+
switches get their own explicit "mutates state on a cadence" confirm
|
|
581
|
+
regardless of preset); then write via the new `saveConfig` — timestamped
|
|
582
|
+
backup (`harness.json.bak-<timestamp>`), atomic same-dir write, unknown
|
|
583
|
+
keys preserved, and a reload that round-trips through the loader's
|
|
584
|
+
coercion path so the printed effective config is exactly what pi reads.
|
|
585
|
+
Scripted form: `/harness setup --preset recommended [--yes]` (`--yes` is
|
|
586
|
+
the scripted consent and skips every prompt). Nothing is written on
|
|
587
|
+
unknown input, a cancelled preset menu, or a declined final write;
|
|
588
|
+
declining an individual feature only skips that feature. The wizard
|
|
589
|
+
touches config files only — never harness state. Preset values derive
|
|
590
|
+
from `DEFAULT_CONFIG` (not hardcoded), so wizard and loader cannot drift.
|
|
591
|
+
Presets are pure data: `PRESETS` / `expandPreset` / `planWrite` / `setPath`
|
|
592
|
+
/ `saveConfig` exported for tests and companions; subcommand +
|
|
593
|
+
`--preset`/`--yes` flags autocomplete in the TUI.
|
|
594
|
+
|
|
595
|
+
- **Awareness reminders for durable sync (`importRemind`)** — with
|
|
596
|
+
`"autoImport": true`, silently-imported instructions now announce
|
|
597
|
+
themselves. The session-start import lists **what arrived** (item id, kind,
|
|
598
|
+
first words of content — bounded to 5 lines), and a one-line `turn_end`
|
|
599
|
+
reminder re-surfaces the imported batch every `importRemind.everyTurns`
|
|
600
|
+
turns (default 25). Bounded by design: at most 3 reminders per imported
|
|
601
|
+
batch, then quiet until the next session — awareness without nagging.
|
|
602
|
+
Config: `"importRemind": { "enabled": true, "everyTurns": 25 }`
|
|
603
|
+
(effective only with `autoImport`; kill-switch `enabled: false` keeps the
|
|
604
|
+
start-of-session listing). `DurableImportResult` now carries
|
|
605
|
+
`createdIds`/`updatedIds` (additive) so the sync knows exactly which items
|
|
606
|
+
to re-surface; `evaluateImportReminder`/`registerImportReminder`/
|
|
607
|
+
`getImportedThisSession`/`summarizeImported` exported for tests and
|
|
608
|
+
companions.
|
|
609
|
+
|
|
610
|
+
- **Aborted/error turns are inert (pi `turn_end` `BoundaryState.outcome`)** —
|
|
611
|
+
a `turn_end` whose outcome is `"aborted"` or `"error"` no longer counts
|
|
612
|
+
toward the auto-refine or reminder cadence: both windows now count
|
|
613
|
+
*completed* turns (never raw turn indices, so an aborted turn in between
|
|
614
|
+
cannot shorten a window). The outcome loop seeds its scan cursor on the
|
|
615
|
+
first observed `turn_end` regardless of outcome, then defers an aborted
|
|
616
|
+
turn's partial assistant text to the next completed turn — a refine can no
|
|
617
|
+
longer fire, and the classifier gate no longer judges, a trajectory that
|
|
618
|
+
was cut off. A missing outcome (synthetic events, earliest 0.99.x) is
|
|
619
|
+
treated as completed. Durable auto-export deliberately still exports on
|
|
620
|
+
aborted turns (committed entries must not be lost). Pure `turnEndedBadly()`
|
|
621
|
+
re-exported from the package entry; `evaluateAutoRefine`/`evaluateReminder`
|
|
622
|
+
accept an optional `outcome` argument (the dead `turnIndex` parameter was
|
|
623
|
+
dropped — the count replaced the index arithmetic).
|
|
624
|
+
- **pi 1.1.0 classifier catalog documented** — `classifier.model` accepts the
|
|
625
|
+
new entries with no code change: OpenAI's GPT-6 Luna (Decisions API, e.g.
|
|
626
|
+
`openai/gpt-6-luna`, needs `OPENAI_API_KEY`) and the native llama.cpp
|
|
627
|
+
decision models (Julia-1, Laya, Kev, lev, OpenJev through `/v1/systemone`,
|
|
628
|
+
llama.cpp ≥ 0.6.0 — classifier-only listings, a free local gate). README +
|
|
629
|
+
MANUAL updated; no minimum-pi change (the APIs used still ship by 0.99.0).
|
|
630
|
+
|
|
631
|
+
### Removed
|
|
632
|
+
|
|
633
|
+
- **`durableScope` config key removed** (deprecated since 0.9.0 as a no-op):
|
|
634
|
+
items carry their own scope and durable I/O is always layered, so the key
|
|
635
|
+
switched nothing. The dead `resolveDurablePath()` helper is gone too (no
|
|
636
|
+
callers). Config files that still set `durableScope` load unchanged —
|
|
637
|
+
unknown keys are ignored; migration is the documented one-time
|
|
638
|
+
`/harness import` + `/harness move`/`split`.
|
|
639
|
+
|
|
640
|
+
### Fixed
|
|
641
|
+
|
|
642
|
+
- **`harness_list` empty results are now diagnostic** — the bare
|
|
643
|
+
`"Harness state is empty."` collapsed three very different situations into
|
|
644
|
+
one opaque line, which users read as "the feature is broken": (1) a
|
|
645
|
+
genuinely empty store (a new session starts empty — durable sync is
|
|
646
|
+
opt-in), (2) everything owned by another model (per-model isolation hides
|
|
647
|
+
it from the default view), (3) a `kind` filter that matched nothing. Each
|
|
648
|
+
case now gets its own actionable text: zero-state guidance (`/refine`,
|
|
649
|
+
`harness_mutate`, `"autoImport": true`), an owner tally with a
|
|
650
|
+
`model:"*"` pointer, and a kind tally respectively. The diagnosis is
|
|
651
|
+
attributed from the store's actual contents (review #23): a kind miss for a
|
|
652
|
+
model that owns items is never reported as model isolation, and the
|
|
653
|
+
`model:"*"` pointer only appears when the requested kind actually exists
|
|
654
|
+
for another model. `structuredContent` is unchanged (stable schema); only
|
|
655
|
+
the model-facing text changed.
|
|
656
|
+
- **`harness_list`'s `model:"*"` pointer also holds when the model owns
|
|
657
|
+
nothing (review #26)** — requesting a kind that exists for NO model used
|
|
658
|
+
to still show the pointer (which returns nothing with the same kind
|
|
659
|
+
filter); that case now says "no `<kind>` items for this model or any other
|
|
660
|
+
model" with an honest kind tally, and the pointer only appears when the
|
|
661
|
+
kind exists elsewhere.
|
|
662
|
+
- **Late `autoImport` enable can no longer clobber the durable layers
|
|
663
|
+
(review #26)** — enabling `autoImport` mid-session (wizard or hand-edit)
|
|
664
|
+
used to make the very next `turn_end` auto-export overwrite the
|
|
665
|
+
global/project durable files with the never-imported live store (the
|
|
666
|
+
session-start import had been skipped, so saved refinements were lost).
|
|
667
|
+
The export hook now runs the layered import FIRST when the session's
|
|
668
|
+
start-sync never happened; a failing catch-up import defers the export
|
|
669
|
+
to the next turn.
|
|
670
|
+
- **Cadences count `turn_end` events, not pi's `turnIndex` (review #26)** —
|
|
671
|
+
pi resets `turnIndex` to 0 on every `agent_start` (each user prompt), so
|
|
672
|
+
the import-reminder (and the older `/refine` reminder) cadences could
|
|
673
|
+
never accumulate across prompts: 25 separate prompts never fired a
|
|
674
|
+
reminder. Both reminders now count completed turn_end events
|
|
675
|
+
(session-stable ordinal; resets on session_start as before).
|
|
676
|
+
- **`saveConfig` is safe under concurrency (review #26)** — the temp file is
|
|
677
|
+
unique per call (UUID + exclusive `wx` create), so two concurrent writes
|
|
678
|
+
(two pi sessions) can never truncate or rename each other's temp file;
|
|
679
|
+
the final rename retries briefly on Windows' transient `EPERM` (target
|
|
680
|
+
just replaced).
|
|
681
|
+
- **`planWrite` merges like `saveConfig` (review #26)** — the wizard's
|
|
682
|
+
reviewed JSON preview previously dropped nested unknown keys (e.g. a
|
|
683
|
+
future `importRemind.<key>`), which the write then re-added from disk, so
|
|
684
|
+
the written file could differ from the confirmed preview. Deltas now
|
|
685
|
+
apply with the same two-level deep-merge: the preview equals the file.
|
|
686
|
+
- **`expandPreset` no longer leaks `Object.prototype` (review #26)** —
|
|
687
|
+
`--preset constructor` (or `toString`/`__proto__`) passed validation and
|
|
688
|
+
crashed; inherited names now report `Unknown preset` and nothing is
|
|
689
|
+
written.
|
|
690
|
+
- **The import-reminder's disable hint is now valid JSON (review #26)** —
|
|
691
|
+
the suggested snippet used single quotes; pasting it broke the whole
|
|
692
|
+
config file (all settings fell back to defaults). It now reads
|
|
693
|
+
`"importRemind": { "enabled": false }`.
|
|
694
|
+
- **Global/project id collisions list once (review #26)** — an id created
|
|
695
|
+
by the global layer then updated by the project layer appeared twice in
|
|
696
|
+
the "what arrived" listing and inflated the reminder counts; the batch
|
|
697
|
+
is now de-duplicated.
|
|
698
|
+
|
|
699
|
+
### Added
|
|
700
|
+
|
|
701
|
+
- **Stale-draft hygiene for auto-refine (pi 0.87 context edits)** — every
|
|
702
|
+
boundary delivery now also appends `context_edit` drafts omitting each LIVE
|
|
703
|
+
prior `harness.auto-refine-request` from future provider context, so at most
|
|
704
|
+
ONE auto-refine request (the newest) is ever model-visible in long sessions.
|
|
705
|
+
Already-omitted drafts are skipped (idempotent — never stacks edits); raw
|
|
706
|
+
history, usage, and UI history are untouched, so `/tree` rollback and the
|
|
707
|
+
HTML export still show every draft. Kill-switch:
|
|
708
|
+
`"autoRefine": { "omitStaleDrafts": false }` (default `true`).
|
|
709
|
+
`buildOmissionDrafts` exported for reuse.
|
|
710
|
+
|
|
711
|
+
### Added (0.12.0 — pi 0.87/0.99 adoption)
|
|
712
|
+
|
|
713
|
+
- **Live re-injection via `context_with_system` (pi 0.87)** — the harness block
|
|
714
|
+
is now rendered on EVERY provider request (full-transcript system-message
|
|
715
|
+
transformation), not baked into the base prompt once per agent run. A
|
|
716
|
+
`harness_mutate` that fixes a stale note mid-run shows up on the very next
|
|
717
|
+
request. `before_agent_start` remains the model-binding bridge (key cache +
|
|
718
|
+
orphan adoption) but no longer rewrites the prompt; when there is nothing to
|
|
719
|
+
inject the extension does not own the transcript at all.
|
|
720
|
+
- **Virtual-model binding policy (pi 0.99)** — `ctx.model` names the *virtual*
|
|
721
|
+
model when a router extension is selected, which silently split the harness
|
|
722
|
+
per router. New config `"virtualBinding": "virtual" | "physical"` (default
|
|
723
|
+
`virtual`, the previous behavior): `physical` keys items by the *physical*
|
|
724
|
+
model behind the latest successful response on the branch, so notes
|
|
725
|
+
accumulated under `anthropic/claude-x` keep injecting through a router that
|
|
726
|
+
routes there; before the first response the binding is unknown (creates stay
|
|
727
|
+
orphans, adopted on first contact). Pure helpers (`isVirtualModel`,
|
|
728
|
+
`physicalKeyFromBranch`, `resolveBindingKey`) re-exported from the package
|
|
729
|
+
entry.
|
|
730
|
+
- **Classifier integration (pi 0.99)** — `ctx.modelRegistry.classify()` behind
|
|
731
|
+
a minimal structural `ClassifyFn` seam (`ProposeInput.classify`, re-exported):
|
|
732
|
+
- `"classifier": { "model": "typesafe/jev-latest" }` (or any classifier
|
|
733
|
+
catalog entry, e.g. a local llama.cpp classifier) enables both paths.
|
|
734
|
+
- **Classifier-confirmed dedupe**: the `dedupe` proposer recall-widens its
|
|
735
|
+
candidate pass (threshold − 0.15, floor 0.35) and confirms each pair with
|
|
736
|
+
ONE batched yes/no call (≤ 20 pairs). Only confirmed pairs merge; a
|
|
737
|
+
classifier error falls back to the plain rule-based plan — the classifier
|
|
738
|
+
can narrow merges, never block them. Telemetry (`modelCall`) recorded in
|
|
739
|
+
the audit entry. `planDedupePairs` / `deltasFromGroups` / `confirmGroups` /
|
|
740
|
+
`buildPairQuestions` exported for reuse.
|
|
741
|
+
- **Auto-refine gate**: `"autoRefine": { "gate": true }` asks one yes/no
|
|
742
|
+
question over the recent trajectory before spending a refine pass;
|
|
743
|
+
"no durable correction" skips it. Errors fall back to the plain cadence.
|
|
744
|
+
- **Structured tool results (pi 0.99)** — `harness_list` and `harness_mutate`
|
|
745
|
+
declare `outputSchema` and return `structuredContent` (items array / applied
|
|
746
|
+
counts), so codemode scripts and `ctx.executeTool()` callers consume the
|
|
747
|
+
harness programmatically; the model still sees the text content.
|
|
748
|
+
- **Boundary delivery for auto-refine (pi 0.87)** — the steering prompt is no
|
|
749
|
+
longer sent as a synthetic user message: `runRefine` (with the new
|
|
750
|
+
`delivery: "boundary"`) returns it undelivered and the `turn_end` handler
|
|
751
|
+
persists it as a structural `harness.auto-refine-request` custom-message
|
|
752
|
+
draft with `{ entries, continue: true }` — one guaranteed next provider
|
|
753
|
+
request with no steering-queue or follow-up-scheduling side effects. Falls
|
|
754
|
+
back to `sendUserMessage` when the boundary cannot continue. Manual `/refine`
|
|
755
|
+
still steers (it may run while idle).
|
|
756
|
+
|
|
757
|
+
### Changed
|
|
758
|
+
|
|
759
|
+
- **Minimum pi version is now 0.99.0** (peer + dev dependencies). All adopted
|
|
760
|
+
APIs ship by 0.99.0 (`context_with_system`, actionable `turn_end`
|
|
761
|
+
boundaries, classifier `classify()`, `outputSchema`/`structuredContent`,
|
|
762
|
+
virtual models).
|
|
763
|
+
|
|
764
|
+
### Added (earlier in this release)
|
|
765
|
+
|
|
766
|
+
- **`/harness export-corpus [path]`** — the calibration corpus exporter for
|
|
767
|
+
the pi-reflex consumer contract (§4, issue #12). Reconstructs two JSONL corpora
|
|
768
|
+
from the session branch's own audit trail: `dedupe-pairs.jsonl` (merged
|
|
769
|
+
pairs as ground-truth `dup` records parsed from delete reasons; same
|
|
770
|
+
key-field non-merged pairs with informative overlap as `needs_review`
|
|
771
|
+
`not_dup` candidates) and `lifecycle.jsonl` (`created` / `cited` / `kept` /
|
|
772
|
+
`dropped` / `pruned` / `deleted`, classified from consecutive snapshot
|
|
773
|
+
diffs). Pure `buildCorpus()` core in `src/corpus.ts` (+
|
|
774
|
+
`parseDedupeDelete`), re-exported from the package entry. Default output
|
|
775
|
+
`./harness-corpus/<yyyy-mm-dd>/`; local file writes only — nothing leaves
|
|
776
|
+
the machine.
|
|
777
|
+
|
|
778
|
+
- **Review fixes (PR #14 feedback)** — three correctness issues in the first
|
|
779
|
+
cut: (1) the dedupe audit entry now records the full `appliedDeltas` list
|
|
780
|
+
(incl. delete reasons) alongside the legacy `applied` count — without it,
|
|
781
|
+
dup pairs were unreconstructable and the exporter crashed on the count-only
|
|
782
|
+
legacy shape; (2) `not_dup` candidates now emit from the CURRENT state — a
|
|
783
|
+
no-merge dedupe run writes no snapshot, so the previous two-snapshot walk
|
|
784
|
+
read a stale state; (3) citation classification is config-aware
|
|
785
|
+
(`buildCorpus(entries, { citeBump })`, wired to `outcomeImportance.bump`,
|
|
786
|
+
default 0.03) instead of a hardcoded `(0, 0.06]` band; keep/drop (±0.1) are
|
|
787
|
+
checked first, so a configured bump of exactly 0.1 classifies as keep.
|
|
788
|
+
|
|
789
|
+
## [0.10.0] — 2026-09-20
|
|
790
|
+
|
|
791
|
+
The merge-capable dedupe proposer: fuzzy-corrections take 1. Spec and research
|
|
792
|
+
grounding in [docs/PLAN-dedupe-merge.md](docs/PLAN-dedupe-merge.md).
|
|
793
|
+
|
|
794
|
+
### Added
|
|
795
|
+
|
|
796
|
+
- **`dedupe` proposer merges instead of deleting** — two active items sharing
|
|
797
|
+
the key fields (kind, owner model, durable layer: scope + project slug) with
|
|
798
|
+
token overlap ≥ the threshold merge into the higher-importance keeper: one
|
|
799
|
+
audited `update` (evidence = line-wise union, capped at 2000 chars) + one
|
|
800
|
+
`delete` per duplicate (`merged into h_x (overlap …)`). The keeper's content
|
|
801
|
+
is never prose-merged (ACE); injection is content-only, so the prompt block
|
|
802
|
+
is unchanged. Identical evidence degenerates to a plain delete.
|
|
803
|
+
- **Config keys** `dedupe: { "threshold": 0.6, "merge": true }` in
|
|
804
|
+
`harness.json` — threshold valid in `(0,1]` (bad values degrade to `0.6`),
|
|
805
|
+
`merge: false` restores the pre-0.10 delete-only behavior.
|
|
806
|
+
- **`/refine --threshold <t>`** — one-shot threshold override for the run
|
|
807
|
+
(affects the `dedupe` proposer; invalid values are ignored with a warning).
|
|
808
|
+
- **`ProposeInput.config`** — `runRefine` now threads the loaded `harness.json`
|
|
809
|
+
into every proposer (optional, additive), so proposers read tuned knobs
|
|
810
|
+
without file I/O.
|
|
811
|
+
- **Public API**: `planDedupe(state, opts)` (the pure planner), `DedupeOptions`
|
|
812
|
+
(with a `similarity?` seam for the future semantic upgrade), `DEFAULT_DEDUPE`,
|
|
813
|
+
`EVIDENCE_MERGE_CAP`, `unionEvidence`, `DEDUPE_THRESHOLD` re-exported from
|
|
814
|
+
the package entry.
|
|
815
|
+
- **`SimilarityResult` abstain seam** — `DedupeOptions.similarity` may return
|
|
816
|
+
`{ score, abstain }` instead of a plain number: an abstaining pair is treated
|
|
817
|
+
as NOT duplicates (keep both), so a semantic engine with conformal-style
|
|
818
|
+
uncertainty (e.g. pi-reflex, formerly pi-jev — local-only for now) can decline to
|
|
819
|
+
merge on uncertainty. `SimilarityResult` is re-exported from the package
|
|
820
|
+
entry; plain numeric comparators keep working unchanged.
|
|
821
|
+
|
|
822
|
+
### Changed
|
|
823
|
+
|
|
824
|
+
- The `dedupe` proposer's default behavior (was: delete-only, hardcoded 0.6).
|
|
825
|
+
Rollback path: set `"dedupe": { "merge": false }`.
|
|
826
|
+
|
|
827
|
+
## [0.9.0] — 2026-09-17
|
|
828
|
+
|
|
829
|
+
The project-scope split (issue #7). Scope becomes a property of each item, the
|
|
830
|
+
durable file becomes durable **layers**, and one opt-in flag makes `/refine`
|
|
831
|
+
output survive into new sessions without export/import ceremony.
|
|
832
|
+
|
|
833
|
+
### Added
|
|
834
|
+
|
|
835
|
+
- **Per-item durable scope** — `HarnessItem.scope: "global" | "project"`
|
|
836
|
+
(+ `project` slug). Scope decides which durable layer an item is exported to
|
|
837
|
+
/ imported from; it never filters per-turn injection.
|
|
838
|
+
- **`/harness move <id> <global|project>`** — flip an item's layer
|
|
839
|
+
(audited update delta, `/tree`-rollback-able; `project` stamps the current
|
|
840
|
+
session's slug). Autocompletes ids then scope values.
|
|
841
|
+
- **`/harness split`** — the interactive migration helper: steers the agent
|
|
842
|
+
to classify every active item global-vs-project and apply the result as one
|
|
843
|
+
`harness_mutate` batch of scope-only updates (visible, audited,
|
|
844
|
+
rollback-able — the push-mem pattern).
|
|
845
|
+
- **Layered durable I/O** — `/harness export` (no path) partitions items
|
|
846
|
+
into `~/.pi/agent/harness-state.md` + `harness-state/<slug>.md`;
|
|
847
|
+
`/harness import` (no path) merges global first then the current project's
|
|
848
|
+
file (project wins id collisions; `--prune` is union-scoped across
|
|
849
|
+
layers). Explicit paths keep classic single-file semantics. Project items
|
|
850
|
+
carry a `scope: project (<slug>)` sub-line so any copy round-trips.
|
|
851
|
+
`/refine --commit` now exports the layers too. `/harness status` shows
|
|
852
|
+
both layers + the scope split.
|
|
853
|
+
- **`scope` on `harness_mutate` create/update deltas** — server-side slug
|
|
854
|
+
stamping from the session cwd (the tool never sees raw slugs).
|
|
855
|
+
- **Opt-in durable sync: `"autoImport": true`** (`src/durable.ts`) — bundles
|
|
856
|
+
both directions: `session_start` layered auto-import (loss-free merge,
|
|
857
|
+
silent when nothing changes — imports are now idempotent and skip the
|
|
858
|
+
persist when the live store already matches the files) + `turn_end` layered
|
|
859
|
+
auto-export whenever the store version changed since the last export. Every
|
|
860
|
+
action is visible and uses the same `harness-state` entries (`/tree`
|
|
861
|
+
rollback covers them).
|
|
862
|
+
|
|
863
|
+
### Changed
|
|
864
|
+
|
|
865
|
+
- **`durableScope` is deprecated** (no-op): durable I/O is always layered on
|
|
866
|
+
per-item scope now. The key still parses so existing configs keep loading;
|
|
867
|
+
migration is one layered `/harness import` + `/harness move`/`split`.
|
|
868
|
+
- Import merge is **idempotent**: an import that changes nothing persists
|
|
869
|
+
nothing (no session-tree noise from repeated or auto imports).
|
|
870
|
+
- Layered import handles the same id in **both** layers (project copy wins,
|
|
871
|
+
no duplicate items).
|
|
872
|
+
|
|
873
|
+
[0.15.1]: https://github.com/pungggi/pi-continual-harness/compare/v0.15.0...v0.15.1
|
|
874
|
+
[0.15.0]: https://github.com/pungggi/pi-continual-harness/compare/v0.13.0...v0.15.0
|
|
875
|
+
[0.9.0]: https://github.com/pungggi/pi-continual-harness/compare/v0.8.1...v0.9.0
|
|
876
|
+
|
|
877
|
+
## [0.8.1] — 2026-09-13
|
|
878
|
+
|
|
879
|
+
The completions release. `/harness` now registers `getArgumentCompletions`, so
|
|
880
|
+
subcommands stop being one long crammed command description and become a
|
|
881
|
+
filtered autocomplete menu instead. Handler logic is unchanged — purely UX.
|
|
882
|
+
|
|
883
|
+
### Added
|
|
884
|
+
|
|
885
|
+
- **`/harness` argument autocomplete** (`src/harness.ts`) — two levels:
|
|
886
|
+
- `/harness <partial>` lists the seven subcommands (import, export, status,
|
|
887
|
+
prune, keep, drop, push-mem), each with a one-line description, filtered
|
|
888
|
+
case-insensitively as you type (matching the handler's `toLowerCase()`
|
|
889
|
+
tolerance).
|
|
890
|
+
- `/harness <sub> …` completes what comes next per subcommand: flags
|
|
891
|
+
(`--prune`, `--decay`, `--all`, `--kind`, `--model` — already-used flags
|
|
892
|
+
are not re-offered), `--kind` values (`prompt|memory|skill|subagent`),
|
|
893
|
+
`--model` values (`active` plus the distinct owner models in the store),
|
|
894
|
+
and `keep`/`drop` item ids straight from the live store with a
|
|
895
|
+
`kind · content-preview` description row.
|
|
896
|
+
- Path/number/free-text arguments return no menu, leaving the editor's own
|
|
897
|
+
completion alone.
|
|
898
|
+
- Completion rows carry the full replacement text (pi replaces the whole
|
|
899
|
+
argument string on selection), so multi-token completion round-trips exactly.
|
|
900
|
+
- The `/harness` command description is now a compact one-liner instead of the
|
|
901
|
+
full subcommand + flag grammar.
|
|
902
|
+
|
|
903
|
+
## [0.8.0] — 2026-08-10
|
|
904
|
+
|
|
905
|
+
The bounded-injection release. The harness ACCUMULATES notes, but the system
|
|
906
|
+
prompt is finite — so selection is now ON BY DEFAULT: injected items are
|
|
907
|
+
importance-ordered, capped per kind, and bounded by a total token budget. The
|
|
908
|
+
store itself is unchanged (nothing is ever lost — only what is *surfaced*
|
|
909
|
+
changes), and the whole policy is opt-out via `injection.enabled: false`.
|
|
910
|
+
|
|
911
|
+
### Added
|
|
912
|
+
|
|
913
|
+
- **Injection selection policy (on by default)** — `src/select.ts` decides
|
|
914
|
+
WHICH active items for the active model get surfaced each turn, and in what
|
|
915
|
+
order. Pure and fully unit-tested; `inject.ts` is now thin glue over it.
|
|
916
|
+
Policy: (1) filter to active + owner-model items (strict per-model isolation,
|
|
917
|
+
unchanged); (2) order by importance desc, ties stable on store index; (3) cap
|
|
918
|
+
via `maxPerKind` (balanced sections — no single kind drowns the block) then
|
|
919
|
+
`maxTokens` (total budget, filled round-robin across kinds by importance rank
|
|
920
|
+
so one kind can't starve the others; an item that doesn't fit is *skipped*, not
|
|
921
|
+
a hard stop, so a large item never blocks smaller higher-priority ones).
|
|
922
|
+
- **Config: `injection`** in `harness.json` — `{ enabled, maxTokens, maxPerKind,
|
|
923
|
+
charsPerToken }`, always resolved by `loadConfig`. Shipped defaults: `enabled:
|
|
924
|
+
true`, `maxTokens: 1500`, `maxPerKind: 10`, `charsPerToken: 4` — generous
|
|
925
|
+
enough to be a NO-OP for small stores (nothing trimmed) and protective as the
|
|
926
|
+
harness accumulates. Opt out with `injection.enabled: false` → legacy "all
|
|
927
|
+
items, in store order".
|
|
928
|
+
- **Transparency footer** — when the policy drops items, the injected block ends
|
|
929
|
+
with a one-line `_(N item(s) not shown — below the injection budget…)_` note,
|
|
930
|
+
so a bounded block is never silently truncated.
|
|
931
|
+
- **Public API** — `selectForInjection`, `normalizeInjection`, `estimateTokens`,
|
|
932
|
+
`DEFAULT_INJECTION`, and the `InjectionConfig` / `NormalizedInjection` types
|
|
933
|
+
are re-exported from the package entry for companion packages and tests.
|
|
934
|
+
|
|
935
|
+
### Changed
|
|
936
|
+
|
|
937
|
+
- `renderHarnessBlock(ownerKey?, cfg?)` now renders the selected subset (defaults
|
|
938
|
+
apply when `cfg` is omitted, so direct callers — including existing tests —
|
|
939
|
+
get the new policy). `before_agent_start` reads `injection` from config and
|
|
940
|
+
passes it through.
|
|
941
|
+
- Default injection is now importance-ordered. This reorders (and, for large
|
|
942
|
+
stores, trims) the supplemental block, but changes no data: items remain in the
|
|
943
|
+
store, `/tree` rollback is unaffected, and the durable round-trip is
|
|
944
|
+
unchanged. Set `injection.enabled: false` to restore the pre-0.8 block exactly.
|
|
945
|
+
|
|
946
|
+
### Docs
|
|
947
|
+
|
|
948
|
+
- README: new "Injection selection (on by default)" section + `injection` in the
|
|
949
|
+
config block; Status updated. MANUAL: injection-selection section. ROADMAP:
|
|
950
|
+
Phase 7. 25 new tests (`test/select.test.ts` + injection/config/integration
|
|
951
|
+
coverage); 137 total, typecheck clean.
|
|
952
|
+
|
|
953
|
+
## [0.7.0] — 2026-08-09
|
|
954
|
+
|
|
955
|
+
The per-model isolation release. Every item is bound to an exact `provider/id`
|
|
956
|
+
and injected only for the model it belongs to — a brand-new model id starts from
|
|
957
|
+
a blank harness, and one model's notes never leak into another's context.
|
|
958
|
+
|
|
959
|
+
### Added
|
|
960
|
+
|
|
961
|
+
- **Per-model isolation (`ownerModel`).** Every item now carries an
|
|
962
|
+
`ownerModel` (`"provider/id"`, or `""` for an orphan) and is injected only for
|
|
963
|
+
the model it belongs to. Binding is at the exact id by design — a new version
|
|
964
|
+
is a clean slate.
|
|
965
|
+
- Creates are stamped automatically: `before_agent_start` caches the active
|
|
966
|
+
model (the model-facing tools receive no `ctx`), then `harness_mutate` and
|
|
967
|
+
direct-apply proposers stamp creates from it.
|
|
968
|
+
- **Orphan adoption** is the migration path: items with no owner (legacy
|
|
969
|
+
session snapshots, old durable files, or created while the model was
|
|
970
|
+
unknown) are adopted by the active model on first contact — persisted as a
|
|
971
|
+
normal `harness-state` entry, so `/tree` rollback covers it.
|
|
972
|
+
- `harness_list({ model? })` — defaults to the active model's items; `"*"`
|
|
973
|
+
returns every model; an explicit `"provider/id"` filters.
|
|
974
|
+
- **`/harness push-mem --model <provider/id|active>`** — scope a pi-mem push to
|
|
975
|
+
one model's items (`active` resolves to the model driving the command).
|
|
976
|
+
|
|
977
|
+
### Changed
|
|
978
|
+
|
|
979
|
+
- **Durable round-trip preserves owner** via a per-item `model:` line; an
|
|
980
|
+
untagged item degrades to an orphan and is adopted on import/first contact.
|
|
981
|
+
- Injection, `harness_list`, and the outcome loop are model-scoped; the `dedupe`
|
|
982
|
+
proposer no longer compares items across models.
|
|
983
|
+
- `/harness status` shows whole-store kind counts, annotated with the active
|
|
984
|
+
model's share.
|
|
985
|
+
|
|
986
|
+
### Fixed
|
|
987
|
+
|
|
988
|
+
- **`harness_mutate` isolation** (PR review): `update`/`delete` are now scoped
|
|
989
|
+
to the active model too — an agent on model A can no longer mutate model B's
|
|
990
|
+
item by id (atomic rollback with a clear error). Cross-model maintenance paths
|
|
991
|
+
(`dedupe`, `/harness keep|drop|prune`) pass no actor and are unaffected.
|
|
992
|
+
- **Durable owner semantics** (PR review): an absent `model:` tag on an existing
|
|
993
|
+
item now orphans it (→ adopted by the active model) instead of silently
|
|
994
|
+
keeping the old owner — "durable wins" now covers owner uniformly.
|
|
995
|
+
- `session_start` resets the cached active-model key across fork/resume.
|
|
996
|
+
|
|
997
|
+
### Docs
|
|
998
|
+
|
|
999
|
+
- README + MANUAL model-binding sections; MANUAL updated for the review fixes
|
|
1000
|
+
(`harness_mutate` actor scoping, model-aware `dedupe`, model-scoped outcome,
|
|
1001
|
+
whole-store `status`); ROADMAP Phase 6; stale `0.5.x` version strings
|
|
1002
|
+
corrected; this CHANGELOG introduced.
|
|
1003
|
+
- Cross-linked the [pi-harness-model-proposer](https://github.com/pungggi/pi-harness-model-proposer)
|
|
1004
|
+
companion.
|
|
1005
|
+
|
|
1006
|
+
### Internal
|
|
1007
|
+
|
|
1008
|
+
- 19 files changed (+876/−52 over 0.6.2). **112 tests** (+25), typecheck clean;
|
|
1009
|
+
new `test/model-binding.test.ts`. Durable format and tool schemas extended
|
|
1010
|
+
backward-compatibly (missing `ownerModel` → orphan → adopted).
|