@llblab/pi-kit 0.23.1 → 0.24.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.
Files changed (30) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +3 -1
  3. package/banner.jpg +0 -0
  4. package/node_modules/@llblab/pi-state-flow/AGENTS.md +5 -4
  5. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +3 -5
  6. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +17 -0
  7. package/node_modules/@llblab/pi-state-flow/README.md +4 -4
  8. package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +50 -1
  9. package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +254 -7
  10. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +36 -27
  11. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +1 -1
  12. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +6 -4
  13. package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +1 -1
  14. package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
  15. package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +6 -2
  16. package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +3 -1
  17. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +8 -4
  18. package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +1 -1
  19. package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +6 -4
  20. package/node_modules/@llblab/pi-state-flow/docs/performance.md +48 -2
  21. package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +6 -3
  22. package/node_modules/@llblab/pi-state-flow/docs/usage.md +10 -0
  23. package/node_modules/@llblab/pi-state-flow/lib/context.ts +228 -8
  24. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +37 -32
  25. package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +8 -4
  26. package/node_modules/@llblab/pi-state-flow/lib/query.ts +1 -1
  27. package/node_modules/@llblab/pi-state-flow/package.json +1 -1
  28. package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +6 -2
  29. package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +3 -1
  30. package/package.json +6 -4
@@ -8,7 +8,7 @@ import { ArtifactReadTracker } from "./acquisition.js";
8
8
  import { classifyArtifactCompilationNeed, inspectRegisteredArtifactPaths, ORDINARY_ARTIFACT_COMPILER, sameArtifactSourceFingerprint, } from "./artifact.js";
9
9
  import { hasCompactionSizedTranscript, planStateFlowCompaction, shouldRequestStateFlowCompaction, stateFlowCompactionResult } from "./compaction.js";
10
10
  import { loadStateFlowConfig } from "./config.js";
11
- import { createPassiveContinuation, currentRunTrajectory, lazyNavigationHint, passiveContinuationMessages, projectSystemProtocol, runtimeContextMessage, syntheticUser } from "./context.js";
11
+ import { ContextProjection, contextView, createPassiveContinuation, currentRunTrajectory, passiveContinuationMessages, projectSystemProtocol, runtimeContextHead, syntheticUser } from "./context.js";
12
12
  import { readNativeSessionHeader } from "./continuation.js";
13
13
  import { cwdScopeKey, resolveSessionAddress, sessionScopeKey, } from "./durable.js";
14
14
  import { completeRun, prepareRun, resumeEpisode, startEpisode, stopEpisode } from "./episode.js";
