@voybio/ace-swarm 2.4.8 → 2.4.10
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 +52 -14
- package/README.md +14 -10
- package/assets/.agents/ACE/ACE_coders/AGENTS.md +23 -30
- package/assets/.agents/ACE/ACE_coders/INSTRUCTIONS.md +21 -30
- package/assets/.agents/skills/landing-review-watcher/SKILL.md +1 -1
- package/assets/.agents/skills/learn-from-work/SKILL.md +41 -0
- package/assets/agent-state/ACE_WORKFLOW.md +25 -2
- package/assets/agent-state/MODULES/schemas/ACE_RUNTIME_PROFILE.schema.json +143 -2
- package/assets/agent-state/MODULES/schemas/RUNTIME_EXECUTOR_SESSION_REGISTRY.schema.json +2 -2
- package/assets/agent-state/MODULES/schemas/SWARM_HANDOFF.schema.json +10 -0
- package/assets/eval-battery/battery.json +1 -1
- package/assets/instructions/ACE_Coder.instructions.md +9 -13
- package/assets/scripts/ace-hook-dispatch.mjs +16 -411
- package/assets/tasks/SWARM_HANDOFF.example_ui_to_coders.json +1 -2
- package/assets/vendor/README.md +9 -0
- package/assets/vendor/vericify-reader.mjs +272 -0
- package/assets/vendor/vericify-reader.provenance.json +10 -0
- package/dist/ace-autonomy.d.ts +58 -0
- package/dist/ace-autonomy.js +211 -6
- package/dist/ace-internal-tools.d.ts +1 -0
- package/dist/ace-internal-tools.js +3 -3
- package/dist/ace-server-instructions.d.ts +3 -3
- package/dist/ace-server-instructions.js +46 -192
- package/dist/cli.js +44 -6
- package/dist/experience-reflector.d.ts +7 -0
- package/dist/experience-reflector.js +39 -0
- package/dist/goal-engine.d.ts +64 -7
- package/dist/goal-engine.js +197 -41
- package/dist/handoff-registry.d.ts +10 -0
- package/dist/handoff-registry.js +59 -0
- package/dist/helpers/artifacts.d.ts +6 -2
- package/dist/helpers/artifacts.js +8 -6
- package/dist/helpers/bootstrap.js +13 -20
- package/dist/helpers/path-utils.js +2 -0
- package/dist/helpers/store-resolution.js +18 -3
- package/dist/helpers/workspace-root.d.ts +2 -0
- package/dist/helpers/workspace-root.js +9 -0
- package/dist/helpers.d.ts +1 -0
- package/dist/helpers.js +1 -0
- package/dist/hermes/bridge-protocol.d.ts +1 -1
- package/dist/hermes/session-manager.d.ts +17 -0
- package/dist/hermes/session-manager.js +67 -14
- package/dist/host-goal-engine.d.ts +138 -0
- package/dist/host-goal-engine.js +793 -0
- package/dist/host-goal-status.d.ts +113 -0
- package/dist/host-goal-status.js +175 -0
- package/dist/host-learning.d.ts +68 -0
- package/dist/host-learning.js +115 -0
- package/dist/host-observation.d.ts +12 -0
- package/dist/host-observation.js +91 -0
- package/dist/host-plan-revision.d.ts +14 -0
- package/dist/host-plan-revision.js +101 -0
- package/dist/host-verification-job.d.ts +12 -0
- package/dist/host-verification-job.js +183 -0
- package/dist/host-work-context.d.ts +12 -0
- package/dist/host-work-context.js +8 -0
- package/dist/job-scheduler.d.ts +58 -13
- package/dist/job-scheduler.js +298 -48
- package/dist/job-worker.d.ts +3 -1
- package/dist/job-worker.js +10 -3
- package/dist/kanban.d.ts +4 -1
- package/dist/kanban.js +44 -16
- package/dist/learned-artifacts.d.ts +64 -0
- package/dist/learned-artifacts.js +321 -0
- package/dist/learning-controls.d.ts +142 -0
- package/dist/learning-controls.js +135 -0
- package/dist/learning-input-context.d.ts +9 -0
- package/dist/learning-input-context.js +62 -0
- package/dist/learning-lifecycle.d.ts +10 -0
- package/dist/learning-lifecycle.js +11 -0
- package/dist/learning-overlays.d.ts +3 -0
- package/dist/learning-overlays.js +17 -0
- package/dist/learning-policy.d.ts +40 -0
- package/dist/learning-policy.js +38 -0
- package/dist/learning-records.d.ts +35 -0
- package/dist/learning-records.js +17 -0
- package/dist/learning-scorecard.d.ts +82 -0
- package/dist/learning-scorecard.js +86 -0
- package/dist/learning-serving.d.ts +29 -0
- package/dist/learning-serving.js +134 -0
- package/dist/linked-work.d.ts +9 -0
- package/dist/linked-work.js +28 -0
- package/dist/local-model-runtime.js +1 -1
- package/dist/mcp-command-progress.d.ts +16 -0
- package/dist/mcp-command-progress.js +70 -0
- package/dist/model-bridge.d.ts +14 -13
- package/dist/model-bridge.js +73 -41
- package/dist/orchestrator-supervisor.d.ts +111 -6
- package/dist/orchestrator-supervisor.js +67 -43
- package/dist/plan-proposal.js +3 -3
- package/dist/playbook-curator.d.ts +1 -0
- package/dist/playbook-curator.js +11 -9
- package/dist/process-identity.d.ts +8 -0
- package/dist/process-identity.js +38 -0
- package/dist/progress-command.d.ts +34 -0
- package/dist/progress-command.js +153 -0
- package/dist/prompts.js +15 -15
- package/dist/resources.js +2 -2
- package/dist/runtime-command.d.ts +8 -1
- package/dist/runtime-command.js +48 -7
- package/dist/runtime-executor.js +33 -11
- package/dist/runtime-profile-syntax.d.ts +6 -0
- package/dist/runtime-profile-syntax.js +121 -0
- package/dist/runtime-profile.d.ts +37 -5
- package/dist/runtime-profile.js +36 -129
- package/dist/runtime-tool-specs.js +1 -1
- package/dist/safe-edit.d.ts +7 -3
- package/dist/safe-edit.js +40 -47
- package/dist/schemas.js +4 -2
- package/dist/server.d.ts +2 -16
- package/dist/server.js +14 -20
- package/dist/shared.d.ts +2 -0
- package/dist/shared.js +2 -0
- package/dist/skill-auditor.d.ts +19 -0
- package/dist/skill-auditor.js +32 -2
- package/dist/skill-catalog.d.ts +4 -1
- package/dist/skill-catalog.js +6 -5
- package/dist/store/ace-packed-store.d.ts +5 -1
- package/dist/store/ace-packed-store.js +33 -1
- package/dist/store/bootstrap-store.js +19 -12
- package/dist/store/cache-workspace.d.ts +8 -0
- package/dist/store/cache-workspace.js +94 -27
- package/dist/store/importer.js +11 -20
- package/dist/store/knowledge-bake.d.ts +3 -1
- package/dist/store/knowledge-bake.js +10 -1
- package/dist/store/legacy-vericify-posts.d.ts +8 -0
- package/dist/store/legacy-vericify-posts.js +76 -0
- package/dist/store/materializers/hook-context-materializer.d.ts +2 -1
- package/dist/store/materializers/hook-context-materializer.js +6 -2
- package/dist/store/materializers/host-file-materializer.js +2 -2
- package/dist/store/materializers/projection-manager.d.ts +4 -1
- package/dist/store/materializers/projection-manager.js +10 -3
- package/dist/store/materializers/todo-syncer.d.ts +39 -20
- package/dist/store/materializers/todo-syncer.js +208 -110
- package/dist/store/materializers/vericify-projector.js +15 -26
- package/dist/store/repositories/goal-repository.d.ts +51 -2
- package/dist/store/repositories/goal-repository.js +114 -48
- package/dist/store/repositories/playbook-repository.d.ts +41 -3
- package/dist/store/repositories/playbook-repository.js +61 -5
- package/dist/store/repositories/todo-repository.d.ts +8 -2
- package/dist/store/repositories/todo-repository.js +1 -1
- package/dist/store/repositories/vericify-repository.d.ts +4 -0
- package/dist/store/repositories/vericify-repository.js +46 -0
- package/dist/store/skills-install.d.ts +1 -0
- package/dist/store/skills-install.js +20 -16
- package/dist/store/source-preservation.d.ts +24 -0
- package/dist/store/source-preservation.js +79 -0
- package/dist/store/store-artifacts.js +8 -1
- package/dist/store/test-write-boundary.d.ts +16 -0
- package/dist/store/test-write-boundary.js +175 -0
- package/dist/store/types.d.ts +5 -1
- package/dist/store/write-coordinator.js +16 -0
- package/dist/task-plan-context.d.ts +11 -0
- package/dist/task-plan-context.js +59 -0
- package/dist/todo-state.d.ts +19 -9
- package/dist/todo-state.js +204 -321
- package/dist/tools-agent.d.ts +1 -1
- package/dist/tools-agent.js +38 -62
- package/dist/tools-files.d.ts +0 -3
- package/dist/tools-files.js +21 -11
- package/dist/tools-framework.js +16 -25
- package/dist/tools-goal.d.ts +2 -4
- package/dist/tools-goal.js +119 -16
- package/dist/tools-handoff.d.ts +3 -2
- package/dist/tools-handoff.js +53 -23
- package/dist/tools-lifecycle.js +1 -1
- package/dist/tools-memory.d.ts +0 -3
- package/dist/tools-memory.js +51 -6
- package/dist/tools-scheduler.js +20 -17
- package/dist/tools-todo.js +160 -148
- package/dist/vericify-bridge.d.ts +8 -0
- package/dist/vericify-bridge.js +18 -0
- package/dist/vericify-context.d.ts +4 -0
- package/dist/vericify-context.js +26 -5
- package/dist/vericify-native.d.ts +101 -0
- package/dist/vericify-native.js +221 -0
- package/dist/verification-worker.d.ts +2 -0
- package/dist/verification-worker.js +31 -0
- package/docs/mcp-tool-reference.md +50 -10
- package/docs/troubleshooting.md +29 -0
- package/package.json +19 -6
- package/scripts/dev/run-tests-isolated.mjs +165 -0
- package/scripts/dev/run_hermes_crispr_live.mjs +1 -0
- package/scripts/dev/test-isolation-guard.mjs +20 -0
- package/scripts/dev/test-preload.mjs +153 -0
- package/scripts/dev/test-source-inventory.mjs +31 -0
- package/docs/development.md +0 -46
package/dist/kanban.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type KanbanColumn = "backlog" | "blocked" | "in_progress" | "gated" | "done";
|
|
1
|
+
export type KanbanColumn = "backlog" | "blocked" | "in_progress" | "gated" | "done" | "cancelled";
|
|
2
2
|
export interface AceKanbanCard {
|
|
3
3
|
id: string;
|
|
4
4
|
title: string;
|
|
@@ -10,8 +10,10 @@ export interface AceKanbanBoard {
|
|
|
10
10
|
in_progress: AceKanbanCard[];
|
|
11
11
|
gated: AceKanbanCard[];
|
|
12
12
|
done: AceKanbanCard[];
|
|
13
|
+
cancelled: AceKanbanCard[];
|
|
13
14
|
}
|
|
14
15
|
export interface AceKanbanSnapshot {
|
|
16
|
+
refresh_scheduled?: boolean;
|
|
15
17
|
version: number;
|
|
16
18
|
generated_at: string;
|
|
17
19
|
trigger: string;
|
|
@@ -21,6 +23,7 @@ export interface AceKanbanSnapshot {
|
|
|
21
23
|
json_path: string;
|
|
22
24
|
}
|
|
23
25
|
export declare function renderKanbanHtml(snapshot: AceKanbanSnapshot): string;
|
|
26
|
+
export declare function formatKanbanRefresh(snapshot: AceKanbanSnapshot): string;
|
|
24
27
|
export declare function refreshKanbanArtifacts(trigger: string): AceKanbanSnapshot;
|
|
25
28
|
export declare function getOrRefreshKanbanSnapshot(trigger: string): AceKanbanSnapshot;
|
|
26
29
|
export declare function shouldAutoRefreshKanbanForPath(path: string): boolean;
|
package/dist/kanban.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { ACE_ROOT_REL, ACE_TASKS_ROOT_REL, safeRead, safeWrite, wsPath } from "./helpers.js";
|
|
1
|
+
import { ACE_ROOT_REL, ACE_TASKS_ROOT_REL, safeRead, safeWrite, wsPath, resolveWorkspaceRoot, withWorkspaceRoot } from "./helpers.js";
|
|
2
2
|
import { isReadError, slugify } from "./shared.js";
|
|
3
3
|
import { readHandoffRegistry } from "./handoff-registry.js";
|
|
4
4
|
import { readTodoState } from "./todo-state.js";
|
|
5
5
|
/** Debounce threshold in milliseconds. Skip re-render if within this window. */
|
|
6
6
|
const KANBAN_DEBOUNCE_MS = 2000;
|
|
7
|
-
|
|
7
|
+
const renderState = new Map();
|
|
8
8
|
const KANBAN_JSON_REL = "agent-state/kanban.json";
|
|
9
9
|
const KANBAN_HTML_REL = "agent-state/kanban.html";
|
|
10
|
-
const COLUMNS = ["backlog", "blocked", "in_progress", "gated", "done"];
|
|
10
|
+
const COLUMNS = ["backlog", "blocked", "in_progress", "gated", "done", "cancelled"];
|
|
11
11
|
function nowIso() {
|
|
12
12
|
return new Date().toISOString();
|
|
13
13
|
}
|
|
@@ -18,6 +18,7 @@ function emptyBoard() {
|
|
|
18
18
|
in_progress: [],
|
|
19
19
|
gated: [],
|
|
20
20
|
done: [],
|
|
21
|
+
cancelled: [],
|
|
21
22
|
};
|
|
22
23
|
}
|
|
23
24
|
function escapeHtml(input) {
|
|
@@ -236,7 +237,7 @@ export function renderKanbanHtml(snapshot) {
|
|
|
236
237
|
"@keyframes spin{to{transform:rotate(360deg)}}",
|
|
237
238
|
"h1{margin:0;font-family:'Space Grotesk','Avenir Next',sans-serif;font-size:24px}",
|
|
238
239
|
".stamp{margin:0;font-size:12px;opacity:.75}",
|
|
239
|
-
".grid{display:grid;grid-template-columns:repeat(
|
|
240
|
+
".grid{display:grid;grid-template-columns:repeat(6,minmax(220px,1fr));gap:12px}",
|
|
240
241
|
".column{background:rgba(255,255,255,.62);border:1px solid var(--line);border-radius:12px;min-height:220px}",
|
|
241
242
|
".column>header{display:flex;justify-content:space-between;align-items:center;padding:10px 12px;border-bottom:1px solid var(--line)}",
|
|
242
243
|
".column h2{margin:0;font-size:14px;text-transform:capitalize}",
|
|
@@ -265,16 +266,38 @@ export function renderKanbanHtml(snapshot) {
|
|
|
265
266
|
"</html>",
|
|
266
267
|
].join("");
|
|
267
268
|
}
|
|
269
|
+
export function formatKanbanRefresh(snapshot) {
|
|
270
|
+
return snapshot.refresh_scheduled
|
|
271
|
+
? `Kanban refresh scheduled: ${snapshot.html_path} (last generated ${snapshot.generated_at})`
|
|
272
|
+
: `Kanban updated: ${snapshot.html_path}`;
|
|
273
|
+
}
|
|
268
274
|
export function refreshKanbanArtifacts(trigger) {
|
|
269
|
-
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (
|
|
275
|
-
|
|
275
|
+
const root = resolveWorkspaceRoot();
|
|
276
|
+
const state = renderState.get(root) ?? { last: 0 };
|
|
277
|
+
const delay = KANBAN_DEBOUNCE_MS - (Date.now() - state.last);
|
|
278
|
+
const cached = readKanbanSnapshot();
|
|
279
|
+
if (delay > 0 && cached) {
|
|
280
|
+
if (state.timer)
|
|
281
|
+
clearTimeout(state.timer);
|
|
282
|
+
state.timer = setTimeout(() => withWorkspaceRoot(root, () => {
|
|
283
|
+
state.timer = undefined;
|
|
284
|
+
try {
|
|
285
|
+
renderKanbanNow(trigger);
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
state.last = 0;
|
|
289
|
+
console.error(`Kanban refresh failed for ${root}: ${error instanceof Error ? error.message : String(error)}`);
|
|
290
|
+
}
|
|
291
|
+
}), delay);
|
|
292
|
+
state.timer.unref();
|
|
293
|
+
renderState.set(root, state);
|
|
294
|
+
return { ...cached, refresh_scheduled: true };
|
|
276
295
|
}
|
|
277
|
-
|
|
296
|
+
if (state.timer)
|
|
297
|
+
clearTimeout(state.timer);
|
|
298
|
+
return renderKanbanNow(trigger);
|
|
299
|
+
}
|
|
300
|
+
function renderKanbanNow(trigger) {
|
|
278
301
|
const board = emptyBoard();
|
|
279
302
|
addTodoCards(board);
|
|
280
303
|
addStatusCard(board);
|
|
@@ -292,15 +315,17 @@ export function refreshKanbanArtifacts(trigger) {
|
|
|
292
315
|
in_progress: board.in_progress.length,
|
|
293
316
|
gated: board.gated.length,
|
|
294
317
|
done: board.done.length,
|
|
318
|
+
cancelled: board.cancelled.length,
|
|
295
319
|
},
|
|
296
320
|
html_path: wsPath(KANBAN_HTML_REL),
|
|
297
321
|
json_path: wsPath(KANBAN_JSON_REL),
|
|
298
322
|
};
|
|
299
323
|
safeWrite(KANBAN_HTML_REL, renderKanbanHtml(snapshot));
|
|
300
324
|
safeWrite(KANBAN_JSON_REL, JSON.stringify(snapshot, null, 2));
|
|
325
|
+
renderState.set(resolveWorkspaceRoot(), { last: Date.now() });
|
|
301
326
|
return snapshot;
|
|
302
327
|
}
|
|
303
|
-
|
|
328
|
+
function readKanbanSnapshot() {
|
|
304
329
|
const raw = safeRead(KANBAN_JSON_REL);
|
|
305
330
|
if (!isReadError(raw)) {
|
|
306
331
|
const parsed = tryParseJson(raw);
|
|
@@ -316,14 +341,17 @@ export function getOrRefreshKanbanSnapshot(trigger) {
|
|
|
316
341
|
version,
|
|
317
342
|
generated_at: generated,
|
|
318
343
|
trigger: savedTrigger,
|
|
319
|
-
board,
|
|
320
|
-
counts,
|
|
344
|
+
board: { ...emptyBoard(), ...board },
|
|
345
|
+
counts: { ...Object.fromEntries(COLUMNS.map((key) => [key, 0])), ...counts },
|
|
321
346
|
html_path: htmlPath,
|
|
322
347
|
json_path: jsonPath,
|
|
323
348
|
};
|
|
324
349
|
}
|
|
325
350
|
}
|
|
326
|
-
return
|
|
351
|
+
return undefined;
|
|
352
|
+
}
|
|
353
|
+
export function getOrRefreshKanbanSnapshot(trigger) {
|
|
354
|
+
return readKanbanSnapshot() ?? refreshKanbanArtifacts(trigger);
|
|
327
355
|
}
|
|
328
356
|
export function shouldAutoRefreshKanbanForPath(path) {
|
|
329
357
|
const normalized = path.replace(/\\/g, "/").replace(/^\.?\//, "");
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type LearnedArtifact } from './learning-records.js';
|
|
2
|
+
export { decodeLearned, type LearnedArtifact } from './learning-records.js';
|
|
3
|
+
import { type PlaybookScope, type PlaybookSection } from './store/repositories/playbook-repository.js';
|
|
4
|
+
import { type IAcePackedStore } from './store/types.js';
|
|
5
|
+
import type { GoalEngineDeps } from './goal-engine.js';
|
|
6
|
+
export interface LearnedPayload {
|
|
7
|
+
lessons?: Array<{
|
|
8
|
+
op: 'add' | 'edit' | 'retire';
|
|
9
|
+
bullet_id?: string;
|
|
10
|
+
section: PlaybookSection;
|
|
11
|
+
content: string;
|
|
12
|
+
scope: PlaybookScope;
|
|
13
|
+
episodes: string[];
|
|
14
|
+
retired_episodes?: string[];
|
|
15
|
+
}>;
|
|
16
|
+
skill?: {
|
|
17
|
+
op: 'add' | 'edit';
|
|
18
|
+
slug: string;
|
|
19
|
+
skill_md: string;
|
|
20
|
+
references?: Record<string, string>;
|
|
21
|
+
episodes: string[];
|
|
22
|
+
retired_episodes?: string[];
|
|
23
|
+
};
|
|
24
|
+
instruction?: {
|
|
25
|
+
role: string;
|
|
26
|
+
text: string;
|
|
27
|
+
episodes: string[];
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export interface LearningAcceptance {
|
|
31
|
+
ok: boolean;
|
|
32
|
+
ids: string[];
|
|
33
|
+
errors: string[];
|
|
34
|
+
reason_code?: string;
|
|
35
|
+
options?: Array<{
|
|
36
|
+
tool: string;
|
|
37
|
+
args: Record<string, unknown>;
|
|
38
|
+
why: string;
|
|
39
|
+
}>;
|
|
40
|
+
}
|
|
41
|
+
export declare function listLearnedArtifacts(root: string): LearnedArtifact[];
|
|
42
|
+
export declare function withLearnedStore<T>(root: string, apply: (store: IAcePackedStore) => Promise<T>): Promise<T>;
|
|
43
|
+
export declare function saveLearnedArtifact(root: string, item: LearnedArtifact): Promise<void>;
|
|
44
|
+
export declare function updateLearnedArtifact(root: string, key: string, update: (item: LearnedArtifact) => LearnedArtifact | undefined): Promise<void>;
|
|
45
|
+
export declare function prepareLearned(root: string, payload: LearnedPayload, context: {
|
|
46
|
+
goal_id: string;
|
|
47
|
+
step_id: string;
|
|
48
|
+
attempt: number;
|
|
49
|
+
}, deps?: GoalEngineDeps): Promise<{
|
|
50
|
+
result: LearningAcceptance;
|
|
51
|
+
publish: () => Promise<void>;
|
|
52
|
+
}>;
|
|
53
|
+
export declare function acceptLearned(root: string, payload: LearnedPayload, context: {
|
|
54
|
+
goal_id: string;
|
|
55
|
+
step_id: string;
|
|
56
|
+
attempt: number;
|
|
57
|
+
}, deps?: GoalEngineDeps): Promise<LearningAcceptance>;
|
|
58
|
+
export declare function regenerateLearnedProjection(root: string): Promise<void>;
|
|
59
|
+
export declare function ensureLearningStep(root: string, force?: {
|
|
60
|
+
scope: PlaybookScope;
|
|
61
|
+
reason: string;
|
|
62
|
+
}): Promise<void>;
|
|
63
|
+
export declare function reopenLearningCorrection(root: string, errors: string[]): Promise<void>;
|
|
64
|
+
//# sourceMappingURL=learned-artifacts.d.ts.map
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { learningLifecycle } from './learning-lifecycle.js';
|
|
2
|
+
import { decodeLearned } from './learning-records.js';
|
|
3
|
+
export { decodeLearned } from './learning-records.js';
|
|
4
|
+
import { readLearningPolicy, learningScopeKey, canonicalLearningScopeKey } from './learning-policy.js';
|
|
5
|
+
/** Checked learned artifacts live in existing playbook / knowledge owners. */
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
import { parse, stringify } from 'yaml';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { GoalRepository, defaultGoalBudgets } from './store/repositories/goal-repository.js';
|
|
10
|
+
import { createTaskPlan, amendTaskPlan } from './orchestrator-supervisor.js';
|
|
11
|
+
import { PlaybookRepository, withPlaybookTransaction } from './store/repositories/playbook-repository.js';
|
|
12
|
+
import { listStoreKeysSync, readStoreBlobSync, getWorkspaceStorePath } from './store/store-snapshot.js';
|
|
13
|
+
import { withStoreWriteCoordinator } from './store/write-coordinator.js';
|
|
14
|
+
import { openStore } from './store/ace-packed-store.js';
|
|
15
|
+
import { ContentSource } from './store/types.js';
|
|
16
|
+
import { operationalArtifactKey } from './store/store-artifacts.js';
|
|
17
|
+
import { listEpisodes, compactEpisode } from './host-learning.js';
|
|
18
|
+
import { exactRedGreen } from './experience-reflector.js';
|
|
19
|
+
import { proveLearnedSkill } from './skill-auditor.js';
|
|
20
|
+
import { ALL_AGENTS, DEFAULTS_SKILLS_ROOT } from './helpers/constants.js';
|
|
21
|
+
import { existsSync } from 'node:fs';
|
|
22
|
+
import { getShingles, calculateJaccard } from './semantic-hash.js';
|
|
23
|
+
const hash = (value) => createHash('sha256').update(JSON.stringify(value)).digest('hex').slice(0, 16);
|
|
24
|
+
function encode(item, front = {}) {
|
|
25
|
+
const { body, key: _, ...metadata } = item;
|
|
26
|
+
return `---\n${stringify({ ...front, origin: 'learned', status: item.status, label: item.label, episodes: item.episodes, checked_at: item.checked_at, ace_learning: metadata }).trim()}\n---\n${body}`;
|
|
27
|
+
}
|
|
28
|
+
export function listLearnedArtifacts(root) {
|
|
29
|
+
return listStoreKeysSync(root, 'knowledge/').filter(k => /(?:\/SKILL\.md|\/LEARNED\.md|\/references\/proposals\/[^/]+\.md)$/.test(k)).flatMap(key => { const item = decodeLearned(readStoreBlobSync(root, key) ?? '', key); return item ? [item] : []; });
|
|
30
|
+
}
|
|
31
|
+
export async function withLearnedStore(root, apply) {
|
|
32
|
+
return withStoreWriteCoordinator(getWorkspaceStorePath(root), async () => {
|
|
33
|
+
const store = await openStore(getWorkspaceStorePath(root));
|
|
34
|
+
try {
|
|
35
|
+
const result = await apply(store);
|
|
36
|
+
await store.commit();
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
store.discardPending();
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
await store.close({ commit: false });
|
|
45
|
+
}
|
|
46
|
+
}, { operation_label: 'learned_artifact' });
|
|
47
|
+
}
|
|
48
|
+
export async function saveLearnedArtifact(root, item) {
|
|
49
|
+
const oldText = readStoreBlobSync(root, item.key);
|
|
50
|
+
let front = {};
|
|
51
|
+
try {
|
|
52
|
+
front = parse(oldText?.match(/^---\n([\s\S]*?)\n---/)?.[1] ?? '') ?? {};
|
|
53
|
+
}
|
|
54
|
+
catch { }
|
|
55
|
+
await withLearnedStore(root, store => writeItem(store, item, front));
|
|
56
|
+
}
|
|
57
|
+
export async function updateLearnedArtifact(root, key, update) {
|
|
58
|
+
await withLearnedStore(root, async (store) => {
|
|
59
|
+
const text = await store.getBlob(key);
|
|
60
|
+
if (!text)
|
|
61
|
+
return;
|
|
62
|
+
const item = decodeLearned(text, key);
|
|
63
|
+
if (!item)
|
|
64
|
+
return;
|
|
65
|
+
const next = update(item);
|
|
66
|
+
if (next)
|
|
67
|
+
await writeItem(store, next, frontBody(text).front);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
async function writeItem(store, item, front = {}) {
|
|
71
|
+
const previous = decodeLearned(await store.getBlob(item.key) ?? '', item.key);
|
|
72
|
+
Object.assign(item, learningLifecycle(item, previous?.id === item.id ? previous : undefined, new Date().toISOString()));
|
|
73
|
+
const skill = item.key.match(/^knowledge\/skills\/(.+)\/([^/]+)$/), agent = item.key.match(/^knowledge\/agents\/([^/]+)\/([^/]+)$/);
|
|
74
|
+
if (skill)
|
|
75
|
+
await store.setSkillContent(skill[1], skill[2], encode(item, front), ContentSource.User);
|
|
76
|
+
else if (agent)
|
|
77
|
+
await store.setAgentInstruction(agent[1], agent[2], encode(item, front), ContentSource.User);
|
|
78
|
+
else
|
|
79
|
+
await store.setSkillContent(item.key.replace(/^knowledge\/skills\//, '').split('/references/')[0], `references/proposals/${item.proposal_id}.md`, encode(item, front), ContentSource.User);
|
|
80
|
+
}
|
|
81
|
+
const similarity = (a, b) => calculateJaccard(getShingles(a.toLowerCase().replace(/\s+/g, ' ').trim(), 3), getShingles(b.toLowerCase().replace(/\s+/g, ' ').trim(), 3));
|
|
82
|
+
const scoped = (scope) => !!(scope.role || scope.failure_signature || scope.paths?.length || scope.task_tags?.length);
|
|
83
|
+
function frontBody(text) {
|
|
84
|
+
const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
|
|
85
|
+
try {
|
|
86
|
+
return { front: m ? parse(m[1]) ?? {} : {}, body: m ? text.slice(m[0].length) : text };
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return { front: {}, body: text };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export async function prepareLearned(root, payload, context, deps = {}) {
|
|
93
|
+
const policy = readLearningPolicy(root);
|
|
94
|
+
const episodes = listEpisodes(root), catalog = listLearnedArtifacts(root), id = hash([context, payload]), at = new Date().toISOString();
|
|
95
|
+
const effects = [];
|
|
96
|
+
const outcome = { ok: true, ids: [], errors: [] };
|
|
97
|
+
const resolveEpisodes = (ids, min = 1) => {
|
|
98
|
+
const cited = episodes.filter(e => ids.includes(e.id));
|
|
99
|
+
const errors = [];
|
|
100
|
+
if (new Set(ids).size < min)
|
|
101
|
+
errors.push(`Cite at least ${min} distinct episodes.`);
|
|
102
|
+
if (cited.length !== new Set(ids).size)
|
|
103
|
+
errors.push('Cited episode unavailable: ' + ids.filter(i => !cited.some(e => e.id === i)).join(', '));
|
|
104
|
+
return { cited, errors };
|
|
105
|
+
};
|
|
106
|
+
const run = deps.runVerification ?? (await import('./goal-engine.js')).resolveGoalDeps({ workspaceRoot: root }).runVerification;
|
|
107
|
+
const demonstrated = (eps, checks) => eps.some(ep => exactRedGreen(ep.observed_runs ?? []).length || (ep.checks ?? []).some(c => !c.ok && checks.some(p => p.ok && p.command === c.command && p.cwd === c.cwd)));
|
|
108
|
+
const snapshots = (eps) => eps.map(compactEpisode);
|
|
109
|
+
if ((payload.lessons?.length ?? 0) > 3)
|
|
110
|
+
outcome.errors.push('At most three lessons per completion.');
|
|
111
|
+
const proposedLessons = [];
|
|
112
|
+
for (const lesson of (payload.lessons ?? []).slice(0, 3)) {
|
|
113
|
+
const resolved = resolveEpisodes(lesson.episodes), errors = [...resolved.errors];
|
|
114
|
+
const repo = new PlaybookRepository(root), previous = lesson.bullet_id ? repo.getBullet(lesson.bullet_id) : undefined;
|
|
115
|
+
if (lesson.content.length > 400 || !lesson.content.trim())
|
|
116
|
+
errors.push('Lesson must contain 1–400 characters.');
|
|
117
|
+
if (!scoped(lesson.scope))
|
|
118
|
+
errors.push('Lesson needs an explicit scope.');
|
|
119
|
+
if (lesson.op !== 'add' && !previous)
|
|
120
|
+
errors.push('Existing lesson not found.');
|
|
121
|
+
if (previous && lesson.op === 'edit' && (lesson.content.length < previous.content.length * 0.6 || previous.evidence_snapshots?.some(e => !lesson.episodes.includes(e.id) && !lesson.retired_episodes?.includes(e.id))))
|
|
122
|
+
errors.push('Edit must retain 60% of content and prior episode citations.');
|
|
123
|
+
const duplicate = repo.listBullets({ status: 'active' }).find(b => b.bullet_id !== lesson.bullet_id && JSON.stringify(b.scope) === JSON.stringify(lesson.scope) && similarity(b.content, lesson.content) >= 0.9);
|
|
124
|
+
if (duplicate && lesson.op === 'add')
|
|
125
|
+
errors.push(`Use or edit existing lesson ${duplicate.bullet_id}.`);
|
|
126
|
+
if (proposedLessons.some(p => JSON.stringify(p.scope) === JSON.stringify(lesson.scope) && similarity(p.content, lesson.content) >= 0.9))
|
|
127
|
+
errors.push('Duplicate lesson in this completion; submit one scoped delta.');
|
|
128
|
+
proposedLessons.push({ content: lesson.content, scope: lesson.scope });
|
|
129
|
+
const check = lesson.content.match(/Check:\s*([^\n]+)/)?.[1]?.trim();
|
|
130
|
+
const checks = [];
|
|
131
|
+
if (!errors.length && lesson.op !== 'retire' && check) {
|
|
132
|
+
const result = await run(check, root).catch(error => ({ ok: false, output: String(error) }));
|
|
133
|
+
checks.push({ command: check, cwd: root, ok: result.ok });
|
|
134
|
+
if (!result.ok)
|
|
135
|
+
errors.push('Lesson check failed: ' + result.output.slice(-1000));
|
|
136
|
+
}
|
|
137
|
+
effects.push(async () => {
|
|
138
|
+
const saved = await withPlaybookTransaction(root, r => {
|
|
139
|
+
if (previous && !errors.length) {
|
|
140
|
+
return r.saveBullet({ ...previous, content: lesson.content, section: lesson.section, scope: lesson.scope, evidence_refs: lesson.episodes, evidence_snapshots: snapshots(resolved.cited), status: lesson.op === 'retire' ? 'retired' : 'active', retired_reason: lesson.op === 'retire' ? lesson.content : undefined, label: demonstrated(resolved.cited, checks) ? 'demonstrated' : 'checked', validation_checks: checks, acceptance_errors: [] });
|
|
141
|
+
}
|
|
142
|
+
const existing = r.listBullets().find(b => b.provenance.run_id === `distill:${id}` && b.content.replace(/\s+/g, ' ').trim() === lesson.content.replace(/\s+/g, ' ').trim() && JSON.stringify(b.scope) === JSON.stringify(lesson.scope));
|
|
143
|
+
if (existing)
|
|
144
|
+
return existing;
|
|
145
|
+
return r.createBullet({ section: lesson.section, scope: lesson.scope, content: lesson.content, evidence_refs: lesson.episodes, evidence_snapshots: snapshots(resolved.cited), status: errors.length ? 'quarantined' : 'active', label: demonstrated(resolved.cited, checks) ? 'demonstrated' : 'checked', validation_checks: checks, acceptance_errors: errors, provenance: { source: 'operator', run_id: `distill:${id}`, note: JSON.stringify(context) } });
|
|
146
|
+
});
|
|
147
|
+
outcome.ids.push(saved.bullet_id);
|
|
148
|
+
});
|
|
149
|
+
outcome.errors.push(...errors);
|
|
150
|
+
}
|
|
151
|
+
if (payload.skill) {
|
|
152
|
+
const s = payload.skill, resolved = resolveEpisodes(s.episodes, 3), errors = [...resolved.errors], parts = frontBody(s.skill_md), scope = (typeof parts.front.scope === 'object' ? parts.front.scope : { task_tags: typeof parts.front.scope === 'string' ? [parts.front.scope] : [] });
|
|
153
|
+
const isMeta = s.slug === 'learn-from-work' && context.goal_id === 'G-learning' && new GoalRepository(root).getPlan(context.goal_id)?.steps.find(s => s.step_id === context.step_id)?.learning_kind === 'meta';
|
|
154
|
+
const artifactId = `learned/${/^[a-z0-9-]{3,48}$/.test(s.slug) ? s.slug : 'invalid-' + id}`, key = isMeta ? 'knowledge/skills/learn-from-work/LEARNED.md' : `knowledge/skills/${artifactId}/SKILL.md`, previous = catalog.find(x => x.key === key && x.status === 'active');
|
|
155
|
+
if (!/^[a-z0-9-]{3,48}$/.test(s.slug))
|
|
156
|
+
errors.push('Skill slug must be 3–48 lowercase letters, digits or hyphens.');
|
|
157
|
+
if (new Set(resolved.cited.map(e => e.goal_id)).size < 2)
|
|
158
|
+
errors.push('A skill needs episodes from at least two goals.');
|
|
159
|
+
if (parts.front.name !== s.slug)
|
|
160
|
+
errors.push('Skill frontmatter name must match its slug.');
|
|
161
|
+
if (!scoped(scope))
|
|
162
|
+
errors.push('Skill frontmatter must have a matching scope.');
|
|
163
|
+
if (!isMeta && existsSync(join(DEFAULTS_SKILLS_ROOT, s.slug)))
|
|
164
|
+
errors.push('Cannot shadow a packaged skill.');
|
|
165
|
+
if (s.op === 'add' && previous)
|
|
166
|
+
errors.push('Skill exists; submit an edit.');
|
|
167
|
+
if (s.op === 'edit' && !previous && !isMeta)
|
|
168
|
+
errors.push('Cannot edit a missing active skill.');
|
|
169
|
+
if (previous && s.skill_md.length < Number(previous.source_length ?? previous.body.length) * 0.6)
|
|
170
|
+
errors.push('Edit retains less than 60% of prior skill.');
|
|
171
|
+
if (previous?.episodes.some(e => !s.episodes.includes(e) && !s.retired_episodes?.includes(e)))
|
|
172
|
+
errors.push('Edit must retain prior episode citations.');
|
|
173
|
+
if (!isMeta && !previous && catalog.filter(x => x.kind === 'skill' && x.status === 'active').length >= policy.max_learned_skills)
|
|
174
|
+
errors.push('Learned skill cap reached; edit an existing skill.');
|
|
175
|
+
const refs = Object.entries(s.references ?? {});
|
|
176
|
+
if (refs.length > 3 || refs.some(([name, text]) => !/^[-a-zA-Z0-9_]+\.md$/.test(name) || text.split(/\r?\n/).length > 400))
|
|
177
|
+
errors.push('At most three safe Markdown reference files, 400 lines each.');
|
|
178
|
+
const proof = await proveLearnedSkill(s.skill_md, root, errors.length ? async () => ({ ok: false, output: 'Not executed: proposal metadata did not qualify' }) : run, previous?.body);
|
|
179
|
+
errors.push(...proof.errors);
|
|
180
|
+
const duplicate = catalog.find(a => a.id !== artifactId && a.kind === 'skill' && a.status === 'active' && similarity(String(frontBody(readStoreBlobSync(root, a.key) ?? '').front.description ?? ''), proof.description) >= 0.9);
|
|
181
|
+
if (duplicate)
|
|
182
|
+
errors.push('Use or edit existing skill ' + duplicate.id);
|
|
183
|
+
const item = { id: isMeta ? `distiller:learn-from-work:${id}` : artifactId, key, kind: isMeta ? 'distiller' : 'skill', status: errors.length ? 'quarantined' : isMeta ? 'proposed' : 'active', label: demonstrated(resolved.cited, proof.checks) ? 'demonstrated' : 'checked', episodes: s.episodes, evidence_snapshots: snapshots(resolved.cited), scope, body: parts.body, acceptance_errors: errors, checked_at: at, proposal_id: id, helpful_count: previous?.helpful_count ?? 0, harmful_count: previous?.harmful_count ?? 0, references: s.references, served_count: previous?.served_count, used_count: previous?.used_count, used_goals: previous?.used_goals, scored_attempts: previous?.scored_attempts, served_outcomes: previous?.served_outcomes, source_length: s.skill_md.length, validation_checks: proof.checks.map(c => ({ command: c.command, cwd: c.cwd, ok: c.ok, output: c.output.slice(-1000) })) };
|
|
184
|
+
if (isMeta && previous)
|
|
185
|
+
item.key = `knowledge/skills/learn-from-work/references/proposals/${id}.md`;
|
|
186
|
+
if (errors.length && previous) {
|
|
187
|
+
item.id = `${artifactId}:proposal:${id}`;
|
|
188
|
+
item.key = `knowledge/skills/${isMeta ? 'learn-from-work' : artifactId}/references/proposals/${id}.md`;
|
|
189
|
+
}
|
|
190
|
+
effects.push(() => withLearnedStore(root, async (store) => {
|
|
191
|
+
const latestText = await store.getBlob(key), latest = latestText ? decodeLearned(latestText, key) : undefined;
|
|
192
|
+
if (latest && (!previous || latest.proposal_id !== previous.proposal_id || latest.status !== 'active')) {
|
|
193
|
+
const error = 'Active skill changed during qualification; review the retained proposal against its current version.';
|
|
194
|
+
errors.push(error);
|
|
195
|
+
outcome.errors.push(error);
|
|
196
|
+
item.status = 'quarantined';
|
|
197
|
+
item.id = `${artifactId}:proposal:${id}`;
|
|
198
|
+
item.key = `knowledge/skills/${isMeta ? 'learn-from-work' : artifactId}/references/proposals/${id}.md`;
|
|
199
|
+
outcome.ids = outcome.ids.map(value => value === artifactId ? item.id : value);
|
|
200
|
+
}
|
|
201
|
+
else if (latest)
|
|
202
|
+
for (const field of ['helpful_count', 'harmful_count', 'served_count', 'used_count', 'used_goals', 'scored_attempts', 'served_outcomes'])
|
|
203
|
+
item[field] = latest[field];
|
|
204
|
+
if (previous && !errors.length && !isMeta) {
|
|
205
|
+
const history = previous.history ?? [];
|
|
206
|
+
const version = `${at.replace(/[^0-9]/g, '')}-${id}`;
|
|
207
|
+
await store.setSkillContent(artifactId, `references/history/${version}.md`, latestText ?? '', ContentSource.User);
|
|
208
|
+
item.history = [...history, version].slice(-3);
|
|
209
|
+
for (const old of history.slice(0, Math.max(0, history.length - 2)))
|
|
210
|
+
await store.delete(`knowledge/skills/${artifactId}/references/history/${old}.md`);
|
|
211
|
+
}
|
|
212
|
+
await writeItem(store, item, parts.front);
|
|
213
|
+
if (!errors.length) {
|
|
214
|
+
for (const [name, body] of refs)
|
|
215
|
+
await store.setSkillContent(artifactId, `references/${name}`, body, ContentSource.User);
|
|
216
|
+
await store.setSkillContent(artifactId, 'references/episodes.md', resolved.cited.map(e => `## ${e.id}\n${JSON.stringify(snapshots([e])[0], null, 2)}`).join('\n\n'), ContentSource.User);
|
|
217
|
+
}
|
|
218
|
+
}));
|
|
219
|
+
outcome.ids.push(item.id);
|
|
220
|
+
outcome.errors.push(...errors);
|
|
221
|
+
}
|
|
222
|
+
if (payload.instruction) {
|
|
223
|
+
const i = payload.instruction, resolved = resolveEpisodes(i.episodes), errors = [...resolved.errors];
|
|
224
|
+
if (!ALL_AGENTS.includes(i.role))
|
|
225
|
+
errors.push('Unknown role.');
|
|
226
|
+
if (!i.text.trim() || i.text.length > 1200)
|
|
227
|
+
errors.push('Instruction must contain 1–1200 characters.');
|
|
228
|
+
if (catalog.some(a => a.kind === 'instruction' && a.scope.role === i.role && a.status !== 'quarantined' && similarity(a.body, i.text) >= 0.9))
|
|
229
|
+
errors.push('Use the existing matching instruction proposal.');
|
|
230
|
+
const key = `knowledge/agents/${i.role}/LEARNED.md`, prior = catalog.find(a => a.key === key);
|
|
231
|
+
const item = { id: `instruction:${i.role}:${id}`, key: prior?.status === 'active' ? `knowledge/skills/learn-from-work/references/proposals/${id}.md` : key, kind: 'instruction', status: errors.length ? 'quarantined' : 'proposed', label: 'checked', episodes: i.episodes, evidence_snapshots: snapshots(resolved.cited), scope: { role: i.role }, body: i.text, acceptance_errors: errors, checked_at: at, proposal_id: id, helpful_count: 0, harmful_count: 0 };
|
|
232
|
+
effects.push(() => saveLearnedArtifact(root, item));
|
|
233
|
+
outcome.ids.push(item.id);
|
|
234
|
+
outcome.errors.push(...errors);
|
|
235
|
+
}
|
|
236
|
+
outcome.ok = !outcome.errors.length;
|
|
237
|
+
if (!outcome.ok) {
|
|
238
|
+
outcome.reason_code = 'learning_unchecked';
|
|
239
|
+
outcome.options = [{ tool: 'next_step', args: { goal_id: 'G-learning' }, why: 'Use the corrective learning step with the retained acceptance errors.' }];
|
|
240
|
+
}
|
|
241
|
+
return { result: outcome, publish: async () => { for (const effect of effects)
|
|
242
|
+
await effect(); outcome.ok = !outcome.errors.length; if (!outcome.ok) {
|
|
243
|
+
outcome.reason_code = 'learning_unchecked';
|
|
244
|
+
outcome.options = [{ tool: 'next_step', args: { goal_id: 'G-learning' }, why: 'Review the retained acceptance errors in the corrective learning assignment.' }];
|
|
245
|
+
} await regenerateLearnedProjection(root); } };
|
|
246
|
+
}
|
|
247
|
+
export async function acceptLearned(root, payload, context, deps = {}) {
|
|
248
|
+
const checked = await prepareLearned(root, payload, context, deps);
|
|
249
|
+
await checked.publish();
|
|
250
|
+
return checked.result;
|
|
251
|
+
}
|
|
252
|
+
export async function regenerateLearnedProjection(root) {
|
|
253
|
+
const bullets = new PlaybookRepository(root).listBullets(), artifacts = listLearnedArtifacts(root);
|
|
254
|
+
const body = ['# Learned in this workspace', '', ...bullets.filter(b => b.evidence_snapshots?.length).map(b => `- ${b.bullet_id} [${b.status}; ${b.label ?? 'candidate'}]: ${b.content}\n Episodes: ${b.evidence_snapshots.map(e => e.id).join(', ')}`), ...artifacts.map(a => `- ${a.id} [${a.status}; ${a.label}]\n Episodes: ${a.episodes.join(', ')}${a.acceptance_errors.length ? '\n Acceptance: ' + a.acceptance_errors.join('; ') : ''}`)].join('\n');
|
|
255
|
+
await withLearnedStore(root, store => store.setBlob(operationalArtifactKey('agent-state/LEARNED.md'), body));
|
|
256
|
+
}
|
|
257
|
+
export async function ensureLearningStep(root, force) {
|
|
258
|
+
const policy = readLearningPolicy(root);
|
|
259
|
+
if (!policy.enabled)
|
|
260
|
+
return;
|
|
261
|
+
const bullets = new PlaybookRepository(root).listBullets().filter(b => ['active', 'quarantined'].includes(b.status) && b.evidence_snapshots?.length);
|
|
262
|
+
const groups = new Map();
|
|
263
|
+
for (const b of bullets) {
|
|
264
|
+
const scope = learningScopeKey(b.scope);
|
|
265
|
+
groups.set(scope, [...(groups.get(scope) ?? []), b]);
|
|
266
|
+
}
|
|
267
|
+
const eligible = [...groups.entries()].filter(([, items]) => items.length >= policy.distill_min_items && new Set(items.flatMap(b => b.evidence_snapshots.map(e => e.goal_id))).size >= policy.distill_min_goals);
|
|
268
|
+
if (!eligible.length && !force)
|
|
269
|
+
return;
|
|
270
|
+
const repo = new GoalRepository(root), now = new Date().toISOString();
|
|
271
|
+
await withStoreWriteCoordinator(repo.planPath('G-learning'), async () => {
|
|
272
|
+
let goal = repo.getGoal('G-learning'), plan = repo.getPlan('G-learning');
|
|
273
|
+
if (plan?.steps.some(s => ['planned', 'running', 'blocked'].includes(s.status)))
|
|
274
|
+
return;
|
|
275
|
+
const cooling = (key) => plan?.steps.some(s => canonicalLearningScopeKey(s.learning_scope ?? '') === key && Date.now() - Date.parse(s.learning_opened_at ?? s.started_at ?? now) < policy.distill_cooldown_days * 86400000);
|
|
276
|
+
const chosen = force ? undefined : eligible.find(([key]) => !cooling(key));
|
|
277
|
+
if (!force && !chosen)
|
|
278
|
+
return;
|
|
279
|
+
const scope = force?.scope ?? chosen[1][0].scope, scopeKey = learningScopeKey(scope);
|
|
280
|
+
if (cooling(scopeKey))
|
|
281
|
+
return;
|
|
282
|
+
const items = force ? bullets.filter(b => b.scope.failure_signature === scope.failure_signature) : chosen[1];
|
|
283
|
+
const cited = [...items.flatMap(b => b.evidence_snapshots ?? []), ...(force ? listEpisodes(root).filter(e => e.failure_signature === scope.failure_signature) : [])];
|
|
284
|
+
const task = `Distill what this workspace has learned about ${scopeKey}.\nInputs: the referenced episodes and existing artifacts.\nProduce, via complete_step outcome.learned:\n(a) up to 3 lessons: trigger → action → check; keep commands, paths, error text verbatim; edit or retire existing lessons rather than duplicate them.\n(b) optionally one skill, only if ≥3 episodes share a reusable procedure.\n(c) optionally one role-instruction proposal, only if a rule applies to every task of that role.\nCite episode ids for every item. Do not invent steps the episodes do not show.\nLoad get_skill_instructions(learn-from-work).\n${force?.reason ?? ''}`;
|
|
285
|
+
const learning_inputs = { episodes: [...new Set(cited.map(e => e.id))], artifacts: [...items.map(b => b.bullet_id), ...listLearnedArtifacts(root).map(a => a.id)] };
|
|
286
|
+
if (!plan) {
|
|
287
|
+
plan = createTaskPlan({ task: 'Keep this workspace\'s learned artifacts current', steps: [] });
|
|
288
|
+
goal = { goal_id: 'G-learning', objective: plan.task, executor: 'host', status: 'executing', plan_id: plan.plan_id, success_contract: { expected_artifacts: [], required_evidence_refs: [], verification_commands: [], gate_ids: [], uncompilable_criteria: [] }, budgets: defaultGoalBudgets(), created_at: now, updated_at: now, tick_count: 0, replan_count: 0, stop_requested: false, escalations: [] };
|
|
289
|
+
}
|
|
290
|
+
plan = amendTaskPlan(plan, { append_steps: [{ role: 'coders', task, learning_inputs, learning_scope: scopeKey, learning_opened_at: now }] });
|
|
291
|
+
// G-learning is a retained container. New work after a closed batch
|
|
292
|
+
// gets a start time here; host restart/resume never resets deadlines.
|
|
293
|
+
if (goal.status === 'done' || !goal.started_at)
|
|
294
|
+
goal.started_at = now;
|
|
295
|
+
goal.status = 'executing';
|
|
296
|
+
goal.stop_requested = false;
|
|
297
|
+
repo.savePlan('G-learning', plan);
|
|
298
|
+
repo.saveGoal(goal);
|
|
299
|
+
}, { operation_label: 'learning_step' });
|
|
300
|
+
}
|
|
301
|
+
export async function reopenLearningCorrection(root, errors) {
|
|
302
|
+
const repo = new GoalRepository(root);
|
|
303
|
+
await withStoreWriteCoordinator(repo.planPath('G-learning'), async () => {
|
|
304
|
+
const g = repo.getGoal('G-learning');
|
|
305
|
+
let p = repo.getPlan('G-learning');
|
|
306
|
+
if (!g || !p || p.steps.some(s => ['planned', 'running'].includes(s.status)))
|
|
307
|
+
return;
|
|
308
|
+
if (g.budgets.replan_budget_explicit && g.replan_count >= g.budgets.max_replans) {
|
|
309
|
+
g.status = 'blocked';
|
|
310
|
+
g.reason_code = 'Learning correction budget reached: ' + errors.join('; ');
|
|
311
|
+
repo.saveGoal(g);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
p = amendTaskPlan(p, { append_steps: [{ role: 'coders', learning_scope: p.steps.at(-1)?.learning_scope ?? 'correction', learning_inputs: { rejected_steps: p.steps.at(-1) ? [p.steps.at(-1).step_id] : [] }, task: 'Correct the retained learned payload using the referenced acceptance errors and original payload.' }] });
|
|
315
|
+
g.replan_count++;
|
|
316
|
+
g.status = 'executing';
|
|
317
|
+
repo.savePlan('G-learning', p);
|
|
318
|
+
repo.saveGoal(g);
|
|
319
|
+
}, { operation_label: 'learning_correction' });
|
|
320
|
+
}
|
|
321
|
+
//# sourceMappingURL=learned-artifacts.js.map
|