@llblab/pi-kit 0.10.7 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +2 -2
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +13 -10
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +15 -1
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +13 -0
- package/node_modules/@llblab/pi-state-flow/README.md +52 -261
- package/node_modules/@llblab/pi-state-flow/docs/README.md +5 -1
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +47 -26
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +97 -0
- package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +47 -0
- package/node_modules/@llblab/pi-state-flow/docs/performance.md +459 -0
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +35 -3
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +134 -0
- package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +19 -3
- package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +74 -0
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +12 -12
- package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +4 -2
- package/node_modules/@llblab/pi-state-flow/lib/discovery.ts +21 -5
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +146 -37
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +142 -42
- package/node_modules/@llblab/pi-state-flow/lib/publication.ts +80 -27
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +109 -7
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +7 -12
- package/node_modules/@llblab/pi-state-flow/lib/storage.ts +2 -1
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +2 -3
- package/node_modules/@llblab/pi-state-flow/package.json +5 -4
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +4 -0
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +1 -1
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/ownership.ts +28 -7
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +3 -10
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +3 -3
|
@@ -12,13 +12,13 @@ The extension owns durable memory while enabled. Global semantic memory is alway
|
|
|
12
12
|
|
|
13
13
|
- `state`, `json`: semantic shape, validation, recursive overlay and deletion.
|
|
14
14
|
- `temporal`, `history`: causal boundaries, checkpoint/tail folding and hot history.
|
|
15
|
-
- `durable`, `storage`, `git`: exact files, CAS publication, Git commits and
|
|
15
|
+
- `durable`, `storage`, `git`: exact files, CAS publication, Git commits/restoration, and owned push processes.
|
|
16
16
|
- `snapshot`, `session`, `runtime`, `recovery`, `episode`: Pi branch/runtime lifecycle.
|
|
17
17
|
- `transition`, `terminal`, `context`: inference barriers, turn resolution, passive projection, and response reconciliation.
|
|
18
18
|
- `artifact`, `acquisition`, `maintenance`, `skills`, `rehydration`: source routing and compilation.
|
|
19
19
|
- `memory`: external promotion records and memory diagnostics.
|
|
20
20
|
- `continuation`: native-header discovery, runtime-provenance inspection, deterministic recommendation, and host startup precedence.
|
|
21
|
-
- `publication`: remote policy, durable CAS queue/store, cross-process leases, and
|
|
21
|
+
- `publication`: remote policy, durable CAS queue/store, cross-process leases, and attempt outcomes.
|
|
22
22
|
- `status`, `telegram`, `extension`: operator projection, the optional fail-open pi-telegram presentation adapter, and Pi adapter wiring.
|
|
23
23
|
|
|
24
24
|
## Semantic state
|
|
@@ -69,6 +69,8 @@ A final-only `patch_state({"final":true})` call changes only ephemeral terminal
|
|
|
69
69
|
|
|
70
70
|
`patch_state` is the sole mutation tool. It validates any supplied global/CWD/session patches against one causal basis and publishes them as one atomic transition, then acts as an inference barrier. Pi executes no sibling tools from the same assistant response; the next inference sees rematerialized `state[0]`.
|
|
71
71
|
|
|
72
|
+
Tool preflight follows Pi's public `getLeafEntry()` / `getEntry(parentId)` links to the nearest assistant containing the current call ID. It inspects that response's complete tool batch without constructing the whole branch or caching a batch across calls/selections. Foreign custom entries and earlier sibling results remain in the native trace. A missing call ID still searches the selected ancestry and preserves the existing unmatched-call behavior; this is not an unconditional constant-time guarantee. See [measured traversal evidence](performance.md#tool-preflight-parent-traversal).
|
|
73
|
+
|
|
72
74
|
`read_state` reads one cached effective or scoped projection at offsets zero through seven. It never publishes or advances history.
|
|
73
75
|
|
|
74
76
|
Every enabled assistant iteration starts terminal-ineligible. Only a successful `patch_state` call containing `final:true` latches eligibility for the next accepted `turn_end`; the call may atomically include global, CWD, and session patches. Eligibility does not stop later reasoning, tools, or patches. If terminal prose arrives before eligibility, State Flow preserves that draft and reconciles it into runtime-owned `response` at `turn_end`, then starts at most two same-run fallback turns whose only purpose is the `final:true` patch. The same path covers an eligible draft whose final validation fails after a later acquisition. Fallback turns never become the response: a successful `final:true` commits its patches and closes resolution with the preserved answer intact, while two failed fallbacks close the iteration with the preserved answer and current state plus one bounded warning and a finalization diagnostic. Failed patch calls do not consume the budget. A following legal patch remains possible, and only an accepted ordinary answer is reconciled into runtime-owned `response` at `turn_end`. State Flow no longer parses `state_flow` or generic HTML comments; historical comments are ordinary text and other extensions retain their own comment handling.
|
|
@@ -81,10 +83,16 @@ TURN ELIGIBILITY false → patch_state(..., final:true) → latched true
|
|
|
81
83
|
CONTEXT PROJECTION active State Flow projection | passive post-stop handoff
|
|
82
84
|
```
|
|
83
85
|
|
|
84
|
-
Stopping State Flow disables semantic tools and
|
|
86
|
+
Stopping State Flow immediately disables semantic tools and switches projection to a frozen effective-state handoff, the active user-run trajectory if interrupted, and post-stop conversation. Paired tool results arriving after Stop remain visible. Foreign context-bearing custom messages survive; completed earlier conversation and private State Flow validation feedback do not. This projection survives same-physical-session reload, resume, and tree restoration. Active restart uses it for one migration run alongside active runtime context. New and forked physical sessions inherit neither projection. No semantic transition is created.
|
|
87
|
+
|
|
88
|
+
The existing native passive-stop marker stores the stop timestamp and an optional `from` timestamp identifying the active run's first user message. Transcript bodies remain in Pi's trace rather than being copied into another state store. Idle and legacy markers without that anchor retain only post-stop conversation plus foreign custom context. The system prompt is composed at `before_agent_start`; an already-issued prompt is not rewritten by Stop, and ordinary prompt composition resumes with the next user run.
|
|
85
89
|
|
|
86
90
|
The current user specification stays at user authority and appears only in synthetic user runtime context. State is fallible assistant-produced data. Completed trajectories leave model context at user-run boundaries, while Pi's full JSONL trace remains inspectable.
|
|
87
91
|
|
|
92
|
+
After a sufficiently large accepted non-bootstrap run settles with no queued input, State Flow may request a native manual compaction under a generation-private marker. Pi still owns preparation and admits the boundary only when its configured `keepRecentTokens` leaves compactable history. The extension supplies no model-generated summary or state body: it keeps the final accepted assistant entry and records the exact durable revision/step in compaction details. `buildContextEntries()` then omits the older completed prefix for active context and resume rendering while the append-only JSONL/tree remains intact. Small histories, foreign custom context in the proposed prefix, stale selection, Stop/bootstrap/fallback/error/abort and pending input do not produce this boundary. User manual and native threshold/overflow compaction remain unmodified; in-progress work not yet accepted into State Flow stays under Pi's native compaction contract.
|
|
93
|
+
|
|
94
|
+
The Pi adapter passes its raw cached scope overlay to `runtimeContextMessage`, which owns model sanitization of the current state. It does not pre-project that input. After anchor selection, `currentRunTrajectory` selects retained messages into one array without copying discarded ordinary prefixes: foreign custom messages survive at any position, while ordinary messages survive only from the selected run anchor and State Flow's private feedback is excluded. The necessary foreign-context scan and Pi's earlier native-message clone remain history-dependent. See [context-cost evidence](performance.md#context-projection-and-trajectory-selection).
|
|
95
|
+
|
|
88
96
|
## Storage and identity
|
|
89
97
|
|
|
90
98
|
The default store is `<agentDir>/state-flow`, independent from Markdown discovery at `<agentDir>/knowledge`.
|
|
@@ -108,13 +116,17 @@ CWD and session keys mirror Pi's native encoding. The Pi UUID remains authoritat
|
|
|
108
116
|
|
|
109
117
|
Session `config.json` owns branch runtime behavior. Scope `meta.json` owns runtime artifact provenance for its scope; the session file additionally owns lineage, counters, identity, publication provenance and remote-publication policy. Pi checkpoints retain only an exact Git revision, an exact `file:<hash>` cohort reference, or a proven ordinary-disabled marker.
|
|
110
118
|
|
|
111
|
-
All owned writes use same-directory atomic replacement, regular-file and symlink checks, prepared byte receipts and CAS validation. Unrelated files
|
|
119
|
+
All owned writes use same-directory atomic replacement, regular-file and symlink checks, prepared byte receipts and CAS validation. Unrelated files and detected concurrent bytes are preserved; Git staging follows the acceptance contract below. Rollback restores only bytes still matching the failed publisher's output.
|
|
112
120
|
|
|
113
121
|
## Optional Git
|
|
114
122
|
|
|
115
123
|
If Git is unavailable specifically through executable `ENOENT`, State Flow uses file-only persistence. File mode retains exact current materialization and proven hot history but offers no arbitrary cold revisions.
|
|
116
124
|
|
|
117
|
-
With Git, each effective semantic cohort creates one local commit immediately through an isolated index that stages the complete non-ignored worktree delta before overlaying the exact prepared State Flow outputs; the caller-visible index is synchronized to the committed tree afterward. Activation returns after local runtime acceptance for normal `turn-end`/`off` policy, skips full predecessor migration planning when the three legacy snapshot names are absent, and defers Markdown discovery until the next enabled inference. State Flow-owned active files keep compare-and-swap protection, and `.gitignore` stays authoritative. Git supplies cold history and exact branch restoration. Runtime `revision: "self"` resolves to the commit that owns the runtime record, never arbitrary `HEAD`. Runtime-only writes may use `temporalRevision` to select older semantic streams without
|
|
125
|
+
With Git, each effective semantic cohort creates one local commit immediately through an isolated index that stages the complete non-ignored worktree delta before overlaying the exact prepared State Flow outputs; the caller-visible index is synchronized to the committed tree afterward. Each prepared content is still hashed separately from its supplied bytes, never substituted by mutable worktree reads or filtered staging. Prepared blobs enter the isolated index through one NUL-delimited `update-index --index-info` batch, preserving literal path characters; explicit removals retain their existing path. Any failed batch aborts before reference publication and follows the same exact-output rollback and temporary-index cleanup. Activation returns after local runtime acceptance for normal `turn-end`/`off` policy, skips full predecessor migration planning when the three legacy snapshot names are absent, and defers Markdown discovery until the next enabled inference. State Flow-owned active files keep compare-and-swap protection, and `.gitignore` stays authoritative. Git supplies cold history and exact branch restoration. Runtime `revision: "self"` resolves to the commit that owns the runtime record, never arbitrary `HEAD`. Runtime-only writes may use `temporalRevision` to select older semantic streams and their matching artifact provenance. They update the current session's config/meta without rewriting live shared checkpoints, tails, or provenance.
|
|
126
|
+
|
|
127
|
+
Branch recovery validates immutable selection before live publication acquisition. `TemporalRuntime.prepareRestore` returns a detached snapshot and an instance-bound, single-use restoration closure. For an exact matching Git owner, that closure reuses the validated cohort and provenance rather than decoding them twice; it still captures the current publication basis under exclusion before installing any runtime fields. Expired file cohorts, legacy snapshot fallbacks, and references redirected to another runtime owner take the fresh-read path. A consumed or failed preparation cannot be replayed, and neither the mutable inspection snapshot nor an old publication basis can become restore authority. This is bounded reuse within one selection, not a cross-session revision cache.
|
|
128
|
+
|
|
129
|
+
Cold temporal reconstruction and semantic publication anchoring share an operation-local Git reader. One NUL-delimited tree query lists exact owned canonical/fallback paths at the selected immutable revision, with literal path handling independent of inherited pathspec settings. Only actually selected files receive regular-blob mode/uniqueness validation and content reads; unused fallback blobs cannot become authority over canonical files. Repeated reads of the same path reuse that immutable result only within the operation. Selected state/runtime blob reads explicitly bypass Node's implicit 1 MiB subprocess-output budget, which otherwise makes valid large checkpoints/tails/metadata unreadable. Other Git commands retain their ordinary output policy; the 15-second command timeout and normal memory/materialization limits remain. Complete catalog framing, selected-file validation, fresh live bases, and publication CAS remain required; no worktree checkout, semantic byte cap or durable read cache is added.
|
|
118
130
|
|
|
119
131
|
Publishing from a restored branch reconciles shared state by adoption rather than rejection: an untouched global/CWD scope whose live stream advanced is adopted at a fresh proven origin together with the selected session stream, while a shared scope the accepted transition actually changes must still match its selected basis or fail closed naming that scope. Adoption preserves causal validity, invents no parent links or semantic transitions, never rewinds live shared files, and leaves older lineage available through Git. Publication CAS rejects any change made after the reconciliation capture.
|
|
120
132
|
|
|
@@ -132,17 +144,17 @@ The persisted `remotePublication` policy is:
|
|
|
132
144
|
|
|
133
145
|
A destination is identified by canonical Git common directory, remote and full ref. Queue files live beneath the Git common directory and are not semantic history.
|
|
134
146
|
|
|
135
|
-
The queue uses exact commit targets, strict versioned JSON, symlink-safe atomic writes, CAS receipts and exclusive writer locks. A proven descendant may supersede an older target; a journal lineage rewrite retargets the live commit and records the retired target, while changed destinations fail closed.
|
|
147
|
+
The queue uses exact scalar-string commit targets/confirmations, strict versioned JSON, symlink-safe atomic writes, CAS receipts and exclusive writer locks. Coercible non-string values are rejected at construction, parsing/serialization, coalescing, confirmation, and asynchronous push boundaries, before ancestry or push effects; malformed persisted records remain untouched. A proven descendant may supersede an older target; a journal lineage rewrite retargets the live commit and records the retired target, while changed destinations fail closed.
|
|
136
148
|
|
|
137
|
-
After accepted response reconciliation, an asynchronous non-interactive worker pushes the newest target. Queue failure never rolls back semantic state or regenerates an answer. Failed and interrupted attempts remain retryable across restart. Destination-scoped worker leases
|
|
149
|
+
After accepted response reconciliation, an asynchronous non-interactive worker pushes the newest target. Queue failure never rolls back semantic state or regenerates an answer. Failed and interrupted attempts remain retryable across restart. Destination-scoped worker leases use exclusive creation. Dead-owner reclamation rechecks a fully validated regular-file record and PID liveness under the existing queue writer lock; only an `ESRCH` result permits reclamation. A fresh claim may win the removal/creation gap and must survive. Release requires the current process's PID and exact token, without waiting for queue writers. Malformed and symlink records are preserved; an occupied or interrupted writer gate defers reclamation rather than authorizing lock deletion. Confirmation removes only the exact completed target; a newer descendant remains queued.
|
|
138
150
|
|
|
139
|
-
|
|
151
|
+
`git.pushGitTarget` owns each asynchronous push with the existing 15,000ms Git command budget, ignored stdin/stdout, bounded diagnostic stderr, and non-interactive credentials. Timeout/cancellation sends `SIGKILL` to its POSIX process group while the owned leader is live; Windows terminates the direct child. The child handle remains referenced, and the promise settles only on process/stdio closure or proven spawn failure. A diagnostic pipe outliving its leader is closed on cancellation/deadline rather than extending the wait indefinitely. Helpers that escape or outlive the process group are not a general process-tree containment guarantee.
|
|
140
152
|
|
|
141
|
-
|
|
153
|
+
`extension` owns one abort controller and completion promise per destination worker. `session_shutdown` permanently closes that generation to new launches, cancels its children, and waits at most 2,000ms for the whole cohort. Late results cannot acknowledge/fail the queue or recursively relaunch; only lease cleanup remains allowed. If OS termination is unconfirmed at the wait deadline, emit a warning and keep ownership until actual exit. Filesystem cleanup failures remain fail-closed. A subsequent generation or activation can retry the same durable target once the lease is available. These policies require the owning Pi process and event loop to remain live: abrupt host death, blocked scheduling, and uninterruptible OS I/O are outside the deadline guarantee. Leases identify the Pi worker PID, not an independently supervised child after host death. Synchronous compatibility publication is unchanged.
|
|
142
154
|
|
|
143
155
|
## Artifact routing
|
|
144
156
|
|
|
145
|
-
Markdown discovery runs after activation and before its next enabled inference. It recursively finds regular lowercase `*.md` beneath the configured Knowledge root, rejects symlinks, hashes opaque bytes and never injects source bodies.
|
|
157
|
+
Markdown discovery runs after activation and before its next enabled inference. It recursively finds regular lowercase `*.md` beneath the configured Knowledge root, rejects symlinks, hashes opaque bytes and never injects source bodies. It rechecks retained canonical in-root Markdown paths for proven absence; external, non-Markdown, and symlink paths are outside removal ownership. A missing whole root preserves state and reports unavailable freshness. Status and restart re-derive removals from the retained registry rather than consuming an in-memory event. The generic `planArtifactInvalidation` helper takes explicit `options.removed`; a partial candidate set alone authorizes no deletion.
|
|
146
158
|
|
|
147
159
|
A model-visible artifact entry requires only a description:
|
|
148
160
|
|
|
@@ -153,7 +165,7 @@ A model-visible artifact entry requires only a description:
|
|
|
153
165
|
}
|
|
154
166
|
```
|
|
155
167
|
|
|
156
|
-
Runtime-owned freshness evidence is retained per scope in `meta.json` as `{sourceHash, compilerRevision, compiledAt}`. Compiler output may add
|
|
168
|
+
Runtime-owned freshness evidence is retained per scope in `meta.json` as `{sourceHash, compilerRevision, compiledAt}`. At artifact-entry level, every model scope patch rejects authored `hash`, `compiler`, `compiled_at`, `sourceHash`, `compilerRevision`, `compiledAt`, and `source_hash_verified` fields, including field-deletion markers, even without a preceding read. Retained legacy evidence stays readable; ordinary semantic edits and whole-artifact deletion remain valid. Compiler output may add other finite non-null JSON metadata; known optional semantic fields include `kind`, `tags` and `compilation`. Tags are unique trimmed non-empty strings and support deterministic candidate filtering, but never authorize reading.
|
|
157
169
|
|
|
158
170
|
Freshness derives capabilities from available evidence: new sources require compilation, `sourceHash` detects source changes, `compilerRevision` detects compiler changes, and `compiledAt` drives age-based maintenance. Missing provenance degrades to unknown freshness instead of forcing migration; malformed present evidence fails closed only for the capability that depends on it. Exact successful native Pi reads are correlated with current candidates; stale reads require same-path compiler output before provenance is recorded in the same durable cohort.
|
|
159
171
|
|
|
@@ -163,7 +175,7 @@ Skills are CWD artifacts with stricter compilation: `kind: "skill"` and a non-em
|
|
|
163
175
|
|
|
164
176
|
## Memory curation and promotion
|
|
165
177
|
|
|
166
|
-
The optional packaged `state-flow-memory` Skill performs bounded explicit audits, scope narrowing, contradiction cleanup and external handoffs. It is not part of ordinary retention or background maintenance. Curation compiles a read Skill at CWD before accumulating global compilation obligations, writes and separately reads a migration destination before source deletion, then verifies the changed scope and effective overlay. Simultaneously pending CWD/global acquisitions
|
|
178
|
+
The optional packaged `state-flow-memory` Skill performs bounded explicit audits, scope narrowing, contradiction cleanup and external handoffs. It is not part of ordinary retention or background maintenance. Curation compiles a read Skill at CWD before accumulating global compilation obligations, writes and separately reads a migration destination before source deletion, then verifies the changed scope and effective overlay. Simultaneously pending CWD/global acquisitions must be compiled together in one atomic `patch_state` call. Destination write, readback, and source deletion remain separate migration steps so accepted-copy verification is not skipped.
|
|
167
179
|
|
|
168
180
|
External promotion remains a semantic two-phase handoff, not a memory-owner mode. Optional global `working.memory_promotions` entries record `pending`, `accepted`, `failed` or `unknown` status plus owner. Accepted records additionally require destination pointer and revision. Failed or uncertain promotion preserves the State Flow candidate; the only accepted copy is never deleted.
|
|
169
181
|
|
|
@@ -178,30 +190,39 @@ The package exposes read-only host contracts that:
|
|
|
178
190
|
- preserve explicit new/resume and native picker precedence;
|
|
179
191
|
- project new-bootstrap, resume-bootstrap and later-step rehydration phases.
|
|
180
192
|
|
|
181
|
-
Pi
|
|
193
|
+
Both [tested Pi SDKs](compatibility.md) choose or create `SessionManager` before package resources and extensions load. Therefore native default auto-resume cannot be installed safely by this extension alone. The remaining host integration requires an upstream pre-session resolver hook or an SDK/launcher that invokes the advisory resolver before constructing the session.
|
|
194
|
+
|
|
195
|
+
## Model tools and embedding
|
|
182
196
|
|
|
183
|
-
|
|
197
|
+
### Model tools
|
|
184
198
|
|
|
185
|
-
`
|
|
199
|
+
`patch_state` accepts optional fixed `global`, `cwd`, and `session` semantic patches plus optional `final:true`. At least one scope or `final:true` is required. Supplied scopes contain only object-valued `artifacts`, `contract`, and `working`; omitted fields preserve their values, recursive object merge updates them, arrays/primitives replace, and nested object-key `null` deletes. Materialized null, empty supplied scopes, material no-ops, unknown top-level fields, model-authored `response`, and retired grammars are rejected. A final-only call changes ephemeral eligibility, not semantic history.
|
|
186
200
|
|
|
187
201
|
```json
|
|
188
|
-
{
|
|
189
|
-
"directory": "~/.pi/agent/state-flow",
|
|
190
|
-
"autoStart": false,
|
|
191
|
-
"logging": false,
|
|
192
|
-
"remotePublication": "turn-end"
|
|
193
|
-
}
|
|
202
|
+
{"session":{"working":{"next":"Verify the corrected behavior"}},"final":true}
|
|
194
203
|
```
|
|
195
204
|
|
|
196
|
-
|
|
205
|
+
`read_state` defaults to effective state at offset zero. It accepts one optional `scope` (`effective`, `global`, `cwd`, or `session`) and integer `offset` from zero to seven, returning `{offset, scope, boundary, state}`. It reads cached selected state without Git queries, publication, checkpoint append, or a semantic step. Pre-origin history is an error, not an empty state.
|
|
197
206
|
|
|
198
|
-
|
|
207
|
+
```json
|
|
208
|
+
{"offset":1,"scope":"cwd"}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Both tools follow branch enablement and host restrictions. The patch barrier also blocks reader siblings. These tools do not impose project schemas or state-size caps; semantic usefulness, scope choice, and compression remain model responsibilities.
|
|
212
|
+
|
|
213
|
+
### Embedding
|
|
199
214
|
|
|
200
|
-
|
|
215
|
+
The default extension factory accepts `StateFlowExtensionOptions`: `agentDir` selects the profile, `repositoryRoot` overrides the configured state store, and `knowledgeRoot` independently selects Markdown sources. `onRuntime` receives a cached `read(offset?, scope?)` accessor; omitted scope means effective state. Use it only after runtime initialization/restoration. The pure `readTemporalState(view, offset, scope?)` accessor is exported separately. SDK hosts with an explicit tool allowlist must include both `patch_state` and `read_state` when they want model access.
|
|
201
216
|
|
|
202
|
-
|
|
217
|
+
Honor Pi's `session_shutdown` lifecycle before disposing an embedded session. On both [tested Pi SDKs](compatibility.md), `AgentSession.reload()` emits and awaits shutdown, but bare `AgentSession.dispose()` only invalidates/disconnects the session. `AgentSessionRuntime` owns native new/resume/fork replacement and its asynchronous `dispose()` delivers quit shutdown; rebind each newly created session's extensions. An SDK host instead disposing a standalone `AgentSession` should first `await session.extensionRunner.emit({ type: "session_shutdown", reason: "quit" })`; ordinary Pi lifecycle owners already deliver the event. Without it, the push attempt budget still applies, but early cancellation and generation fencing are not notified.
|
|
218
|
+
|
|
219
|
+
Native replacement teardown and State Flow adoption are separate responsibilities. On a native fork start, the adapter verifies the direct parent header and selected source revision, then copies only the session stream/provenance into a distinct child owner over current live shared scopes. The child has a fresh origin and its own checkpoint, never a UUID alias or historical shared-state rewind. A child-owned native reset marker fences inherited passive Stop projection across reload. Parent-owned checkpoints selected later cannot fall through to an ordinary-disabled marker and reset child storage. See the [fork contract](fork-contract.md) and [operating limits](usage.md#fork-support-and-limits).
|
|
220
|
+
|
|
221
|
+
Agent configuration is read once per extension load; session runtime configuration remains branch-selected. See [configuration](usage.md#configuration) for settings and path precedence. Memory ownership while enabled and global availability are invariants, not configuration switches.
|
|
222
|
+
|
|
223
|
+
## Observability
|
|
203
224
|
|
|
204
|
-
|
|
225
|
+
Status is a projection of the selected runtime and semantic view, not a second store. Missing evidence stays unavailable instead of appearing empty. The optional Telegram leaf adapter reads the same snapshot and calls the same Start/Stop owners; registration is fail-open and disposal belongs to session shutdown. Local diagnostics stay outside semantic state, scope metadata, checkpoints, and publication, and failures cannot change accepted state. Operator-facing fields and privacy boundaries are in [usage](usage.md#status-and-controls).
|
|
205
226
|
|
|
206
227
|
## Validation boundaries
|
|
207
228
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Pi SDK compatibility
|
|
2
|
+
|
|
3
|
+
This matrix records exact tested dependency stacks, not the version of an operator's running Pi process. Package peer ranges admit `^0.84.4 || ^0.85.1`; keep Pi, AI and agent-core on a matching release line. Mixed-version stacks and later releases are not separate test evidence.
|
|
4
|
+
|
|
5
|
+
## Tested matrix
|
|
6
|
+
|
|
7
|
+
The compatibility checks use Linux/x64, Node 26.8.1, Git 2.55.0, TypeScript 7.0.2 and Node types 26.4.0.
|
|
8
|
+
|
|
9
|
+
| Pi / AI / agent-core | SDK's pi-tui / TypeBox | Typecheck / import | Full suite |
|
|
10
|
+
| --- | --- | --- | --- |
|
|
11
|
+
| 0.84.4 / 0.84.4 / 0.84.4 | 0.84.4 / 1.3.7 | Pass / pass | 417/417 |
|
|
12
|
+
| 0.85.1 / 0.85.1 / 0.85.1 | 0.85.1 / 1.3.7 | Pass / pass | 417/417 |
|
|
13
|
+
|
|
14
|
+
The first row is the repository-local stack. The second used a disposable copy of the same working source, with read-only dependency links to the already installed 0.85.1 SDK and its actual AI/agent-core/pi-tui graph. All commands exited zero for the accepted results. The repository lockfile still resolves 0.84.4; broadening its root peer metadata did not install or upgrade dependencies. No live Pi process, installation, production session or state store was modified.
|
|
15
|
+
|
|
16
|
+
Both stacks passed `npm run validate` after adding the four [fatal-writer interruption witnesses](temporal-acceptance.md#fatal-writer-interruption). No runtime correction was needed: runtime SHA-256 remains `ce820cd33c34c4c882c11fc55e93f3914cfc8dffae57683e32cdcaa70f225cd2` from the [context-copy correction](performance.md#context-projection-and-trajectory-selection). Sorted `index.ts`, `lib/*.ts` and `tests/*.ts`, framed as path + NUL + bytes + NUL, now yield `1c5fafc1433631fed33177381e925eeeca52827932ba327690f615d2a4db81ba`. Copied/original source, runtime, test and package bytes were checked unchanged through validation, along with both selected dependency-resolution graphs and their manifests. These hashes do not cover every installed binary or environmental influence. The [benchmark workload fingerprint](performance.md) remains `b861fae36798bf76f56f411cc654c70fc948597bb84cc7a422c83f69ba1ca3e9`. Subsequent [matched runtime-performance controls](performance.md#matched-final-candidate-controls) use the 0.84.4 graph only, not a two-SDK timing comparison. The earlier fork/context witnesses remain included; these results do not replace separate integrated-candidate acceptance.
|
|
17
|
+
|
|
18
|
+
### Post-measurement integrated acceptance
|
|
19
|
+
|
|
20
|
+
After the nine-invocation performance series, inline review traced accumulated Git/CAS, provenance, worker ownership, fork/recovery, Stop/context and discovery changes against their callers and negative/native witnesses, without a confirmed blocker in that inspected closure. Separate `state-flow-integrated-0844` and `state-flow-integrated-0851` Runs repeated `npm run validate`: each passed 417/417 with no failed, cancelled, skipped or todo tests, plus typecheck and import-check, and actual command exit zero. Full captures are 47,957 and 47,950 bytes. All 78 selected runtime/test/package files and both actual dependency graphs remained unchanged; documentation is separately reviewed rather than represented by that source hash.
|
|
21
|
+
|
|
22
|
+
The twenty-property map resolves its quoted witnesses; that structural check is not exhaustive semantic proof. Domain validation reports 31 source files/133 acyclic local edges and no reverse entrypoint imports, with 25 existing header warnings. Context validation reports zero errors/five warnings. The package dry run includes 44 files and no tests. This accepts the local candidate within the documented synthetic/Linux/SDK boundaries, not arbitrary-host compatibility, production-incident attribution, publication permission or remote release-CI success. That checkpoint preceded the final compaction/status slice and its intentional 0.10.0 version alignment.
|
|
23
|
+
|
|
24
|
+
The final slice adds seven compaction policy/race tests and two native lifecycle witnesses, bringing the complete repository-local 0.84.4 suite to 426/426 with typecheck/import-check. On 0.85.1, all affected policy/status and native compaction/tree tests plus typecheck pass; the operator declined a redundant second full-suite repetition after this focused equivalence evidence. Native completed-history compaction preserves full JSONL/tree/state/UUID while shortening active/resumed entries without another model summary; threshold compaction before the first patch remains Pi-owned. Package version is 0.10.0. Remote release CI remains authoritative for the tagged tree.
|
|
25
|
+
|
|
26
|
+
Matching versions do not imply one physical dependency instance. Before/after resolution walks retain separate root and SDK-local 0.84.4 AI/agent-core copies and three TypeBox 1.3.7 locations in the repository installation. The first row describes that actual graph, not a deduplicated installation. The disposable 0.85.1 copy explicitly shares the SDK's AI/agent-core/pi-tui instances. Record canonical resolution edges as well as versions for comparisons; no dependency installation was changed to force a verifier assumption.
|
|
27
|
+
|
|
28
|
+
The focused parent-traversal and barrier cohort passed 3/3 on each stack. The earlier 0.85.1 native cohort passed 10/10: large state/answers, selected-tree restoration, quit/reload push ownership, ordinary/bootstrap mid-tool Stop, compaction/tree/restart, scoped barriers, historical reads, and sibling-tool rejection. The full suite includes those witnesses plus benchmark, persistence, provenance, migration and concurrency contracts; tests are available in a source checkout, not the published runtime package.
|
|
29
|
+
|
|
30
|
+
## Public host seams
|
|
31
|
+
|
|
32
|
+
Inspect the installed package's `dist/core/` implementations and declarations alongside upstream [SDK documentation](https://github.com/earendil-works/pi-mono/blob/v0.85.1/packages/coding-agent/docs/sdk.md). Both tested stacks retain these seams:
|
|
33
|
+
|
|
34
|
+
- `session-manager.js` / `session-manager.d.ts`: Read-only extension context exposes `getLeafEntry()`, `getEntry(id)` and each entry's `parentId`. Both lookups use the native ID map; `getBranch()` instead walks to the root and reverses a newly allocated path. State Flow now uses that public parent traversal for preflight without deleting or replacing native history.
|
|
35
|
+
- `agent-session.js`, `_handleAgentEvent()` / `_installAgentToolHooks()`, and agent-core's `agent-loop.js`: `message_end` extension handlers run before session persistence; the awaited event then appends the accepted assistant message before tool preflight. Inspect that synchronized current assistant, not a presumed last persisted assistant inside `message_end`. The native sibling-tool regression is the executable barrier witness.
|
|
36
|
+
- `sdk.js` and `extensions/runner.js`, `emitContext()`: Pi connects context transformation to the extension runner, which first `structuredClone`s native messages. State Flow projection runs after that host copy. Reducing extension traversal cannot make total inference work independent of history size.
|
|
37
|
+
- `agent-session.js`, `prompt()` / `reload()` / `dispose()`: The system prompt is composed at `before_agent_start`; Stop can change tools/projection but not that already composed prompt for the current run. Reload awaits `session_shutdown` before replacement. Bare disposal invalidates/disconnects without emitting shutdown; use the [embedding contract](architecture.md#embedding).
|
|
38
|
+
- `agent-session-runtime.js`, `teardownCurrent()`: Owner-driven new/resume/fork aborts the outgoing session, awaits shutdown, then disposes and creates the replacement. Active-push witnesses now cover new, same-file resume and fork teardown before invalidation, alongside quit and actual reload. Correct teardown does not imply successful fork restoration.
|
|
39
|
+
- `sdk.js`, `createAgentSession()`: Session selection precedes default resource/extension loading. No extension-only pre-session resolver seam was established; native default continuation remains an [external integration boundary](architecture.md#session-continuation).
|
|
40
|
+
|
|
41
|
+
These observations establish the specific seams used here, not compatibility with every Pi UI mode, extension combination, provider or operating system. Native tests use deterministic faux inference, temporary sessions/stores and local remotes. An installed SDK version does not fingerprint a still-running host. Actual-process incident attribution remains unproven; final comparable measurements and integrated acceptance remain in [BACKLOG.md](../BACKLOG.md).
|
|
42
|
+
|
|
43
|
+
## Native replacement witnesses
|
|
44
|
+
|
|
45
|
+
`tests/pi-harness.ts` now constructs a public `AgentSessionRuntime` from its existing isolated sessions and coherent CWD-bound services, forwarding complete session-start metadata and binding each replacement. No private host hook or production runtime change was needed. The three `tests/integration.test.ts` replacement witnesses pass on both SDK stacks and require:
|
|
46
|
+
|
|
47
|
+
- An accepted multi-scope patch and answer to supersede a still-running activation push.
|
|
48
|
+
- The exact shutdown reason/target, child exit and a claimable lease at `setBeforeSessionInvalidate()`, before a successor can start.
|
|
49
|
+
- Byte-identical outgoing JSONL and session checkpoint/tail/config/meta, the same selected leaf, and exact cold state at the accepted revision.
|
|
50
|
+
- An empty private layer plus inherited shared state for configured new sessions, and exact selected state/identity for same-file resume.
|
|
51
|
+
- No old-generation relaunch or queue writes after replacement; only a valid successor attempts the exact pending target and acknowledges its controlled child result.
|
|
52
|
+
|
|
53
|
+
**Native fork replacement now creates a child-owned session copy over current shared memory.** The active-push case forks before the first user request: private state is empty at that selected origin, not the parent's later accepted private work. Current global/CWD state remains visible, and the new child publication target descends from the retained parent target. Native session naming still does not prove the child JSONL is already persisted when its prefix contains no assistant.
|
|
54
|
+
|
|
55
|
+
Further native witnesses cover nonempty selected private state versus newer parent/shared state, retained native prefix and checkpoint/tail data, owned reload/resume, independent child writes and aligned post-origin history. Disabled sources remain disabled and copied parent Stop projection cannot reappear after child reload. Header UUID/CWD mismatches leave storage and selection unchanged; Start retries the exact source in the same loaded fork when evidence is corrected. Selecting inherited pre-origin pointers is unavailable, not permission to fall through to an older disabled marker and erase child state. That last witness failed with `true !== false` before the targeted ownership-aware recovery correction.
|
|
56
|
+
|
|
57
|
+
The fork-specific 13-test focused cohort covers these native cases plus runtime copying/CAS/file-expiration and header safety. An initial prefix assertion compared persisted JSON with in-memory objects containing explicit `undefined` fields; it was corrected to compare native persisted entries, without changing runtime behavior. The earlier outgoing-shutdown falsifier failed before invalidation with `null !== 'SIGKILL'`. Controlled push children do not prove external delivery; arbitrary cross-CWD replacement and every UI mode remain outside these witnesses. Ordinary inference abort retains accepted patches for same-session continuation and has no new immediate-enqueue requirement. See [fork support and limits](usage.md#fork-support-and-limits) and the [copy contract](fork-contract.md).
|
|
58
|
+
|
|
59
|
+
## Isolated validation procedure
|
|
60
|
+
|
|
61
|
+
Keep the live host and repository dependencies unchanged:
|
|
62
|
+
|
|
63
|
+
1. Copy the intended current source, including retained uncommitted files and tests, to a fresh temporary directory. Do not substitute baseline `HEAD` for a dirty candidate. Give the copy its own synthetic Git `HEAD` for benchmark source-identity checks; never copy production session/store data.
|
|
64
|
+
2. Create a real `node_modules` directory in the copy. Link the existing development dependencies, then link `@earendil-works/pi-coding-agent` to the selected installed SDK and AI/agent-core/pi-tui to the dependencies actually resolved by that SDK. Do not use `--preserve-symlinks` or install into the live package. Verify the SDK and extension resolve the same selected dependency instances.
|
|
65
|
+
3. Resolve import-only package manifests with `findPackageJSON(name, pathToFileURL(ownerManifest))`, not `require.resolve(name)`. Record canonical manifest paths, package names/versions and hashes before and after testing. For example, run the following inside the copy:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
node --input-type=module <<'JS'
|
|
69
|
+
import { findPackageJSON } from 'node:module';
|
|
70
|
+
import { readFileSync, realpathSync } from 'node:fs';
|
|
71
|
+
import { pathToFileURL } from 'node:url';
|
|
72
|
+
const owner = pathToFileURL(`${process.cwd()}/package.json`);
|
|
73
|
+
for (const name of ['pi-coding-agent', 'pi-ai', 'pi-agent-core', 'pi-tui']) {
|
|
74
|
+
const path = realpathSync(findPackageJSON(`@earendil-works/${name}`, owner));
|
|
75
|
+
console.log(path, JSON.parse(readFileSync(path, 'utf8')).version);
|
|
76
|
+
}
|
|
77
|
+
JS
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Use a fresh `PI_CODING_AGENT_DIR`, `PI_OFFLINE=1`, `GIT_CONFIG_NOSYSTEM=1` and a temporary `GIT_CONFIG_GLOBAL` containing only a synthetic commit identity and disabled commit signing. Fixtures already supply in-memory credentials, no model-catalog refresh and explicit resource/session roots. Leave fixture-local Git hooks enabled: tests intentionally use temporary `pre-receive`/`post-receive` hooks.
|
|
81
|
+
|
|
82
|
+
Inside that prepared copy, the compatibility checks are ordinary project commands:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
npm run typecheck
|
|
86
|
+
npm run check
|
|
87
|
+
node --experimental-strip-types --test \
|
|
88
|
+
--test-name-pattern='tool preflight walks only|patch_state is the only tool|real Pi executes only patch_state|replacement closes its outgoing publisher' \
|
|
89
|
+
tests/extension.test.ts tests/integration.test.ts
|
|
90
|
+
npm test
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`npm run validate` combines typecheck, the full suite and import smoke for subsequent candidates. Retain actual command exits, complete logs and source/dependency identities; a filtered command succeeding does not replace the full suite.
|
|
94
|
+
|
|
95
|
+
### Rejected setup and correction
|
|
96
|
+
|
|
97
|
+
The first 0.85.1 full run passed 395/397 because the temporary global Git config incorrectly set `core.hooksPath` to an empty directory. This disabled two fixtures' hooks: prepared receipts no longer observed the synthetic post-push write, and file-to-Git adoption no longer observed the intentionally pending push. The same two failures reproduced on 0.84.4 with that config. Removing only the temporary override made both witnesses pass on both SDKs, followed by 397/397 on 0.85.1. No runtime or test correction was needed; the failed run is not an SDK incompatibility or a successful full-suite result.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Physical fork: session-stream copy
|
|
2
|
+
|
|
3
|
+
Status: **locally implemented and validated; not released**. [Usage](usage.md#fork-support-and-limits) owns operation and recovery; [BACKLOG.md](../BACKLOG.md) owns the remaining 0.10.0 work.
|
|
4
|
+
|
|
5
|
+
## Contract
|
|
6
|
+
|
|
7
|
+
A physical Pi fork creates a new session with a separate copy of the source's session memory. Global and CWD memory remain the existing shared layers, not historical copies for the child.
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
B.global = existing shared global
|
|
11
|
+
B.cwd = existing shared CWD
|
|
12
|
+
B.session = copy of source session checkpoint + retained patch tail
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- Resolve the source session stream at the native fork boundary. An earlier selection does not copy the parent's later live private state.
|
|
16
|
+
- Copy the session checkpoint, retained tail of up to seven patches and matching session artifact provenance. Preserve replay records and transition identities rather than flattening them into a new materialized-only snapshot.
|
|
17
|
+
- Adopt current proven live global/CWD streams and provenance without rewriting or pruning them. They need not equal the selected source revision's older shared layers.
|
|
18
|
+
- Give B its own UUID, native session key, config/meta and durable checkpoint. Retain selected enablement/publication policy and any pending bootstrap requirement, but start at step zero without the parent's run specification, validation feedback, publication acknowledgement or process ownership.
|
|
19
|
+
- Preserve A's private files, native trace and accepted history. Subsequent B session writes do not modify A's session layer.
|
|
20
|
+
- Forking conversation/memory does not clone or roll back project files or tool effects.
|
|
21
|
+
|
|
22
|
+
This is session inheritance, not an exact historical snapshot of the whole effective state. It adds no historical shared-owner reference or semantic mode.
|
|
23
|
+
|
|
24
|
+
## History and lifecycle
|
|
25
|
+
|
|
26
|
+
`TemporalRuntime.prepareFork()` validates the source before any installation and returns a detached inspection snapshot plus a single-use copy operation. Git source inspection is immutable; file-only input is revalidated against its exact complete cohort. Installation captures a fresh live basis, uses existing stream adoption at a new origin, and publishes only the new session cohort under the existing CAS/exclusion rules. An occupied live or current-HEAD namespace is not a fresh target. Forking does not initialize missing shared storage or run migrations.
|
|
27
|
+
|
|
28
|
+
The checkpoint/tail copy retains replay data, but its length is not B's available hot-history depth. B begins at a new origin with `state[0]`; subsequent accepted transitions build its aligned `state[0..7]` window. Pre-origin records are not newly fabricated child transitions or indexes into independent local-scope clocks.
|
|
29
|
+
|
|
30
|
+
B's own Git-backed runtime history begins with its first child-owned cohort. A's earlier Git history remains intact under A. Copied Pi checkpoint entries do not become B-owned historical references: selecting one cannot fall through to an older disabled marker and reset B. Select an owned child checkpoint or resume the parent instead.
|
|
31
|
+
|
|
32
|
+
The adapter handles native `session_start` with reason `fork`, verifies a regular canonical parent header and matching CWD, then records the child checkpoint. A child-owned passive-projection reset prevents copied parent Stop markers from resurfacing after child reload/resume. Disabled sources remain disabled; ordinary activation policy is not overridden.
|
|
33
|
+
|
|
34
|
+
## Support boundary
|
|
35
|
+
|
|
36
|
+
- Initial native copying requires a persisted direct-parent locator and a readable temporal source. The parent filename/key, header UUID and selected runtime identity must agree; no arbitrary session search or UUID aliasing occurs.
|
|
37
|
+
- Source or publication failure leaves the selected reference intact. Explicit Start can retry an unaccepted copy in that same loaded fork instance after evidence or contention is corrected.
|
|
38
|
+
- Cold recovery before the first child checkpoint, startup/CLI paths that do not emit the native fork reason, in-memory parent locators and arbitrary cross-CWD imports are not added by this slice. Existing child-owned checkpoints use normal reload/resume without rereading the parent header.
|
|
39
|
+
- Nested copying works only where the selected pointer is owned by the direct parent; inherited pointers to earlier ancestors are not recursively resolved.
|
|
40
|
+
- The file backend copies only an available exact current cohort. Expired file references do not authorize copying newer parent data. Legacy Git storage requires its existing explicit migration path rather than migration during fork.
|
|
41
|
+
- Uncommitted shared streams, collisions and concurrent modifications retain the existing publication guards; failure does not authorize broadening the fork's writes.
|
|
42
|
+
|
|
43
|
+
## Evidence
|
|
44
|
+
|
|
45
|
+
Both supported [SDK stacks](compatibility.md) pass the full suite. Native Git-backed witnesses cover selected private state versus newer parent/shared state, independent child mutation, owned reload/resume, disabled sources, Stop projection fencing, malformed parent identity/CWD and retry, inherited-pointer reset refusal, plus active-push replacement ownership.
|
|
46
|
+
|
|
47
|
+
`tests/runtime.test.ts` covers an exact seven-record session copy with provenance, unchanged live shared files including unreferenced provenance, detached/single-use preparation, occupied live/HEAD targets, CAS races and file-only source expiration. `tests/continuation.test.ts` checks header-only reading and refusal of non-regular/symlink locators. These are synthetic fixtures, not production-session or arbitrary-host validation.
|