@@ -61,6 +61,7 @@ export default function stateFlowExtension(pi, options = {}) {
61
61
  const compactionMarker = `state-flow-boundary:${randomUUID()}`;
62
62
  let passiveContinuation;
63
63
  let bootstrapContinuation;
64
+ const contextProjection = new ContextProjection();
64
65
  let inferencePreparation;
65
66
  let runAnchorTimestamp;
66
67
  let runtime;
@@ -402,6 +403,7 @@ export default function stateFlowExtension(pi, options = {}) {
402
403
  }
403
404
  /** Select the active native branch under one owned restoration lifetime; only current accepted work installs memory. */
404
405
  function restoreActiveBranch(ctx, sessionStartReason, notifyRecovery = true, startOwner) {
406
+ contextProjection.reset();
405
407
  cancelBranchRestoration();
406
408
  // Start-owned attachment/fork recovery keeps its owner; only accepted Start cancels Stop.
407
409
  if (!startOwner) {
@@ -760,6 +762,7 @@ export default function stateFlowExtension(pi, options = {}) {
760
762
  const selected = runtime ??= createRuntime(ctx);
761
763
  const acquiredArtifacts = structuredClone([...artifactReads.successful.values()]);
762
764
  const acquiredSkills = structuredClone([...skillReads.successful.values()]);
765
+ const previousEffective = overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session);
763
766
  return await selected.withPatchTransaction((transaction) => {
764
767
  if (runtime !== selected)
765
768
  throw new Error("State Flow session selection changed while awaiting publication");
@@ -789,9 +792,14 @@ export default function stateFlowExtension(pi, options = {}) {
789
792
  }
790
793
  clearAcceptedAcquisitions(new Set(acquiredArtifacts.map(({ path }) => path)));
791
794
  updateUi(ctx);
792
- return { content: [{ type: "text", text: changed
793
- ? `\nState materialized atomically at ${scopes.join("+")} scope${scopes.length === 1 ? "" : "s"}.`
794
- : "\nState already current." }], details: { scopes, step: snapshot.meta.step, changed } };
795
+ const updates = contextProjection.acceptPatch(previousEffective, overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session), patches, artifactHints);
796
+ const acknowledgement = changed
797
+ ? `\nState materialized atomically at ${scopes.join("+")} scope${scopes.length === 1 ? "" : "s"}.`
798
+ : "\nState already current.";
799
+ return { content: [
800
+ { type: "text", text: acknowledgement },
801
+ ...(updates ? [{ type: "text", text: `\n${presentationJson({ state_updates: updates })}` }] : []),
802
+ ], details: { scopes, step: snapshot.meta.step, changed } };
795
803
  }, signal);
796
804
  }
797
805
  catch (error) {
@@ -914,6 +922,7 @@ export default function stateFlowExtension(pi, options = {}) {
914
922
  stopPersistenceError = undefined;
915
923
  installScopeStates();
916
924
  clearRunTransient();
925
+ contextProjection.reset();
917
926
  passiveContinuation = undefined;
918
927
  bootstrapContinuation = snapshot.meta.bootstrap ? continuation : undefined;
919
928
  deferInferencePreparation();
@@ -999,6 +1008,7 @@ export default function stateFlowExtension(pi, options = {}) {
999
1008
  && !shuttingDown && ctx.sessionManager.getSessionId() === owner && !snapshot.config.enabled;
1000
1009
  const superseded = () => ({ ok: false, message: "State Flow Stop was superseded" });
1001
1010
  const freezeHandoff = () => {
1011
+ contextProjection.reset();
1002
1012
  const handoff = (current.config.enabled || stopPersistenceError) && selected?.view && current.meta.validation?.attempt !== 0
1003
1013
  ? createPassiveContinuation(projectModelState(overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session)), incomingBoundary?.startedAt ?? stoppedAt, incomingBoundary ? incomingBoundary.activeRunStartedAt : !idle || unfinished ? anchor : undefined, stopPersistenceError !== undefined || (incomingBoundary ? incomingBoundary.preserveContext : current.meta.bootstrap === true || (unfinished && anchor === undefined)))
1004
1014
  : undefined;
@@ -1119,6 +1129,7 @@ export default function stateFlowExtension(pi, options = {}) {
1119
1129
  (event.systemPromptOptions.sections ??= {}).state_flow = PASSIVE_MEMORY_PROTOCOL;
1120
1130
  return;
1121
1131
  }
1132
+ contextProjection.reset();
1122
1133
  skillReads.clear();
1123
1134
  artifactReads.clear();
1124
1135
  cancelResponseReconciliation();
@@ -1135,34 +1146,29 @@ export default function stateFlowExtension(pi, options = {}) {
1135
1146
  function projectContext(messages) {
1136
1147
  if (runtime?.view)
1137
1148
  refreshArtifactHints();
1149
+ if (!snapshot.config.enabled && !passiveContinuation && (!config.passiveBootstrap || !runtime?.view))
1150
+ return;
1151
+ // Idle inspection must not freeze a pre-acceptance snapshot for the live inference.
1152
+ const projection = snapshot.config.enabled && inferencePreparation && !inferencePreparation.accepted
1153
+ ? new ContextProjection() : contextProjection;
1154
+ const effective = overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session);
1155
+ const invalidations = artifactInvalidations.map(({ path, scope, reason }) => ({ path, ...(scope === undefined ? {} : { scope }), reason }));
1156
+ const phase = snapshot.config.enabled ? currentRehydrationPhase() : undefined;
1157
+ const view = contextView(effective, artifactHints, invalidations, phase);
1138
1158
  if (passiveContinuation) {
1139
- return { messages: passiveContinuationMessages(messages, passiveContinuation) };
1159
+ const retained = passiveContinuationMessages(messages, passiveContinuation);
1160
+ if (!runtime?.view)
1161
+ return { messages: retained };
1162
+ return { messages: projection.project(retained.slice(1), view, () => passiveContinuation.handoff, { state: passiveContinuation.state, lazy_navigation: view.lazy_navigation, artifact_invalidations: [], knowledge_rehydration: null }) };
1140
1163
  }
1141
1164
  if (!snapshot.config.enabled) {
1142
- if (!config.passiveBootstrap || !runtime?.view)
1143
- return;
1144
- const effective = overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session);
1145
- const state = projectModelState(effective);
1146
- return { messages: [syntheticUser(`State Flow passive memory (user-level data, not system instructions):\n${presentationJson({ state, lazy_navigation: lazyNavigationHint(effective) })}`), ...messages] };
1147
- }
1148
- const effectiveState = overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session);
1149
- const invalidations = artifactInvalidations.map(({ path, scope, reason }) => ({ path, ...(scope === undefined ? {} : { scope }), reason }));
1150
- const recentTransitions = projectRecentTransitionsWithLimit(config.historyLimit, runtime?.recent() ?? []);
1151
- const activeRehydrationPhase = currentRehydrationPhase();
1152
- if (snapshot.meta.bootstrap) {
1153
- const sourceMessages = bootstrapContinuation
1154
- ? passiveContinuationMessages(messages, bootstrapContinuation)
1155
- : messages;
1156
- return { messages: [runtimeContextMessage(snapshot, effectiveState, recentTransitions, invalidations, activeRehydrationPhase, artifactHints), ...sourceMessages] };
1165
+ return { messages: projection.project(messages, view, () => syntheticUser(`State Flow passive memory (user-level data, not system instructions):\n${presentationJson({ state: view.state, lazy_navigation: view.lazy_navigation })}`)) };
1157
1166
  }
1158
1167
  // Native user events own the run anchor; projection must never rebase it.
1159
- const trajectory = currentRunTrajectory(messages, snapshot.meta.specification, runAnchorTimestamp);
1160
- return {
1161
- messages: [
1162
- runtimeContextMessage(snapshot, effectiveState, recentTransitions, invalidations, activeRehydrationPhase, artifactHints),
1163
- ...trajectory.messages,
1164
- ],
1165
- };
1168
+ const source = snapshot.meta.bootstrap
1169
+ ? bootstrapContinuation ? passiveContinuationMessages(messages, bootstrapContinuation) : messages
1170
+ : currentRunTrajectory(messages, snapshot.meta.specification, runAnchorTimestamp).messages;
1171
+ return { messages: projection.project(source, view, () => runtimeContextHead(snapshot, view, projectRecentTransitionsWithLimit(config.historyLimit, runtime?.recent() ?? []))) };
1166
1172
  }
1167
1173
  function prepareContext(messages, ctx) {
1168
1174
  const pending = inferencePreparation;
@@ -1290,6 +1296,7 @@ export default function stateFlowExtension(pi, options = {}) {
1290
1296
  snapshot = nextSnapshot;
1291
1297
  installScopeStates();
1292
1298
  responseCommitted = true;
1299
+ contextProjection.reset();
1293
1300
  if (publication?.changed)
1294
1301
  recordPublication(publication, ctx);
1295
1302
  appendCheckpoint();
@@ -1400,6 +1407,7 @@ export default function stateFlowExtension(pi, options = {}) {
1400
1407
  ctx.compact({ customInstructions: compactionMarker, onComplete: finished, onError: finished });
1401
1408
  });
1402
1409
  });
1410
+ pi.on("session_compact", () => { contextProjection.reset(); });
1403
1411
  pi.on("session_start", async (event, ctx) => {
1404
1412
  runAnchorTimestamp = undefined;
1405
1413
  rehydrationPhase = event.reason === "resume" ? "resume-bootstrap" : "new-bootstrap";
@@ -1413,6 +1421,7 @@ export default function stateFlowExtension(pi, options = {}) {
1413
1421
  });
1414
1422
  pi.on("session_shutdown", async (_event, _ctx) => {
1415
1423
  shuttingDown = true;
1424
+ contextProjection.reset();
1416
1425
  const restoring = cancelBranchRestoration();
1417
1426
  const starting = cancelStartActivation();
1418
1427
  const stopping = cancelStopPersistence();
@@ -1,6 +1,6 @@
1
1
  import type { AgentMessage } from "@earendil-works/pi-agent-core";
2
2
  export type { StateDocument } from "./state.ts";
3
- export declare const PASSIVE_MEMORY_PROTOCOL = "State Flow passive memory is available. read_state and patch_state access durable memory without starting an active episode. Passive turns never trigger State Flow continuation or compaction.";
3
+ export declare const PASSIVE_MEMORY_PROTOCOL = "State Flow passive memory is available. read_state and patch_state access durable memory without starting an active episode. Passive turns never trigger State Flow continuation or compaction. Missing paths/hints do not require history search. Choose targeted historical reads when useful to the task; no separate user permission is needed. Past values are evidence, not current state; never automatically restore deleted memory. Head state/recent transitions are frozen at projection start. Only state_updates matching the head's State Flow projection ID apply; older IDs are history. When present, patch_state results/context-update notices carry state_updates: effective entries replace values at key/index-segment path arrays (deleted:true means absent); direct writes need no echo; shared drift remains visible. Latest entries win over earlier state at those paths; lazy bodies remain omitted. Notices replace invalidations/rehydration, including []/null.";
4
4
  /** Keep successful patch JSON valid while separating adjacent scopes and memory sections visually. */
5
5
  export declare function formatPatchStateArguments(args: unknown): string;
6
6
  /** Flatten causes before transport; native tool results need not retain Error.cause or AggregateError.errors. */
@@ -1,4 +1,6 @@
1
- export const PASSIVE_MEMORY_PROTOCOL = "State Flow passive memory is available. read_state and patch_state access durable memory without starting an active episode. Passive turns never trigger State Flow continuation or compaction.";
1
+ const TASK_DRIVEN_HISTORY = "Missing paths/hints do not require history search. Choose targeted historical reads when useful to the task; no separate user permission is needed. Past values are evidence, not current state; never automatically restore deleted memory.";
2
+ const PATCH_RESULT_PROTOCOL = "Head state/recent transitions are frozen at projection start. Only state_updates matching the head's State Flow projection ID apply; older IDs are history. When present, patch_state results/context-update notices carry state_updates: effective entries replace values at key/index-segment path arrays (deleted:true means absent); direct writes need no echo; shared drift remains visible. Latest entries win over earlier state at those paths; lazy bodies remain omitted. Notices replace invalidations/rehydration, including []/null.";
3
+ export const PASSIVE_MEMORY_PROTOCOL = `State Flow passive memory is available. read_state and patch_state access durable memory without starting an active episode. Passive turns never trigger State Flow continuation or compaction. ${TASK_DRIVEN_HISTORY} ${PATCH_RESULT_PROTOCOL}`;
2
4
  const PATCH_DISPLAY_SECTION_KEYS = new Set(["global", "cwd", "session", "intents", "contract", "working", "artifacts", "response", "lazy"]);
3
5
  /** Keep successful patch JSON valid while separating adjacent scopes and memory sections visually. */
4
6
  export function formatPatchStateArguments(args) {
@@ -91,15 +93,15 @@ ${bootstrapProtocol}STATE:
91
93
 
92
94
  SCOPES: Use the narrowest scope: session=branch/run continuation by default; cwd=reusable project truth; global=established cross-project/user/environment knowledge.
93
95
 
94
- READ: Use read_state for concrete scope/retained-history gaps. lazy_navigation lists bounded effective lazy keys, not bodies. Unscoped=effective; effective/global/cwd/session select overlay or owner. Arrays use indices or [start..end]; keys gives structure, patch the intersected change.
96
+ READ: Use read_state for concrete scope/retained-history gaps. lazy_navigation lists bounded effective lazy keys, not bodies. Unscoped=effective; effective/global/cwd/session select overlay or owner. Arrays use indices or [start..end]; keys gives structure, patch the intersected change. ${TASK_DRIVEN_HISTORY}
95
97
 
96
- WRITE: patch_state is the sole model-authored semantic mutation mechanism; all supplied scopes are validated and durably accepted as one atomic transition. Call alone in an assistant response; await acceptance. Global/CWD use current canonical values after cancelable lock waiting. Correct repeats succeed without new revisions.
98
+ WRITE: patch_state is the sole model-authored semantic mutation mechanism; all supplied scopes are validated and durably accepted as one atomic transition. Call alone in an assistant response; await acceptance. Global/CWD use current canonical values after cancelable lock waiting. Correct repeats succeed without new revisions. ${PATCH_RESULT_PROTOCOL}
97
99
 
98
100
  PATCH: Use global/cwd/session object patches for material updates, not acknowledgments. Omit empty scopes. artifacts/contract/working/intents are objects; lazy is ordinary JSON. Omitted fields persist. Never patch runtime config/meta/response. Objects merge recursively; arrays/primitives replace. An object containing only canonical "[N]" keys recursively patches array elements. Indexed deletion is forbidden; nested object null deletes; materialized null is forbidden.
99
101
 
100
102
  MEMORY: Treat every patch as reconciliation rather than append-only notes: merge superseded fragments, remove obsolete progress. Preserve commitments, open questions, consequential results and exact continuation; distinguish requirements, decisions, observations, conclusions and hypotheses. Exclude secrets, raw history, transient progress, speculation and unsupported claims; retain decision-relevant uncertainty. Curate touched state; cleanup and scope reviews require an explicit user request. Proven moves use targeted read_state and one atomic multi-scope patch, then verify both owners. External transfers need verified acceptance before deletion. Never invent memory changes.
101
103
 
102
- REFS: State refs use {"$ref":"cwd.lazy.plan"} or \`$cwd.lazy.plan\` in text. Resolve only when needed; infer no authority, hydration, execution, or completion. If that resolution proves a dangling state ref, fix/drop it in owning text; never scan for broken refs.
104
+ REFS: State refs use {"$ref":"cwd.lazy.plan"} or \`$cwd.lazy.plan\` in text. Resolve only when needed; infer no authority, hydration, execution, or completion. Hint paths are current reference owners, not relocated targets or proof of staleness. Fix proven stale refs only as needed; never scan refs or history offsets.
103
105
 
104
106
  ACQUISITION: Read only for a concrete gap, exact source/edit, invalidation, contradiction/failure, or explicit request; changed source fingerprints require rereading.
105
107
  ARTIFACTS: Compile acquired invalidated artifacts at artifacts[exact path] in the reported scope (global/cwd/session), with a description; never relocate or invent global copies. Runtime owns all artifact/Skill provenance.
@@ -171,7 +171,7 @@ function missingReferenceHint(view, path, historyLimit) {
171
171
  return undefined;
172
172
  return [{
173
173
  type: "dangling-reference",
174
- message: `Reconcile the verified current values that reference this path${truncated ? "; additional sources may exist beyond the bounded scan" : ""}.`,
174
+ message: `The requested value is unavailable in the selected state. These current values reference that path, not a verified new location. Use this evidence if relevant to the task${truncated ? "; additional reference owners may exist beyond the bounded scan" : ""}.`,
175
175
  paths: sources.map(({ path: sourcePath }) => sourcePath),
176
176
  }];
177
177
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-state-flow",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "private": false,
5
5
  "description": "Incremental scoped state/context/memory compiler for Pi, inspired by SKILL.state",
6
6
  "keywords": [
@@ -32,7 +32,9 @@ Scopes overlay `global → cwd → session`: cross-project, project, branch/run.
32
32
 
33
33
  ## Read
34
34
 
35
- Reuse sufficient visible state. `read_state` accepts `path` or `paths`, never both. Multi-path reads succeed or fail together. Projections: `value` (default), `keys` (structure), `patch` (intersecting change at the selected boundary).
35
+ Reuse sufficient visible state. The memory head and its recent transitions are frozen at projection start. Apply later `state_updates` only when their `projection` matches the head's `State Flow projection:` ID; older native results remain historical. Effective update paths are key/index arrays whose values replace that path, while `deleted: true` means absence. Current notices can replace invalidation lists or rehydration phase, including clearing them with `[]` or `null`; lazy bodies still require explicit reads.
36
+
37
+ `read_state` accepts `path` or `paths`, never both. Multi-path reads succeed or fail together. Projections: `value` (default), `keys` (structure), `patch` (intersecting change at the selected boundary).
36
38
 
37
39
  Example arguments:
38
40
 
@@ -44,7 +46,9 @@ Example arguments:
44
46
  {"paths":["cwd.working","session.working"]}
45
47
  ```
46
48
 
47
- Unscoped paths use effective state. `cwd[1].working` reads the preceding causal boundary. Materialized-history and scope patch-history paths such as `cwd.patches[1]` share the configured `historyLimit` bound (default 7) and require actually retained history. Lowering the limit folds excess tails without erasing current state; increasing it does not reconstruct discarded history. Array ranges such as `cwd.lazy.checks[0..3]` exclude the endpoint and require existing elements. Missing paths fail: inspect parent keys to verify deletion. Missing history is not empty history. Read `lazy` explicitly. Treat structured `$ref` values and `$`-prefixed `read_state` paths inside ordinary strings, such as `$effective.lazy.memory[7]`, as semantic-state references. Other resources retain their native locators. Resolve any reference through the appropriate read/tool only when needed. Neither form proves authority or existence, hydrates, or executes anything. Never scan or resolve references merely to test them. A missing single value path with exact durable sources returns `{value:null, hint:[{type:"dangling-reference", message, paths}]}`. Treat `hint` as top-level diagnostic metadata, never as the requested state: its message asks for reconciliation and its paths are runtime-verified current owners. The hint proves provenance rather than staleness and is absent when no current durable source matches; keys, patch, and batch reads keep all-or-error behavior. Only then inspect ownership as needed and patch a proven stale owning value while preserving its surrounding meaning. Effective absence, inaccessible external resources, and transient failures are not proof.
49
+ Unscoped paths use effective state. `cwd[1].working` reads the preceding causal boundary. Materialized-history and scope patch-history paths such as `cwd.patches[1]` share the configured `historyLimit` bound (default 7) and require actually retained history. Lowering the limit folds excess tails without erasing current state; increasing it does not reconstruct discarded history. Array ranges such as `cwd.lazy.checks[0..3]` exclude the endpoint and require existing elements. Missing paths are unavailable; inspect parent keys only when needed for the task. Missing history is not empty history. Read `lazy` explicitly. Treat structured `$ref` values and `$`-prefixed `read_state` paths inside ordinary strings, such as `$effective.lazy.memory[7]`, as semantic-state references. Other resources retain their native locators. Resolve any reference through the appropriate read/tool only when needed. Neither form proves authority or existence, hydrates, or executes anything. Never scan or resolve references merely to test them. A missing single value path with exact durable sources returns `{value:null, hint:[{type:"dangling-reference", message, paths}]}`. Treat `hint` as top-level diagnostic metadata, never as the requested state: its message is descriptive and conditional; its paths are runtime-verified current reference owners, not verified new locations of the target. The hint proves provenance rather than staleness and is absent when no current durable source matches; keys, patch, and batch reads keep all-or-error behavior. Only then inspect ownership as needed and patch a proven stale owning value while preserving its surrounding meaning. Effective absence, inaccessible external resources, and transient failures are not proof.
50
+
51
+ Missing paths or runtime hints alone do not require historical search. The agent may choose a targeted historical read when a previous value is useful to the current task, without separate user permission. Otherwise continue without searching. Use found values as historical evidence, not automatically as current state; never automatically restore deleted memory. Do not scan all offsets, hydrate automatically or request repair inference. A hint does not prove prior existence, retained history or relocation. A proven stale reference may be repaired within touched work without resurrecting its target. Automatic state and recent-transition projections omit lazy bodies; bounded `lazy_navigation` preserves structure, and explicit current/historical reads still return requested lazy values or patches.
48
52
 
49
53
  ## Write
50
54
 
@@ -22,9 +22,11 @@ Follow the installed runtime contract. This registered Skill follows its Pi sour
22
22
  1. **Limit the review.** Address the requested scope. A completed phase may motivate recommending cleanup, not starting it without a request. For a whole-state cleanup, inspect global, CWD, and session ownership explicitly; for a narrower request, inspect only affected owners. Use targeted reads for gaps, contradictions, ownership, or verification; do not rerun the project.
23
23
  2. **Classify.** Put user requirements and binding confirmed decisions in `contract`, observations, assistant conclusions, and unresolved work in `working`, chosen actions in `intents`, and inactive reusable detail in `lazy`. Never give an assistant conclusion user authority. Remove fulfilled, abandoned, superseded, or impossible intents; retain consequential results. Possibilities are not commitments.
24
24
  3. **Keep evidence boundaries.** Preserve corrections, prerequisites, bounded negative results, and useful uncertainty. Separate requirements, decisions, observations, conclusions, and hypotheses. Silence is not acceptance; repetition is not verification. One implementation's failure does not reject an approach. Neither freeze provisional methods nor reopen confirmed decisions without grounds.
25
- 4. **Compact for continuation.** Remove duplicates, obsolete progress, unsupported claims, and secrets. Keep sufficient results, real retrieval pointers, pending interaction, and known next checks. Observations are not live external facts. Keep `lazy` shallow and priority-ordered. Recognize optional structured `$ref` values and `$`-prefixed `read_state` paths inside ordinary strings as semantic-state references; other resources retain native locators. No reference form proves authority or existence, authorizes execution, or implies completion. Never scan or resolve references merely to find broken ones. When the bounded review independently needs a reference, a missing single value path with exact durable sources returns `{value:null, hint:[{type:"dangling-reference", message, paths}]}`. Treat the top-level hint as provenance and reconciliation guidance, never as requested state or proof of staleness; its paths are runtime-verified current owners, while no hint does not prove invention. Inspect ownership only as needed, then patch a proven stale owning value while preserving surrounding meaning. Effective absence or external inaccessibility is insufficient.
25
+ 4. **Compact for continuation.** Remove duplicates, obsolete progress, unsupported claims, and secrets. Keep sufficient results, real retrieval pointers, pending interaction, and known next checks. Observations are not live external facts. Keep `lazy` shallow and priority-ordered. Recognize optional structured `$ref` values and `$`-prefixed `read_state` paths inside ordinary strings as semantic-state references; other resources retain native locators. No reference form proves authority or existence, authorizes execution, or implies completion. Never scan or resolve references merely to find broken ones. When the bounded review independently needs a reference, a missing single value path with exact durable sources returns `{value:null, hint:[{type:"dangling-reference", message, paths}]}`. Treat the top-level hint as conditional navigation and provenance, never as requested state or proof of staleness; its paths are runtime-verified current reference owners, not verified new locations of the target, while no hint does not prove invention. Inspect ownership only as needed, then patch a proven stale owning value while preserving surrounding meaning. Effective absence or external inaccessibility is insufficient.
26
26
  5. **Check ownership.** Prefer `session` for branch/run continuation, `cwd` for project knowledge, and `global` for established cross-project knowledge. Effective values do not prove ownership; inspect owners before moves. Broader applicability requires evidence.
27
27
 
28
+ Missing paths or runtime hints alone do not require historical search. The agent may choose a targeted historical read when a previous value is useful to the current task, without separate user permission. Otherwise continue without searching. Use found values as historical evidence, not automatically as current state; never automatically restore deleted memory. Do not scan all offsets, hydrate automatically or request repair inference. A hint does not prove prior existence, retained history or relocation. A proven stale reference may be repaired within touched work without resurrecting its target. Lazy bodies require explicit reads; automatic state/history projections retain navigation without hydrating those bodies.
29
+
28
30
  ## Transfer only when needed
29
31
 
30
32
  Resolve destination conflicts without overwriting stronger or unrelated knowledge. For a proven move between scopes of one State Flow store, inspect both owners, then use one atomic multi-scope `patch_state` for destination and source changes. Verify both owners and effective inheritance afterward; reconcile affected references. A rejected cohort leaves neither side partially accepted.
@@ -41,7 +41,7 @@ Every materialized scope has exactly this shape:
41
41
  - `working` retains verified current facts, unresolved work and exact continuation.
42
42
  - `artifacts` maps exact source paths to compiled routing metadata.
43
43
  - `response` is owned only by the session scope and stores the exact latest accepted assistant answer, including the empty string. Global and CWD retain the required key as an empty structural placeholder so canonical scopes keep one shape; the effective overlay receives `response` only from Session.
44
- - `lazy` is a required object root for ordinary JSON detail, omitted from baseline model state and read explicitly.
44
+ - `lazy` is a required object root for ordinary JSON detail, omitted from baseline model state and read explicitly. Automatic recent-transition projection also removes each whole `patch.lazy`, including deletions. Empty scoped patches and transitions disappear; an empty projected window is omitted. Visible hot patches retain their original identities, order and positions. Canonical history and explicit current/historical reads are unchanged. Bounded lazy navigation remains available without bodies; previously communicated user/tool/response text is not redacted.
45
45
 
46
46
  Effective state recursively overlays:
47
47
 
@@ -73,7 +73,11 @@ A changed accepted response is runtime-owned, session-only semantic state and ad
73
73
 
74
74
  ## Pi lifecycle
75
75
 
76
- `patch_state` is the sole mutation tool. It acquires store exclusion before selecting current Global/CWD, stages the authored operations while preserving private Session ownership, and validates/publishes one atomic cohort. Replay, history and revisions use that actual basis; correct repeats create no semantic transition. It then acts as an inference barrier. Pi executes no sibling tools from the same assistant response; the next inference sees the rematerialized current effective state.
76
+ `patch_state` is the sole mutation tool. It acquires store exclusion before selecting current Global/CWD, stages the authored operations while preserving private Session ownership, and validates/publishes one atomic cohort. Replay, history and revisions use that actual basis; correct repeats create no semantic transition. It then acts as an inference barrier. Pi executes no sibling tools from the same assistant response; the next inference sees the rematerialized current effective state. Its successful native tool result includes a `state_updates` block, separate from the compact acknowledgement used by interactive rendering. `effective` entries contain a `path` array of exact object keys/array indices plus either a replacing `value` or `deleted: true`. These are accepted effective values, not authored merge operations: scope-local deletion can reveal a lower value, and higher scopes can mask an accepted lower-scope write. Entries conservatively cover changed scope fallbacks, masked or overlapping multi-scope touches and projected changes since the last communicated view, including shared refreshes before or during the transaction. Predictable direct non-null leaf writes are omitted when the accepted effective value matches the authored value and no other authored scope touches that path or its ancestors/descendants; disjoint multi-scope writes are independent. Explicit Session scalar/array replacements also omit matching receipts despite lower-scope overlap; ambiguous object merges and deletions remain conservative. Coalesced object additions/replacements can likewise omit an exact authored object, without suppressing foreign sibling fields. Indexed-array patch selectors become numeric update paths only against a communicated in-bounds array basis; object keys with the same spelling are literal. Deletions are omitted only when the accepted effective value equals the previously communicated value and no other authored scope overlaps. An effective-only head cannot prove a hidden lower-scope fallback from its own basis, so changed fallbacks remain visible. Artifact card replacements or merges can be omitted when their projected authored fields applied to an already communicated card exactly match the accepted card after stripping retired provenance; an unchanged previously communicated hint remains part of that known card. New, removed or changed hints and unexpected semantic drift remain visible. Lazy navigation exposes only bounded key/kind summaries, never bodies. An accepted summary needs no receipt when the complete previously communicated catalog and non-deleting top-level writes predict the entire accepted catalog; deletions, overlapping keys, incomplete catalogs and drift remain conservative. Unrelated unchanged branches are omitted. A result with no remaining reconciliation has only the acknowledgement. Arrays with changed length are replaced as a unit, while equal-length changes may target indices. Artifact cards use the normal metadata filter and touched cards replace their whole projected entry. Lazy bodies never enter this block; changed bounded `lazy_navigation` may accompany it. Each update carries a volatile `projection` ID matching the head's `State Flow projection:` text block. The protocol applies only matching updates: retained native results with older IDs remain historical and cannot override a new head. Within that projection, the latest entry supersedes earlier context only at its path. The context domain owns this projection, not storage or the lifecycle composition root.
77
+
78
+ `ContextProjection` freezes the whole initial head, including its timestamp, specification and recent transitions. Signal-less inspection before active preparation accepts uses a disposable projection, so it cannot freeze a stale specification or pre-maintenance state for the live inference. Active/bootstrap runs rebase on new input and accepted completion; a native continuation after completion gets a fresh head without resurrecting the completed specification. Passive runs keep their head across ordinary user turns. Both modes rebase on Start/Stop, selection/resume/reload, native compaction and a removed/replaced native prefix. No per-patch or size-threshold rebase exists. The volatile ID is not a semantic transition identity and enters neither canonical files nor runtime metadata.
79
+
80
+ Unreported state changes, changing artifact hints, invalidation lists and rehydration phase append as synthetic tail notices at the native-message boundary where they first appeared. Later inference retains those exact messages at those positions before new native messages, rather than moving them to the latest tail. Empty invalidation lists and a null rehydration envelope explicitly clear earlier notices. Accepted patch receipts advance the communicated view; their state deltas are not redundantly emitted as synthetic notices. Stop handoff uses its captured state as the initial basis, including changes before its first projection. Skill acquisition guidance remains attached to native read results. The cache owns no persistence, publication authority or continuation scheduler, and does not redact native history.
77
81
 
78
82
  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).
79
83
 
@@ -109,7 +113,7 @@ After an accepted non-bootstrap run settles with no queued input, State Flow may
109
113
 
110
114
  Pi 0.87 supports retain-none boundary compactions, but State Flow intentionally does not use them. Completed canonical state omits the exact user prompt, and foreign custom context can legitimately occur inside the latest retained iteration; hiding both would make the projected semantic state a lossy substitute for native context. Unknown or smaller usage, foreign custom metadata or native `custom_message` context in the removed prefix, stale selection, Stop/bootstrap/error/abort, and pending input do not produce this boundary. User manual and native threshold/overflow compaction remain unmodified; unaccepted work stays under Pi's native compaction contract.
111
115
 
112
- 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. `currentRunTrajectory` selects a unique captured user timestamp without requiring specification-text equality: Pi may append image normalization hints after `before_agent_start`. Without a captured timestamp, only a unique exact specification match can select a projected suffix. Missing, nonfinite, or ambiguous selection retains all available context. Projection never assigns `runAnchorTimestamp`; native user events own that lifecycle identity, so a projection fallback cannot become compaction authority. With a selected boundary, one retained-message array preserves foreign custom messages at every position and ordinary messages from the original run, including images, tools and steering, without copying discarded ordinary prefixes. The necessary scan and Pi's earlier native-message clone remain history-dependent. See [context-cost evidence](performance.md#context-projection-and-trajectory-selection).
116
+ The Pi adapter passes its raw cached scope overlay to the context domain's `contextView`, which owns model sanitization. `runtimeContextHead` serializes that already-projected view only when the projection cache needs a new head; `runtimeContextMessage` remains the raw-input convenience builder. The adapter never sanitizes or pre-projects the overlay itself. `currentRunTrajectory` selects a unique captured user timestamp without requiring specification-text equality: Pi may append image normalization hints after `before_agent_start`. Without a captured timestamp, only a unique exact specification match can select a projected suffix. Missing, nonfinite, or ambiguous selection retains all available context. Projection never assigns `runAnchorTimestamp`; native user events own that lifecycle identity, so a projection fallback cannot become compaction authority. With a selected boundary, one retained-message array preserves foreign custom messages at every position and ordinary messages from the original run, including images, tools and steering, without copying discarded ordinary prefixes. The necessary scan and Pi's earlier native-message clone remain history-dependent. See [context-cost evidence](performance.md#context-projection-and-trajectory-selection).
113
117
 
114
118
  ## Storage and identity
115
119
 
@@ -250,7 +254,7 @@ The [tested Pi SDK baseline](compatibility.md) chooses or creates `SessionManage
250
254
  {"session":{"intents":{"next":"Verify the corrected behavior"}}}
251
255
  ```
252
256
 
253
- `intents` is the hot plane for active commitments, not requirements, observations, alternatives, or completed plans. Removing an intent does not remove its consequences or any referenced state. Semantic-state references use either the optional structured `{"$ref":"cwd.lazy.plan"}` convention or `$` immediately followed by one valid `read_state` path inside ordinary text, for example `$effective.lazy.memory[7]`. The text prefix distinguishes references from incidental path-like prose and leaves a deterministic seam for possible future parsing. Resource paths, document locators, URIs, Skill identities, and agent identities retain their native syntax. State Flow stores all forms as ordinary JSON and currently does not parse or validate targets. The agent resolves a relevant locator explicitly through `read_state` or the appropriate external tool; presence alone creates no authority, existence proof, dependency, hydration, execution, or completion semantics. Reference repair is reactive: the agent never scans or resolves references merely to test them. Only after one requested value path is missing does the query domain perform one bounded reverse lookup over current model-patchable semantic planes for exact structured `$ref` or `$path` matches. When matches exist, `read_state` returns the explicit diagnostic sentinel `{value:null, hint:[{type:"dangling-reference", message, paths}]}`. `hint` is a top-level sibling rather than state data; its message asks for reconciliation and `paths` contains at most three runtime-verified current owning addresses. The null sentinel is never returned alone for this case. Keys, patch, and multi-path reads keep all-or-error semantics, while no durable match retains the ordinary missing-path error. A match establishes durable semantic provenance, not staleness; no match does not prove invention. The agent may then inspect ownership and patch a proven stale source without discarding surrounding meaning. Effective absence does not establish ownership, and unavailable history, external inaccessibility, or transient read failure does not prove a broken reference.
257
+ `intents` is the hot plane for active commitments, not requirements, observations, alternatives, or completed plans. Removing an intent does not remove its consequences or any referenced state. Semantic-state references use either the optional structured `{"$ref":"cwd.lazy.plan"}` convention or `$` immediately followed by one valid `read_state` path inside ordinary text, for example `$effective.lazy.memory[7]`. The text prefix distinguishes references from incidental path-like prose and leaves a deterministic seam for possible future parsing. Resource paths, document locators, URIs, Skill identities, and agent identities retain their native syntax. State Flow stores all forms as ordinary JSON and currently does not parse or validate targets. The agent resolves a relevant locator explicitly through `read_state` or the appropriate external tool; presence alone creates no authority, existence proof, dependency, hydration, execution, or completion semantics. Reference repair is reactive: the agent never scans or resolves references merely to test them. Only after one requested value path is missing does the query domain perform one bounded reverse lookup over current model-patchable semantic planes for exact structured `$ref` or `$path` matches. When matches exist, `read_state` returns the explicit diagnostic sentinel `{value:null, hint:[{type:"dangling-reference", message, paths}]}`. `hint` is a top-level sibling rather than state data; its message describes unavailability conditionally, and `paths` contains at most three runtime-verified current reference-owner addresses, not verified new locations of the requested data. The hint contains no lazy bodies and does not prove prior existence, retention or relocation. The null sentinel is never returned alone for this case. Keys, patch, and multi-path reads keep all-or-error semantics, while no durable match retains the ordinary missing-path error. A match establishes durable semantic provenance, not staleness; no match does not prove invention. The agent may inspect ownership and patch a proven stale source when useful to the current task, without discarding surrounding meaning or resurrecting its target. Missing paths or hints alone do not require historical search. The agent may independently choose targeted historical reading when a previous value is useful to the current task; no separate user permission is required. Historical values are evidence, not automatically current state or a reason to restore deleted memory. Runtime never scans history for reference owners, triggers repair inference or hydrates lazy bodies. Effective absence does not establish ownership, and unavailable history, external inaccessibility, or transient read failure does not prove a broken reference.
254
258
 
255
259
  `read_state` accepts one unified path. `effective == effective[0]` is the current effective materialization; `global == global[0]` (and CWD/session equivalents) selects that scope at the same composed causal boundary. `global.patches == global.patches[0]` reads the latest accepted retained global patch, with higher patch indices walking only that scope's retained accepted patches. Indices are bounded by the configured `historyLimit`; unavailable pre-origin or pre-tail history is an error. Resolver aliases are not literal JSON containers. Reads stay cached and create no Git query, publication, checkpoint append, or semantic step.
256
260
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  State Flow requires matching `pi-coding-agent`, `pi-agent-core`, `pi-ai`, and `pi-tui` packages at `>=0.87.0`. Keep all four on the same release line. The open-ended peer range permits newer SDK releases; it does not certify them.
6
6
 
7
- The repository-local verified stack is Linux/x64, Node 26.8.1, Git 2.55.0 and Pi SDK 0.87.0. The current release candidate passes 711 tests, typecheck, build, compiled imports and package dry-run. The release workflow uses Node 24; its result is a separate verification gate.
7
+ The repository-local verified stack is Linux/x64, Node 26.8.1, Git 2.55.0 and Pi SDK 0.87.0. Repository validation covers tests, typecheck, build, compiled imports and package dry-run; successful results are bound to the validated source and dependency identities. The release workflow uses Node 24; its result is a separate verification gate.
8
8
 
9
9
  Tests use isolated stores and scripted providers through the real Pi SDK. They do not certify installed Telegram/TUI reachability, live provider behavior, other operating systems, mixed SDK versions or untested newer SDK releases. Detailed behavioral witnesses live in [temporal acceptance](temporal-acceptance.md); benchmark methodology and source-bound results live in [performance](performance.md).
10
10
 
@@ -49,8 +49,8 @@ global | CWD | session
49
49
  `intents`, `contract`, `working`, and `artifacts` remain hot in every scope. Session-owned `response` is also hot; Global and CWD keep only its required empty structural slot, and Effective inherits the Session value. `intents` may keep compact active direction while referring to large supporting detail in `lazy`. `lazy` differs only in projection policy:
50
50
 
51
51
  - It is canonical semantic JSON, validated and versioned with its owning scope.
52
- - It is excluded from the ordinary baseline effective-state body.
53
- - It becomes model-visible only through bounded baseline navigation hints or explicit `read_state` output.
52
+ - Its bodies are excluded from automatic state and recent-transition projections, including lazy writes, replacements and deletions. Empty visible patches/transitions disappear without renumbering history; hot changes remain visible.
53
+ - Bounded baseline navigation exposes presence, path and structure without bodies. Explicit current/historical `read_state` values and patches may contain lazy bodies; already communicated native/user/tool/response text is not redacted.
54
54
  - Reading it does not mutate state, freshness, usage metadata, history, or future context.
55
55
 
56
56
  ### Semantic references
@@ -59,7 +59,9 @@ A reference is semantic content, not a runtime type. The optional `{"$ref":"cwd.
59
59
 
60
60
  State Flow preserves all forms exactly as ordinary JSON. It does not scan prose, index targets, validate existence, rewrite relative locators, or infer authority, dependency, hydration, execution, or completion. When a reference matters, the agent resolves it explicitly with `read_state` for semantic paths or the appropriate external read/tool for other resources. A locator supports retrieval but does not replace content required for the current decision.
61
61
 
62
- Reference repair is reactive, not a maintenance scan. The agent does not enumerate, audit, or resolve references merely to test them. Only after one requested `read_state` value path is missing does State Flow perform one bounded reverse lookup over current model-patchable semantic planes for exact structured `$ref` and `$path` matches. If found, the tool returns `{value:null, hint:[{type:"dangling-reference", message, paths}]}`. `hint` is explicit top-level metadata rather than state data; its action message asks for reconciliation and its path array contains at most three runtime-verified current owners. The null sentinel is never returned alone for this case. Keys, patch, and multi-path reads keep ordinary all-or-error semantics; no durable match retains the missing-path error and does not prove the agent invented the path. The agent may then reconcile a proven stale owning value while preserving surrounding meaning. This applies equally to `$ref` objects and contextual references in prose. Effective-state absence alone does not identify the owner, and unavailable history, inaccessible external resources, or transient read failure do not prove that a durable reference is broken.
62
+ Reference repair is reactive, not a maintenance scan. The agent does not enumerate, audit, or resolve references merely to test them. Only after one requested `read_state` value path is missing does State Flow perform one bounded reverse lookup over current model-patchable semantic planes for exact structured `$ref` and `$path` matches. If found, the tool returns `{value:null, hint:[{type:"dangling-reference", message, paths}]}`. `hint` is explicit top-level metadata rather than state data; its conditional message describes unavailability, and its path array contains at most three runtime-verified current reference owners, not verified new locations of the target. The hint contains no lazy bodies and proves neither prior existence, retention nor relocation. The null sentinel is never returned alone for this case. Keys, patch, and multi-path reads keep ordinary all-or-error semantics; no durable match retains the missing-path error and does not prove the agent invented the path. The agent may then reconcile a proven stale owning value while preserving surrounding meaning. This applies equally to `$ref` objects and contextual references in prose. Effective-state absence alone does not identify the owner, and unavailable history, inaccessible external resources, or transient read failure do not prove that a durable reference is broken.
63
+
64
+ Missing paths or runtime hints alone do not require historical search. The agent may choose a targeted historical read when a previous value is useful to the current task, without separate user permission; otherwise continue without searching. Found values are historical evidence, not automatically current memory. Never automatically restore deleted data, scan all offsets, hydrate bodies or trigger repair inference. The reverse lookup searches current reference owners only, never history. A proven stale reference can be repaired within touched work without resurrecting its target.
63
65
 
64
66
  The `lazy` root must be an object. Its nested values may include arrays, objects, and scalars, for example:
65
67
 
@@ -126,7 +128,7 @@ There are three projections:
126
128
  | `keys` | `{ "meta": ..., "keys": ... }` | Minimal structural facts followed by immediate keys |
127
129
  | `patch` | `{ "patch": ... }` | Historical semantic patch at the selected boundary |
128
130
 
129
- A single-path request returns one payload. A multi-path request returns positionally aligned arrays under the same projection fields. One missing value path with exact current durable references instead returns `{ "value": null, "hint": [{ "type": "dangling-reference", "message": "Reconcile the verified current values that reference this path.", "paths": ["cwd.working.note"] }] }`; this explicit sentinel is diagnostic metadata, not semantic state.
131
+ A single-path request returns one payload. A multi-path request returns positionally aligned arrays under the same projection fields. One missing value path with exact current durable references instead returns `{ "value": null, "hint": [{ "type": "dangling-reference", "message": "The requested value is unavailable in the selected state. These current values reference that path, not a verified new location. Use this evidence if relevant to the task.", "paths": ["cwd.working.note"] }] }`; this explicit sentinel is diagnostic metadata, not semantic state.
130
132
 
131
133
  `value` otherwise deliberately mirrors the effective-state snapshot injected at iteration start: it is semantic state without revision, provenance, range, transport, or storage fields. `patch` likewise contains only the selected semantic patch. Only structural discovery earns `meta`, and `keys` remains the final and most valuable field in that response.
132
134
 
@@ -28,7 +28,53 @@ The v3 report records `promptPrefixRuns` for each State Flow and native Pi user
28
28
 
29
29
  A bounded local sample used `BENCH_PATCHES=2 BENCH_SAMPLES=1 BENCH_ROUNDS=2 BENCH_STATE_BYTES=1024 BENCH_POST_RESUME=1 npm run benchmark` on Node 26.8.1 Linux/x64 and Pi/AI 0.87.0. The runtime-source SHA-256 was `0f4eea2394c52c6ac545260cae967a16581b8768a24a8c611c13a389e6df01a9`, workload-source SHA-256 `e9b8158d953d8622776aa075835c4aa95c98855ed5c90e85de05dccae1087e91`, base commit `5f0f688650738d4d0e9850b2529b0273c47121a8` (the measured tree had uncommitted 0.18.1 changes). Both source hashes stayed unchanged during the run and all correctness phases passed. At user run 1, native Pi's second inference shared 2,776 of 2,777 prior serialized bytes; State Flow shared 9,365 of 9,467 at inference 2 and 9,173 with inference 2 at inference 3 (one accepted barrier). In both State Flow runs, the 21-byte serialized `specification` value duplicated the 21-byte current native user string value; native Pi had no projection field. These are synthetic short-run observations, not representative cache-hit rates or timing predictions.
30
30
 
31
- A later 0.19 decision about freezing the projected head must compare compatible workloads and include correctness of fresh state visibility after barriers. Byte-prefix measurements omit provider framing, tool schemas, tokenization, cache policies and quality; this probe does **not** justify changing context projection in 0.18.1.
31
+ Byte-prefix measurements omit provider framing, tool schemas, tokenization, cache policies and quality. In particular, serialized synthetic-message timestamps can shorten this proxy prefix without proving provider-visible cache churn. Fresh state visibility after barriers must be validated independently of prefix preservation.
32
+
33
+ ### Trajectory-dominant baseline
34
+
35
+ The opt-in `BENCH_PREFIX=1` probe in the [benchmark guide](../benchmarks/README.md) exercises active memory, ordinary passive memory and Stop handoff separately. Each measured run issues six 20,497-byte native reads, a patch, two more reads and a second patch. Exact read content remains visible through all eleven inferences; both patches and terminal completion are checked outside the provider. The v3 `trajectory[].promptPrefixRuns` entries retain every inference, not just aggregate ratios.
36
+
37
+ Baseline: Node 26.8.1 Linux/x64, Pi/AI 0.87.0, base commit `2834dcb447f58867b579c6f2709bc71211b9537a` with uncommitted benchmark changes; runtime SHA-256 `a5a961d4d706d4a2d6649201553e39484d0e4c29d27cf4014f072b39cd78a01a`, workload SHA-256 `1785e73e4992c3d986d2851f2d81963c0b0cfde8ceacfd4337659ebc9d1492ba`. Both source identities remained unchanged and the native workload passed. The local report is `/tmp/state-flow-prefix-baseline.json`; the reproducible command and measurements below do not depend on retaining that temporary file.
38
+
39
+ Each cell is **shared prefix / current serialized context bytes**; `—` means first inference. Inferences 8 and 11 follow accepted patches.
40
+
41
+ | Inference | Active | Passive | Stop handoff |
42
+ | ---: | ---: | ---: | ---: |
43
+ | 1 | — / 9860 | — / 7197 | — / 6019 |
44
+ | 2 | 9732 / 31025 | 5801 / 28360 | 6018 / 27183 |
45
+ | 3 | 9731 / 52193 | 5801 / 49528 | 27182 / 48353 |
46
+ | 4 | 9732 / 73359 | 5801 / 70696 | 48352 / 69519 |
47
+ | 5 | 9732 / 94528 | 5801 / 91865 | 69518 / 90688 |
48
+ | 6 | 9733 / 115695 | 5802 / 113034 | 90687 / 111857 |
49
+ | 7 | 9732 / 136864 | 5801 / 134201 | 111856 / 133026 |
50
+ | 8 | 9212 / 137813 | 5666 / 134999 | 133025 / 133800 |
51
+ | 9 | 9907 / 158983 | 5825 / 156169 | 133799 / 154965 |
52
+ | 10 | 9907 / 180150 | 5825 / 177338 | 154964 / 176134 |
53
+ | 11 | 9235 / 181075 | 5689 / 178112 | 176133 / 176908 |
54
+
55
+ In that baseline, Stop handoff already reused a frozen message, unlike active and ordinary passive projection. A warm prefix alone does not prove updated memory reaches the model; freshness has separate native-SDK regressions.
56
+
57
+ ### Frozen-head measurement
58
+
59
+ The implemented projection freezes whole heads, including timestamps, and delivers accepted values and changing notices at stable tail positions. Active completion/new runs, native compaction/selection and Start/Stop are cache boundaries; passive user turns and patches are not. Volatile projection IDs distinguish current updates from retained results after a rebase. See [projection semantics](architecture.md#pi-lifecycle) for ownership and limits.
60
+
61
+ The identical trajectory workload (`1785e73e4992c3d986d2851f2d81963c0b0cfde8ceacfd4337659ebc9d1492ba`) on the same Node/Pi stack measured runtime SHA-256 `d2c424a52d55b0c1ca47a8b1a1beba9c0dda665c8f024d6aa3b6ad95af9d3b46`, with unchanged base commit and uncommitted implementation changes. Both source identities remained stable; all native workload assertions passed. Local report: `/tmp/state-flow-prefix-after.json`.
62
+
63
+ | Inference | Active | Passive | Stop handoff |
64
+ | ---: | ---: | ---: | ---: |
65
+ | 1 | — / 10461 | — / 7985 | — / 6620 |
66
+ | 2 | 10460 / 31625 | 7984 / 29148 | 6619 / 27784 |
67
+ | 3 | 31624 / 52792 | 29147 / 50316 | 27783 / 48952 |
68
+ | 4 | 52791 / 73959 | 50315 / 71484 | 48951 / 70120 |
69
+ | 5 | 73958 / 95127 | 71483 / 92653 | 70119 / 91287 |
70
+ | 6 | 95126 / 116295 | 92652 / 113822 | 91286 / 112456 |
71
+ | 7 | 116294 / 137463 | 113821 / 134989 | 112455 / 133625 |
72
+ | 8 | 137462 / 138416 | 134988 / 135941 | 133624 / 134579 |
73
+ | 9 | 138415 / 159580 | 135940 / 157106 | 134578 / 155742 |
74
+ | 10 | 159579 / 180746 | 157105 / 178275 | 155741 / 176911 |
75
+ | 11 | 180745 / 181697 | 178274 / 179229 | 176910 / 177863 |
76
+
77
+ Every continuation shares **all prior serialized bytes except the closing array bracket**, including both patch barriers in all three modes. At inference 8, the active prefix grows from 9,212 baseline bytes to 137,462; passive grows from 5,666 to 134,988. Initial contexts grow modestly because result guidance and projection identity are explicit. These are message-byte measurements, not provider cache accounting, latency, token-cost or quality guarantees. Regression tests assert prefix equality independently of exact host timestamps/IDs; separate native tests prove accepted-state freshness, repeated barriers, passive cross-turn stability and bootstrap rebasing.
32
78
 
33
79
  ## Current cost model
34
80
 
@@ -49,7 +95,7 @@ This is a bounded-allocation improvement, not an unconditional constant-time cla
49
95
 
50
96
  ## Context projection and trajectory selection
51
97
 
52
- `runtimeContextMessage` projects the cached semantic overlay once per context emission. `currentRunTrajectory` allocates one retained-message array rather than arrays for discarded ordinary prefixes. Foreign custom context may require scanning earlier entries, and Pi may clone native messages before the extension runs.
98
+ The context domain projects the cached semantic overlay once per context emission to compare current state with its last communicated view. It serializes the complete head only at a projection boundary; later synthetic notices retain their original native-message positions. Projection caching targets request-prefix stability, not constant-time state processing: view copies/diffs remain state-dependent and notices accumulate until a natural reset, without a size threshold. `currentRunTrajectory` allocates one retained-message array rather than arrays for discarded ordinary prefixes. Foreign custom context may require scanning earlier entries, and Pi may clone native messages before the extension runs.
53
99
 
54
100
  `tests/context.test.ts` exercises small and large semantic payloads, zero and two hundred prior request/answer pairs, repeated requests, stale/missing anchors, foreign custom messages, and post-barrier context emission. These tests assert projection counts and retained identities; they impose no wall-time threshold.
55
101
 
@@ -15,8 +15,8 @@ This is a maintained property-to-test map for the current canonical-file contrac
15
15
  9. **Unchanged scopes stay stable:** The sparse temporal example checks global state at a boundary where only CWD/session change; “true no-ops do not enter history while response-only changes do” also checks that an unchanged session receives no new patch.
16
16
  10. **Historical deletion overlay:** `tests/temporal.test.ts` — “mixed sparse changes and deletion overlays match an independent snapshot oracle through compaction” explicitly checks session → CWD → global fallback and retained historical values.
17
17
  11. **Barrier shifts current to offset 1:** `tests/integration.test.ts` — “real Pi patch_state barriers rematerialize every scope before the next inference” observes the predecessor immediately after a barrier.
18
- 12. **Next inference sees new current state:** The same real-Pi test inspects actual model-input projections after session, CWD, and global barriers. “real Pi reads prior scoped state lazily after a barrier and rejects path offset eight without a transition” adds model-tool access to the predecessor.
19
- 13. **No automatic old full-state duplication:** `tests/context.test.ts` — “projects only the latest seven compact accepted transitions” rejects full-state records in transition context. The real-Pi barrier test requires exactly one current runtime projection per inference. Explicitly requested history remains ordinary tool-result trajectory, not eager snapshot injection.
18
+ 12. **Next inference sees new current state:** The same real-Pi test reconstructs current state from the frozen head plus matching-projection result/tail replacements after session, CWD, and global barriers. “real Pi reads prior scoped state lazily after a barrier and rejects path offset eight without a transition” adds model-tool access to the predecessor.
19
+ 13. **No automatic old full-state duplication:** `tests/context.test.ts` — “projects only the latest seven compact accepted transitions” rejects full-state records in transition context. The real-Pi barrier test requires exactly one frozen runtime head per inference, with fresh changes in matching-projection tails. Explicitly requested history remains ordinary tool-result trajectory, not eager snapshot injection.
20
20
  14. **Accepted response changes are transitions:** `tests/extension.test.ts` verifies that an ordinary accepted answer becomes runtime-owned `response` without a finalization patch or fallback inference, and “an empty accepted answer finalizes the run and stores an empty response” proves `""` is accepted after an earlier barrier. `tests/transition.test.ts` proves the empty value is an ordinary Session-owned semantic change when it replaces prior text.
21
21
  15. **Correct repeats succeed without semantic changes:** “accepts canonical atomic scope patches and correct repeats without another checkpoint” preserves canonical bytes and native checkpoint count on repetition, while still rejecting empty supplied scopes and obsolete finalization-shaped calls. Runtime current-head witnesses preserve revisions, lineage and step across identical patches and compilations; a changed accepted response remains a runtime-owned transition.
22
22
  16. **Configured hot-history bounds:** `tests/temporal.test.ts` verifies hot-range and unavailable pre-origin boundaries; the real-Pi history-reader test rejects offset eight at the default limit seven without a transition. `tests/config.test.ts` exercises materialized and scope patch-history paths at limits 0, 1, 7, and 12, including single-path/one-item batch reads above seven and distinct configured versus actually retained boundaries.
@@ -44,6 +44,8 @@ This is a maintained property-to-test map for the current canonical-file contrac
44
44
  38. **Stop cannot manufacture a restoration failure:** Held-store retained-boundary and auto-start fixtures require repeated Stop to return promptly without cancelling memory acceptance, adding an error fence or publishing early. Release accepts the selected/initial memory with passive policy, permits passive patches and retains them across reload. Stop→Start while waiting activates only after memory acceptance. A separate Start-owned attachment witness withdraws the Start waiter without cancelling restoration. The native SDK tree witness invokes `/state-flow-stop` during awaited `navigateTree`, then verifies selected private memory, passive tool availability and a successful next-provider patch; selected conversation remains visible and later-branch private memory stays absent. This public SDK route is not installed Telegram/TUI reachability certification.
45
45
  39. **Native Skill compilation reconciles an independent writer without private leakage:** The Global/CWD × duplicate/competing/invalid matrix runs real Pi acquisition and compiler tool calls while a separate process publishes between the model's source read and its patch. Identical compiler output leaves shared checkpoint/tail/provenance bytes, scope revision and runtime step unchanged. Different output replaces the complete artifact and matching source-hash/compiler evidence, preserves independent peer fields, and advances the affected revision once. The next provider sees adopted shared state and local private memory, never the peer's private sentinel; all five peer-private files remain unchanged. An invalid compilation combined with a Session mutation rejects without changing any captured canonical file or accepting the accompanying Session field. The peer uses the runtime publication API, not a second provider; these ordered interleavings supplement the separate held-writer cancellation tests.
46
46
  40. **A public SDK host can control a child while fork binding awaits memory:** The fixture observes the public `createAgentSession` result before awaiting `bindExtensions`, without private SDK hooks or direct calls to State Flow command handlers. During a held-store `runtime.fork`, it calls the child's public `prompt('/state-flow-stop')`; an optional subsequent Start waits for memory acceptance. Stop returns while the fork remains pending. After release, the child has a distinct identity, selected private memory and the requested passive/active policy. Its next provider sees selected rather than later-parent private state, successfully patches child memory and leaves all parent-private files unchanged. This certifies the embedding route on the tested SDK, not whether the installed CLI or Telegram exposes that child before its runtime replacement completes.
47
+ 41. **Automatic history does not hydrate lazy memory:** Context tests cover large writes, replacements and deletions in Global/CWD/Session, mixed hot/lazy cohorts, empty scoped patches/transitions and omission of an empty window. Visible records retain their identities, order and original positions; artifact evidence is still hidden. Native SDK active/bootstrap/reload/Stop→Start witnesses keep lazy bodies out of State Flow-owned automatic blocks while retained canonical files still contain them. Current tool trajectory and unfinished bootstrap retain previously communicated bodies. Semantic files remain byte-identical through projection/preparation, and no hydration or repair inference is added. The four native mode witnesses fail against the unfiltered projection in an isolated source copy.
48
+ 42. **Hints do not create a recovery task:** Query tests preserve single-value hint conditions, current-only reference-owner lookup, no body disclosure and existing keys/patch/batch/unavailable-history behavior. Active/passive native witnesses compare exact canonical bytes and checkpoint counts around hints and reads, and require the exact scripted tool and provider-call counts. A task that needs no old detail performs no historical read; a task that needs it explicitly reads the exact retained Global/CWD/Session values without separate permission or restoring deleted data. Protocol and both Skill tests enforce this permission and boundary. Native provider assertions have completion sentinels outside the provider callbacks so a swallowed scripted provider error cannot produce a false pass. These are runtime/contract witnesses, not a claim about every model's discretionary behavior.
47
49
 
48
50
  ## Additional preservation boundaries
49
51
 
@@ -54,9 +56,10 @@ This is a maintained property-to-test map for the current canonical-file contrac
54
56
  - `tests/integration.test.ts` native new/resume/fork witnesses preserve canonical files and retained boundaries. Fork witnesses cover selected private state over current shared layers, fresh child ownership, reload/resume, disabled-source fencing, identity/CWD refusal, retry, and expired-boundary refusal. `tests/runtime.test.ts` restore/fork provenance witnesses preserve current-head, untouched-path, and shared evidence while discarding evidence for later-changed private artifacts, including change-away-and-back. The test-only provenance reader is checked against nonempty canonical `meta.json` evidence in every scope before and after reload; native restore/fork tests verify missing-evidence invalidation, explicit recompilation, parent preservation, and provenance across reload.
55
57
  - `tests/recovery.test.ts` and `tests/runtime.test.ts` distinguish malformed checkpoints from expired retained boundaries, prevent fallthrough or newer-state substitution, and require detached single-use restoration followed by canonical origin acceptance.
56
58
  - `tests/extension.test.ts` — “tool preflight walks only the selected native suffix for matching calls, without rebuilding a branch” uses real in-memory `SessionManager` trees with zero/two hundred prior request-answer pairs, intervening custom/results, and a later unselected assistant reusing a call ID. It requires exact parent visits, no full-branch construction, preserved sibling/duplicate-patch rejection and unchanged complete native entries. The native sibling-tool witness in `tests/integration.test.ts` separately observes all three start/end events and requires zero branch reads during preflight/execution while accepting the correct state/answer.
59
+ - `tests/context.test.ts` proves whole-head identity, stable-position changing/cleared notices, drift since the last communicated view, no redundant post-receipt notices, and reset identity fencing. Native “real Pi bootstrap/passive keeps a byte-stable head” witnesses repeated barriers, active completion rebasing and passive cross-turn continuity. “real Pi passive reload distinguishes retained old receipts from the refreshed head” keeps old native results while requiring a different projection ID and the newly adopted shared value. The trajectory benchmark's contract test requires every continuation prefix to equal the entire preceding serialized array minus its closing bracket in active, passive and Stop-handoff modes; this is not provider cache accounting.
57
60
  - `tests/context.test.ts` — “enabled context projects the complete overlay once in ordinary and bootstrap runs” counts one full-overlay clone at 8 KiB and 1 MiB while preserving selected/cached state, native entries, input messages and artifact semantics. “current run trajectory allocates no arrays of discarded ordinary history” observes source-derived arrays after zero/two hundred historical request-answer pairs while preserving foreign context, current tools, steering, reference identity and order. Anchor tests prefer captured identity over normalized text, retain images/tools/steering, and preserve available context on missing/nonfinite/colliding identities or ambiguous specification matches; unique text fallback remains projection-only. The native barrier witness separately counts one marked full-overlay clone per post-barrier `emitContext()` invocation; it still requires each next inference to see all accepted scope changes.
58
61
  - `tests/compaction.test.ts` owns State Flow's completed-run compaction policy: captured run-anchor retention across steering/tools, missing/ambiguous/unfinished-anchor refusal, large-history eligibility, generation-owned invocation, foreign `custom`/`custom_message` prefix refusal, queued-work exclusion, stale selection, benign preparation refusal and shutdown fencing. Its harness emits native user `message_end` and checks that repeated projection plus steering cannot promote missing, ambiguous or unobserved anchors into compaction authority. The native “real Pi compaction retains the original run through steering, tool results, and foreign context” cases actually compact plaintext and SDK-normalized image requests with two steering messages, a successful read/patch, and persisted custom context; image/read-result evidence reaches later model calls, exact message ids survive reload, the trace prefix stays byte-identical, and no model summary is requested. The short “real Pi retains a normalized image and read evidence through steering without compaction” controls check State Flow enabled/disabled parity before any compaction. Existing native witnesses cover ordinary compaction/cold resume and leave threshold compaction after partial tool work native.
59
- - `tests/integration.test.ts` — “real Pi boundary continuation keeps accepted State Flow memory” exercises actual companion `turn_end` and `agent_before_settle` draft entries and continuation. Each next provider input has exactly one current-memory projection before/after a continued patch, the accepted response and current tool declarations. There is only one `before_agent_start`, no restored specification in runtime checkpoints or model projection, exactly one requested continuation and correct final response/step. The pure “projects accepted memory without resurrecting a completed specification for boundary continuation” witness preserves source state, omits lazy bodies, and keeps available context when both specification and native capture are absent.
62
+ - `tests/integration.test.ts` — “real Pi boundary continuation keeps accepted State Flow memory” exercises actual companion `turn_end` and `agent_before_settle` draft entries and continuation. Each next provider input has one freshly rebased head after accepted completion and matching-projection updates after a continued patch, the accepted response and current tool declarations. There is only one `before_agent_start`, no restored specification in runtime checkpoints or model projection, exactly one requested continuation and correct final response/step. The pure “projects accepted memory without resurrecting a completed specification for boundary continuation” witness preserves source state, omits lazy bodies, and keeps available context when both specification and native capture are absent.
60
63
  - `tests/integration.test.ts` — “real Pi context edits remain canonical through tools, tree selection and reload” proves native user-content replacement, assistant/custom-message omission and live tool-result replacement reach provider input without resurrecting raw history. Selecting an unedited branch restores its native view without overwriting accepted semantic files; selecting the edited branch and reloading preserves its edits and independent memory. Raw JSONL stays append-only.
61
64
  - `tests/integration.test.ts` — “real Pi structured prompt composes with context hooks” covers active, passive, disabled and explicit foreign-force modes. Conversation hooks exclude systems; full-system hooks receive them; companion before-run and per-request sections plus actual tool declarations survive. User specifications remain outside system authority. Stop/Start on subsequent user requests removes/reinstates the owned section once.
62
65
  - `tests/integration.test.ts` — “real Pi preserves native run identity across mode toggles” covers initially enabled, mid-tool Start and repeated Start/Stop with passive bootstrap on/off. Actual provider inputs retain original user/read evidence but not the preceding disabled request; persisted Stop markers name the observed native user, and fixture reload preserves trajectory, frozen state and raw trace prefix. `tests/compaction.test.ts` — “native session boundaries invalidate observed run capture without projection reacquisition” pairs session-start/tree resets with an admitted unchanged-run control. Run identity remains native rather than guessed; an independent optional Stop-marker flag preserves uncompiled bootstrap context without changing the captured anchor.
@@ -141,6 +141,16 @@ Fatal process termination can leave an incomplete canonical file cohort. Before
141
141
 
142
142
  Independently valid shared streams may require a fresh composed origin without inventing cross-writer history. Authored patches use that current basis; raw precomputed replay still refuses an advanced target instead of applying stale normalized changes. Rollback restores only bytes still matching that publisher's output and preserves detected external changes. See [performance evidence](performance.md) for measured contention and the [acceptance map](temporal-acceptance.md) for the tested boundaries; the [backlog](../BACKLOG.md) owns open implementation work.
143
143
 
144
+ ## Lazy navigation and historical reading
145
+
146
+ Lazy bodies require explicit reads. Automatic state and recent-transition projections omit them, including lazy deletions; bounded `lazy_navigation` can still show the current layer's presence, path and key types. Explicit `session.lazy.releasePlan` reads the current value, while `session[3].lazy.releasePlan` reads the exact older value only if that causal boundary remains available. Filtering automatic visibility does not renumber history or erase already communicated native/user/tool/response text.
147
+
148
+ A missing path or runtime hint does not by itself require historical search. If the old value is unnecessary, continue without searching. If it can help the current task, the agent may choose a targeted historical read without separate user permission. Treat any found value as historical evidence, not automatically as current memory; do not restore deleted data without an independent reason. Do not scan all offsets or use repair inference or automatic hydration.
149
+
150
+ A dangling-reference hint accompanies only an unresolved single value read with verified current reference sources. Its `paths` are the owners of those references, not verified new locations of the requested data. It does not prove that the target existed, remains retained or was moved. The bounded lookup searches current state only and emits no lazy bodies. A proven stale reference may be repaired within touched work without resurrecting its target. Without a match, ordinary missing-path errors remain; keys, patch and batch projections retain their existing contracts.
151
+
152
+ Artifact freshness/invalidations and optional Skill acquisition hints keep their exact source/scope targets. Hints expose possibilities and diagnostic evidence; the current task determines whether action is necessary.
153
+
144
154
  ## Memory and source acquisition
145
155
 
146
156
  The agent should use sufficient materialized knowledge before rereading files. Read for a concrete gap, exact-source/edit operation, evidenced invalidation, contradiction/failure, explicit request, or bounded maintenance—not simply because a new session began.