@llblab/pi-kit 0.23.1 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +3 -1
- package/banner.jpg +0 -0
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +5 -4
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +3 -5
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +17 -0
- package/node_modules/@llblab/pi-state-flow/README.md +4 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +50 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +254 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +36 -27
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +6 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +6 -2
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +3 -1
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +8 -4
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +1 -1
- package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +6 -4
- package/node_modules/@llblab/pi-state-flow/docs/performance.md +48 -2
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +6 -3
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +10 -0
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +228 -8
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +37 -32
- package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +8 -4
- package/node_modules/@llblab/pi-state-flow/lib/query.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +6 -2
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +3 -1
- package/package.json +6 -4
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
2
3
|
import { projectArtifactForModel, type ArtifactInvalidationNotice, type ArtifactModelHints } from "./artifact.ts";
|
|
3
4
|
import type { RecentTransitionWindow } from "./history.ts";
|
|
4
|
-
import { isObject, presentationJson, type JsonValue } from "./json.ts";
|
|
5
|
+
import { applyPatch, isObject, presentationJson, sameJson, type JsonValue } from "./json.ts";
|
|
5
6
|
import type { Snapshot } from "./snapshot.ts";
|
|
6
7
|
import type { RehydrationPhase } from "./rehydration.ts";
|
|
7
|
-
import { projectStateForModel, type MaterializedState, type ModelState } from "./state.ts";
|
|
8
|
+
import { projectStateForModel, type AtomicScopePatches, type MaterializedState, type ModelState, type StateScope } from "./state.ts";
|
|
8
9
|
|
|
9
10
|
/** Refresh only our section; Pi owns system frames, tools and forced-prompt precedence. */
|
|
10
11
|
export function projectSystemProtocol(messages: AgentMessage[], protocol: string | undefined): AgentMessage[] {
|
|
@@ -53,12 +54,222 @@ export function lazyNavigationHint(state: MaterializedState): { available: boole
|
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
|
|
57
|
+
export type ModelStateUpdate = { path: (string | number)[] } & ({ value: JsonValue } | { deleted: true });
|
|
58
|
+
|
|
59
|
+
/** Exact projected replacements, not authored merge patches; paths are unambiguous key/index segments. */
|
|
60
|
+
export function acceptedStateUpdates(before: MaterializedState, after: MaterializedState, patches: AtomicScopePatches) {
|
|
61
|
+
return projectedStateUpdates(projectStateForModel(before), projectStateForModel(after), patches, lazyNavigationHint(before), lazyNavigationHint(after));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function projectedStateUpdates(previous: ModelState, current: ModelState, patches: AtomicScopePatches,
|
|
65
|
+
beforeNavigation: ReturnType<typeof lazyNavigationHint> | undefined, navigation: ReturnType<typeof lazyNavigationHint> | undefined) {
|
|
66
|
+
const effective: ModelStateUpdate[] = [];
|
|
67
|
+
const prefix = (parent: readonly (string | number)[], child: readonly (string | number)[]) =>
|
|
68
|
+
parent.length <= child.length && parent.every((part, index) => part === child[index]);
|
|
69
|
+
const put = (path: (string | number)[], value: JsonValue | undefined) => {
|
|
70
|
+
if (effective.some((entry) => prefix(entry.path, path))) return;
|
|
71
|
+
for (let index = effective.length - 1; index >= 0; index--) {
|
|
72
|
+
if (prefix(path, effective[index]!.path)) effective.splice(index, 1);
|
|
73
|
+
}
|
|
74
|
+
effective.push({ path, ...(value === undefined ? { deleted: true as const } : { value: structuredClone(value) }) });
|
|
75
|
+
};
|
|
76
|
+
const child = (value: JsonValue | undefined, key: string | number): JsonValue | undefined =>
|
|
77
|
+
value !== null && typeof value === "object" && Object.hasOwn(value, key)
|
|
78
|
+
? (value as Record<string | number, JsonValue>)[key] : undefined;
|
|
79
|
+
const diff = (left: JsonValue | undefined, right: JsonValue | undefined, path: (string | number)[]) => {
|
|
80
|
+
if (left === undefined && right === undefined || left !== undefined && right !== undefined && sameJson(left, right)) return;
|
|
81
|
+
if (isObject(left) && isObject(right)) {
|
|
82
|
+
for (const key of new Set([...Object.keys(left), ...Object.keys(right)])) diff(child(left, key), child(right, key), [...path, key]);
|
|
83
|
+
} else if (Array.isArray(left) && Array.isArray(right) && left.length === right.length) {
|
|
84
|
+
for (let index = 0; index < right.length; index++) diff(left[index], right[index], [...path, index]);
|
|
85
|
+
} else put(path, right);
|
|
86
|
+
};
|
|
87
|
+
const touched = (patch: JsonValue, value: JsonValue | undefined, path: (string | number)[]) => {
|
|
88
|
+
if (!isObject(patch)) { put(path, value); return; }
|
|
89
|
+
const keys = Object.keys(patch);
|
|
90
|
+
if (keys.length === 0) return;
|
|
91
|
+
if (Array.isArray(value) && keys.every((key) => /^\[(0|[1-9]\d*)\]$/.test(key))) {
|
|
92
|
+
for (const key of keys) {
|
|
93
|
+
const index = Number(key.slice(1, -1));
|
|
94
|
+
// A higher-scope array may mask the patched array with a different length.
|
|
95
|
+
if (index >= value.length) { put(path, value); return; }
|
|
96
|
+
touched(patch[key]!, value[index], [...path, index]);
|
|
97
|
+
}
|
|
98
|
+
} else if (isObject(value)) {
|
|
99
|
+
for (const key of keys) touched(patch[key]!, child(value, key), [...path, key]);
|
|
100
|
+
} else put(path, value);
|
|
101
|
+
};
|
|
102
|
+
diff(previous, current, []);
|
|
103
|
+
for (const patch of Object.values(patches)) for (const [plane, value] of Object.entries(patch)) {
|
|
104
|
+
if (plane === "lazy") continue;
|
|
105
|
+
if (plane === "artifacts" && isObject(value)) {
|
|
106
|
+
for (const path of Object.keys(value)) put([plane, path], child(current.artifacts, path));
|
|
107
|
+
} else touched(value as JsonValue, child(current, plane), [plane]);
|
|
108
|
+
}
|
|
109
|
+
return { effective, ...(navigation !== undefined && (beforeNavigation === undefined || !sameJson(beforeNavigation, navigation)) ? { lazy_navigation: navigation } : {}) };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface ContextView {
|
|
113
|
+
state: ModelState;
|
|
114
|
+
lazy_navigation?: ReturnType<typeof lazyNavigationHint>;
|
|
115
|
+
artifact_invalidations: readonly ArtifactInvalidationNotice[];
|
|
116
|
+
knowledge_rehydration: { phase: RehydrationPhase } | null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function contextView(state: MaterializedState, hints: ArtifactModelHints, invalidations: readonly ArtifactInvalidationNotice[], phase?: RehydrationPhase): ContextView {
|
|
120
|
+
return { state: projectStateForModel(state, hints), lazy_navigation: lazyNavigationHint(state),
|
|
121
|
+
artifact_invalidations: structuredClone(invalidations), knowledge_rehydration: phase === undefined ? null : { phase } };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Volatile model projection only. Native messages own trajectory; this cache owns no persistence or lifecycle. */
|
|
125
|
+
export class ContextProjection {
|
|
126
|
+
private identity = randomUUID();
|
|
127
|
+
private head: AgentMessage | undefined;
|
|
128
|
+
private view: ContextView | undefined;
|
|
129
|
+
private native: string[] = [];
|
|
130
|
+
private notices: Array<{ after: number; message: AgentMessage }> = [];
|
|
131
|
+
|
|
132
|
+
reset(): void {
|
|
133
|
+
this.identity = randomUUID();
|
|
134
|
+
this.head = undefined;
|
|
135
|
+
this.view = undefined;
|
|
136
|
+
this.native = [];
|
|
137
|
+
this.notices = [];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Called only after successful publication and ancillary acceptance, immediately before returning the native result. */
|
|
141
|
+
acceptPatch(before: MaterializedState, after: MaterializedState, patches: AtomicScopePatches, hints: ArtifactModelHints) {
|
|
142
|
+
const state = projectStateForModel(after, hints);
|
|
143
|
+
const navigation = lazyNavigationHint(after);
|
|
144
|
+
const beforeNavigation = this.view?.lazy_navigation ?? lazyNavigationHint(before);
|
|
145
|
+
const updates = projectedStateUpdates(this.view?.state ?? projectStateForModel(before, hints), state, patches,
|
|
146
|
+
beforeNavigation, navigation);
|
|
147
|
+
// Suppress direct writes only when the accepted effective value matches.
|
|
148
|
+
// Overlap stays conservative except for explicit top-scope replacements:
|
|
149
|
+
// Session scalars/arrays mask every lower-scope value at that path.
|
|
150
|
+
const leaves: Array<{ scope: StateScope; path: (string | number)[]; value: JsonValue; artifact?: true }> = [];
|
|
151
|
+
const objects: typeof leaves = [];
|
|
152
|
+
const known = (path: readonly (string | number)[]): JsonValue | undefined => {
|
|
153
|
+
let value: JsonValue | undefined = this.view?.state;
|
|
154
|
+
for (const part of path) {
|
|
155
|
+
if (value === undefined || value === null || typeof value !== "object" || !Object.hasOwn(value, part)) return undefined;
|
|
156
|
+
value = (value as Record<string | number, JsonValue>)[part];
|
|
157
|
+
}
|
|
158
|
+
return value;
|
|
159
|
+
};
|
|
160
|
+
const visit = (scope: StateScope, value: JsonValue, path: (string | number)[]) => {
|
|
161
|
+
if (isObject(value) && Object.keys(value).length > 0) {
|
|
162
|
+
// Diff may coalesce a newly created/replaced object at this path.
|
|
163
|
+
// Keep its authored value without widening the overlap frontier.
|
|
164
|
+
objects.push({ scope, path, value });
|
|
165
|
+
const basis = known(path);
|
|
166
|
+
const entries = Object.entries(value);
|
|
167
|
+
const indexed = Array.isArray(basis) && entries.every(([key]) => {
|
|
168
|
+
if (!/^\[(0|[1-9]\d*)\]$/.test(key)) return false;
|
|
169
|
+
const index = Number(key.slice(1, -1));
|
|
170
|
+
return Number.isSafeInteger(index) && index < basis.length;
|
|
171
|
+
});
|
|
172
|
+
for (const [key, child] of entries) visit(scope, child,
|
|
173
|
+
[...path, indexed ? Number(key.slice(1, -1)) : key]);
|
|
174
|
+
} else leaves.push({ scope, path, value });
|
|
175
|
+
};
|
|
176
|
+
for (const scope of ["global", "cwd", "session"] as const) for (const [plane, value] of Object.entries(patches[scope] ?? {})) {
|
|
177
|
+
if (plane === "lazy") continue;
|
|
178
|
+
if (plane === "artifacts" && isObject(value)) {
|
|
179
|
+
for (const [path, card] of Object.entries(value)) leaves.push({ scope, path: ["artifacts", path], value: card, artifact: true });
|
|
180
|
+
} else visit(scope, value as JsonValue, [plane]);
|
|
181
|
+
}
|
|
182
|
+
const prefix = (a: readonly (string | number)[], b: readonly (string | number)[]) =>
|
|
183
|
+
a.length <= b.length && a.every((part, index) => part === b[index]);
|
|
184
|
+
updates.effective = updates.effective.filter((entry) => {
|
|
185
|
+
const matches = ({ path }: typeof leaves[number]) => path.length === entry.path.length && prefix(path, entry.path);
|
|
186
|
+
const authored = leaves.findLast(matches) ?? objects.findLast(matches);
|
|
187
|
+
if (!authored) return true;
|
|
188
|
+
const sessionReplacement = authored.scope === "session" && authored.value !== null && !isObject(authored.value);
|
|
189
|
+
if (!sessionReplacement && leaves.some(({ scope, path }) => scope !== authored.scope && (prefix(path, authored.path) || prefix(authored.path, path)))) return true;
|
|
190
|
+
if (authored.value !== null) {
|
|
191
|
+
if (authored.artifact) {
|
|
192
|
+
// Projected authored fields merge into the communicated card. Hints
|
|
193
|
+
// are not authored; keeping one is predictable, changing it is not.
|
|
194
|
+
const prior = known(entry.path);
|
|
195
|
+
const card = projectArtifactForModel(authored.value);
|
|
196
|
+
if (!isObject(card)) return true;
|
|
197
|
+
let expected: JsonValue = card;
|
|
198
|
+
if (isObject(prior)) {
|
|
199
|
+
try { expected = applyPatch(prior, card); }
|
|
200
|
+
catch {
|
|
201
|
+
// Canonical acceptance already succeeded. A masked effective
|
|
202
|
+
// array may reject an index valid in the authored scope.
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return !("value" in entry && sameJson(entry.value, expected));
|
|
207
|
+
}
|
|
208
|
+
return !("value" in entry && sameJson(entry.value, authored.value));
|
|
209
|
+
}
|
|
210
|
+
// A deletion cannot predict a fallback from effective state alone. It
|
|
211
|
+
// needs no echo only when the communicated and accepted values coincide.
|
|
212
|
+
if (!this.view) return true;
|
|
213
|
+
const before = known(entry.path);
|
|
214
|
+
return "value" in entry ? before === undefined || !sameJson(before, entry.value) : before !== undefined;
|
|
215
|
+
});
|
|
216
|
+
// A complete communicated key/kind catalog can predict non-deleting
|
|
217
|
+
// top-level lazy writes. Missing/over-budget catalogs, deletions and
|
|
218
|
+
// overlapping scopes cannot prove the post-patch navigation summary.
|
|
219
|
+
if (updates.lazy_navigation && this.view && (beforeNavigation.keys || !beforeNavigation.available) && navigation.keys) {
|
|
220
|
+
const expected = new Map(Object.entries(beforeNavigation.keys ?? {}));
|
|
221
|
+
let predictable = true;
|
|
222
|
+
const seen = new Set<string>();
|
|
223
|
+
for (const scope of ["global", "cwd", "session"] as const) for (const [key, value] of Object.entries(patches[scope]?.lazy ?? {})) {
|
|
224
|
+
if (seen.has(key) || value === null || isObject(value) && expected.get(key) === "array") predictable = false;
|
|
225
|
+
seen.add(key);
|
|
226
|
+
if (value !== null) expected.set(key, lazyValueKind(value));
|
|
227
|
+
}
|
|
228
|
+
if (predictable && seen.size > 0 && sameJson(Object.fromEntries(expected), navigation.keys)) delete updates.lazy_navigation;
|
|
229
|
+
}
|
|
230
|
+
if (this.view) this.view = { ...this.view, state, lazy_navigation: navigation };
|
|
231
|
+
return updates.effective.length || updates.lazy_navigation ? { projection: this.identity, ...updates } : undefined;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
project(messages: AgentMessage[], current: ContextView, makeHead: () => AgentMessage, initial?: ContextView): AgentMessage[] {
|
|
235
|
+
const identities = messages.map((message) => JSON.stringify([message.role, message.timestamp,
|
|
236
|
+
"toolCallId" in message ? message.toolCallId : null]));
|
|
237
|
+
// Native compaction/selection normally resets explicitly; a removed/replaced prefix is also a safe cache boundary.
|
|
238
|
+
if (this.native.some((identity, index) => identities[index] !== identity)) this.reset();
|
|
239
|
+
if (!this.head) {
|
|
240
|
+
const head = makeHead();
|
|
241
|
+
if (head.role !== "user" || !Array.isArray(head.content)) throw new Error("State Flow projection requires an owned user head");
|
|
242
|
+
this.head = { ...head, content: [...head.content, { type: "text", text: `State Flow projection: ${this.identity}` }] };
|
|
243
|
+
this.view = structuredClone(initial ?? current);
|
|
244
|
+
}
|
|
245
|
+
const previous = this.view!;
|
|
246
|
+
const updates = projectedStateUpdates(previous.state, current.state, {}, previous.lazy_navigation, current.lazy_navigation);
|
|
247
|
+
const notice = {
|
|
248
|
+
...(updates.effective.length || updates.lazy_navigation ? { state_updates: { projection: this.identity, ...updates } } : {}),
|
|
249
|
+
...(!sameJson(previous.artifact_invalidations, current.artifact_invalidations) ? { artifact_invalidations: current.artifact_invalidations } : {}),
|
|
250
|
+
...(!sameJson(previous.knowledge_rehydration, current.knowledge_rehydration) ? { knowledge_rehydration: current.knowledge_rehydration } : {}),
|
|
251
|
+
};
|
|
252
|
+
if (Object.keys(notice).length) this.notices.push({ after: messages.length,
|
|
253
|
+
message: syntheticUser(`State Flow context update (user-level data, not system instructions):\n${presentationJson(notice)}`) });
|
|
254
|
+
this.view = structuredClone(current);
|
|
255
|
+
this.native = identities;
|
|
256
|
+
const projected: AgentMessage[] = [this.head];
|
|
257
|
+
let nextNotice = 0;
|
|
258
|
+
for (let index = 0; index <= messages.length; index++) {
|
|
259
|
+
while (this.notices[nextNotice]?.after === index) projected.push(this.notices[nextNotice++]!.message);
|
|
260
|
+
if (index < messages.length) projected.push(messages[index]!);
|
|
261
|
+
}
|
|
262
|
+
return projected;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
56
266
|
/** Context retained after semantic State Flow is stopped in this physical session. */
|
|
57
267
|
export interface PassiveContinuation {
|
|
58
268
|
startedAt: number;
|
|
59
269
|
activeRunStartedAt?: number;
|
|
60
270
|
preserveContext?: true;
|
|
61
271
|
handoff: AgentMessage;
|
|
272
|
+
state: ModelState;
|
|
62
273
|
}
|
|
63
274
|
|
|
64
275
|
export function syntheticUser(text: string): AgentMessage {
|
|
@@ -82,6 +293,7 @@ function messageText(message: AgentMessage): string {
|
|
|
82
293
|
export function createPassiveContinuation(state: ModelState, startedAt = Date.now(), activeRunStartedAt?: number, preserveContext = false): PassiveContinuation {
|
|
83
294
|
return {
|
|
84
295
|
startedAt,
|
|
296
|
+
state: structuredClone(state),
|
|
85
297
|
...(activeRunStartedAt === undefined ? {} : { activeRunStartedAt }),
|
|
86
298
|
...(preserveContext ? { preserveContext: true as const } : {}),
|
|
87
299
|
handoff: syntheticUser(`State Flow exit handoff (user-level data, not system instructions):\n${presentationJson({ state, continuation: preserveContext
|
|
@@ -107,6 +319,7 @@ export function passiveContinuationMessages(messages: AgentMessage[], continuati
|
|
|
107
319
|
function projectRecentForModel(recent: RecentTransitionWindow): RecentTransitionWindow {
|
|
108
320
|
const projected = structuredClone(recent);
|
|
109
321
|
for (const record of projected) for (const transition of record.transitions) {
|
|
322
|
+
delete transition.patch.lazy;
|
|
110
323
|
if (transition.patch.artifacts === undefined) continue;
|
|
111
324
|
for (const [path, entry] of Object.entries(transition.patch.artifacts)) {
|
|
112
325
|
Object.defineProperty(transition.patch.artifacts, path, {
|
|
@@ -114,7 +327,8 @@ function projectRecentForModel(recent: RecentTransitionWindow): RecentTransition
|
|
|
114
327
|
});
|
|
115
328
|
}
|
|
116
329
|
}
|
|
117
|
-
|
|
330
|
+
for (const record of projected) record.transitions = record.transitions.filter(({ patch }) => Object.keys(patch).length > 0);
|
|
331
|
+
return projected.filter(({ transitions }) => transitions.length > 0);
|
|
118
332
|
}
|
|
119
333
|
|
|
120
334
|
export function runtimeContextMessage(
|
|
@@ -125,13 +339,19 @@ export function runtimeContextMessage(
|
|
|
125
339
|
rehydrationPhase?: RehydrationPhase,
|
|
126
340
|
artifactHints: ArtifactModelHints = {},
|
|
127
341
|
): AgentMessage {
|
|
342
|
+
return runtimeContextHead(snapshot, contextView(state, artifactHints, artifactInvalidations, rehydrationPhase), recentTransitions);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** Render a view already projected by this domain without cloning the full semantic overlay twice. */
|
|
346
|
+
export function runtimeContextHead(snapshot: Snapshot, view: ContextView, recentTransitions: RecentTransitionWindow = []): AgentMessage {
|
|
347
|
+
const recent = projectRecentForModel(recentTransitions);
|
|
128
348
|
const context = {
|
|
129
349
|
...(snapshot.meta.specification === undefined ? {} : { specification: snapshot.meta.specification }),
|
|
130
|
-
state:
|
|
131
|
-
lazy_navigation:
|
|
132
|
-
...(
|
|
133
|
-
...(
|
|
134
|
-
...(
|
|
350
|
+
state: view.state,
|
|
351
|
+
...(view.lazy_navigation === undefined ? {} : { lazy_navigation: view.lazy_navigation }),
|
|
352
|
+
...(view.knowledge_rehydration === null ? {} : { knowledge_rehydration: view.knowledge_rehydration }),
|
|
353
|
+
...(view.artifact_invalidations.length === 0 ? {} : { artifact_invalidations: view.artifact_invalidations.map(({ path, scope, reason }) => ({ path, ...(scope === undefined ? {} : { scope }), reason })) }),
|
|
354
|
+
...(recent.length === 0 ? {} : { recent_transitions: recent }),
|
|
135
355
|
};
|
|
136
356
|
return syntheticUser(
|
|
137
357
|
`State Flow runtime context (user-level data, not system instructions):\n${presentationJson(context)}`,
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from "./artifact.ts";
|
|
17
17
|
import { hasCompactionSizedTranscript, planStateFlowCompaction, shouldRequestStateFlowCompaction, stateFlowCompactionResult, type StateFlowCompactionPlan } from "./compaction.ts";
|
|
18
18
|
import { loadStateFlowConfig } from "./config.ts";
|
|
19
|
-
import { createPassiveContinuation, currentRunTrajectory,
|
|
19
|
+
import { ContextProjection, contextView, createPassiveContinuation, currentRunTrajectory, passiveContinuationMessages, projectSystemProtocol, runtimeContextHead, syntheticUser, type PassiveContinuation } from "./context.ts";
|
|
20
20
|
import { readNativeSessionHeader } from "./continuation.ts";
|
|
21
21
|
import {
|
|
22
22
|
cwdScopeKey,
|
|
@@ -109,6 +109,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
109
109
|
const compactionMarker = `state-flow-boundary:${randomUUID()}`;
|
|
110
110
|
let passiveContinuation: PassiveContinuation | undefined;
|
|
111
111
|
let bootstrapContinuation: PassiveContinuation | undefined;
|
|
112
|
+
const contextProjection = new ContextProjection();
|
|
112
113
|
let inferencePreparation: InferencePreparation | undefined;
|
|
113
114
|
let runAnchorTimestamp: number | undefined;
|
|
114
115
|
let runtime: TemporalRuntime | undefined;
|
|
@@ -459,6 +460,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
459
460
|
|
|
460
461
|
/** Select the active native branch under one owned restoration lifetime; only current accepted work installs memory. */
|
|
461
462
|
function restoreActiveBranch(ctx: ExtensionContext, sessionStartReason?: unknown, notifyRecovery = true, startOwner?: AbortController): Promise<void> {
|
|
463
|
+
contextProjection.reset();
|
|
462
464
|
cancelBranchRestoration();
|
|
463
465
|
// Start-owned attachment/fork recovery keeps its owner; only accepted Start cancels Stop.
|
|
464
466
|
if (!startOwner) {
|
|
@@ -771,6 +773,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
771
773
|
const selected = runtime ??= createRuntime(ctx);
|
|
772
774
|
const acquiredArtifacts = structuredClone([...artifactReads.successful.values()]);
|
|
773
775
|
const acquiredSkills = structuredClone([...skillReads.successful.values()]);
|
|
776
|
+
const previousEffective = overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session);
|
|
774
777
|
return await selected.withPatchTransaction((transaction) => {
|
|
775
778
|
if (runtime !== selected) throw new Error("State Flow session selection changed while awaiting publication");
|
|
776
779
|
if (!passiveToolsAvailable()) throw new Error("State Flow tools are disabled by configuration");
|
|
@@ -800,9 +803,14 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
800
803
|
}
|
|
801
804
|
clearAcceptedAcquisitions(new Set(acquiredArtifacts.map(({ path }) => path)));
|
|
802
805
|
updateUi(ctx);
|
|
803
|
-
|
|
806
|
+
const updates = contextProjection.acceptPatch(previousEffective, overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session), patches, artifactHints);
|
|
807
|
+
const acknowledgement = changed
|
|
804
808
|
? `\nState materialized atomically at ${scopes.join("+")} scope${scopes.length === 1 ? "" : "s"}.`
|
|
805
|
-
: "\nState already current."
|
|
809
|
+
: "\nState already current.";
|
|
810
|
+
return { content: [
|
|
811
|
+
{ type: "text" as const, text: acknowledgement },
|
|
812
|
+
...(updates ? [{ type: "text" as const, text: `\n${presentationJson({ state_updates: updates })}` }] : []),
|
|
813
|
+
], details: { scopes, step: snapshot.meta.step, changed } };
|
|
806
814
|
}, signal);
|
|
807
815
|
} catch (error) {
|
|
808
816
|
let attempted: unknown;
|
|
@@ -908,6 +916,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
908
916
|
stopPersistenceError = undefined;
|
|
909
917
|
installScopeStates();
|
|
910
918
|
clearRunTransient();
|
|
919
|
+
contextProjection.reset();
|
|
911
920
|
passiveContinuation = undefined;
|
|
912
921
|
bootstrapContinuation = snapshot.meta.bootstrap ? continuation : undefined;
|
|
913
922
|
deferInferencePreparation();
|
|
@@ -993,6 +1002,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
993
1002
|
&& !shuttingDown && ctx.sessionManager.getSessionId() === owner && !snapshot.config.enabled;
|
|
994
1003
|
const superseded = (): StateFlowTelegramControlResult => ({ ok: false, message: "State Flow Stop was superseded" });
|
|
995
1004
|
const freezeHandoff = (): PassiveContinuation | undefined => {
|
|
1005
|
+
contextProjection.reset();
|
|
996
1006
|
const handoff = (current.config.enabled || stopPersistenceError) && selected?.view && current.meta.validation?.attempt !== 0
|
|
997
1007
|
? createPassiveContinuation(
|
|
998
1008
|
projectModelState(overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session)),
|
|
@@ -1112,6 +1122,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1112
1122
|
(event.systemPromptOptions.sections ??= {}).state_flow = PASSIVE_MEMORY_PROTOCOL;
|
|
1113
1123
|
return;
|
|
1114
1124
|
}
|
|
1125
|
+
contextProjection.reset();
|
|
1115
1126
|
skillReads.clear();
|
|
1116
1127
|
artifactReads.clear();
|
|
1117
1128
|
cancelResponseReconciliation();
|
|
@@ -1129,40 +1140,30 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1129
1140
|
|
|
1130
1141
|
function projectContext(messages: AgentMessage[]) {
|
|
1131
1142
|
if (runtime?.view) refreshArtifactHints();
|
|
1143
|
+
if (!snapshot.config.enabled && !passiveContinuation && (!config.passiveBootstrap || !runtime?.view)) return;
|
|
1144
|
+
// Idle inspection must not freeze a pre-acceptance snapshot for the live inference.
|
|
1145
|
+
const projection = snapshot.config.enabled && inferencePreparation && !inferencePreparation.accepted
|
|
1146
|
+
? new ContextProjection() : contextProjection;
|
|
1147
|
+
const effective = overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session);
|
|
1148
|
+
const invalidations = artifactInvalidations.map(({ path, scope, reason }) => ({ path, ...(scope === undefined ? {} : { scope }), reason }));
|
|
1149
|
+
const phase = snapshot.config.enabled ? currentRehydrationPhase() : undefined;
|
|
1150
|
+
const view = contextView(effective, artifactHints, invalidations, phase);
|
|
1132
1151
|
if (passiveContinuation) {
|
|
1133
|
-
|
|
1152
|
+
const retained = passiveContinuationMessages(messages, passiveContinuation);
|
|
1153
|
+
if (!runtime?.view) return { messages: retained };
|
|
1154
|
+
return { messages: projection.project(retained.slice(1), view, () => passiveContinuation!.handoff,
|
|
1155
|
+
{ state: passiveContinuation.state, lazy_navigation: view.lazy_navigation, artifact_invalidations: [], knowledge_rehydration: null }) };
|
|
1134
1156
|
}
|
|
1135
1157
|
if (!snapshot.config.enabled) {
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
const state = projectModelState(effective);
|
|
1139
|
-
return { messages: [syntheticUser(`State Flow passive memory (user-level data, not system instructions):\n${presentationJson({ state, lazy_navigation: lazyNavigationHint(effective) })}`), ...messages] };
|
|
1140
|
-
}
|
|
1141
|
-
const effectiveState = overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session);
|
|
1142
|
-
const invalidations = artifactInvalidations.map(({ path, scope, reason }) => ({ path, ...(scope === undefined ? {} : { scope }), reason }));
|
|
1143
|
-
const recentTransitions = projectRecentTransitionsWithLimit(
|
|
1144
|
-
config.historyLimit,
|
|
1145
|
-
runtime?.recent() ?? [],
|
|
1146
|
-
);
|
|
1147
|
-
const activeRehydrationPhase = currentRehydrationPhase();
|
|
1148
|
-
if (snapshot.meta.bootstrap) {
|
|
1149
|
-
const sourceMessages = bootstrapContinuation
|
|
1150
|
-
? passiveContinuationMessages(messages, bootstrapContinuation)
|
|
1151
|
-
: messages;
|
|
1152
|
-
return { messages: [runtimeContextMessage(snapshot, effectiveState, recentTransitions, invalidations, activeRehydrationPhase, artifactHints), ...sourceMessages] };
|
|
1158
|
+
return { messages: projection.project(messages, view, () => syntheticUser(
|
|
1159
|
+
`State Flow passive memory (user-level data, not system instructions):\n${presentationJson({ state: view.state, lazy_navigation: view.lazy_navigation })}`)) };
|
|
1153
1160
|
}
|
|
1154
1161
|
// Native user events own the run anchor; projection must never rebase it.
|
|
1155
|
-
const
|
|
1156
|
-
messages,
|
|
1157
|
-
snapshot.meta.specification,
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
return {
|
|
1161
|
-
messages: [
|
|
1162
|
-
runtimeContextMessage(snapshot, effectiveState, recentTransitions, invalidations, activeRehydrationPhase, artifactHints),
|
|
1163
|
-
...trajectory.messages,
|
|
1164
|
-
],
|
|
1165
|
-
};
|
|
1162
|
+
const source = snapshot.meta.bootstrap
|
|
1163
|
+
? bootstrapContinuation ? passiveContinuationMessages(messages, bootstrapContinuation) : messages
|
|
1164
|
+
: currentRunTrajectory(messages, snapshot.meta.specification, runAnchorTimestamp).messages;
|
|
1165
|
+
return { messages: projection.project(source, view, () => runtimeContextHead(snapshot, view,
|
|
1166
|
+
projectRecentTransitionsWithLimit(config.historyLimit, runtime?.recent() ?? []))) };
|
|
1166
1167
|
}
|
|
1167
1168
|
|
|
1168
1169
|
function prepareContext(messages: AgentMessage[], ctx: ExtensionContext): ReturnType<typeof projectContext> | Promise<ReturnType<typeof projectContext>> {
|
|
@@ -1281,6 +1282,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1281
1282
|
snapshot = nextSnapshot;
|
|
1282
1283
|
installScopeStates();
|
|
1283
1284
|
responseCommitted = true;
|
|
1285
|
+
contextProjection.reset();
|
|
1284
1286
|
if (publication?.changed) recordPublication(publication, ctx);
|
|
1285
1287
|
appendCheckpoint();
|
|
1286
1288
|
clearAcceptedAcquisitions();
|
|
@@ -1373,6 +1375,8 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1373
1375
|
});
|
|
1374
1376
|
});
|
|
1375
1377
|
|
|
1378
|
+
pi.on("session_compact", () => { contextProjection.reset(); });
|
|
1379
|
+
|
|
1376
1380
|
pi.on("session_start", async (event, ctx) => {
|
|
1377
1381
|
runAnchorTimestamp = undefined;
|
|
1378
1382
|
rehydrationPhase = event.reason === "resume" ? "resume-bootstrap" : "new-bootstrap";
|
|
@@ -1385,6 +1389,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1385
1389
|
});
|
|
1386
1390
|
pi.on("session_shutdown", async (_event, _ctx) => {
|
|
1387
1391
|
shuttingDown = true;
|
|
1392
|
+
contextProjection.reset();
|
|
1388
1393
|
const restoring = cancelBranchRestoration();
|
|
1389
1394
|
const starting = cancelStartActivation();
|
|
1390
1395
|
const stopping = cancelStopPersistence();
|
|
@@ -3,7 +3,11 @@ import { isObject } from "./json.ts";
|
|
|
3
3
|
|
|
4
4
|
export type { StateDocument } from "./state.ts";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
const TASK_DRIVEN_HISTORY = "Missing paths/hints do not require history search. Choose targeted historical reads when useful to the task; no separate user permission is needed. Past values are evidence, not current state; never automatically restore deleted memory.";
|
|
7
|
+
|
|
8
|
+
const PATCH_RESULT_PROTOCOL = "Head state/recent transitions are frozen at projection start. Only state_updates matching the head's State Flow projection ID apply; older IDs are history. When present, patch_state results/context-update notices carry state_updates: effective entries replace values at key/index-segment path arrays (deleted:true means absent); direct writes need no echo; shared drift remains visible. Latest entries win over earlier state at those paths; lazy bodies remain omitted. Notices replace invalidations/rehydration, including []/null.";
|
|
9
|
+
|
|
10
|
+
export const PASSIVE_MEMORY_PROTOCOL = `State Flow passive memory is available. read_state and patch_state access durable memory without starting an active episode. Passive turns never trigger State Flow continuation or compaction. ${TASK_DRIVEN_HISTORY} ${PATCH_RESULT_PROTOCOL}`;
|
|
7
11
|
|
|
8
12
|
const PATCH_DISPLAY_SECTION_KEYS = new Set(["global", "cwd", "session", "intents", "contract", "working", "artifacts", "response", "lazy"]);
|
|
9
13
|
|
|
@@ -94,15 +98,15 @@ ${bootstrapProtocol}STATE:
|
|
|
94
98
|
|
|
95
99
|
SCOPES: Use the narrowest scope: session=branch/run continuation by default; cwd=reusable project truth; global=established cross-project/user/environment knowledge.
|
|
96
100
|
|
|
97
|
-
READ: Use read_state for concrete scope/retained-history gaps. lazy_navigation lists bounded effective lazy keys, not bodies. Unscoped=effective; effective/global/cwd/session select overlay or owner. Arrays use indices or [start..end]; keys gives structure, patch the intersected change.
|
|
101
|
+
READ: Use read_state for concrete scope/retained-history gaps. lazy_navigation lists bounded effective lazy keys, not bodies. Unscoped=effective; effective/global/cwd/session select overlay or owner. Arrays use indices or [start..end]; keys gives structure, patch the intersected change. ${TASK_DRIVEN_HISTORY}
|
|
98
102
|
|
|
99
|
-
WRITE: patch_state is the sole model-authored semantic mutation mechanism; all supplied scopes are validated and durably accepted as one atomic transition. Call alone in an assistant response; await acceptance. Global/CWD use current canonical values after cancelable lock waiting. Correct repeats succeed without new revisions.
|
|
103
|
+
WRITE: patch_state is the sole model-authored semantic mutation mechanism; all supplied scopes are validated and durably accepted as one atomic transition. Call alone in an assistant response; await acceptance. Global/CWD use current canonical values after cancelable lock waiting. Correct repeats succeed without new revisions. ${PATCH_RESULT_PROTOCOL}
|
|
100
104
|
|
|
101
105
|
PATCH: Use global/cwd/session object patches for material updates, not acknowledgments. Omit empty scopes. artifacts/contract/working/intents are objects; lazy is ordinary JSON. Omitted fields persist. Never patch runtime config/meta/response. Objects merge recursively; arrays/primitives replace. An object containing only canonical "[N]" keys recursively patches array elements. Indexed deletion is forbidden; nested object null deletes; materialized null is forbidden.
|
|
102
106
|
|
|
103
107
|
MEMORY: Treat every patch as reconciliation rather than append-only notes: merge superseded fragments, remove obsolete progress. Preserve commitments, open questions, consequential results and exact continuation; distinguish requirements, decisions, observations, conclusions and hypotheses. Exclude secrets, raw history, transient progress, speculation and unsupported claims; retain decision-relevant uncertainty. Curate touched state; cleanup and scope reviews require an explicit user request. Proven moves use targeted read_state and one atomic multi-scope patch, then verify both owners. External transfers need verified acceptance before deletion. Never invent memory changes.
|
|
104
108
|
|
|
105
|
-
REFS: State refs use {"$ref":"cwd.lazy.plan"} or \`$cwd.lazy.plan\` in text. Resolve only when needed; infer no authority, hydration, execution, or completion.
|
|
109
|
+
REFS: State refs use {"$ref":"cwd.lazy.plan"} or \`$cwd.lazy.plan\` in text. Resolve only when needed; infer no authority, hydration, execution, or completion. Hint paths are current reference owners, not relocated targets or proof of staleness. Fix proven stale refs only as needed; never scan refs or history offsets.
|
|
106
110
|
|
|
107
111
|
ACQUISITION: Read only for a concrete gap, exact source/edit, invalidation, contradiction/failure, or explicit request; changed source fingerprints require rereading.
|
|
108
112
|
ARTIFACTS: Compile acquired invalidated artifacts at artifacts[exact path] in the reported scope (global/cwd/session), with a description; never relocate or invent global copies. Runtime owns all artifact/Skill provenance.
|
|
@@ -187,7 +187,7 @@ function missingReferenceHint(view: TemporalState, path: string, historyLimit: n
|
|
|
187
187
|
if (sources.length === 0) return undefined;
|
|
188
188
|
return [{
|
|
189
189
|
type: "dangling-reference",
|
|
190
|
-
message: `
|
|
190
|
+
message: `The requested value is unavailable in the selected state. These current values reference that path, not a verified new location. Use this evidence if relevant to the task${truncated ? "; additional reference owners may exist beyond the bounded scan" : ""}.`,
|
|
191
191
|
paths: sources.map(({ path: sourcePath }) => sourcePath),
|
|
192
192
|
}];
|
|
193
193
|
}
|
|
@@ -32,7 +32,9 @@ Scopes overlay `global → cwd → session`: cross-project, project, branch/run.
|
|
|
32
32
|
|
|
33
33
|
## Read
|
|
34
34
|
|
|
35
|
-
Reuse sufficient visible state. `
|
|
35
|
+
Reuse sufficient visible state. The memory head and its recent transitions are frozen at projection start. Apply later `state_updates` only when their `projection` matches the head's `State Flow projection:` ID; older native results remain historical. Effective update paths are key/index arrays whose values replace that path, while `deleted: true` means absence. Current notices can replace invalidation lists or rehydration phase, including clearing them with `[]` or `null`; lazy bodies still require explicit reads.
|
|
36
|
+
|
|
37
|
+
`read_state` accepts `path` or `paths`, never both. Multi-path reads succeed or fail together. Projections: `value` (default), `keys` (structure), `patch` (intersecting change at the selected boundary).
|
|
36
38
|
|
|
37
39
|
Example arguments:
|
|
38
40
|
|
|
@@ -44,7 +46,9 @@ Example arguments:
|
|
|
44
46
|
{"paths":["cwd.working","session.working"]}
|
|
45
47
|
```
|
|
46
48
|
|
|
47
|
-
Unscoped paths use effective state. `cwd[1].working` reads the preceding causal boundary. Materialized-history and scope patch-history paths such as `cwd.patches[1]` share the configured `historyLimit` bound (default 7) and require actually retained history. Lowering the limit folds excess tails without erasing current state; increasing it does not reconstruct discarded history. Array ranges such as `cwd.lazy.checks[0..3]` exclude the endpoint and require existing elements. Missing paths
|
|
49
|
+
Unscoped paths use effective state. `cwd[1].working` reads the preceding causal boundary. Materialized-history and scope patch-history paths such as `cwd.patches[1]` share the configured `historyLimit` bound (default 7) and require actually retained history. Lowering the limit folds excess tails without erasing current state; increasing it does not reconstruct discarded history. Array ranges such as `cwd.lazy.checks[0..3]` exclude the endpoint and require existing elements. Missing paths are unavailable; inspect parent keys only when needed for the task. Missing history is not empty history. Read `lazy` explicitly. Treat structured `$ref` values and `$`-prefixed `read_state` paths inside ordinary strings, such as `$effective.lazy.memory[7]`, as semantic-state references. Other resources retain their native locators. Resolve any reference through the appropriate read/tool only when needed. Neither form proves authority or existence, hydrates, or executes anything. Never scan or resolve references merely to test them. A missing single value path with exact durable sources returns `{value:null, hint:[{type:"dangling-reference", message, paths}]}`. Treat `hint` as top-level diagnostic metadata, never as the requested state: its message is descriptive and conditional; its paths are runtime-verified current reference owners, not verified new locations of the target. The hint proves provenance rather than staleness and is absent when no current durable source matches; keys, patch, and batch reads keep all-or-error behavior. Only then inspect ownership as needed and patch a proven stale owning value while preserving its surrounding meaning. Effective absence, inaccessible external resources, and transient failures are not proof.
|
|
50
|
+
|
|
51
|
+
Missing paths or runtime hints alone do not require historical search. The agent may choose a targeted historical read when a previous value is useful to the current task, without separate user permission. Otherwise continue without searching. Use found values as historical evidence, not automatically as current state; never automatically restore deleted memory. Do not scan all offsets, hydrate automatically or request repair inference. A hint does not prove prior existence, retained history or relocation. A proven stale reference may be repaired within touched work without resurrecting its target. Automatic state and recent-transition projections omit lazy bodies; bounded `lazy_navigation` preserves structure, and explicit current/historical reads still return requested lazy values or patches.
|
|
48
52
|
|
|
49
53
|
## Write
|
|
50
54
|
|
|
@@ -22,9 +22,11 @@ Follow the installed runtime contract. This registered Skill follows its Pi sour
|
|
|
22
22
|
1. **Limit the review.** Address the requested scope. A completed phase may motivate recommending cleanup, not starting it without a request. For a whole-state cleanup, inspect global, CWD, and session ownership explicitly; for a narrower request, inspect only affected owners. Use targeted reads for gaps, contradictions, ownership, or verification; do not rerun the project.
|
|
23
23
|
2. **Classify.** Put user requirements and binding confirmed decisions in `contract`, observations, assistant conclusions, and unresolved work in `working`, chosen actions in `intents`, and inactive reusable detail in `lazy`. Never give an assistant conclusion user authority. Remove fulfilled, abandoned, superseded, or impossible intents; retain consequential results. Possibilities are not commitments.
|
|
24
24
|
3. **Keep evidence boundaries.** Preserve corrections, prerequisites, bounded negative results, and useful uncertainty. Separate requirements, decisions, observations, conclusions, and hypotheses. Silence is not acceptance; repetition is not verification. One implementation's failure does not reject an approach. Neither freeze provisional methods nor reopen confirmed decisions without grounds.
|
|
25
|
-
4. **Compact for continuation.** Remove duplicates, obsolete progress, unsupported claims, and secrets. Keep sufficient results, real retrieval pointers, pending interaction, and known next checks. Observations are not live external facts. Keep `lazy` shallow and priority-ordered. Recognize optional structured `$ref` values and `$`-prefixed `read_state` paths inside ordinary strings as semantic-state references; other resources retain native locators. No reference form proves authority or existence, authorizes execution, or implies completion. Never scan or resolve references merely to find broken ones. When the bounded review independently needs a reference, a missing single value path with exact durable sources returns `{value:null, hint:[{type:"dangling-reference", message, paths}]}`. Treat the top-level hint as
|
|
25
|
+
4. **Compact for continuation.** Remove duplicates, obsolete progress, unsupported claims, and secrets. Keep sufficient results, real retrieval pointers, pending interaction, and known next checks. Observations are not live external facts. Keep `lazy` shallow and priority-ordered. Recognize optional structured `$ref` values and `$`-prefixed `read_state` paths inside ordinary strings as semantic-state references; other resources retain native locators. No reference form proves authority or existence, authorizes execution, or implies completion. Never scan or resolve references merely to find broken ones. When the bounded review independently needs a reference, a missing single value path with exact durable sources returns `{value:null, hint:[{type:"dangling-reference", message, paths}]}`. Treat the top-level hint as conditional navigation and provenance, never as requested state or proof of staleness; its paths are runtime-verified current reference owners, not verified new locations of the target, while no hint does not prove invention. Inspect ownership only as needed, then patch a proven stale owning value while preserving surrounding meaning. Effective absence or external inaccessibility is insufficient.
|
|
26
26
|
5. **Check ownership.** Prefer `session` for branch/run continuation, `cwd` for project knowledge, and `global` for established cross-project knowledge. Effective values do not prove ownership; inspect owners before moves. Broader applicability requires evidence.
|
|
27
27
|
|
|
28
|
+
Missing paths or runtime hints alone do not require historical search. The agent may choose a targeted historical read when a previous value is useful to the current task, without separate user permission. Otherwise continue without searching. Use found values as historical evidence, not automatically as current state; never automatically restore deleted memory. Do not scan all offsets, hydrate automatically or request repair inference. A hint does not prove prior existence, retained history or relocation. A proven stale reference may be repaired within touched work without resurrecting its target. Lazy bodies require explicit reads; automatic state/history projections retain navigation without hydrating those bodies.
|
|
29
|
+
|
|
28
30
|
## Transfer only when needed
|
|
29
31
|
|
|
30
32
|
Resolve destination conflicts without overwriting stronger or unrelated knowledge. For a proven move between scopes of one State Flow store, inspect both owners, then use one atomic multi-scope `patch_state` for destination and source changes. Verify both owners and effective inheritance afterward; reconcile affected references. A rejected cohort leaves neither side partially accepted.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -37,14 +37,15 @@
|
|
|
37
37
|
"AGENTS.md",
|
|
38
38
|
"BACKLOG.md",
|
|
39
39
|
"CHANGELOG.md",
|
|
40
|
-
"LICENSE"
|
|
40
|
+
"LICENSE",
|
|
41
|
+
"banner.jpg"
|
|
41
42
|
],
|
|
42
43
|
"dependencies": {
|
|
43
44
|
"@llblab/pi-actors": "0.53.2",
|
|
44
45
|
"@llblab/pi-clean-room": "0.2.0",
|
|
45
46
|
"@llblab/pi-codex-usage": "0.10.0",
|
|
46
47
|
"@llblab/pi-grow-loop": "0.8.2",
|
|
47
|
-
"@llblab/pi-state-flow": "0.
|
|
48
|
+
"@llblab/pi-state-flow": "0.21.0",
|
|
48
49
|
"@llblab/pi-telegram": "0.51.4",
|
|
49
50
|
"@llblab/skills": "1.15.0"
|
|
50
51
|
},
|
|
@@ -72,7 +73,8 @@
|
|
|
72
73
|
"./node_modules/@llblab/pi-state-flow/dist/skills",
|
|
73
74
|
"./node_modules/@llblab/pi-telegram/dist/skills",
|
|
74
75
|
"./node_modules/@llblab/skills/"
|
|
75
|
-
]
|
|
76
|
+
],
|
|
77
|
+
"image": "https://raw.githubusercontent.com/llblab/pi-kit/main/banner.jpg"
|
|
76
78
|
},
|
|
77
79
|
"bundleDependencies": [
|
|
78
80
|
"@llblab/pi-actors",
|