@llblab/pi-kit 0.15.0 → 0.16.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 (106) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +2 -2
  3. package/node_modules/@llblab/pi-state-flow/AGENTS.md +9 -9
  4. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +0 -13
  5. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +15 -0
  6. package/node_modules/@llblab/pi-state-flow/README.md +7 -6
  7. package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +5 -2
  8. package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +17 -16
  9. package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +8 -0
  10. package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +24 -0
  11. package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +4 -3
  12. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +1 -0
  13. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +8 -3
  14. package/node_modules/@llblab/pi-state-flow/dist/lib/episode.d.ts +2 -0
  15. package/node_modules/@llblab/pi-state-flow/dist/lib/episode.js +4 -0
  16. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.d.ts +5 -0
  17. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +93 -35
  18. package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +20 -23
  19. package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +8 -4
  20. package/node_modules/@llblab/pi-state-flow/dist/lib/json.js +29 -3
  21. package/node_modules/@llblab/pi-state-flow/dist/lib/migration.js +34 -18
  22. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +14 -16
  23. package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +27 -1
  24. package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +182 -10
  25. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +2 -0
  26. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +34 -5
  27. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +5 -5
  28. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +20 -24
  29. package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +6 -3
  30. package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +5 -3
  31. package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +1 -1
  32. package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +2 -0
  33. package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +55 -20
  34. package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.d.ts +8 -6
  35. package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +10 -3
  36. package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +7 -3
  37. package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
  38. package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +11 -3
  39. package/node_modules/@llblab/pi-state-flow/docs/README.md +1 -0
  40. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +5 -3
  41. package/node_modules/@llblab/pi-state-flow/docs/filesystem-recovery.md +3 -2
  42. package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +504 -0
  43. package/node_modules/@llblab/pi-state-flow/docs/usage.md +11 -8
  44. package/node_modules/@llblab/pi-state-flow/lib/config.ts +18 -15
  45. package/node_modules/@llblab/pi-state-flow/lib/context.ts +24 -0
  46. package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +4 -3
  47. package/node_modules/@llblab/pi-state-flow/lib/durable.ts +8 -4
  48. package/node_modules/@llblab/pi-state-flow/lib/episode.ts +5 -0
  49. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +86 -34
  50. package/node_modules/@llblab/pi-state-flow/lib/git.ts +23 -22
  51. package/node_modules/@llblab/pi-state-flow/lib/history.ts +8 -4
  52. package/node_modules/@llblab/pi-state-flow/lib/json.ts +31 -3
  53. package/node_modules/@llblab/pi-state-flow/lib/migration.ts +33 -16
  54. package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +14 -16
  55. package/node_modules/@llblab/pi-state-flow/lib/query.ts +173 -9
  56. package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +32 -4
  57. package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +22 -25
  58. package/node_modules/@llblab/pi-state-flow/lib/state.ts +11 -6
  59. package/node_modules/@llblab/pi-state-flow/lib/status.ts +1 -1
  60. package/node_modules/@llblab/pi-state-flow/lib/storage.ts +46 -18
  61. package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +19 -6
  62. package/node_modules/@llblab/pi-state-flow/lib/transition.ts +7 -3
  63. package/node_modules/@llblab/pi-state-flow/package.json +1 -1
  64. package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +11 -3
  65. package/node_modules/@llblab/pi-telegram/AGENTS.md +1 -1
  66. package/node_modules/@llblab/pi-telegram/BACKLOG.md +1 -0
  67. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +7 -0
  68. package/node_modules/@llblab/pi-telegram/README.md +1 -0
  69. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +2 -1
  70. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +2 -1
  71. package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +134 -2
  72. package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +297 -16
  73. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.d.ts +2 -0
  74. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.js +11 -0
  75. package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +68 -8
  76. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.d.ts +4 -3
  77. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.js +17 -13
  78. package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +2 -1
  79. package/node_modules/@llblab/pi-telegram/dist/lib/pi.js +1 -0
  80. package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +1 -0
  81. package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +55 -5
  82. package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.d.ts +23 -0
  83. package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.js +20 -0
  84. package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +18 -0
  85. package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +152 -6
  86. package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +1 -0
  87. package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +5 -3
  88. package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
  89. package/node_modules/@llblab/pi-telegram/docs/architecture.md +3 -3
  90. package/node_modules/@llblab/pi-telegram/docs/callback-namespaces.md +1 -1
  91. package/node_modules/@llblab/pi-telegram/docs/public-api.md +4 -3
  92. package/node_modules/@llblab/pi-telegram/docs/sections.md +2 -2
  93. package/node_modules/@llblab/pi-telegram/docs/ui-style.md +2 -1
  94. package/node_modules/@llblab/pi-telegram/docs/updates.md +1 -1
  95. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +3 -0
  96. package/node_modules/@llblab/pi-telegram/lib/commands.ts +466 -21
  97. package/node_modules/@llblab/pi-telegram/lib/delivery.ts +15 -0
  98. package/node_modules/@llblab/pi-telegram/lib/extension.ts +77 -9
  99. package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +25 -10
  100. package/node_modules/@llblab/pi-telegram/lib/pi.ts +3 -0
  101. package/node_modules/@llblab/pi-telegram/lib/routing.ts +84 -17
  102. package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +30 -0
  103. package/node_modules/@llblab/pi-telegram/lib/threads.ts +199 -6
  104. package/node_modules/@llblab/pi-telegram/lib/updates.ts +5 -2
  105. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  106. package/package.json +3 -3
