@llblab/pi-kit 0.37.1 → 0.38.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +4 -4
- package/node_modules/@llblab/pi-claude-usage/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-claude-usage/CHANGELOG.md +4 -0
- package/node_modules/@llblab/pi-claude-usage/README.md +3 -3
- package/node_modules/@llblab/pi-claude-usage/lib/status.ts +1 -1
- package/node_modules/@llblab/pi-claude-usage/lib/usage-store.ts +2 -2
- package/node_modules/@llblab/pi-claude-usage/package.json +1 -1
- package/node_modules/@llblab/pi-clean-room/CHANGELOG.md +5 -0
- package/node_modules/@llblab/pi-clean-room/index.ts +39 -9
- package/node_modules/@llblab/pi-clean-room/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +7 -3
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +679 -2
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +17 -0
- package/node_modules/@llblab/pi-state-flow/README.md +30 -14
- package/node_modules/@llblab/pi-state-flow/api/modules.ts +11 -0
- package/node_modules/@llblab/pi-state-flow/dist/api/modules.d.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/api/modules.js +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.d.ts +7 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.js +77 -36
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.d.ts +2 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.js +26 -27
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +13 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +27 -21
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +144 -46
- package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +14 -18
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +6 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +60 -50
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +1049 -310
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.d.ts +4 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +15 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/json.d.ts +2 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/json.js +2 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/memory.d.ts +3 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/memory.js +7 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.d.ts +29 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.js +186 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/model-interface.d.ts +17 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/model-interface.js +158 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/module-registry.d.ts +21 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/module-registry.js +108 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/operation.js +3 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/ownership.d.ts +4 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/ownership.js +5 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/planes.d.ts +29 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/planes.js +84 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +6 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +33 -16
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +21 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +53 -13
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +5 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.d.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.js +10 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +84 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +570 -20
- package/node_modules/@llblab/pi-state-flow/dist/lib/selection.d.ts +10 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/selection.js +32 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/semantic-module.d.ts +79 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/semantic-module.js +187 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.d.ts +98 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.js +398 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.d.ts +7 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.js +55 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +67 -14
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +129 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +26 -14
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +61 -19
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +14 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +59 -22
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +111 -10
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +728 -11
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.d.ts +10 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +50 -14
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +40 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +247 -43
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +41 -8
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +132 -39
- package/node_modules/@llblab/pi-state-flow/dist/package.json +6 -1
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +28 -16
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +12 -8
- package/node_modules/@llblab/pi-state-flow/docs/README.md +9 -0
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +76 -43
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +9 -1
- package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +6 -0
- package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +23 -19
- package/node_modules/@llblab/pi-state-flow/docs/protocol-composition-decision.md +66 -0
- package/node_modules/@llblab/pi-state-flow/docs/public-module-contract.md +73 -0
- package/node_modules/@llblab/pi-state-flow/docs/scope-selection-contract.md +814 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-compatibility-decision.md +212 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-extensibility-decision.md +75 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-handoff-contract.md +174 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-memory-evaluation.md +166 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-module-contract.md +84 -0
- package/node_modules/@llblab/pi-state-flow/docs/single-scope-interface-contract.md +201 -0
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +22 -1
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +32 -18
- package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +68 -31
- package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +21 -22
- package/node_modules/@llblab/pi-state-flow/lib/config.ts +14 -1
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +142 -46
- package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +14 -17
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +53 -44
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +897 -297
- package/node_modules/@llblab/pi-state-flow/lib/history.ts +16 -7
- package/node_modules/@llblab/pi-state-flow/lib/json.ts +2 -1
- package/node_modules/@llblab/pi-state-flow/lib/memory.ts +10 -9
- package/node_modules/@llblab/pi-state-flow/lib/migration.ts +189 -0
- package/node_modules/@llblab/pi-state-flow/lib/model-interface.ts +136 -0
- package/node_modules/@llblab/pi-state-flow/lib/module-registry.ts +119 -0
- package/node_modules/@llblab/pi-state-flow/lib/operation.ts +3 -2
- package/node_modules/@llblab/pi-state-flow/lib/ownership.ts +6 -6
- package/node_modules/@llblab/pi-state-flow/lib/planes.ts +98 -0
- package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +37 -16
- package/node_modules/@llblab/pi-state-flow/lib/query.ts +59 -18
- package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +5 -2
- package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +10 -1
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +574 -21
- package/node_modules/@llblab/pi-state-flow/lib/selection.ts +39 -0
- package/node_modules/@llblab/pi-state-flow/lib/semantic-module.ts +239 -0
- package/node_modules/@llblab/pi-state-flow/lib/session.ts +387 -3
- package/node_modules/@llblab/pi-state-flow/lib/skills.ts +45 -4
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +156 -16
- package/node_modules/@llblab/pi-state-flow/lib/state.ts +69 -28
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +65 -31
- package/node_modules/@llblab/pi-state-flow/lib/storage.ts +770 -18
- package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +58 -20
- package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +267 -50
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +184 -52
- package/node_modules/@llblab/pi-state-flow/package.json +6 -1
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +28 -16
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +12 -8
- package/package.json +4 -4
|
@@ -9,17 +9,22 @@ import {
|
|
|
9
9
|
} from "./artifact.ts";
|
|
10
10
|
import type { SuccessfulArtifactRead } from "./acquisition.ts";
|
|
11
11
|
import { createAcceptedTransition, type AcceptedTransition } from "./history.ts";
|
|
12
|
-
import {
|
|
12
|
+
import { applyOwnedPatch, containsNull, hashJson, isObject, sameJson, validatePatch, type JsonObject, type MissingDeletion } from "./json.ts";
|
|
13
13
|
import { cascadeDeletionPatch, computeIntentCascade, type OwnedPath } from "./ownership.ts";
|
|
14
14
|
import { hasCompiledSkillArtifact, SKILL_ARTIFACT_COMPILER, type SuccessfulSkillRead } from "./skills.ts";
|
|
15
15
|
import type { Snapshot } from "./snapshot.ts";
|
|
16
|
-
import { emptyState } from "./state.ts";
|
|
16
|
+
import { assertScopeStateValues, emptyState, pruneSemanticState } from "./state.ts";
|
|
17
|
+
import { captureScopeSelection, type ScopeSelection } from "./selection.ts";
|
|
18
|
+
import { MODEL_PLANE_NAMES } from "./planes.ts";
|
|
19
|
+
import { validateModuleObject, type RetainedModules, type StateFlowModule } from "./semantic-module.ts";
|
|
20
|
+
import type { JsonValue } from "./json.ts";
|
|
17
21
|
import type {
|
|
18
22
|
AtomicScopePatches,
|
|
19
23
|
SemanticState,
|
|
20
24
|
ScopePatch,
|
|
21
25
|
ScopedPatch,
|
|
22
26
|
ScopedSemanticStates,
|
|
27
|
+
ScopeStateValues,
|
|
23
28
|
SemanticTransition,
|
|
24
29
|
StateDocument,
|
|
25
30
|
StatePatch,
|
|
@@ -27,21 +32,44 @@ import type {
|
|
|
27
32
|
TerminalTransition,
|
|
28
33
|
} from "./state.ts";
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
nextStates:
|
|
35
|
+
interface StagedTransition<States extends ScopeStateValues> {
|
|
36
|
+
nextStates: States;
|
|
32
37
|
/** Authored no-op deletions; presentation evidence only, never replay input. */
|
|
33
38
|
missingDeletions: MissingDeletion[];
|
|
34
|
-
/** Scope-local targets
|
|
35
|
-
cascades:
|
|
36
|
-
stateHashes:
|
|
39
|
+
/** Scope-local targets cleared before other writes; may be recreated, never replay input. */
|
|
40
|
+
cascades: { [Owner in keyof States]: OwnedPath[] };
|
|
41
|
+
stateHashes: { [Owner in keyof States]: string };
|
|
37
42
|
/** Fresh runtime-owned provenance for artifacts compiled in this transition. */
|
|
38
|
-
provenanceUpdates:
|
|
43
|
+
provenanceUpdates: { [Owner in keyof States]: Record<string, ArtifactProvenance> };
|
|
39
44
|
causalBasis: string;
|
|
40
45
|
committed: boolean;
|
|
46
|
+
moduleAdditions?: Partial<Record<StateScope, RetainedModules>>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type StagedScopedTransition = StagedTransition<ScopedSemanticStates> & { selection?: never };
|
|
50
|
+
export type StagedSelectedTransition = StagedTransition<ScopeStateValues> & { selection: ScopeSelection };
|
|
51
|
+
|
|
52
|
+
/** Internal host proposals only; never accepted from model tool arguments. */
|
|
53
|
+
export interface ModuleReplacement {
|
|
54
|
+
scope: StateScope;
|
|
55
|
+
id: string;
|
|
56
|
+
content: JsonObject;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface SelectedSemanticBasis {
|
|
60
|
+
selection: ScopeSelection;
|
|
61
|
+
states: ScopeStateValues;
|
|
62
|
+
modules?: Partial<Record<StateScope, RetainedModules>>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function selectedOwners(basis: SelectedSemanticBasis): ScopeSelection {
|
|
66
|
+
const selection = captureScopeSelection(basis.selection);
|
|
67
|
+
assertScopeStateValues(basis.states, selection);
|
|
68
|
+
return selection;
|
|
41
69
|
}
|
|
42
70
|
|
|
43
71
|
const SCOPES = new Set<StateScope>(["global", "cwd", "session"]);
|
|
44
|
-
const PATCH_KEYS = new Set(
|
|
72
|
+
const PATCH_KEYS = new Set(MODEL_PLANE_NAMES);
|
|
45
73
|
|
|
46
74
|
function compileReadArtifacts(
|
|
47
75
|
nextState: StateDocument,
|
|
@@ -86,9 +114,10 @@ function validateSkillCompilerOutput(scope: StateScope, path: string, output: un
|
|
|
86
114
|
function validateSkillCompilerTargets(
|
|
87
115
|
patches: ReadonlyMap<StateScope, ScopePatch>,
|
|
88
116
|
successfulSkillReads: Iterable<SuccessfulSkillRead>,
|
|
117
|
+
owners: readonly StateScope[],
|
|
89
118
|
): void {
|
|
90
119
|
for (const read of successfulSkillReads) {
|
|
91
|
-
for (const scope of
|
|
120
|
+
for (const scope of owners) {
|
|
92
121
|
if (scope === read.scope) continue;
|
|
93
122
|
const artifacts = patches.get(scope)?.artifacts;
|
|
94
123
|
if (artifacts && Object.hasOwn(artifacts, read.path) && artifacts[read.path] !== null) {
|
|
@@ -136,22 +165,29 @@ function validateMaterializedTransition(nextState: StateDocument, scope: StateSc
|
|
|
136
165
|
throw new Error("Materialized state cannot contain null; use null only as an object-key deletion marker");
|
|
137
166
|
}
|
|
138
167
|
validateArtifactRegistry(nextState.artifacts, `${scope}.artifacts`);
|
|
139
|
-
if (Object.hasOwn(nextState.
|
|
140
|
-
throw new Error("
|
|
168
|
+
if (Object.hasOwn(nextState.strategy, "compiled_skills")) {
|
|
169
|
+
throw new Error("strategy.compiled_skills is retired; Skill compilations belong only in source-addressed artifacts");
|
|
141
170
|
}
|
|
142
171
|
}
|
|
143
172
|
|
|
144
|
-
function validateScopePatch(scope: unknown, patch: unknown): asserts patch is ScopePatch {
|
|
173
|
+
function validateScopePatch(scope: unknown, patch: unknown, definitions: readonly StateFlowModule<JsonObject, JsonValue>[] = [], retained?: RetainedModules): asserts patch is ScopePatch {
|
|
145
174
|
if (typeof scope !== "string" || !SCOPES.has(scope as StateScope)) {
|
|
146
175
|
throw new Error(`Unknown State Flow transition scope: ${String(scope)}`);
|
|
147
176
|
}
|
|
148
177
|
validatePatch(patch);
|
|
149
178
|
for (const key of Object.keys(patch)) {
|
|
150
179
|
if (!PATCH_KEYS.has(key)) {
|
|
151
|
-
|
|
180
|
+
const definition = definitions.find(module => module.id === key);
|
|
181
|
+
if (!definition) throw new Error(`Unknown State Flow patch key ${JSON.stringify(key)}; expected one of: ${[...PATCH_KEYS].join(", ")}`);
|
|
182
|
+
if (definition.immutable) throw new Error(`Immutable State Flow patch key ${JSON.stringify(key)}`);
|
|
183
|
+
const descriptor = retained?.[key];
|
|
184
|
+
if (descriptor && (descriptor.schemaVersion !== definition.schemaVersion || descriptor.immutable !== definition.immutable)) {
|
|
185
|
+
throw new Error(`Unavailable State Flow module descriptor: ${key}`);
|
|
186
|
+
}
|
|
187
|
+
if (!isObject(patch[key])) throw new Error(`Scoped State Flow patch field ${key} must be a JSON object`);
|
|
152
188
|
}
|
|
153
189
|
}
|
|
154
|
-
for (const key of ["artifacts", "
|
|
190
|
+
for (const key of ["artifacts", "strategy", "tactics", "intents"] as const) {
|
|
155
191
|
if (Object.hasOwn(patch, key) && !isObject(patch[key])) {
|
|
156
192
|
throw new Error(`Scoped State Flow patch field ${key} must be a JSON object`);
|
|
157
193
|
}
|
|
@@ -164,13 +200,17 @@ function validateScopePatch(scope: unknown, patch: unknown): asserts patch is Sc
|
|
|
164
200
|
|
|
165
201
|
/** Stage all scope updates against one immutable basis before any state is published. */
|
|
166
202
|
function stageScopedSemanticTransition(
|
|
167
|
-
currentStates:
|
|
203
|
+
currentStates: ScopeStateValues,
|
|
168
204
|
transition: SemanticTransition,
|
|
169
205
|
successfulSkillReads: Iterable<SuccessfulSkillRead>,
|
|
170
206
|
causalBasis: string,
|
|
171
207
|
successfulArtifactReads: Iterable<SuccessfulArtifactRead>,
|
|
172
208
|
acceptedResponse?: string,
|
|
173
|
-
|
|
209
|
+
owners: readonly StateScope[] = [...SCOPES],
|
|
210
|
+
definitions: readonly StateFlowModule<JsonObject, JsonValue>[] = [],
|
|
211
|
+
modules: Partial<Record<StateScope, RetainedModules>> = {},
|
|
212
|
+
replacements: readonly ModuleReplacement[] = [],
|
|
213
|
+
): StagedTransition<ScopeStateValues> {
|
|
174
214
|
if (!Array.isArray(transition.transitions)) throw new Error("State Flow transitions must be an array");
|
|
175
215
|
const patches = new Map<StateScope, ScopePatch>();
|
|
176
216
|
for (const item of transition.transitions) {
|
|
@@ -179,46 +219,83 @@ function stageScopedSemanticTransition(
|
|
|
179
219
|
if (keys.length !== 2 || keys[0] !== "patch" || keys[1] !== "scope") {
|
|
180
220
|
throw new Error('Every State Flow transition must contain exactly "scope" and "patch"');
|
|
181
221
|
}
|
|
182
|
-
validateScopePatch(item.scope, item.patch);
|
|
183
222
|
const scope = item.scope as StateScope;
|
|
223
|
+
if (SCOPES.has(scope) && !owners.includes(scope)) throw new Error(`State Flow ${scope} scope is excluded`);
|
|
224
|
+
validateScopePatch(scope, item.patch, definitions, modules[scope]);
|
|
184
225
|
if (patches.has(scope)) throw new Error(`Duplicate State Flow transition scope: ${scope}`);
|
|
185
226
|
patches.set(scope, item.patch);
|
|
186
227
|
}
|
|
187
228
|
|
|
229
|
+
const produced = new Map<StateScope, Map<string, JsonObject>>();
|
|
230
|
+
for (const item of replacements) {
|
|
231
|
+
if (!isObject(item) || !owners.includes(item.scope as StateScope)) throw new Error("State Flow module replacement owner is excluded");
|
|
232
|
+
if (Object.keys(item).sort().join(",") !== "content,id,scope" || typeof item.id !== "string" || !/^x_[a-z][a-z0-9_]*$/.test(item.id)) throw new Error("Invalid State Flow module replacement");
|
|
233
|
+
const scope = item.scope as StateScope, definition = definitions.find(module => module.id === item.id), descriptor = modules[scope]?.[item.id];
|
|
234
|
+
if (!definition || descriptor && (descriptor.schemaVersion !== definition.schemaVersion || descriptor.immutable !== definition.immutable)) throw new Error(`Unavailable State Flow module replacement: ${item.id}`);
|
|
235
|
+
if (patches.get(scope) && Object.hasOwn(patches.get(scope)!, item.id)) throw new Error("Model patches and producer replacements cannot conflict");
|
|
236
|
+
const content = item.content;
|
|
237
|
+
validateModuleObject(content);
|
|
238
|
+
if (containsNull(content)) throw new Error("State Flow module replacements cannot contain stored null");
|
|
239
|
+
const scoped = produced.get(scope) ?? new Map<string, JsonObject>();
|
|
240
|
+
if (scoped.has(item.id)) throw new Error("Duplicate State Flow module replacement");
|
|
241
|
+
scoped.set(item.id, structuredClone(content)); produced.set(scope, scoped);
|
|
242
|
+
}
|
|
243
|
+
|
|
188
244
|
const artifactReads = [...successfulArtifactReads];
|
|
189
245
|
const skillReads = [...successfulSkillReads];
|
|
190
|
-
validateSkillCompilerTargets(patches, skillReads);
|
|
246
|
+
validateSkillCompilerTargets(patches, skillReads, owners);
|
|
191
247
|
const nextStates = { ...currentStates };
|
|
192
|
-
const provenanceUpdates
|
|
193
|
-
const cascades
|
|
248
|
+
const provenanceUpdates = Object.fromEntries(owners.map((scope) => [scope, {}])) as Partial<Record<StateScope, Record<string, ArtifactProvenance>>>;
|
|
249
|
+
const cascades = Object.fromEntries(owners.map((scope) => [scope, []])) as Partial<Record<StateScope, OwnedPath[]>>;
|
|
194
250
|
const missingDeletions: MissingDeletion[] = [];
|
|
195
|
-
|
|
251
|
+
const moduleAdditions: Partial<Record<StateScope, RetainedModules>> = {};
|
|
252
|
+
for (const scope of owners) {
|
|
253
|
+
const currentState = currentStates[scope]!;
|
|
196
254
|
const authored = patches.get(scope) ?? {};
|
|
197
255
|
const patch = { ...authored, ...(scope === "session" && acceptedResponse !== undefined ? { response: acceptedResponse } : {}) };
|
|
198
256
|
const scopedMissing: MissingDeletion[] = [];
|
|
199
257
|
const normalize = patches.has(scope) || scope === "session" && acceptedResponse !== undefined;
|
|
200
|
-
const
|
|
201
|
-
const
|
|
258
|
+
const { intents, ...other } = patch;
|
|
259
|
+
const intentState = applyOwnedPatch(structuredClone(currentState), intents === undefined ? {} : { intents }, scopedMissing);
|
|
260
|
+
// Intent ownership ends before other writes, independent of JSON key order.
|
|
261
|
+
const cascade = cascades[scope] = computeIntentCascade(scope, currentState, normalize ? pruneSemanticState(intentState) : intentState);
|
|
262
|
+
const cleared = cascade.length > 0 ? applyOwnedPatch(intentState, cascadeDeletionPatch(cascade)) : intentState;
|
|
263
|
+
const applied = applyOwnedPatch(cleared, other, scopedMissing);
|
|
264
|
+
for (const [id, content] of produced.get(scope) ?? []) {
|
|
265
|
+
if (Object.keys(content).length === 0) delete applied[id];
|
|
266
|
+
else Object.defineProperty(applied, id, { value: content, enumerable: true, configurable: true, writable: true });
|
|
267
|
+
}
|
|
268
|
+
const patched = normalize ? pruneSemanticState(applied) : applied;
|
|
202
269
|
missingDeletions.push(...scopedMissing.map(({ path, unavailablePath }) => ({
|
|
203
270
|
path: `/${scope}${path}`, unavailablePath: `/${scope}${unavailablePath}`,
|
|
204
271
|
})));
|
|
205
272
|
let materialized = { ...emptyState(), ...patched } as StateDocument;
|
|
206
|
-
|
|
207
|
-
const cascade = cascades[scope] = computeIntentCascade(scope, currentStates[scope], materialized);
|
|
208
|
-
if (cascade.length > 0) materialized = applyPatch(materialized, cascadeDeletionPatch(cascade)) as StateDocument;
|
|
273
|
+
|
|
209
274
|
compileReadArtifacts(
|
|
210
275
|
materialized,
|
|
211
276
|
{ artifacts: authored.artifacts ?? {} },
|
|
212
277
|
artifactReads.filter((read) => (read.scope ?? "global") === scope),
|
|
213
|
-
provenanceUpdates[scope]
|
|
278
|
+
provenanceUpdates[scope]!,
|
|
214
279
|
);
|
|
215
|
-
compileReadSkills(scope, materialized, { artifacts: authored.artifacts ?? {} }, skillReads.filter((read) => read.scope === scope), provenanceUpdates[scope]);
|
|
280
|
+
compileReadSkills(scope, materialized, { artifacts: authored.artifacts ?? {} }, skillReads.filter((read) => read.scope === scope), provenanceUpdates[scope]!);
|
|
216
281
|
const nextState: SemanticState = materialized;
|
|
217
282
|
for (const key of Object.keys(emptyState())) {
|
|
218
283
|
if (!Object.hasOwn(patched, key)
|
|
219
|
-
&& !(key === "artifacts" && Object.keys(provenanceUpdates[scope]).length > 0)) delete nextState[key];
|
|
284
|
+
&& !(key === "artifacts" && Object.keys(provenanceUpdates[scope]!).length > 0)) delete nextState[key];
|
|
285
|
+
}
|
|
286
|
+
const normalized = normalize ? pruneSemanticState(nextState) : nextState;
|
|
287
|
+
for (const id of new Set([...Object.keys(authored).filter(key => !PATCH_KEYS.has(key)), ...(produced.get(scope)?.keys() ?? [])])) {
|
|
288
|
+
const definition = definitions.find(module => module.id === id)!;
|
|
289
|
+
const content = normalized[id];
|
|
290
|
+
if (content === undefined && produced.get(scope)?.has(id)) continue;
|
|
291
|
+
validateModuleObject(content);
|
|
292
|
+
if (containsNull(content)) throw new Error("State Flow external content cannot contain stored null");
|
|
293
|
+
if (Object.keys(content).length === 0) delete normalized[id];
|
|
294
|
+
else {
|
|
295
|
+
if (definition.validate(structuredClone(content)) !== true) throw new Error(`Invalid State Flow module content: ${id}`);
|
|
296
|
+
if (!Object.hasOwn(modules[scope] ?? {}, id)) (moduleAdditions[scope] ??= {})[id] = { schemaVersion: definition.schemaVersion, immutable: definition.immutable };
|
|
297
|
+
}
|
|
220
298
|
}
|
|
221
|
-
const normalized = normalize ? pruneEmptyObjects(nextState) : nextState;
|
|
222
299
|
validateMaterializedTransition({ ...emptyState(), ...normalized }, scope);
|
|
223
300
|
nextStates[scope] = normalized;
|
|
224
301
|
}
|
|
@@ -227,13 +304,10 @@ function stageScopedSemanticTransition(
|
|
|
227
304
|
missingDeletions,
|
|
228
305
|
cascades,
|
|
229
306
|
provenanceUpdates,
|
|
230
|
-
stateHashes:
|
|
231
|
-
global: hashJson(currentStates.global),
|
|
232
|
-
cwd: hashJson(currentStates.cwd),
|
|
233
|
-
session: hashJson(currentStates.session),
|
|
234
|
-
},
|
|
307
|
+
stateHashes: Object.fromEntries(owners.map((scope) => [scope, hashJson(currentStates[scope]!)])),
|
|
235
308
|
causalBasis,
|
|
236
309
|
committed: false,
|
|
310
|
+
...(Object.keys(moduleAdditions).length ? { moduleAdditions } : {}),
|
|
237
311
|
};
|
|
238
312
|
}
|
|
239
313
|
|
|
@@ -245,21 +319,13 @@ export function stageAtomicScopePatches(
|
|
|
245
319
|
causalBasis: string,
|
|
246
320
|
successfulArtifactReads: Iterable<SuccessfulArtifactRead> = [],
|
|
247
321
|
): StagedScopedTransition {
|
|
248
|
-
if (!isObject(patches)) throw new Error("Atomic State Flow scope patches must be an object");
|
|
249
|
-
for (const key of Object.keys(patches)) {
|
|
250
|
-
if (!SCOPES.has(key as StateScope)) throw new Error(`Unknown atomic State Flow scope: ${key}`);
|
|
251
|
-
}
|
|
252
|
-
const transitions: ScopedPatch[] = [];
|
|
253
|
-
for (const scope of ["global", "cwd", "session"] as const) {
|
|
254
|
-
if (Object.hasOwn(patches, scope)) transitions.push({ scope, patch: patches[scope] as ScopePatch });
|
|
255
|
-
}
|
|
256
322
|
return stageScopedSemanticTransition(
|
|
257
323
|
currentStates,
|
|
258
|
-
|
|
324
|
+
atomicScopeTransitions(patches),
|
|
259
325
|
successfulSkillReads,
|
|
260
326
|
causalBasis,
|
|
261
327
|
successfulArtifactReads,
|
|
262
|
-
);
|
|
328
|
+
) as StagedScopedTransition;
|
|
263
329
|
}
|
|
264
330
|
|
|
265
331
|
export function stageScopedTransition(
|
|
@@ -279,7 +345,47 @@ export function stageScopedTransition(
|
|
|
279
345
|
causalBasis,
|
|
280
346
|
successfulArtifactReads,
|
|
281
347
|
transition.response,
|
|
282
|
-
);
|
|
348
|
+
) as StagedScopedTransition;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function atomicScopeTransitions(patches: AtomicScopePatches, owners: readonly StateScope[] = [...SCOPES]): SemanticTransition {
|
|
352
|
+
if (!isObject(patches)) throw new Error("Atomic State Flow scope patches must be an object");
|
|
353
|
+
for (const key of Object.keys(patches)) {
|
|
354
|
+
if (!SCOPES.has(key as StateScope)) throw new Error(`Unknown atomic State Flow scope: ${key}`);
|
|
355
|
+
if (!owners.includes(key as StateScope)) throw new Error(`State Flow ${key} scope is excluded`);
|
|
356
|
+
}
|
|
357
|
+
const transitions: ScopedPatch[] = [];
|
|
358
|
+
for (const scope of owners) if (Object.hasOwn(patches, scope)) transitions.push({ scope, patch: patches[scope] as ScopePatch });
|
|
359
|
+
return { transitions };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** Value-only selected staging; a selection or causal token is not physical publication authority. */
|
|
363
|
+
export function stageSelectedScopePatches(
|
|
364
|
+
current: SelectedSemanticBasis,
|
|
365
|
+
patches: AtomicScopePatches,
|
|
366
|
+
successfulSkillReads: Iterable<SuccessfulSkillRead>,
|
|
367
|
+
causalBasis: string,
|
|
368
|
+
successfulArtifactReads: Iterable<SuccessfulArtifactRead> = [],
|
|
369
|
+
definitions: readonly StateFlowModule<JsonObject, JsonValue>[] = [],
|
|
370
|
+
replacements: readonly ModuleReplacement[] = [],
|
|
371
|
+
): StagedSelectedTransition {
|
|
372
|
+
const selection = selectedOwners(current);
|
|
373
|
+
return { ...stageScopedSemanticTransition(current.states, atomicScopeTransitions(patches, selection), successfulSkillReads,
|
|
374
|
+
causalBasis, successfulArtifactReads, undefined, selection, definitions, current.modules, replacements), selection };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** Excluded Session suppresses capture, not ordinary-answer delivery or lifecycle completion. */
|
|
378
|
+
export function stageSelectedScopedTransition(
|
|
379
|
+
current: SelectedSemanticBasis,
|
|
380
|
+
transition: TerminalTransition,
|
|
381
|
+
successfulSkillReads: Iterable<SuccessfulSkillRead>,
|
|
382
|
+
causalBasis: string,
|
|
383
|
+
successfulArtifactReads: Iterable<SuccessfulArtifactRead> = [],
|
|
384
|
+
): StagedSelectedTransition {
|
|
385
|
+
const selection = selectedOwners(current);
|
|
386
|
+
if (typeof transition.response !== "string") throw new Error("Accepted State Flow response body must be a string");
|
|
387
|
+
return { ...stageScopedSemanticTransition(current.states, transition, successfulSkillReads, causalBasis, successfulArtifactReads,
|
|
388
|
+
selection.includes("session") ? transition.response : undefined, selection), selection };
|
|
283
389
|
}
|
|
284
390
|
|
|
285
391
|
/** Commit one accepted transition; durable publication receives all changed scopes as one cohort. */
|
|
@@ -295,11 +401,39 @@ export function commitScopedTransition(
|
|
|
295
401
|
publishDurable: (accepted: AcceptedTransition | undefined, nextSnapshot: Snapshot) => void,
|
|
296
402
|
causalBasis: string,
|
|
297
403
|
options: CommitScopedTransitionOptions = {},
|
|
404
|
+
): boolean {
|
|
405
|
+
if (Object.hasOwn(stage, "selection")) throw new Error("Selected State Flow stages require selected-cohort commit");
|
|
406
|
+
return commitSemanticTransition(snapshot, states, stage, publishDurable, causalBasis, options, [...SCOPES]);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export function commitSelectedTransition(
|
|
410
|
+
snapshot: Snapshot,
|
|
411
|
+
current: SelectedSemanticBasis,
|
|
412
|
+
stage: StagedSelectedTransition,
|
|
413
|
+
publishDurable: (accepted: AcceptedTransition | undefined, nextSnapshot: Snapshot) => void,
|
|
414
|
+
causalBasis: string,
|
|
415
|
+
options: CommitScopedTransitionOptions = {},
|
|
416
|
+
): boolean {
|
|
417
|
+
if (stage.committed) return false;
|
|
418
|
+
const selection = selectedOwners(current);
|
|
419
|
+
if (!sameJson(selection, stage.selection)) throw new Error("State Flow selection changed before reconciliation");
|
|
420
|
+
selectedOwners({ selection, states: stage.nextStates });
|
|
421
|
+
return commitSemanticTransition(snapshot, current.states, stage, publishDurable, causalBasis, options, selection);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function commitSemanticTransition(
|
|
425
|
+
snapshot: Snapshot,
|
|
426
|
+
states: ScopeStateValues,
|
|
427
|
+
stage: StagedTransition<ScopeStateValues>,
|
|
428
|
+
publishDurable: (accepted: AcceptedTransition | undefined, nextSnapshot: Snapshot) => void,
|
|
429
|
+
causalBasis: string,
|
|
430
|
+
options: CommitScopedTransitionOptions,
|
|
431
|
+
owners: readonly StateScope[],
|
|
298
432
|
): boolean {
|
|
299
433
|
if (stage.committed) return false;
|
|
300
434
|
if (causalBasis !== stage.causalBasis) throw new Error("State Flow causal basis changed before response reconciliation; rematerialize state before retrying");
|
|
301
|
-
for (const scope of
|
|
302
|
-
if (hashJson(states[scope]) !== stage.stateHashes[scope]) {
|
|
435
|
+
for (const scope of owners) {
|
|
436
|
+
if (hashJson(states[scope]!) !== stage.stateHashes[scope]) {
|
|
303
437
|
throw new Error(`State Flow ${scope} scope changed before response reconciliation; rematerialize state before retrying`);
|
|
304
438
|
}
|
|
305
439
|
}
|
|
@@ -316,9 +450,7 @@ export function commitScopedTransition(
|
|
|
316
450
|
nextSnapshot.meta.bootstrap = false;
|
|
317
451
|
}
|
|
318
452
|
publishDurable(accepted, nextSnapshot);
|
|
319
|
-
states
|
|
320
|
-
states.cwd = structuredClone(stage.nextStates.cwd);
|
|
321
|
-
states.session = structuredClone(stage.nextStates.session);
|
|
453
|
+
for (const scope of owners) states[scope] = structuredClone(stage.nextStates[scope]!);
|
|
322
454
|
if (accepted !== undefined) snapshot.meta.step += 1;
|
|
323
455
|
if (options.finalizeRun !== false) {
|
|
324
456
|
snapshot.meta.validation = undefined;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-state-flow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Incremental scoped state/context/memory compiler for Pi, inspired by SKILL.state",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"type": "module",
|
|
21
21
|
"files": [
|
|
22
22
|
"index.ts",
|
|
23
|
+
"api",
|
|
23
24
|
"lib",
|
|
24
25
|
"dist",
|
|
25
26
|
"skills",
|
|
@@ -43,6 +44,10 @@
|
|
|
43
44
|
".": {
|
|
44
45
|
"types": "./dist/index.d.ts",
|
|
45
46
|
"default": "./dist/index.js"
|
|
47
|
+
},
|
|
48
|
+
"./modules": {
|
|
49
|
+
"types": "./dist/api/modules.d.ts",
|
|
50
|
+
"default": "./dist/api/modules.js"
|
|
46
51
|
}
|
|
47
52
|
},
|
|
48
53
|
"pi": {
|
|
@@ -21,53 +21,63 @@ Operator commands: `/state-flow-status` inspects; `/state-flow-active` selects s
|
|
|
21
21
|
|
|
22
22
|
| Field | Purpose |
|
|
23
23
|
| --- | --- |
|
|
24
|
-
| `intents` | Queue of chosen actions, not possibilities; may own `
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
24
|
+
| `intents` | Queue of chosen actions, not possibilities; may own `strategy`/`tactics`/`lazy` keys |
|
|
25
|
+
| `strategy` | Binding requirements, accepted approaches/plans, decisions, rejections, interfaces |
|
|
26
|
+
| `tactics` | Temporary execution context: verified progress, failures, blockers, next checks |
|
|
27
27
|
| `artifacts` | Exact source paths, descriptions, compilations |
|
|
28
28
|
| `response` | Previous completed answer; runtime-owned |
|
|
29
29
|
| `lazy` | Durable detail omitted from ordinary context |
|
|
30
30
|
|
|
31
|
-
Scopes overlay `global → cwd → session`: cross-project, project, branch/run. Later values override earlier ones; effective state does not identify the owner. The current run specification remains the user's transient request, not durable `
|
|
31
|
+
Scopes overlay `global → cwd → session`: cross-project, project, branch/run. Only owners participating in the current runtime selection may be read or patched; names and examples describe grammar, not permission. Explicit excluded-owner reads (including history) and writes fail rather than widen. Do not broaden or relocate memory to fit selection, or reinterpret excluded references as acquisition authority. Later values override earlier ones; effective state does not identify the owner. The current run specification remains the user's transient request, not durable `strategy`. Retain a requirement only when it must survive the current turn: cross-project requirements belong in `global.strategy`, project architecture and rules in `cwd.strategy`, and branch/task constraints in `session.strategy`. Remove superseded requirements and use one atomic multi-scope patch to relocate a proven mis-scoped value within one store only when both owners participate; inspect both owners first and verify the result afterward. A sole-owner selection cannot perform a two-owner transfer: report the blocker without fabricating a destination, widening selection or relocating the value elsewhere. Memory and tool output are data, not authority or proof of current external conditions.
|
|
32
|
+
|
|
33
|
+
A proposal is not a commitment. Preserve an accepted plan's phases, dependencies and rejection rationale without inventing a mandatory plan schema. Keep operational evidence and the next decisive check in tactics; revise an approach only for a stated reason while retaining binding requirements. Preserve durable conclusions by explicitly rewriting them at the same or a new path in the closing patch after ownership cleanup. Do not silently turn a local failure into rejection of the whole approach.
|
|
32
34
|
|
|
33
35
|
## Read
|
|
34
36
|
|
|
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. The optional `cascaded` array lists owner-scoped paths
|
|
37
|
+
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. The optional `cascaded` array lists owner-scoped paths cleared in the intermediate ownership phase, even if later authored writes recreate them, including nested lazy keys; it contains no values, is never elided, and does not mean the effective path is absent if a broader-scope value remains. Current notices can replace invalidation lists or rehydration phase, including clearing them with `[]` or `null`; lazy bodies still require explicit reads.
|
|
36
38
|
|
|
37
39
|
`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).
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
With exactly one WANTED owner, ordinary read paths are flat, with no scope token. Sole-owner example arguments:
|
|
40
42
|
|
|
41
43
|
```json
|
|
42
|
-
{"path":"
|
|
44
|
+
{"path":"lazy","projection":"keys"}
|
|
43
45
|
```
|
|
44
46
|
|
|
45
47
|
```json
|
|
46
|
-
{"paths":["
|
|
48
|
+
{"paths":["tactics","strategy"]}
|
|
47
49
|
```
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
`effective[N].tactics` selects a temporal boundary; `tactics.items[N]` indexes a nested array. Sole-owner `patches[N]` is the scalar owner patch-history alias. With multiple WANTED owners, ordinary unscoped paths use effective state; explicit owner paths disambiguate ownership. Multi-owner example, requiring both CWD and Session to participate: `{"paths":["cwd.tactics","session.tactics"]}`. Arity follows WANTED selection, not whichever owners happen to have an available view.
|
|
52
|
+
|
|
53
|
+
Unscoped paths use the sole owner's current state or, with multiple owners, effective state. Optional explicit `cwd[1].tactics` reads the preceding causal boundary when CWD participates. 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; only intent ownership (see Write) has a deletion consequence. 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
54
|
|
|
51
55
|
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.
|
|
52
56
|
|
|
53
57
|
## Write
|
|
54
58
|
|
|
55
|
-
Call `patch_state` alone per assistant response; await acceptance before dependent work.
|
|
59
|
+
Call `patch_state` alone per assistant response; await acceptance before dependent work. With exactly one WANTED owner, supply flat object planes (`intents`, `strategy`, `tactics`, `artifacts`, `lazy`), without an owner wrapper. With multiple WANTED owners, supply one or more explicit participating owners from `global`, `cwd`, and `session`; supplied scopes commit atomically. Arity follows WANTED selection, not available view; selection is not a grant of authority. Default to Session for current work only when it participates, CWD for reusable project knowledge, and Global for established cross-project knowledge. Preserve these meanings; an unavailable owner is not a reason to write its data into a broader one. Omit unchanged planes in a sole-owner patch and unchanged scopes in a multi-owner patch.
|
|
56
60
|
|
|
57
61
|
The runtime waits cancelably for publication ownership, then applies authored Global/CWD operations to current canonical values. Untouched fields survive; overlapping targets follow successful acceptance order. Correct repeats succeed as `State already current.` without another semantic revision. Do not repeat external actions during a memory wait, or rebuild an entire scope from an older snapshot. Session ownership/history fences remain private, not a universal merge.
|
|
58
62
|
|
|
59
|
-
When present, semantic planes `intents`, `
|
|
63
|
+
When present, semantic planes `intents`, `strategy`, `tactics`, `artifacts`, and `lazy` are objects; nested lazy values may contain ordinary JSON without stored nulls. Stored checkpoints and patches may omit any documented plane. Current and historical views assemble only known fields present in the selected scopes. Absent fields and empty responses are omitted from views. Checkpoint/tail readers ignore unknown top-level fields, and writers emit only known fields. Nested data within known planes remains intact. Explicit value reads of an absent documented top-level field return `null`. Authored `patch_state` keeps its documented field grammar. Objects merge, arrays/scalars replace, and omitted nonempty fields persist. Accepted supplied scopes recursively omit empty object fields and planes, including explicit `{}`; the scope root remains `{}`. This also cleans legacy empty branches in that scope, which can advance its revision once. Array slots and empty arrays remain; empty object fields inside elements disappear without shifting indices. Model projection hides legacy empty branches, but exact current/historical reads and untouched scopes are not normalized. Nested `null` removes an owned object key; empty ancestors disappear and inherited content may reappear. Canonical `"[N]"` keys patch existing array elements; indexed deletion and out-of-bounds indices are forbidden. Replace the whole array to add/remove elements. Missing authored object-key deletions succeed with a nonfatal hint showing owner-scoped JSON Pointers for the target and first unavailable component. Hints are not state, do not advance revisions and do not prove a typo; check the address only when deletion was expected to change something. Never guess or automatically correct the target.
|
|
60
64
|
|
|
61
|
-
Work from intents. A structured `{"$ref"}` anywhere inside an intent owns ("delete with me") an existing object key under `
|
|
65
|
+
Work from intents. A structured `{"$ref"}` anywhere inside an intent owns ("delete with me") an existing object key under `strategy`, `tactics` or `lazy` in the same scope; a textual `$path` mention only uses it. In a sole-owner patch, current flat structured refs anchor to that exact owner before canonical intent ownership/cascade processing. Existing canonical historical, effective, cross-owner and resource refs, and textual mentions are never stripped or rebound. Opening a task (sole-owner example, valid for any one WANTED owner):
|
|
62
66
|
|
|
63
67
|
```json
|
|
64
|
-
{"
|
|
68
|
+
{"intents":{"check_api":{"action":"Verify the API","notes":{"$ref":"tactics.api"},"plan":{"$ref":"lazy.api_plan"}}},"tactics":{"api":"draft findings"},"lazy":{"api_plan":["probe","compare"]}}
|
|
65
69
|
```
|
|
66
70
|
|
|
67
|
-
|
|
71
|
+
Fixed semantic order, independent of JSON insertion order: apply intents only, clear eligible targets of deleted intent keys, apply remaining authored planes, then existing compilation/normalization and atomic publication. Final remaining and newly added same-scope intents protect targets they reference directly or through ancestors/descendants. Supersede in one patch with such a replacement intent. Preserve consequences by explicitly rewriting them at the same or a new appropriate path in that patch: cleared targets start fresh, without old fields. Strategy is owned too, not an automatic safe haven. Unowned entries remain legal. Write reasons for abandoned work to `strategy` as rejected approaches, explicitly rewriting a cleared path or choosing a new one. Only keys matching `[A-Za-z_$][A-Za-z0-9_$-]*` can be owned. Cross-scope, historical/effective, plane-root, array-element and non-`strategy`/`tactics`/`lazy` targets are never deleted; nothing is rejected or warned about. Existing strategy refs own future accepted deletions only; accepted history replays explicit net patches without rerunning cleanup. Final effective receipt values are authoritative. Illustrative sole-owner closing, only for an actually completed intent and after satisfying pending acquisitions:
|
|
68
72
|
|
|
69
73
|
```json
|
|
70
|
-
{"
|
|
74
|
+
{"intents":{"check_api":null},"strategy":{"api":"verified: v2 only"}}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Sole-owner preservation example: retain the known exact source path and existing explicit references unchanged (the cross-owner reference is data, not permission to read that owner):
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{"tactics":{"evidence":{"source":"/project/specs/api.md","historic":{"$ref":"session[1].tactics.api"},"cross":{"$ref":"global.strategy.api"},"effective":{"$ref":"effective.tactics.api"},"resource":{"$ref":"https://example.invalid/api"},"text":"$session.tactics.api"}}}
|
|
71
81
|
```
|
|
72
82
|
|
|
73
83
|
Name object keys in ASCII matching `[A-Za-z_$][A-Za-z0-9_$-]*` (for example `api_plan`, not a Cyrillic or spaced key): other keys cannot be addressed by `read_state` paths or `$` references, and cannot be owned by intents. Values may use any language.
|
|
@@ -78,7 +88,9 @@ Never edit backing files, `response`, configuration, provenance, or runtime meta
|
|
|
78
88
|
|
|
79
89
|
Read sources for gaps, exact-source/edit needs, invalidation, contradiction, or explicit requests; descriptions are not acquired content.
|
|
80
90
|
|
|
81
|
-
In active mode, compile each required invalidated ordinary artifact at its exact path in the reported scope (`global`, `cwd`, or `session`); do not relocate it or invent a global copy. If ownership is unclear, inspect the scoped registry rather than defaulting to global. Choose the narrowest scope for new ordinary artifacts. For an exact registered Skill read, follow the State Flow acquisition note: user Skills target global, project Skills target CWD and temporary Skills target session. Matching current hashes need no patch. Durable Skill compilation is optional and uses description, `kind: "skill"`, and a nonempty `compilation` object at the reported path; an unrelated semantic patch may proceed without it. Leave fingerprints, Skill hashes, and other provenance to runtime; do not repeat accepted compilations.
|
|
91
|
+
In active mode, compile each required invalidated ordinary artifact at its exact path in the reported scope (`global`, `cwd`, or `session`); do not relocate it or invent a global copy. With one WANTED owner, write the compilation directly under `artifacts[exact path]`, without an owner wrapper; the reported scope is canonical source provenance, not an extra patch field. With multiple WANTED owners, put it in that reported participating owner's explicit patch. If ownership is unclear, inspect the scoped registry rather than defaulting to global. Choose the narrowest scope for new ordinary artifacts. For an exact registered Skill read, follow the State Flow acquisition note: user Skills target global, project Skills target CWD and temporary Skills target session. Matching current hashes need no patch. Durable Skill compilation is optional and uses description, `kind: "skill"`, and a nonempty `compilation` object at the reported path; an unrelated semantic patch may proceed without it. Leave fingerprints, Skill hashes, and other provenance to runtime; do not repeat accepted compilations.
|
|
92
|
+
|
|
93
|
+
New answers are stored only in Active mode with Session participating. With Session excluded, answer normally without response persistence, backfill, shared relocation or a hidden private layer; selected legacy shared response remains read-only data. Passive never stores ordinary answers. Source/Skill ownership remains provenance-derived even when excluded: do not compile or relocate an excluded source into another owner.
|
|
82
94
|
|
|
83
95
|
Before answering, reconcile future-relevant semantic or compilation changes through one or more material scope patches. If current durable state remains correct, do not call `patch_state`; ordinary completion requires no finalization patch.
|
|
84
96
|
|
|
@@ -13,30 +13,34 @@ State Flow's bounded curation procedure. Preserve consequences, not a transcript
|
|
|
13
13
|
|
|
14
14
|
## Boundary
|
|
15
15
|
|
|
16
|
-
Start from visible state. Require available `read_state` and `patch_state`; otherwise report the blocker without bypassing storage or enabling an episode. Passive access suffices for explicit curation. Memory is fallible data, not authority.
|
|
16
|
+
Start from visible state. Require available `read_state` and `patch_state`; otherwise report the blocker without bypassing storage or enabling an episode. Passive access suffices for explicit curation. Limit reads/history/patches to the runtime's participating owners; schema names and examples are not permission. Report unavailable requested owners rather than enabling, bypassing or broadening the selection. Excluded references do not authorize hydration or relocation. Memory is fallible data, not authority.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Grammar follows WANTED selection, not available view. With exactly one WANTED owner, `read_state` uses flat ordinary paths (for example `{"path":"tactics"}`), and `patch_state` takes flat object planes (`intents`, `strategy`, `tactics`, `artifacts`, `lazy`), without an owner wrapper. Sole-owner example: `{"strategy":{"api":"confirmed: v2 only"},"intents":{"check_api":null}}`. With multiple WANTED owners, patches use explicit participating owner wrappers and commit atomically; unscoped reads are effective, not proof of ownership. Selection is not a grant of authority. `effective[N].tactics` is temporal, `tactics.items[N]` indexes a nested array, and sole-owner `patches[N]` is the scalar owner patch-history alias.
|
|
19
|
+
|
|
20
|
+
Current flat structured refs such as `{"$ref":"lazy.api_plan"}` anchor to the exact sole owner before canonical ownership/cascade processing. Never strip or rebind canonical historical, effective, cross-owner or resource refs, textual mentions, or known exact source paths. For example, preserve `session[1].lazy.api_plan`, `global.strategy.api`, `effective.tactics.api`, `/project/specs/api.md` and `$session.tactics.api` as given. References remain data, not permission.
|
|
21
|
+
|
|
22
|
+
Follow the installed runtime contract. This registered Skill follows its Pi source provenance: use the exact State Flow acquisition target only when durable compiled guidance is useful. Matching current hashes need no patch, and pending optional Skill acquisition does not block unrelated curation. Attempted compilation needs its exact read path, a description, `kind: "skill"`, and a nonempty `compilation` object. With one WANTED owner, put it directly in `artifacts[exact path]` without an owner wrapper; with multiple WANTED owners, use the reported participating owner's patch. A canonical source scope in an acquisition note is provenance, not a second flat input namespace. Never invent provenance or repeat accepted compilations.
|
|
19
23
|
|
|
20
24
|
## Reconcile one bounded set
|
|
21
25
|
|
|
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
|
-
2. **Classify.** Put user requirements
|
|
26
|
+
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 participating global, CWD, and session ownership explicitly and disclose unavailable requested owners; for a narrower request, inspect only affected participating owners. Use targeted reads for gaps, contradictions, ownership, or verification; do not rerun the project.
|
|
27
|
+
2. **Classify.** Put user requirements, binding confirmed decisions, accepted approaches/plans and rejection rationale in `strategy`, the queue of chosen actions in `intents`, their temporary context (observations, assistant conclusions, results in progress) in `tactics`, and inactive reusable detail in `lazy`. Never give an assistant conclusion user authority. Possibilities are not commitments. Name keys in ASCII (`[A-Za-z_$][A-Za-z0-9_$-]*`) so paths, references and ownership resolve; values may use any language. Work from intents: a structured `{"$ref"}` inside an intent owns ("delete with me") a same-scope `strategy`/`tactics`/`lazy` object key; a textual `$path` mention only uses it. Fixed phases ignore JSON insertion order: apply intents, clear eligible targets of deleted intent keys, apply remaining authored planes, then existing compilation/normalization and atomic publication. Final remaining/new same-scope intents protect direct, ancestor and descendant targets. Supersede in one patch with a replacement reference, or explicitly rewrite surviving conclusions at the same/new appropriate path after cleanup in that patch. Cleared targets start fresh with no old fields; strategy is not exempt. Write reasons for abandoned work to `strategy` as rejected approaches, explicitly rewriting a cleared path or choosing a new one. Unowned entries remain legal. `cascaded` lists intermediate cleared paths even if recreated; final effective values govern. Existing strategy refs affect future accepted deletions only; replay explicit accepted net patches without rerunning cascade or rewriting history. Scope, historical/effective, array, plane-root, structured-key and source boundaries remain unchanged.
|
|
24
28
|
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
29
|
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; only intent ownership has a deletion consequence. 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
|
-
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.
|
|
30
|
+
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 participating owners before moves. Do not broaden or relocate memory merely because its proper owner is excluded. Broader applicability requires evidence.
|
|
27
31
|
|
|
28
32
|
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
33
|
|
|
30
34
|
## Transfer only when needed
|
|
31
35
|
|
|
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.
|
|
36
|
+
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. Both owners must participate: a sole-owner selection cannot perform this two-owner transfer. Report the blocker without fabricating an unavailable destination, widening selection or relocating data to an available owner. Verify both owners and effective inheritance afterward; reconcile affected references. A rejected cohort leaves neither side partially accepted.
|
|
33
37
|
|
|
34
38
|
External transfers require confirmed destination and write authority. Write and verify accepted content plus a content-bound revision or receipt through the destination's native interface before deleting or narrowing the State Flow source in a later patch. Recheck the source for intervening changes. Preserve it when acceptance is ambiguous. Never export secrets or broaden sensitive material without authorization.
|
|
35
39
|
|
|
36
40
|
## Apply, verify, stop
|
|
37
41
|
|
|
38
|
-
A fresh executor must recover constraints, results, open questions, commitments, and the next action without inheriting an unapproved method.
|
|
42
|
+
A fresh executor must recover constraints, results, open questions, commitments, and the next action without inheriting an unapproved method. New answers persist only in Active mode with Session participating; otherwise deliver them without response backfill or shared relocation. Never rewrite selected legacy shared response as a new answer.
|
|
39
43
|
|
|
40
|
-
Patch only material changes with `patch_state`, alone per assistant response; await acceptance. Never edit backing files, `response`, configuration, or runtime metadata. Read changed owner paths; inspect parent keys for deletions and effective state for inheritance changes.
|
|
44
|
+
Patch only material changes with `patch_state`, alone per assistant response; await acceptance. Plane values must be objects; nested `null` deletes an owned object key, not a stored value. Canonical `"[N]"` keys patch existing array elements; indexed deletion and out-of-bounds indices are forbidden. Replace the whole array to add/remove elements. Ordinary completion requires no finalization patch. Never edit backing files, `response`, configuration, or runtime metadata. Read changed owner paths; inspect parent keys for deletions and effective state for inheritance changes.
|
|
41
45
|
|
|
42
46
|
After rejection or interruption, inspect accepted state before bounded recovery. Report unresolved checks and partial transfers without dumping memory or implying historical erasure. Before answering, apply only material durable changes; when nothing needs changing, make no `patch_state` call. Stop after this review, including when nothing needs changing.
|