mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
docs(agent): finalize bound values handoff
This commit is contained in:
parent
cae09adcd8
commit
c0bb6af3a7
6 changed files with 352 additions and 76 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -378,7 +378,7 @@ export const laneLeafInventoryPrefix = () =>
|
|||
value<string | null>("pi.lane.leaf");
|
||||
|
||||
export const operationMeta = (operationId: string) =>
|
||||
value<Operation>("pi.op.meta", operationId);
|
||||
value<OperationMeta>("pi.op.meta", operationId);
|
||||
export const operationState = (operationId: string) =>
|
||||
value<OperationState>("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.
|
||||
|
|
|
|||
|
|
@ -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<T>(namespace, key?)` and `list<T>(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<AssistantMessageFrame>` constructor.
|
||||
|
|
|
|||
|
|
@ -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<string | null>("pi.lane.leaf");
|
||||
|
||||
export const operationMeta = (operationId: string) =>
|
||||
value<Operation>("pi.op.meta", operationId);
|
||||
value<OperationMeta>("pi.op.meta", operationId);
|
||||
export const operationState = (operationId: string) =>
|
||||
value<OperationState>("pi.op.state", operationId);
|
||||
export const operationToolArgs = (operationId: string, stepId: string, sourceIndex: number) =>
|
||||
|
|
@ -237,6 +237,8 @@ export const sessionName = value<string>("pi.session.name");
|
|||
export const entryLabel = (entryId: string) => value<string>("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.
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<T>` and `ValueList<T>` 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<T>` and `ValueList<T>` 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<OperationMeta>`; 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<T>` and `ValueList<T>` address types;
|
||||
- universal `value<T>(namespace, key?)` and `list<T>(namespace, key?)` constructors;
|
||||
- namespace/key validation only: non-empty namespace and no `\u0000` component;
|
||||
- `StoredValue<T>`, `ListElement<T>`, `ListCursor`, and `ListReadOptions`;
|
||||
- typed `setValue`, `deleteValue`, `appendList`, and `deleteList` write helpers using `NoInfer<T>`;
|
||||
- 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<T>(address: Value<T>): Promise<StoredValue<T> | undefined>;
|
||||
scanValues<T>(prefix: Value<T>): Promise<StoredValue<T>[]>;
|
||||
readList<T>(address: ValueList<T>, options?: ListReadOptions): Promise<ListElement<T>[]>;
|
||||
```
|
||||
|
||||
`SessionTree` and therefore `Session` additionally expose one-commit direct writes:
|
||||
|
||||
```ts
|
||||
setValue<T>(address: Value<T>, next: NoInfer<T>): Promise<void>;
|
||||
deleteValue<T>(address: Value<T>): Promise<void>;
|
||||
appendList<T>(address: ValueList<T>, element: NoInfer<T>): Promise<void>;
|
||||
deleteList<T>(address: ValueList<T>): Promise<void>;
|
||||
```
|
||||
|
||||
`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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue