From c0bb6af3a78ba6e36166e1ffae55c025bb970c11 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Mon, 17 Aug 2026 13:42:20 +0200 Subject: [PATCH] docs(agent): finalize bound values handoff --- packages/agent/docs/assistant-durability.md | 4 +- packages/agent/docs/harness.md | 41 ++- packages/agent/docs/runtime2.md | 8 +- .../agent/docs/{registers.md => values.md} | 33 +- .../docs/work-packages/00-runtime1-removal.md | 2 +- .../work-packages/01-bound-values-lists.md | 340 ++++++++++++++++-- 6 files changed, 352 insertions(+), 76 deletions(-) rename packages/agent/docs/{registers.md => values.md} (91%) diff --git a/packages/agent/docs/assistant-durability.md b/packages/agent/docs/assistant-durability.md index beec9029e..9fb2de016 100644 --- a/packages/agent/docs/assistant-durability.md +++ b/packages/agent/docs/assistant-durability.md @@ -2,7 +2,7 @@ This document specifies durable partial assistant messages for ordinary assistant generation and deferred-response polling. It builds on: -- bound typed value/list addresses from `registers.md`; +- bound typed value/list addresses from `values.md`; - `AssistantMessageFrame`, `assistantMessageEventToFrame()`, and `reduceAssistantMessageFrames()` from `@earendil-works/pi-ai`; - the assistant intent/effect/settlement state machine in `harness.md`. @@ -337,7 +337,7 @@ JSONL retains deleted frame bytes until snapshot compaction. Logical deletion is Expected runtime areas: - built-in `pendingAssistantFrames(operationId, responseEntryId)` address constructor in `session/values.ts`; -- bound value/list storage implementation from `registers.md`; +- bound value/list storage implementation from `values.md`; - assistant execution observer and generation procedure; - deferred polling procedure; - activation and cancellation recovery; diff --git a/packages/agent/docs/harness.md b/packages/agent/docs/harness.md index e70e52e7e..fee47adfe 100644 --- a/packages/agent/docs/harness.md +++ b/packages/agent/docs/harness.md @@ -378,7 +378,7 @@ export const laneLeafInventoryPrefix = () => value("pi.lane.leaf"); export const operationMeta = (operationId: string) => - value("pi.op.meta", operationId); + value("pi.op.meta", operationId); export const operationState = (operationId: string) => value("pi.op.state", operationId); export const operationToolArgs = ( @@ -449,7 +449,7 @@ The complete rules are: - an empty key is legal and naturally addresses one session-wide value or list; - object identity has no durable meaning; equal `(kind, namespace, key)` triples name the same location; - constructing one location with incompatible TypeScript types is a trusted-programming defect; -- value and list addresses may not share one `(namespace, key)` in a storage version; +- value and list addresses may not share one `(namespace, key)` in a storage version; violating this is a trusted-programming defect and storage performs no cross-kind collision check; - changing namespace, key grammar, kind, or incompatible value shape requires migration (§7.4); - built-in constructors live in `session/values.ts` and are imported directly; there is no runtime catalog or dependency-injection bundle; - later operations never accept another key after address construction. @@ -480,7 +480,7 @@ type DurableStructuralPreparation = | `laneConfig(lane)` | value | `pi.lane.config`, lane | `LaneConfiguration` | total lane configuration | | `laneState(lane)` | value | `pi.lane.state`, lane | `LaneState` (§3.3) | `currentOperationId`, `pendingNextRun` | | `laneLastResult(lane)` | value | `pi.lane.lastResult`, lane | `LaneLastResult` (§3.13) | latest terminal outcome | -| `operationMeta(opId)` | value | `pi.op.meta`, operation id | `Operation` (§3.1) | acceptance data; written once | +| `operationMeta(opId)` | value | `pi.op.meta`, operation id | `OperationMeta` (§3.1) | acceptance data; written once | | `operationState(opId)` | value | `pi.op.state`, operation id | `OperationState` (§3.2) | total durable restart point | | `operationToolArgs(opId, stepId, sourceIndex)` | value | `pi.op.tool_args`, `{opId}:{stepId}:{sourceIndex}` | effective arguments | written once at clearance | | `operationToolMemo(opId, invocationId, name)` | value | `pi.op.tool_memo`, `{opId}:{invocationId}:{name}` | `JsonValue` | invocation-scoped durable memo | @@ -591,13 +591,13 @@ interface ListCursor { seq: number } interface ListReadOptions { cursor?: ListCursor; // exclusive order?: "asc" | "desc"; // default "asc" - limit?: number; // positive safe integer; default 1,000, cap 10,000 + limit?: number; // query-page size: positive safe integer; default 1,000; values above 10,000 clamp to 10,000 } ``` List read semantics: ascending reads return `seq > cursor.seq`, descending reads `seq < cursor.seq`; results are ordered before `limit` applies; absent and empty keys both return `[]`; callers continue with the last returned element's `seq`, and an empty page ends iteration. A cursor is a sequence filter, not a snapshot or key-incarnation token: concurrent later appends may appear on later ascending pages, and after a whole-key delete a read simply applies the comparison to surviving elements. There is deliberately no unbounded “read the whole list” helper. -`scanValues(prefixAddress)` is namespace-scoped and interprets the bound key as a prefix. Core inventory/cleanup uses only `laneLeafInventoryPrefix`, `operationToolArgsPrefix`, `operationToolMemoPrefix`, `operationPreparationPrefix`, and `pendingToolOutputPrefix`; core call sites do not repeat raw reserved namespace/key grammar. Ordinary reads use exact addresses. There is no cross-namespace value dump or durable write log. Entry inventory uses `scanEntries`; ledger reads use `scanUsage`; totals use the stats projection (§1.6); test-order assertions wrap `commit()` with the instrumented-storage decorator (Part 9). +`scanValues(prefixAddress)` is namespace-scoped, interprets the bound key as a prefix, and returns values in key-ascending order. Core inventory/cleanup uses only `laneLeafInventoryPrefix`, `operationToolArgsPrefix`, `operationToolMemoPrefix`, `operationPreparationPrefix`, and `pendingToolOutputPrefix`; core call sites do not repeat raw reserved namespace/key grammar. Ordinary reads use exact addresses. There is no cross-namespace value dump or durable write log. Entry inventory uses `scanEntries`; ledger reads use `scanUsage`; totals use the stats projection (§1.6); test-order assertions wrap `commit()` with the instrumented-storage decorator (Part 9). Recovery and execution reads must be index-driven and bounded. They may not infer state from an absent value, and there is no value history to fold. Exact dereference is allowed: current typed state may name a bounded set of entries and bound values. An exact list address derived from current state may be read in bounded sequence pages and reduced by its consumer—assistant frames use pi-ai's `reduceAssistantMessageFrames` (§3.7). Base restore never reads lists (§4.4). Public inventory and debugging APIs expose explicit limits/pagination at the `SessionTree` layer. @@ -660,7 +660,7 @@ The file is not the state; it is the **replay recipe** for the Memory maps above - This is format 4. The incompatible format-4 code currently in the source tree is unfinished and is replaced in place; no migration for it is required. Coding-agent format 3 remains supported (Appendix B). - Open replays lines in order into the Memory maps: entries and usage rows accumulate; a later scalar `set` overwrites the key, `delete` removes it; a list `append` adds `{ seq, value }` under its key and a list `delete` removes the whole key. That is *decoding*, not recovery logic. Open verifies persisted sequence monotonicity — strictly increasing, gaps legal (§1.4) — and timestamps, and never regenerates committed timestamps. All queries then run in RAM. - **A torn final line is discarded whole**, including every element of an array, and is truncated before new writes are admitted. This is what makes "no crash prefix inside a transaction" true here. -- A malformed *interior* line or invalid transaction framing is corruption. Superseded old-shape register lines from before a schema migration replay as keyed raw JSON so the migration can convert the current value (Part 7); compaction retires the old bytes. Externally edited shape-invalid data is unsupported rather than runtime-validated on read. +- A malformed *interior* line or invalid transaction framing is corruption. WP01 supports no pre-WP01 format-4 record spelling. A future older storage version is decoded only when an explicit R11 migration defines that total mapping; post-migration compaction retires its bytes. Externally edited shape-invalid data is unsupported rather than runtime-validated on read. - Durability is process-crash level: a resolved `commit()` survives process death. No fsync promise. - Optional: retain `(offset, length)` per entry and load payloads lazily, keeping only structure and current values/lists resident. Do this only if profiling demands it. @@ -687,8 +687,9 @@ entries(id TEXT PRIMARY KEY, parent_id TEXT, seq INTEGER, type TEXT, CREATE INDEX ix_entry_parent ON entries(parent_id); CREATE INDEX ix_entry_seq ON entries(seq, type); -scalar_values(namespace TEXT, key TEXT, seq INTEGER, value TEXT, - PRIMARY KEY (namespace, key)); +scalar_values(namespace TEXT NOT NULL, key TEXT NOT NULL, seq INTEGER NOT NULL, + value TEXT NOT NULL, + PRIMARY KEY (namespace, key)) WITHOUT ROWID; list_values(namespace TEXT NOT NULL, key TEXT NOT NULL, seq INTEGER NOT NULL, value TEXT NOT NULL, @@ -717,7 +718,7 @@ session(created_at, parent_session_id, storage_version, metadata, writer_lease(owner_id TEXT, fence INTEGER, expires_at_ms INTEGER); ``` -These are logical table names. An implementation may retain an already-applied physical table name while exposing the bound-address API; do not rewrite applied migrations solely for terminology. +WP01 replaces the unfinished format-4 schema in place: `001_initial.sql` uses these physical table names, keeps storage version 1, and supports no pre-WP01 format-4 SQLite file. Migration machinery belongs to R11, not this WIP schema replacement. One `commit()` is one SQL transaction: insert entries, insert ledger rows, replace or delete scalar values, insert or whole-list-delete list elements, maintain the branch index, and bump session stats (`message_count` and aggregate `usage_payload`). Never update or delete an entry or ledger row; mutability is confined to values/lists, the branch index, stats, sequences, the session catalog row, and leases. List paging is `SELECT seq, value FROM list_values WHERE namespace = ? AND key = ? AND seq > ? ORDER BY seq ASC LIMIT ?` (descending symmetric; omit the predicate without a cursor); assert with `EXPLAIN QUERY PLAN` that it uses the primary key with no temporary sort. @@ -985,7 +986,7 @@ Tests assert these invariants and the required query plans. No wall-clock thresh ## 2.7 Forks -A fork is a repository operation over one coherent source-session snapshot. It copies selected entries, latest semantic values, lane leaves, and total configuration; it never copies operation/pending values, assistant frame lists, or ledger rows — destination lanes start with a fresh empty `LaneState`. Application-defined values and lists require an explicit fork policy when their consuming feature is added; a precise rewrite (§2.9) that retains a list element preserves its sequence cursor unless it explicitly remaps the whole destination sequence space. +A fork is a repository operation over one coherent source-session snapshot. It copies selected entries, latest semantic values, lane leaves, and total configuration; it never copies operation/pending values, assistant frame lists, or ledger rows — destination lanes start with a fresh empty `LaneState`. Application-defined values and lists are not copied by the generic fork; a consuming feature must add an explicit address-specific policy before relying on copied application state; a precise rewrite (§2.9) that retains a list element preserves its sequence cursor unless it explicitly remaps the whole destination sequence space. ```ts type ForkOptions = @@ -996,7 +997,7 @@ type ForkOptions = - Memory and JSONL obtain the snapshot as one job on the source storage queue. SQLite uses one read transaction. - Branch scope copies one path and creates only destination `main`. Tree scope copies the whole tree and every lane leaf/configuration. - The destination is idle and its usage/cost ledger starts at zero. Entry-local display usage remains on copied entries. -- Semantic values follow their declared scope: the session name copies; labels copy only when their target copies unless tree scope copies all targets; application-defined values/lists follow their explicit policy. +- Semantic values follow their declared scope: the session name copies; labels copy only when their target copies unless tree scope copies all targets; application-defined values/lists are excluded unless a later consuming feature adds an explicit address-specific policy. - Any message may be the fork point. Request construction heals orphaned tool calls. - Copied entries keep their ids. - `id` optionally supplies the destination session id. @@ -1207,7 +1208,7 @@ Entries and usage rows are never deleted (§1.2). The sole sanctioned exception ## 3.1 Operations ```ts -interface Operation { +interface OperationMeta { operationId: string; lane: string; sourceLeafId: string | null; @@ -1220,7 +1221,7 @@ interface Operation { } ``` -Acceptance data lives in the `operationMeta(operationId)` value: written once at acceptance, never overwritten, and deleted by the terminal transaction (§3.13). `sourceLeafId` is the lane's leaf *before* the operation; entries the operation itself appends come after it. `promptEntryIds` name the caller's normalized prompt entries, born placed in the acceptance transaction (§3.6). The id is either supplied to `accept` or minted before the acceptance command; it correlates a hosted submission with current operation and `pi.lane.lastResult`, but the harness retains no forever-idempotency index after a later terminal result replaces that value. +`OperationMeta` is immutable acceptance metadata. It lives in the `operationMeta(operationId)` value: written once at acceptance, never overwritten, and deleted by the terminal transaction (§3.13). The process-local `Operation` projection is `{ meta: OperationMeta, state: OperationState }`, assembled from the separate `pi.op.meta` and `pi.op.state` values; it is never persisted as one value. `sourceLeafId` is the lane's leaf *before* the operation; entries the operation itself appends come after it. `promptEntryIds` name the caller's normalized prompt entries, born placed in the acceptance transaction (§3.6). The id is either supplied to `accept` or minted before the acceptance command; it correlates a hosted submission with current operation and `pi.lane.lastResult`, but the harness retains no forever-idempotency index after a later terminal result replaces that value. ## 3.2 Operation state — the durable restart point @@ -1231,6 +1232,12 @@ That value is authoritative after process loss, but it is not the finer instruct ```ts type OperationState = RunState | CompactionState | NavigationState; +/** Process-local owned projection assembled from two durable values. */ +interface Operation { + meta: OperationMeta; + state: OperationState; +} + type Control = | { status: "running" } | { status: "cancel_requested"; requestedAt: number; @@ -2168,7 +2175,6 @@ Recovery is point lookups against bound values. No history, no folding, no journ ```ts interface CurrentOperation { operation: Operation; - state: OperationState; laneState: LaneState; leafId: string | null; configuration: LaneConfiguration; @@ -2197,8 +2203,7 @@ async function restore(lane: string): Promise< } return { kind: "suspended", current: { - operation: meta.value, - state: opState.value, + operation: { meta: meta.value, state: opState.value }, laneState: state.value, leafId: leaf.value, configuration: config.value, @@ -3400,7 +3405,7 @@ open session: Chained migrations run under the writer lease before `open()` returns (§2.8). Each step commits its conversions and version bump atomically, so a crash mid-chain resumes at the recorded version; conversions must be idempotent over already-converted values, which field mappings are by construction. -JSONL has one wrinkle in each direction. Replay must decode superseded old-shape register lines leniently — as keyed raw JSON, overwrite-by-key only — because pre-migration bytes remain in the file (§1.7). And a migration must trigger snapshot compaction, whose temp-file-and-rename both persists the new header version atomically and retires the old-shape bytes. Between crash and compaction, lenient replay plus idempotent conversion make the intermediate state harmless. +JSONL has one wrinkle in each direction. When R11 adds migrations, replay must decode exactly the older-version value/list records named by that migration because pre-migration bytes remain in the file (§1.7). A migration then triggers snapshot compaction, whose temp-file-and-rename both persists the new header version atomically and retires the old-version bytes. Between crash and compaction, version-specific decoding plus idempotent conversion make the intermediate state harmless. This does not add compatibility for the pre-WP01 WIP format-4 spelling. Legacy coding-agent format 3 predates `storageVersion` entirely; it normalizes through Appendix B on load and receives the current version with its first format-4 write. @@ -3508,7 +3513,7 @@ Tree: Operations: -12. `laneState(lane)` confers lane ownership, and `operationState(operationId)` confers operation-state ownership. An open lane names operation O, `operationMeta(O)` holds that lane's compatible `Operation`, and `operationState(O)` holds an `OperationState` compatible with O's intent kind; state values carry no duplicate owner metadata. +12. `laneState(lane)` confers lane ownership, and `operationState(operationId)` confers operation-state ownership. An open lane names operation O, `operationMeta(O)` holds that lane's compatible `OperationMeta`, and `operationState(O)` holds an `OperationState` compatible with O's intent kind; state values carry no duplicate owner metadata. 13. Operation-owned values and lists may exist only while their operation is open: the terminal transaction deletes them atomically with clearing `currentOperationId` (§3.13). Lane-owned `pendingNextRun` values are never deleted by it. 14. Acceptance must observe `currentOperationId === null`, commits no `ActiveOperation`, and returns before any hook/provider/tool/timer work begins. Run acceptance commits payload-free `starting`; only its consuming command may apply `before_run` output and replace it with `checkpoint`. A supplied operation id obeys §1.2 and is the exact id written to `pi.op.meta`, events, and `pi.lane.lastResult`. 15. A reserved id may exist only with the content its intent named. Queued-content ids begin in `pi.pending.entry`; settlement-family ids begin as strings in `pi.op.state`. A tool-result id may then move through `string only → outcome-ready pi.pending.entry → immutable entry`; no two representations coexist at a commit boundary (§2.2). An effect-pending response id may additionally key its auxiliary frame list (§3.7); frames are observation, not a content representation, and die with settlement. diff --git a/packages/agent/docs/runtime2.md b/packages/agent/docs/runtime2.md index 43e186d94..8c36f9f2d 100644 --- a/packages/agent/docs/runtime2.md +++ b/packages/agent/docs/runtime2.md @@ -29,7 +29,7 @@ A slice must close the entire named concern at parity, not only its immediately ## Historical reading policy -`packages/agent/docs/harness.md` is the normative source. Until their designs are folded into it, `registers.md`, `tool-durability.md`, and `assistant-durability.md` are binding handoffs for bound typed value/list addresses, tool outcomes/checkpoints/memos, and assistant frames. Runtime1 is implementation inspiration only: do not preserve its abstractions, internal bookkeeping, or accidental behavior unless the normative contract independently requires them. For each slice, read its listed sections completely. If any handoff or `harness.md` changed since the previous slice, inspect that diff and reread affected sections. +`packages/agent/docs/harness.md` is the normative source. Until their designs are folded into it, `values.md`, `tool-durability.md`, and `assistant-durability.md` are binding handoffs for bound typed value/list addresses, tool outcomes/checkpoints/memos, and assistant frames. Runtime1 is implementation inspiration only: do not preserve its abstractions, internal bookkeeping, or accidental behavior unless the normative contract independently requires them. For each slice, read its listed sections completely. If any handoff or `harness.md` changed since the previous slice, inspect that diff and reread affected sections. Read a source or test file completely before editing that file. For dependencies that will not be edited, begin with the relevant exported types or functions and expand only when a concrete question requires it. @@ -113,7 +113,7 @@ Do not read provider/tool procedures or implement ownership in a no-work shell. Read: - `harness.md`: §§3.2, 3.7, 3.12–3.13, 4.1–4.2, 5.5–5.8; -- `registers.md` assistant-frame consumer and `assistant-durability.md` fresh-stream/settlement sections; +- `values.md` assistant-frame consumer and `assistant-durability.md` fresh-stream/settlement sections; - pi-ai's exported assistant-message frame converter/reducer; - `execution/assistant.ts`; - checkpoint, ordinary-ready/intent/settlement, and terminal paths in: @@ -167,10 +167,10 @@ If implementation exposes a concrete dependency not listed above: ## Value/list and durability prerequisites -Before further assistant/tool runtime slices, implement `registers.md` repo-wide: +Before further assistant/tool runtime slices, implement `values.md` repo-wide: - replace global namespace/value maps and register tokens with `value(namespace, key?)` and `list(namespace, key?)`; -- put direct built-in address constructors in `session/values.ts`, using the universal `value()`/`list()` constructors and the `pi.*` namespaces/key grammars documented by `registers.md`; +- put direct built-in address constructors in `session/values.ts`, using the universal `value()`/`list()` constructors and the `pi.*` namespaces/key grammars documented by `values.md`; - expose `getValue`/`setValue`/`deleteValue`/`scanValues` and `readList`/`appendList`/`deleteList` without a second key argument; core lane inventory and cleanup use only the five exported `*Prefix` constructors as `scanValues` inputs; - add backend-conformant append/read/whole-list-delete storage; - add `pendingAssistantFrames(operationId, responseEntryId)` as a `ValueList` constructor. diff --git a/packages/agent/docs/registers.md b/packages/agent/docs/values.md similarity index 91% rename from packages/agent/docs/registers.md rename to packages/agent/docs/values.md index 43e8654b4..e3af3feb4 100644 --- a/packages/agent/docs/registers.md +++ b/packages/agent/docs/values.md @@ -1,4 +1,4 @@ -# Typed values and lists — implementation handoff +# Typed values and lists This document specifies the mutable storage primitive used by Session, the harness, and applications. @@ -113,7 +113,7 @@ Rules: - object identity has no durable meaning; - separately constructed addresses with the same `(kind, namespace, key)` identify the same durable location; - constructing the same durable location with incompatible TypeScript types is a trusted-programming defect; -- scalar and list addresses may not share the same `(namespace, key)` in one storage version; +- scalar and list addresses may not share the same `(namespace, key)` in one storage version; violating this is a trusted-programming defect and storage performs no cross-kind collision check; - changing an address's namespace, key, kind, or incompatible value shape requires migration. The two components remain separate rather than concatenated. Dynamic application keys and operation IDs therefore require no escaping convention beyond the storage separator rule. @@ -188,7 +188,7 @@ export const laneLastResult = (lane: string) => export const laneLeafInventoryPrefix = () => value("pi.lane.leaf"); export const operationMeta = (operationId: string) => - value("pi.op.meta", operationId); + value("pi.op.meta", operationId); export const operationState = (operationId: string) => value("pi.op.state", operationId); export const operationToolArgs = (operationId: string, stepId: string, sourceIndex: number) => @@ -237,6 +237,8 @@ export const sessionName = value("pi.session.name"); export const entryLabel = (entryId: string) => value("pi.entry.label", entryId); ``` +`OperationMeta` is immutable acceptance metadata stored at `pi.op.meta`. The process-local `Operation` projection is `{ meta: OperationMeta, state: OperationState }`, assembled from the separate metadata and state values; it is never stored at one address. + The five exported scan-prefix constructors are `laneLeafInventoryPrefix`, `operationToolArgsPrefix`, `operationToolMemoPrefix`, `operationPreparationPrefix`, and `pendingToolOutputPrefix`. Their addresses are consumed only by `scanValues()`. Applications define their own `value()` and `list()` addresses directly; there is no built-in custom application-state namespace or custom-state API. `AgentHarnessToolInvocation.getMemo()` and `setMemo()` are invocation-fenced capabilities over `operationToolMemo(...)`, not raw Session access. Invocation memos remain operation-owned and are deleted when their tool outcome becomes durable. @@ -269,7 +271,7 @@ export interface ListReadOptions { cursor?: ListCursor; /** Default: asc. */ order?: "asc" | "desc"; - /** Default: 1,000. Maximum: 10,000. */ + /** Query-page size. Default: 1,000. Values above 10,000 clamp to 10,000. */ limit?: number; } @@ -286,7 +288,7 @@ interface ValueReader { } ``` -`scanValues(prefixAddress)` scans scalar addresses with exactly that namespace and keys beginning with the bound key. Core call sites use only the exported prefix constructors above so raw namespace/key grammar stays in `session/values.ts`. Prefix addresses are passed only to `scanValues()`, never to exact get/set/delete operations. There is no unrestricted cross-namespace dump. Ordinary application reads use exact addresses. +`scanValues(prefixAddress)` scans scalar addresses with exactly that namespace and keys beginning with the bound key, returning them in key-ascending order. Core call sites use only the exported prefix constructors above so raw namespace/key grammar stays in `session/values.ts`. Prefix addresses are passed only to `scanValues()`, never to exact get/set/delete operations. There is no unrestricted cross-namespace dump. Ordinary application reads use exact addresses. `SessionTree` exposes direct one-transition writes using the same addresses: @@ -397,7 +399,7 @@ Rules: - absent and empty both return `[]`; - callers continue with the last returned element's `seq`; - an empty page ends iteration; -- `limit` must be a positive safe integer, defaults to 1,000, and is capped at 10,000. +- `limit` is only the query-page size: it must be a positive safe integer, defaults to 1,000, and values above 10,000 clamp to 10,000; it never limits total list length or bytes. ```ts let cursor: ListCursor | undefined; @@ -516,7 +518,7 @@ CREATE TABLE list_values ( ) WITHOUT ROWID; ``` -An implementation may retain an already-applied physical table name while exposing this logical API; do not rewrite an applied migration solely for terminology. Adding list storage uses a new migration when required by the backend's existing schema history. +WP01 replaces the unfinished format-4 schema in place: edit `sqlite/migrations/001_initial.sql`, rename the physical `registers` table to `scalar_values`, add `list_values`, and keep `SQLITE_STORAGE_VERSION = 1`. There is no migration runner in this WIP implementation, and pre-WP01 SQLite files are unsupported. Do not add migration machinery in this package. List operations: @@ -545,7 +547,7 @@ Logical records carry the bound address's physical components: {"kind":"list","op":"delete","seq":52,"namespace":"pi.pending.assistant_frame","key":"O:R"} ``` -Scalar records use `kind:"value"` with `op:"set"|"delete"`. An implementation may retain an old physical record spelling while decoding it into the new logical API when compatibility requires it. +Scalar records use `kind:"value"` with `op:"set"|"delete"`. WP01 keeps JSONL format 4 and storage version 1 but replaces the unfinished record spelling in place; pre-WP01 format-4 files are unsupported and no legacy `kind:"register"` decoder remains. Replay folds records into the Memory state: @@ -569,7 +571,7 @@ Fork and precise-rewrite code decides policy per concrete address grammar: - operation-owned `pi.op.*` scalar values are not copied into an idle fork; - `pi.pending.entry`, `pi.pending.tool_output`, and `pi.pending.assistant_frame` values/lists are not copied; - lane and semantic session values follow their existing scope rules; -- application-defined values/lists require an explicit policy when their consuming feature is added. +- application-defined values/lists are not copied by the generic fork; a consuming feature must add an explicit address-specific policy before relying on copied application state. A precise rewrite retaining list elements preserves their `seq` values unless it explicitly remaps the entire destination sequence space. @@ -584,7 +586,7 @@ A bound address's namespace, key grammar, kind, and value type are durable schem - a list migration pages elements in sequence order and either maps them while preserving `seq` or deletes the complete list; - a migration must not load an unbounded logical list at once. -Adding generic list storage is a backend schema change. Constructing a new application address with no persisted value requires no migration. +Adding generic list storage replaces the current WIP backend schema in place. Constructing a new application address with no persisted value requires no migration. ## Instrumentation and telemetry @@ -600,7 +602,7 @@ Append-path tests prove that no `readList` call occurs before append commit. Fra 2. Address object identity has no durable meaning. 3. Namespace `pi` and every `pi.*` are reserved by contract; every built-in namespace starts with `pi.`, and application use is a trusted-programming defect. 4. Exactly five built-in prefix constructors encapsulate lane inventory and operation cleanup grammar; their results are consumed only by namespace-scoped `scanValues()`. -5. Scalar and list addresses cannot occupy the same physical location. +5. Scalar and list addresses must not occupy the same physical location; this is a trusted-programming rule, not a runtime cross-kind collision check. 6. Typed reads and helper-constructed writes preserve `T`. 7. Scalar helpers reject list addresses; list helpers reject scalar addresses. 8. A Session/Storage operation never requires a second key after address construction. @@ -643,7 +645,7 @@ Append-path tests prove that no `readList` call occurs before append commit. Fra - replacement retains only the latest logical value and latest set `seq`; - typed write helpers preserve mixed transaction order; - prefix scans interpret the bound address key as a prefix and remain namespace-scoped; -- existing scalar JSONL/SQLite data opens through the compatibility policy chosen by those backends. +- new scalar JSONL/SQLite files use only the value/list schema; pre-WP01 WIP files are explicitly unsupported. ### List conformance @@ -677,7 +679,7 @@ Extend the shared backend conformance suite: - direct Session writes serialize and commit once; - explicit `Session.mutate()` can atomically combine typed value/list writes with entries and usage. -### Assistant-frame integration +### Assistant-frame integration — deferred beyond WP01 - every converted non-terminal frame appends under the exact bound effect-pending response address; - terminal `done`/`error` events append nothing; @@ -702,8 +704,7 @@ Expected primary changes: - expose `ValueReader` through Storage, SessionReader, SessionMutator, Session, and SessionTree; - add direct application scalar/list methods to SessionTree using bound addresses; - update Memory state, JSONL codec/storage, snapshots, fork/rewrite code, instrumentation, and conformance suites; -- add SQLite list storage and address-based adapters without rewriting applied migrations solely for terminology; -- update assistant execution, deferred polling, recovery, snapshots, and cleanup to use `pendingAssistantFrames(operationId, responseEntryId)`; +- replace SQLite's unfinished initial schema in place with `scalar_values` and `list_values`; keep storage version 1 and add no migration runner; - update telemetry schema sources and regenerate `telemetry-schema.md`; do not edit that generated file manually. -`assistant-durability.md` specifies the consuming lifecycle. `harness.md` must replace raw namespace/key examples and global register maps with this bound-address model before implementation. +WP01 stops after generic addresses/storage and projection-only restore coverage. `assistant-durability.md` specifies the later consuming lifecycle; assistant execution, deferred polling, recovery, snapshot hydration, memo/checkpoint capabilities, and operation cleanup land only with their runtime work packages. diff --git a/packages/agent/docs/work-packages/00-runtime1-removal.md b/packages/agent/docs/work-packages/00-runtime1-removal.md index b9a279951..3b707410f 100644 --- a/packages/agent/docs/work-packages/00-runtime1-removal.md +++ b/packages/agent/docs/work-packages/00-runtime1-removal.md @@ -10,7 +10,7 @@ Make runtime2 the sole public harness implementation, delete runtime1 and its ob ## Prerequisites -- The approved acceptance/hook redesign and durability handoffs are present in `runtime2.md`, `registers.md`, `assistant-durability.md`, and `tool-durability.md`. +- The approved acceptance/hook redesign and durability handoffs are present in `runtime2.md`, `values.md`, `assistant-durability.md`, and `tool-durability.md`. - Existing tests are evidence, not authority. ## Work, in order diff --git a/packages/agent/docs/work-packages/01-bound-values-lists.md b/packages/agent/docs/work-packages/01-bound-values-lists.md index 2d52dd67a..888c16d76 100644 --- a/packages/agent/docs/work-packages/01-bound-values-lists.md +++ b/packages/agent/docs/work-packages/01-bound-values-lists.md @@ -2,56 +2,326 @@ ## Status -Ready after WP00. The normative design is [`registers.md`](../registers.md). +Implementation-ready after WP00; implementation not started. `harness.md` is normative. [`values.md`](../values.md) supplies the detailed address, backend, and conformance design. Start only on explicit instruction. ## Goal -Replace the retained register/custom-state API with bound `Value` and `ValueList` addresses across every existing backend and application surface, then stop before runtime execution work. +Replace the retained register/custom-state storage surface with bound `Value` and `ValueList` addresses across Session, Memory, JSONL, SQLite, instrumentation, tests, and public application access. Stop before runtime execution consumers. -## Scope +## Decisions fixed for this package -1. Add `session/values.ts` with universal `value()` / `list()`, typed write helpers, all `pi.*` built-in address/prefix constructors, and no registry, catalog, privilege path, or global type map. -2. Replace `RegisterValues`, namespace unions, `Register`, `getRegister`, `listRegisters`, raw register writes, custom-state APIs, and old persisted semantic names. -3. Expose the same bound-address reads through Storage, SessionReader, SessionMutator, Session, and SessionTree; add direct Session scalar/list writes and retain one-commit `Session.mutate()` composition. -4. Implement scalar replacement and list append/page/whole-list-delete in Memory, JSONL, and SQLite, including replay, compaction, snapshots, forks/rewrites, instrumentation, and shared conformance. -5. Use `pi.session.name`, `pi.entry.label`, and the complete built-in namespace/key grammar from `registers.md`. Applications define their own addresses directly. -6. Update telemetry schema source for value/list write kinds and regenerate `telemetry-schema.md`. -7. Update all retained source/tests and public exports; no old API shim remains. +1. **Core and application namespaces.** Every core address uses the exact documented `pi.*` namespace. Applications use their own non-reserved namespaces through the same public `value()` / `list()` constructors. `fact.custom` and its API are deleted, not renamed to a built-in custom namespace. +2. **Forks.** Generic forks copy only explicitly handled core addresses: lane leaf/configuration plus fresh lane state, session name, and labels whose targets copy. They copy no `pi.op.*`, `pi.pending.*`, list, ledger, or arbitrary application address. A later application feature must add an address-specific fork policy before relying on copied application state. +3. **Trusted kind discipline.** Using one `(namespace, key)` as both a value and a list is a trusted-programming defect. Backends do not add cross-kind collision checks, triggers, registries, or catalogs. +4. **Operation names.** Keep source `OperationMeta` for immutable acceptance metadata and `Operation` for the process-local `{ meta: OperationMeta, state: OperationState }` projection. `operationMeta(id)` binds `Value`; the composite is never persisted as one value. +5. **Query ordering and bounds.** `scanValues()` returns key-ascending results. `readList()` limits only one query page, never total list length or bytes: reject non-positive or non-safe limits, default to 1,000, and clamp larger values to 10,000. +6. **No WIP compatibility.** Replace the unfinished format-4 storage schema in place. JSONL remains format 4/storage version 1 but accepts only the new value/list records. SQLite keeps `SQLITE_STORAGE_VERSION = 1`, edits `001_initial.sql` in place, renames `registers` to `scalar_values`, and adds `list_values`. Pre-WP01 format-4 JSONL and SQLite files are unsupported. Add no migration runner or legacy decoder. +7. **Generic infrastructure only.** WP01 defines every built-in value/list constructor, including future assistant/tool addresses, but does not implement their runtime consumers. -## Preserve +## Public and storage contract -- transaction atomicity and global write `seq` ordering; -- JSONL torn-tail and compaction behavior, preserving list-element sequences; -- SQLite `BEGIN IMMEDIATE`, writer fencing, and indexed list paging without temporary sorting; -- repository/fork semantics, branch indexes, stats, UUIDv7/follower IDs, v3 normalization, and existing entry/usage behavior; -- projection-only restore: no list read until bounded consumption-time hydration; -- trusted in-process values with no codecs, cloning, or runtime shape validation. +Add `packages/agent/src/harness/session/values.ts` with: + +- invariant `Value` and `ValueList` address types; +- universal `value(namespace, key?)` and `list(namespace, key?)` constructors; +- namespace/key validation only: non-empty namespace and no `\u0000` component; +- `StoredValue`, `ListElement`, `ListCursor`, and `ListReadOptions`; +- typed `setValue`, `deleteValue`, `appendList`, and `deleteList` write helpers using `NoInfer`; +- every exact built-in constructor and the five scan-prefix constructors from `values.md`. + +Replace the old API throughout: + +```ts +getRegister(namespace, key) -> getValue(address) +listRegisters(namespace, prefix) -> scanValues(prefixAddress) +register set/delete writes -> typed value helpers +``` + +Storage, `SessionReader`, `SessionMutator`, `Session`, and `SessionTree` expose the same bound-address reads: + +```ts +getValue(address: Value): Promise | undefined>; +scanValues(prefix: Value): Promise[]>; +readList(address: ValueList, options?: ListReadOptions): Promise[]>; +``` + +`SessionTree` and therefore `Session` additionally expose one-commit direct writes: + +```ts +setValue(address: Value, next: NoInfer): Promise; +deleteValue(address: Value): Promise; +appendList(address: ValueList, element: NoInfer): Promise; +deleteList(address: ValueList): Promise; +``` + +`SessionMutator` retains one explicit `commit()` and does not gain direct committing methods. Explicit transactions compose helper-constructed value/list writes with entries and usage. + +Keep `getName` / `setName` and `getLabel` / `setLabel` as wrappers over `sessionName` and `entryLabel(id)`. Delete `getCustomFact` / `setCustomFact`. Rename the public passive metadata event from `fact_update` to the `value_update` shape already specified by `harness.md`; it covers only session-name and entry-label wrappers, not arbitrary application writes. + +## Files + +### Add + +- `packages/agent/src/harness/session/values.ts` +- `packages/agent/test/harness/values.test.ts` +- `packages/session-backends/sqlite-node/src/sqlite/session/values.ts` + +### Delete or rename + +- delete `packages/session-backends/sqlite-node/src/sqlite/session/registers.ts` after moving its scalar behavior to `values.ts`; +- remove all register/global-map/custom-fact declarations from `packages/agent/src/harness/session/types.ts`. + +### Agent source + +- `packages/agent/src/harness/agent-harness.ts` +- `packages/agent/src/harness/session/types.ts` +- `packages/agent/src/harness/session/commit.ts` +- `packages/agent/src/harness/session/storage-state.ts` +- `packages/agent/src/harness/session/memory.ts` +- `packages/agent/src/harness/session/session.ts` +- `packages/agent/src/harness/session/fork.ts` +- `packages/agent/src/harness/session/index.ts` +- `packages/agent/src/harness/session/jsonl/storage.ts` +- `packages/agent/src/harness/session/jsonl/repo.ts` +- `packages/agent/src/harness/session/testing/storage-decorator.ts` +- `packages/agent/src/harness/session/testing/instrumented-storage.ts` +- `packages/agent/src/harness/session/testing/types.ts` +- `packages/agent/src/harness/session/testing/conformance/storage.ts` +- `packages/agent/src/harness/session/testing/conformance/session-repo.ts` +- `packages/agent/src/harness/session/testing/benchmark/storage.ts` +- `packages/agent/src/harness/session/testing/benchmark/session-repo.ts` +- `packages/agent/src/harness/session/testing/index.ts` +- `packages/agent/src/harness/runtime2/restore.ts` +- `packages/agent/src/harness/runtime2/harness.ts` +- `packages/agent/src/harness/runtime2/lane.ts` +- `packages/agent/src/harness/telemetry.ts` +- `packages/agent/src/index.ts` and `packages/agent/src/node.ts` only as needed to verify the new public exports; do not add a second export path. + +### Agent tests and generated documentation + +- `packages/agent/test/harness/memory-storage.test.ts` +- `packages/agent/test/harness/memory-conformance.test.ts` +- `packages/agent/test/harness/memory-session-repo.test.ts` +- `packages/agent/test/harness/jsonl-storage.test.ts` +- `packages/agent/test/harness/jsonl-storage-conformance.test.ts` +- `packages/agent/test/harness/jsonl-session-repo.test.ts` +- `packages/agent/test/harness/jsonl-session-repo-conformance.test.ts` +- `packages/agent/test/harness/storage-backed-session.test.ts` +- `packages/agent/test/harness/session-tree.test.ts` +- `packages/agent/test/harness/session-create-lane.test.ts` +- `packages/agent/test/harness/instrumented-storage.test.ts` +- `packages/agent/test/harness/types.test.ts` +- `packages/agent/test/harness/telemetry.test.ts` +- `packages/agent/test/harness/runtime2/harness.test.ts` +- `packages/agent/test/harness/runtime2/lane.test.ts` +- `packages/agent/test/harness/runtime2/restore.test.ts` +- regenerated `packages/agent/docs/telemetry-schema.md` + +### SQLite backend + +- `packages/session-backends/sqlite-node/src/sqlite/migrations/001_initial.sql` +- `packages/session-backends/sqlite-node/src/sqlite/repo.ts` +- `packages/session-backends/sqlite-node/src/sqlite/session.ts` +- `packages/session-backends/sqlite-node/src/sqlite/storage.ts` +- `packages/session-backends/sqlite-node/src/sqlite/index.ts` if needed for the renamed module +- `packages/session-backends/sqlite-node/test/storage.test.ts` +- `packages/session-backends/sqlite-node/test/storage-conformance.test.ts` +- `packages/session-backends/sqlite-node/test/repo.test.ts` +- `packages/session-backends/sqlite-node/test/adapter.test.ts` +- `packages/session-backends/sqlite-node/test/sql.test.ts` + +### Coding-agent consumer + +- `packages/coding-agent/test/experimental-session-support.ts` +- verify `packages/coding-agent/test/experimental-remote-runtime.test.ts` +- verify `packages/coding-agent/test/experimental-server-replacement.test.ts` + +If the final old-API grep identifies another retained source/test call site, it belongs to WP01; do not add a compatibility shim to avoid touching it. + +## Work, in order + +1. **Add the address vocabulary.** Implement `values.ts`, export it through the existing session/root path, and add focused compile-time/runtime address tests. Include exact built-in namespace/key/kind tests and prefix-constructor tests before migrating callers. +2. **Cut the shared API once.** Replace register types and writes in `types.ts`/`commit.ts`; split `StorageState` into current scalar values and surviving list elements; implement Memory reads/writes, ordered prefix scans, paged list reads, transaction validation/application, snapshots, and direct Session methods. Remove custom-fact APIs and migrate name/label wrappers. +3. **Migrate JSONL and generic fork/snapshot code.** Encode only `kind:"value"` and `kind:"list"`; replay set/delete/append/delete; preserve transaction-line torn-tail atomicity; serialize surviving list elements with original `seq` merged in global sequence order; preserve the sequence high-water mark. This extends existing snapshot serialization only—do not add a new compaction trigger or precise-rewrite feature. Forks copy the fixed core set from Decisions item 2, re-sequence destination scalar values after copied entries as today, and copy no lists. +4. **Migrate instrumentation, conformance, and benchmarks.** The storage decorator exposes all three reads; instrumented storage records erased value/list writes in exact order without content telemetry. Extend shared conformance before backend-specific assertions. +5. **Migrate runtime2 shell call sites.** Replace lane/harness raw writes with built-in constructors/helpers. `restore.ts` uses `scanValues(laneLeafInventoryPrefix())` plus exact `getValue` lookups and performs no `readList()` call. Do not add acceptance, drive, hydration, or cleanup behavior. +6. **Replace the SQLite WIP schema and adapter.** Edit `001_initial.sql` in place, implement scalar operations and indexed list append/delete/paging in `session/values.ts`, keep every write inside the existing `BEGIN IMMEDIATE` writer-lease transaction, update both fork snapshot paths, and retain all entry/usage/branch/lease behavior from current `dev`. +7. **Migrate public events, tests, and the coding-agent helper.** Remove old type assertions and raw namespaces. Change `fact_update` to `value_update`. Keep the two remote prompt tests skipped for the existing WP00 reason; WP01 must not alter runtime execution. +8. **Update telemetry and documentation.** Change `pi.session.write` item kinds from `register` to `value` and `list`, regenerate `telemetry-schema.md`, run the old-API sweeps, and record any branch-policy-deferred changelog requirement. Do not edit a changelog on `gramps` unless it becomes a pull-request branch or the user requests it. + +## Backend requirements + +### Memory + +- prepare and validate the complete transaction before mutating entries, values, lists, usage, or stats; +- current scalar replacement stores only the latest value and set `seq`; +- list append performs no list read and stores each global write `seq`; +- list delete removes the whole exact key; +- snapshots include current scalar values and surviving list elements. + +### JSONL + +- keep format 4/storage version 1 with no legacy register decode; +- one single-write object or multi-write array remains one atomic line; +- replay produces the same logical state as Memory; +- torn final lines expose no partial transaction; +- snapshot serialization preserves surviving list-element sequences and the next-sequence high-water mark. + +### SQLite + +Use: + +```sql +CREATE TABLE scalar_values ( + namespace TEXT NOT NULL, + key TEXT NOT NULL, + seq INTEGER NOT NULL, + value TEXT NOT NULL, + PRIMARY KEY (namespace, key) +) WITHOUT ROWID; + +CREATE TABLE list_values ( + namespace TEXT NOT NULL, + key TEXT NOT NULL, + seq INTEGER NOT NULL, + value TEXT NOT NULL, + PRIMARY KEY (namespace, key, seq) +) WITHOUT ROWID; +``` + +Ascending and descending list queries use the primary key with an exclusive sequence predicate and `LIMIT`. Add `EXPLAIN QUERY PLAN` assertions proving no table scan or temporary ordering b-tree. Do not change storage version, add migrations, or weaken the current lease/fence/fork behavior. ## Required coverage -- type inference and incompatible scalar/list write rejection; -- exact `pi.*` built-in addresses and five scan-prefix constructors; -- application-wide and dynamically keyed addresses with no second operation-time key; -- scalar set/get/delete/recreate and namespace-scoped prefix scans; -- list append, global element sequences, asc/desc cursors, limits, absent reads, whole delete, and delete-then-append; -- atomic entry + usage + scalar + list transactions and rollback; -- identical Memory/JSONL/SQLite behavior; -- JSONL replay, torn transactions, and cursor-preserving compaction; -- SQLite query plans; -- forks excluding operation-owned values/lists while preserving semantic session values; -- close rejecting later reads while admitted commits drain; -- zero old register or custom-state API references outside immutable release history. +### Address and type tests -## Non-goals +- invariant address typing and inferred scalar/list result types; +- `NoInfer` rejects incompatible set/append values; +- scalar helpers reject list addresses and list helpers reject scalar addresses; +- independently constructed equal addresses resolve the same location; +- empty key works; empty namespace and `\u0000` components reject; +- exact built-in namespaces/key grammars and exactly five prefix constructors; +- application-wide and dynamic non-reserved addresses require no second operation-time key; +- no registry, catalog, privilege constructor, global value map, or runtime `pi.*` gate. -- No runtime acceptance, drive, provider, assistant-frame consumer, tool memo/checkpoint consumer, or operation-state redesign. -- No generic event log, list truncation, per-element deletion, registry, catalog, or compatibility facade. -- Do not rewrite applied backend migrations solely for terminology; follow the compatibility policy in `registers.md`. +### Shared scalar/list conformance + +- scalar set/get/delete/delete-absent/recreate and latest set `seq`; +- namespace-scoped, key-ascending prefix scans; +- append one and several elements, including several appends in one transaction; +- appends separated by unrelated writes preserve per-list order and global element sequences; +- ascending/descending exclusive cursors; +- default, explicit, invalid, and clamped query-page limits; +- absent list, whole-list delete, delete-absent, and delete-then-append; +- atomic entry + usage + value + list transactions; +- rollback when any sibling write is invalid; +- no list read on append; +- close rejects later reads while already-admitted commits drain. + +Do not add a value/list collision test: cross-kind misuse is intentionally an unenforced trusted-programming defect. + +### Backend and repository tests + +- identical Memory/JSONL/SQLite pages and cursors; +- JSONL single/multi-write replay, torn-tail behavior, and sequence-preserving snapshot output; +- SQLite query plans and writer-lease transaction behavior; +- branch/tree forks copy session name, eligible labels, and lane configuration/leaf with fresh lane state; +- forks exclude operation/pending values, all lists, application addresses, last results, queues, and ledger rows; +- repository parent metadata, entry IDs, stats, branch indexes, v3 normalization, UUIDv7/follower IDs, and current SQLite lease/fork scenarios remain unchanged; +- runtime2 restore enumerates lanes through the one prefix constructor and reads no list. + +## Deferred consumers + +The following `values.md` requirements are explicitly not WP01 coverage: + +- assistant frame conversion, append scheduling, settlement, recovery, cancellation, snapshot hydration, and byte-growth tests (R2/R3/R6/R12); +- invocation `getMemo` / `setMemo`, tool-output checkpoint writes, outcome cleanup, and prefix-driven operation cleanup (R4/R6); +- any consumption-time list hydration beyond proving base restore reads no list; +- runtime acceptance, driving, provider/tool effects, or operation-state redesign. + +WP01 still exports `pendingAssistantFrames`, `operationToolMemo`, `pendingToolOutput`, and every cleanup prefix so later packages do not redesign storage. + +## Removal checks + +These must have zero matches in retained source/tests, excluding immutable released changelog history and archived prose: + +```bash +rg -n 'getRegister\(|listRegisters\(|RegisterValues|RegisterNamespace|RegisterSetWrite|\bRegister<' \ + packages/agent packages/session-backends/sqlite-node packages/coding-agent \ + --glob '!**/dist/**' --glob '!**/CHANGELOG.md' --glob '!**/docs/**' + +rg -n 'kind: "register"|getCustomFact\(|setCustomFact\(|fact\.(name|label|custom)|fact_update' \ + packages/agent packages/session-backends/sqlite-node packages/coding-agent \ + --glob '!**/dist/**' --glob '!**/CHANGELOG.md' --glob '!**/docs/**' + +rg -n '"(lane\.(leaf|config|state|lastResult)|op\.(meta|state|tool_args|preparation)|pending\.entry)"' \ + packages/agent packages/session-backends/sqlite-node packages/coding-agent \ + --glob '!**/dist/**' --glob '!**/CHANGELOG.md' --glob '!**/docs/**' + +rg -n '\bregisters\b' packages/agent/src/harness/session packages/session-backends/sqlite-node/src \ + --glob '*.ts' --glob '*.sql' + +rg -n '"register"' \ + packages/agent/src/harness/telemetry.ts \ + packages/agent/test/harness/telemetry.test.ts \ + packages/agent/docs/telemetry-schema.md +``` + +Do not treat unrelated model/provider/hook registration terminology as storage API residue. ## Validation -Run every modified test individually, the shared backend conformance suites, SQLite package tests, agent/root TypeScript, `git diff --check`, and `npm run check`. +Run each created or modified test file directly and iterate until green. At minimum: + +```bash +# From packages/agent +node "$(git rev-parse --show-toplevel)/node_modules/vitest/dist/cli.js" --run \ + test/harness/values.test.ts \ + test/harness/memory-storage.test.ts \ + test/harness/memory-conformance.test.ts \ + test/harness/memory-session-repo.test.ts \ + test/harness/jsonl-storage.test.ts \ + test/harness/jsonl-storage-conformance.test.ts \ + test/harness/jsonl-session-repo.test.ts \ + test/harness/jsonl-session-repo-conformance.test.ts \ + test/harness/storage-backed-session.test.ts \ + test/harness/session-tree.test.ts \ + test/harness/session-create-lane.test.ts \ + test/harness/instrumented-storage.test.ts \ + test/harness/types.test.ts \ + test/harness/telemetry.test.ts \ + test/harness/runtime2/harness.test.ts \ + test/harness/runtime2/lane.test.ts \ + test/harness/runtime2/restore.test.ts + +# From packages/session-backends/sqlite-node +node "$(git rev-parse --show-toplevel)/node_modules/vitest/dist/cli.js" --run \ + test/adapter.test.ts \ + test/repo.test.ts \ + test/sql.test.ts \ + test/storage.test.ts \ + test/storage-conformance.test.ts + +# From packages/coding-agent +node "$(git rev-parse --show-toplevel)/node_modules/vitest/dist/cli.js" --run \ + test/experimental-remote-runtime.test.ts \ + test/experimental-server-replacement.test.ts +``` + +Then run from the repository root: + +```bash +cd packages/agent && npm run check:telemetry-docs +cd "$(git rev-parse --show-toplevel)" +node_modules/.bin/tsgo --noEmit -p packages/agent/tsconfig.build.json +node_modules/.bin/tsgo --noEmit +git diff --check +npm run check +./test.sh +``` + +Never run unrestricted Vitest, `npm test`, paid-provider tests, or `npm run build`. ## Stop condition -Stop when every existing backend and retained Session surface uses bound values/lists, old APIs are absent, and all checks pass. Report schema/compatibility choices; do not begin runtime execution work. +Stop when every retained backend and Session surface uses bound values/lists; all core addresses use the exact `pi.*` grammar; arbitrary application addresses work but generic forks exclude them; old register/fact/custom-state APIs and physical names are absent; base restore performs no list read; schema/compatibility decisions above are implemented; focused, conformance, TypeScript, telemetry-doc, diff, and repository checks pass. Report the final schema and fork behavior. Do not begin runtime acceptance, assistant/tool consumers, or any later work package.