@@ -2,7 +2,7 @@
2
2
  // Excludes: temporal algebra, Pi lifecycle, Git objects/remotes, and backend fallback policy.
3
3
  import { spawnSync } from "node:child_process";
4
4
  import { createHash } from "node:crypto";
5
- import { closeSync, lstatSync, mkdirSync, openSync, rmSync, writeFileSync } from "node:fs";
5
+ import { closeSync, lstatSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from "node:fs";
6
6
  import { dirname, relative, resolve } from "node:path";
7
7
  import { assertOwnedFileUpdates, captureTemporalFileBases, parseScopeProvenance, parseScopeStream, restoreDurableFileBases, serializeScopeMetadata, sessionRuntimePaths, temporalScopePaths, temporalStateFileUpdates, writeOwnedFileUpdates, } from "./durable.js";
8
8
  import { parseArtifactProvenanceRegistry } from "./artifact.js";
@@ -36,18 +36,52 @@ export function initializeFileStore(root) {
36
36
  assertStorageDirectory(root);
37
37
  mkdirSync(resolve(root), { recursive: true });
38
38
  }
39
+ const PUBLICATION_LOCK_WAIT_MS = 2_000;
40
+ const PUBLICATION_LOCK_POLL_MS = 25;
41
+ const publicationLockWait = new Int32Array(new SharedArrayBuffer(4));
42
+ function liveForeignLockOwner(path) {
43
+ let owner;
44
+ try {
45
+ owner = readFileSync(path, "utf8").trim();
46
+ }
47
+ catch {
48
+ return true;
49
+ }
50
+ if (owner.length === 0)
51
+ return true;
52
+ if (!/^[1-9]\d*$/.test(owner))
53
+ return false;
54
+ const pid = Number(owner);
55
+ if (!Number.isSafeInteger(pid) || pid === process.pid)
56
+ return false;
57
+ try {
58
+ process.kill(pid, 0);
59
+ return true;
60
+ }
61
+ catch (error) {
62
+ return error.code === "EPERM";
63
+ }
64
+ }
65
+ /** Wait only for a cooperating live owner; interrupted or malformed locks remain explicit recovery errors. */
66
+ export function acquirePublicationLock(path, unavailable) {
67
+ const deadline = Date.now() + PUBLICATION_LOCK_WAIT_MS;
68
+ while (true) {
69
+ try {
70
+ return openSync(path, "wx", 0o600);
71
+ }
72
+ catch (error) {
73
+ if (error.code !== "EEXIST" || !liveForeignLockOwner(path) || Date.now() >= deadline)
74
+ throw unavailable(error);
75
+ Atomics.wait(publicationLockWait, 0, 0, Math.min(PUBLICATION_LOCK_POLL_MS, deadline - Date.now()));
76
+ }
77
+ }
78
+ }
39
79
  /** Git writers also acquire this lock before their common-Git-directory lock. */
40
80
  export function withStoragePublicationLock(repositoryRoot, action) {
41
81
  const root = resolve(repositoryRoot);
42
82
  assertStorageDirectory(root);
43
83
  const path = resolve(root, ".state-flow-publication.lock");
44
- let descriptor;
45
- try {
46
- descriptor = openSync(path, "wx", 0o600);
47
- }
48
- catch (error) {
49
- throw new RevisionUnavailableError(`State Flow publication lock is unavailable at ${path}; reconcile the active or interrupted publisher before retrying`, { cause: error });
50
- }
84
+ const descriptor = acquirePublicationLock(path, (cause) => new RevisionUnavailableError(`State Flow publication lock is unavailable at ${path}; reconcile the active or interrupted publisher before retrying`, { cause }));
51
85
  try {
52
86
  writeFileSync(descriptor, `${process.pid}\n`);
53
87
  return action(root);
@@ -83,9 +117,9 @@ export function planTemporalPublication(cwd, sessionId, view, scopes, current, r
83
117
  changedScopes.push(scope);
84
118
  }
85
119
  const provenanceUpdates = [];
86
- // Shared metadata owns provenance, temporal boundaries, and CWD identity beside semantic files.
120
+ // Every scope metadata file owns provenance and temporal boundaries beside semantic files.
87
121
  if (!runtimeOnly) {
88
- for (const scope of ["global", "cwd"]) {
122
+ for (const scope of SCOPES) {
89
123
  const paths = temporalScopePaths(cwd, sessionId, scope, root, sessionKey);
90
124
  const registry = provenance?.[scope] ?? parseScopeProvenance(files.get(paths.meta).content, paths.meta);
91
125
  const currentFile = files.get(paths.meta);
@@ -98,22 +132,23 @@ export function planTemporalPublication(cwd, sessionId, view, scopes, current, r
98
132
  }
99
133
  }
100
134
  const runtimePaths = sessionRuntimePaths(cwd, sessionId, root, sessionKey);
101
- const previousRuntime = parseSessionRuntime(files.get(runtimePaths.config).content, files.get(runtimePaths.meta).content, cwd, sessionId);
135
+ const previousRuntime = parseSessionRuntime(files.get(runtimePaths.config).content, files.get(runtimePaths.runtime).content, cwd, sessionId, files.get(runtimePaths.meta).content);
102
136
  if (previousRuntime !== undefined && changedScopes.length > 0 && runtime === undefined)
103
137
  throw new Error("Temporal semantic publication requires its session runtime cohort");
104
138
  const runtimeUpdates = [];
105
139
  if (runtime !== undefined) {
106
- const sources = serializeSessionRuntime(runtime, cwd, sessionId, runtimeOnly ? undefined : view.scopes.session, files.get(runtimePaths.meta).content);
140
+ const sources = serializeSessionRuntime(runtime, cwd, sessionId);
107
141
  if (!sameJson(runtime.meta.lineage, view.lineage))
108
142
  throw new Error("Runtime lineage does not match the temporal cohort");
109
- if (files.get(runtimePaths.config).content !== sources.config || files.get(runtimePaths.meta).content !== sources.meta) {
110
- runtimeUpdates.push({ path: runtimePaths.config, content: sources.config }, { path: runtimePaths.meta, content: sources.meta });
143
+ if (files.get(runtimePaths.config).content !== sources.config || files.get(runtimePaths.runtime).content !== sources.runtime) {
144
+ runtimeUpdates.push({ path: runtimePaths.config, content: sources.config }, { path: runtimePaths.runtime, content: sources.runtime });
111
145
  }
112
- }
113
- else if (!runtimeOnly && changedScopes.includes("session")) {
114
- const content = serializeScopeMetadata(undefined, view.scopes.session, "session", undefined, files.get(runtimePaths.meta).content);
115
- if (files.get(runtimePaths.meta).content !== content)
146
+ if (files.get(runtimePaths.runtime).identity === "missing" && files.get(runtimePaths.meta).content !== undefined
147
+ && previousRuntime !== undefined && !provenanceUpdates.some(({ path }) => path === runtimePaths.meta)) {
148
+ const registry = provenance?.session ?? parseArtifactProvenanceRegistry(previousRuntime.meta.artifacts, "State Flow session artifact provenance");
149
+ const content = serializeScopeMetadata(registry, view.scopes.session, "session", undefined, files.get(runtimePaths.meta).content);
116
150
  runtimeUpdates.push({ path: runtimePaths.meta, content });
151
+ }
117
152
  }
118
153
  const changedPaths = new Set(changedScopes.flatMap((scope) => {
119
154
  const paths = temporalScopePaths(cwd, sessionId, scope, root, sessionKey);
@@ -150,7 +185,7 @@ function decodeFileCohort(cwd, sessionId, root, base, sessionKey = sessionId) {
150
185
  scopes[scope] = stream;
151
186
  }
152
187
  const paths = sessionRuntimePaths(cwd, sessionId, root, sessionKey);
153
- const runtime = parseSessionRuntime(files.get(paths.config), files.get(paths.meta), cwd, sessionId);
188
+ const runtime = parseSessionRuntime(files.get(paths.config), files.get(paths.runtime), cwd, sessionId, files.get(paths.meta));
154
189
  if (!runtime || runtime.meta.publication !== "files")
155
190
  throw new Error("File-only recovery requires file publication provenance, not a Git self reference");
156
191
  const view = { scopes, lineage: runtime.meta.lineage };
@@ -158,7 +193,7 @@ function decodeFileCohort(cwd, sessionId, root, base, sessionKey = sessionId) {
158
193
  const provenance = {
159
194
  global: parseScopeProvenance(files.get(temporalScopePaths(cwd, sessionId, "global", root, sessionKey).meta), temporalScopePaths(cwd, sessionId, "global", root, sessionKey).meta),
160
195
  cwd: parseScopeProvenance(files.get(temporalScopePaths(cwd, sessionId, "cwd", root, sessionKey).meta), temporalScopePaths(cwd, sessionId, "cwd", root, sessionKey).meta),
161
- session: parseArtifactProvenanceRegistry(runtime.meta.artifacts, "State Flow session artifact provenance"),
196
+ session: parseScopeProvenance(files.get(paths.meta), paths.meta),
162
197
  };
163
198
  return { runtime, view, provenance };
164
199
  }
@@ -13,21 +13,23 @@ export interface StateFlowTelegramState {
13
13
  contract: Record<string, unknown>;
14
14
  working: Record<string, unknown>;
15
15
  response: string;
16
+ lazy?: unknown;
16
17
  }
18
+ export type StateFlowTelegramRichText = string | StateFlowTelegramRichText[] | {
19
+ type: "bold" | "code";
20
+ text: StateFlowTelegramRichText;
21
+ };
17
22
  export type StateFlowTelegramRichBlock = {
18
23
  type: "heading";
19
- text: string;
24
+ text: StateFlowTelegramRichText;
20
25
  size: 3;
21
26
  } | {
22
27
  type: "pre";
23
- text: string;
28
+ text: StateFlowTelegramRichText;
24
29
  language?: string;
25
30
  } | {
26
31
  type: "details";
27
- summary: string | {
28
- type: "bold" | "code";
29
- text: string;
30
- };
32
+ summary: StateFlowTelegramRichText;
31
33
  blocks: StateFlowTelegramRichBlock[];
32
34
  is_open?: true;
33
35
  };
@@ -77,6 +77,9 @@ function renderStateFlowTelegramField(value) {
77
77
  const length = Math.floor((low + high) / 2);
78
78
  const candidate = JSON.stringify({
79
79
  truncated: true,
80
+ ...(value !== null && typeof value === "object" && !Array.isArray(value)
81
+ ? { keys: Object.keys(value) }
82
+ : {}),
80
83
  preview: json.slice(0, length),
81
84
  omittedChars: json.length - length,
82
85
  }, null, 2);
@@ -91,14 +94,18 @@ function renderStateFlowTelegramField(value) {
91
94
  return rendered;
92
95
  }
93
96
  export function renderStateFlowRichState(scope, step, state) {
94
- const fields = ["artifacts", "contract", "working", "response"];
97
+ const fields = ["artifacts", "contract", "working", "response", "lazy"];
95
98
  return {
96
99
  blocks: [
97
- { type: "heading", text: `${STATE_FLOW_SCOPE_LABELS[scope]}: \`#${step}\``, size: 3 },
100
+ {
101
+ type: "heading",
102
+ text: [`${STATE_FLOW_SCOPE_LABELS[scope]}: `, { type: "code", text: `#${step}` }],
103
+ size: 3,
104
+ },
98
105
  ...fields.map((field) => ({
99
106
  type: "details",
100
107
  summary: { type: "code", text: field },
101
- blocks: [{ type: "pre", language: "json", text: renderStateFlowTelegramField(state[field]) }],
108
+ blocks: [{ type: "pre", language: "json", text: renderStateFlowTelegramField(state[field] ?? {}) }],
102
109
  })),
103
110
  ],
104
111
  skip_entity_detection: true,
@@ -3,7 +3,7 @@ import { createAcceptedTransition } from "./history.js";
3
3
  import { applyPatch, containsNull, hashJson, isObject, validatePatch } from "./json.js";
4
4
  import { hasCompiledSkillArtifact, SKILL_ARTIFACT_COMPILER } from "./skills.js";
5
5
  const SCOPES = new Set(["global", "cwd", "session"]);
6
- const PATCH_KEYS = new Set(["artifacts", "contract", "working"]);
6
+ const PATCH_KEYS = new Set(["artifacts", "contract", "working", "lazy"]);
7
7
  function compileReadArtifacts(nextState, patch, successfulArtifactReads, provenance) {
8
8
  for (const read of successfulArtifactReads) {
9
9
  const output = patch.artifacts[read.path];
@@ -77,14 +77,17 @@ function validateScopePatch(scope, patch) {
77
77
  validatePatch(patch);
78
78
  for (const key of Object.keys(patch)) {
79
79
  if (!PATCH_KEYS.has(key)) {
80
- throw new Error(`Scoped State Flow patches cannot modify ${key}; only artifacts, contract, and working are model-owned`);
80
+ throw new Error(`Scoped State Flow patches cannot modify ${key}; only artifacts, contract, working, and lazy are model-owned`);
81
81
  }
82
82
  }
83
- for (const key of PATCH_KEYS) {
83
+ for (const key of ["artifacts", "contract", "working"]) {
84
84
  if (Object.hasOwn(patch, key) && !isObject(patch[key])) {
85
85
  throw new Error(`Scoped State Flow patch field ${key} must be a JSON object`);
86
86
  }
87
87
  }
88
+ if (Object.hasOwn(patch, "lazy") && patch.lazy === null) {
89
+ throw new Error("Scoped State Flow patch field lazy cannot be null");
90
+ }
88
91
  if (isObject(patch.artifacts))
89
92
  validateModelArtifactPatch(patch.artifacts);
90
93
  }
@@ -94,6 +97,7 @@ function completePatch(patch, response) {
94
97
  contract: patch.contract ?? {},
95
98
  working: patch.working ?? {},
96
99
  response,
100
+ ...(Object.hasOwn(patch, "lazy") ? { lazy: structuredClone(patch.lazy) } : {}),
97
101
  };
98
102
  }
99
103
  /** Stage all scope updates against one immutable basis before any state is published. */
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-state-flow",
3
- "version": "0.13.3",
3
+ "version": "0.14.0",
4
4
  "private": false,
5
5
  "description": "Incremental scoped state/context/memory compiler for Pi, inspired by SKILL.state",
6
6
  "keywords": [
@@ -46,6 +46,8 @@ Separate a binding requirement from the method currently proposed to satisfy it.
46
46
 
47
47
  When it affects continuation, retain what was proposed, accepted, rejected, corrected, explained, or left unresolved, and what the next response or action must address. Preserve enough referents for pending follow-ups to make sense.
48
48
 
49
+ Treat every completed work slice as a possible restart boundary. Its checkpoint should let a fresh executor recover the achieved outcome, surviving evidence, active commitments, decision-relevant uncertainty, and exact continuation without replaying the prior reasoning trajectory. Optimize decomposition for resumability as well as functional completion; if the next safe action depends on transient context that will disappear, the slice is not yet at a sufficient boundary.
50
+
49
51
  Keep consequences, not a transcript or a personality dossier. Do not invent shared history or claim subjective continuity. A fresh run should not unnecessarily reopen a settled exchange or treat an unanswered proposal as approved.
50
52
 
51
53
  ### Preserve learning at its demonstrated boundary
@@ -70,11 +72,17 @@ A locator supports later retrieval; it does not replace content needed for the n
70
72
 
71
73
  Do not rerun the underlying project merely to curate its memory. Leave an exact unresolved check when verification falls outside the requested boundary.
72
74
 
73
- ### Compact without flattening
75
+ ### Preserve priority and keep Lazy shallow
76
+
77
+ Treat array order in Lazy as semantic priority: earlier entries are higher priority. Preserve that order deliberately; do not reorder entries for aesthetics, incidental grouping, or normalization.
78
+
79
+ Minimize Lazy nesting, especially for top-level collections. Keep a top-level collection as a direct array when its members are the domain values. Represent a standalone item directly, normally as a string; use an object only when that item genuinely owns structured or nested fields. Do not add `items`, `owner`, `source`, or similar wrapper objects merely to describe the collection, and do not introduce nested arrays unless the domain itself requires a matrix or grouped sequence.
80
+
81
+ ### Compact without flattening meaning
74
82
 
75
83
  Merge redundant fragments and remove obsolete scaffolding, repeated argumentation, and routine progress. Do not rewrite unchanged state merely to normalize wording.
76
84
 
77
- Do not erase a meaningful correction, uncertainty, commitment, negative result, or continuation dependency to make state shorter. Do not retain the previous chain of reasoning solely to steer the next run toward the same method.
85
+ Do not erase a meaningful correction, uncertainty, commitment, negative result, priority order, or continuation dependency to make state shorter. Do not retain the previous chain of reasoning solely to steer the next run toward the same method.
78
86
 
79
87
  ### Reconcile phase boundaries
80
88
 
@@ -90,7 +98,7 @@ Effective state does not prove which scope owns a value. When ownership matters
90
98
 
91
99
  Before writing, review the proposed changes once within the requested boundary:
92
100
 
93
- - Would a fresh executor know what must still hold, what changed, what remains unresolved, and how to continue?
101
+ - Would a fresh executor know what must still hold, what changed, what remains unresolved, and the exact next action without replaying the prior cognitive trajectory?
94
102
  - Could an omission cause a known failed attempt, an unnecessary repeated explanation, or loss of an active commitment?
95
103
  - Could a retained claim impose an unapproved method, overgeneralize a result, or hide a live alternative?
96
104
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  - [Usage and recovery](usage.md): Configuration, new/resumed sessions, Start/Stop, diagnostics, privacy, and storage recovery.
4
4
  - [Architecture](architecture.md): Semantic state, temporal algebra, Pi lifecycle, storage, publication, artifacts, and embedding contracts.
5
+ - [Lazy state](lazy-state.md): Implemented ordinary-JSON lazy planes, an effective-by-default `lazy` read path, pure state/patch snapshots, narrow structural `meta` + `keys`, and recursive indexed array patches.
5
6
  - [Filesystem recovery](filesystem-recovery.md): Cohort-wide absence, partial-presence, malformed-evidence, repair-authority, and transaction rules.
6
7
  - [Temporal acceptance](temporal-acceptance.md): The twenty required temporal properties and their executable witnesses.
7
8
  - [SDK compatibility](compatibility.md): Tested dependency stacks, public lifecycle seams, isolated validation, and host limits.
@@ -100,6 +100,7 @@ The default store is `<agentDir>/state-flow`, independent from Markdown discover
100
100
  Owned paths are:
101
101
 
102
102
  ```text
103
+ config.json
103
104
  checkpoint.json
104
105
  patches.jsonl
105
106
  meta.json
@@ -108,13 +109,14 @@ meta.json
108
109
  <cwd-key>/meta.json
109
110
  <cwd-key>/<session-key>/checkpoint.json
110
111
  <cwd-key>/<session-key>/patches.jsonl
111
- <cwd-key>/<session-key>/config.json
112
112
  <cwd-key>/<session-key>/meta.json
113
+ <cwd-key>/<session-key>/config.json
114
+ <cwd-key>/<session-key>/runtime.json
113
115
  ```
114
116
 
115
117
  CWD and session keys mirror Pi's native encoding. The Pi UUID remains authoritative; readable directory keys never replace identity validation.
116
118
 
117
- `checkpoint.json` is only the canonical materialized semantic state, and each nonblank `patches.jsonl` line is only one semantic patch. Scope `meta.json` owns the checkpoint/tail boundaries, CWD owner identity, and runtime artifact provenance for its scope; the session file additionally owns lineage, counters, session identity, publication provenance and remote-publication policy. Metadata writers replace only their owned leaves and preserve JSON-safe unknown siblings. Pi checkpoints retain only an exact Git revision, an exact `file:<hash>` cohort reference, or a proven ordinary-disabled marker.
119
+ Root `config.json` is the read-only operator configuration shared by every session in the repository; it never participates in semantic overlay. `checkpoint.json` is only the canonical materialized semantic state, and each nonblank `patches.jsonl` line is only one semantic patch. Every scope's `meta.json` symmetrically owns checkpoint/tail boundaries and artifact provenance, with CWD ownership added where applicable. Session `config.json` owns behavior; session `runtime.json` asymmetrically owns lineage, counters, session identity, publication provenance, remote-publication policy, and the full specification only while a run is unfinished. The predecessor combined session `meta.json` remains readable as migration input and is separated by the next normal CAS publication. Metadata writers replace only their owned leaves and preserve JSON-safe unknown siblings. Pi checkpoints retain only an exact Git revision, an exact `file:<hash>` cohort reference, or a proven ordinary-disabled marker.
118
120
 
119
121
  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.
120
122
 
@@ -122,7 +124,7 @@ All owned writes use same-directory atomic replacement, regular-file and symlink
122
124
 
123
125
  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.
124
126
 
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 only when legacy snapshots and complete predecessor checkpoint/tail envelopes 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.
127
+ 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 only when legacy snapshots and complete predecessor checkpoint/tail envelopes 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.json`/`runtime.json` without rewriting live shared checkpoints, tails, or provenance.
126
128
 
127
129
  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
130
 
@@ -8,7 +8,8 @@ State Flow classifies absence separately from partial or malformed evidence. Rec
8
8
  | CWD `checkpoint.json` + `patches.jsonl` | State Flow; authoritative shared semantics with CWD identity | Same as global; selected values are not resurrected | Same as global; owner mismatch also fails closed | Normal CAS publication may materialize the complete empty pair |
9
9
  | Session `checkpoint.json` + `patches.jsonl` | State Flow; authoritative private semantics | Fresh lifecycle origin may initialize; an existing selected session requires exact retained authority | Partial or malformed pair fails closed | Fresh initialization or exact selected-revision recovery only |
10
10
  | Global/CWD `meta.json` | State Flow; temporal boundaries, CWD identity, and artifact provenance | Missing metadata removes temporal authority and fails closed; only an omitted `artifacts` leaf degrades provenance to `{}` | Malformed metadata or semantic/boundary mismatch fails closed | Normal CAS publication from a complete proven cohort |
11
- | Session `config.json` + `meta.json` | State Flow; authoritative runtime identity, lineage, temporal boundaries and session provenance | Fresh origin may initialize; selected sessions recover only from exact authority | Partial, malformed, contradictory identity, lineage, boundary, or revision fails closed | Canonical runtime publication from proven lifecycle/selected state |
11
+ | Session `meta.json` | State Flow; session temporal boundaries and artifact provenance | Fresh origin may initialize; selected sessions recover only from exact scope authority | Partial, malformed, or contradictory boundary evidence fails closed | Canonical scope publication from the selected temporal state |
12
+ | Session `config.json` + `runtime.json` | State Flow; behavior plus authoritative runtime identity, lineage, counters, and publication recovery | Fresh origin may initialize; selected sessions recover only from exact authority | Partial, malformed, contradictory identity, lineage, or revision fails closed | Canonical runtime publication from proven lifecycle/selected state; predecessor combined `meta.json` is migration input only |
12
13
  | Unsupported `state.json`, hashed layouts, or semantic Pi checkpoints | No current authority | Ignored | Presence never becomes recovery or migration input | Operator-managed removal or external conversion only |
13
14
  | Selected Git revision blobs/modes | Git object database; immutable cold authority | A required blob/revision is unavailable | Mode, owner, hash, or cohort contradiction fails closed | Read-only reconstruction; never checkout/reset the live worktree |
14
15
  | File-only revision pointer/cohort | State Flow/Pi entry; current exact authority only | No cold history can be invented | Any identity mismatch or incomplete retained cohort fails closed | Exact current cohort only; normal locked publication writes repairs |
@@ -16,7 +17,7 @@ State Flow classifies absence separately from partial or malformed evidence. Rec
16
17
  | Worker lease | State Flow; operational ownership | Unclaimed | Malformed/foreign live evidence is preserved; live owner excludes peers | Existing dead-process reclamation protocol only |
17
18
  | Publication locks | State Flow; mutual exclusion | Unlocked | Present lock excludes publishers, including interrupted owners | Current owner releases; no opportunistic deletion |
18
19
  | Temporary queue files / isolated Git index | Creating State Flow operation; transient | No pending preparation | Unknown surviving files grant no authority | Creating operation cleans its own temporary path; fatal residue is not adopted |
19
- | Extension `state-flow.json` | Operator; optional external configuration | Built-in defaults | Present unreadable/malformed/unknown settings fail extension configuration | State Flow never creates or rewrites it |
20
+ | Repository-root `config.json` | Operator; optional global configuration, versioned with the store | Built-in defaults | Present unreadable/malformed/unknown settings fail extension configuration | State Flow never creates or rewrites it; ordinary repository publication preserves and versions operator edits |
20
21
  | Knowledge root and Markdown | External Knowledge owner | Freshness unavailable; durable semantic state remains | Unsafe paths or malformed/unreadable sources disable acquisition locally | Never create; semantic removal only under existing confirmed ownership rules |
21
22
  | Skill and external artifact sources | External package/user owner | Freshness unavailable unless ownership proves removal semantics | Unsafe/non-regular/unreadable sources disable acquisition locally | Never create or fabricate source/provenance |
22
23
  | Pi State Flow entries and diagnostics | Pi session log / State Flow entry owner | Missing optional diagnostics provide no evidence; missing required selected pointer blocks that restore | Malformed or contradictory owner/version/pointer fails the dependent restore | Append through Pi entry APIs only; no standalone diagnostics file exists |