@voybio/ace-swarm 2.4.9 → 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 +48 -14
- package/dist/job-scheduler.js +169 -23
- 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 +108 -6
- package/dist/orchestrator-supervisor.js +4 -32
- 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.js +28 -4
- 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 +19 -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 +44 -9
- 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
|
@@ -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
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { type LearnedArtifact } from './learned-artifacts.js';
|
|
2
|
+
export declare function listLearning(root: string): {
|
|
3
|
+
lessons: import("./store/repositories/playbook-repository.js").PlaybookBullet[];
|
|
4
|
+
artifacts: LearnedArtifact[];
|
|
5
|
+
};
|
|
6
|
+
export declare function showLearning(root: string, id: string): import("./store/repositories/playbook-repository.js").PlaybookBullet | LearnedArtifact | undefined;
|
|
7
|
+
export declare function approveLearning(root: string, id: string): Promise<void>;
|
|
8
|
+
export declare function retireLearning(root: string, id: string, reason: string): Promise<void>;
|
|
9
|
+
export { approvedInstructionText, approvedDistillerText } from './learning-overlays.js';
|
|
10
|
+
export declare function learningReport(root: string): {
|
|
11
|
+
scorecard: {
|
|
12
|
+
active_learned_artifacts: {
|
|
13
|
+
lessons: number;
|
|
14
|
+
skills: number;
|
|
15
|
+
approved_instructions: number;
|
|
16
|
+
};
|
|
17
|
+
repeat_failure_rate: {
|
|
18
|
+
signatures_served: number;
|
|
19
|
+
signatures_recurred: number;
|
|
20
|
+
rate: number | null;
|
|
21
|
+
missing_context_attempts: number;
|
|
22
|
+
interpretation: string;
|
|
23
|
+
};
|
|
24
|
+
first_attempt_success_by_scope: {
|
|
25
|
+
scope: string;
|
|
26
|
+
before: ReturnType<() => {
|
|
27
|
+
done: number;
|
|
28
|
+
steps: number;
|
|
29
|
+
rate: number | null;
|
|
30
|
+
}>;
|
|
31
|
+
after: ReturnType<() => {
|
|
32
|
+
done: number;
|
|
33
|
+
steps: number;
|
|
34
|
+
rate: number | null;
|
|
35
|
+
}>;
|
|
36
|
+
unclassified: ReturnType<() => {
|
|
37
|
+
done: number;
|
|
38
|
+
steps: number;
|
|
39
|
+
rate: number | null;
|
|
40
|
+
}>;
|
|
41
|
+
unexposed: ReturnType<() => {
|
|
42
|
+
done: number;
|
|
43
|
+
steps: number;
|
|
44
|
+
rate: number | null;
|
|
45
|
+
}>;
|
|
46
|
+
}[];
|
|
47
|
+
first_attempt_coverage: {
|
|
48
|
+
missing_first_attempts: number;
|
|
49
|
+
reconstructed_scopes: number;
|
|
50
|
+
interpretation: string;
|
|
51
|
+
};
|
|
52
|
+
served_helpfulness: {
|
|
53
|
+
helpful: number;
|
|
54
|
+
harmful: number;
|
|
55
|
+
ratio: number | null;
|
|
56
|
+
counter_scope: string;
|
|
57
|
+
};
|
|
58
|
+
distill_acceptance: {
|
|
59
|
+
checked: number;
|
|
60
|
+
submitted: number;
|
|
61
|
+
rate: number | null;
|
|
62
|
+
demonstrated_current_artifacts: number;
|
|
63
|
+
};
|
|
64
|
+
learning_overhead: {
|
|
65
|
+
learning_steps: number;
|
|
66
|
+
all_steps: number;
|
|
67
|
+
ratio: number | null;
|
|
68
|
+
opportunistic_payload_steps: number;
|
|
69
|
+
assignments: {
|
|
70
|
+
episode_id: string;
|
|
71
|
+
utf8_bytes: number;
|
|
72
|
+
token_upper_bound: number;
|
|
73
|
+
}[];
|
|
74
|
+
max_served_token_upper_bound: number | null;
|
|
75
|
+
token_method: string;
|
|
76
|
+
};
|
|
77
|
+
retirement_health: {
|
|
78
|
+
window_days: number;
|
|
79
|
+
retired: number;
|
|
80
|
+
ever_active: number;
|
|
81
|
+
ratio: number | null;
|
|
82
|
+
unknown_history: number;
|
|
83
|
+
};
|
|
84
|
+
distillation_steps: {
|
|
85
|
+
goal_id: string;
|
|
86
|
+
step_id: string;
|
|
87
|
+
checked: boolean;
|
|
88
|
+
items_after_20_serves: number;
|
|
89
|
+
mean_strong_helpfulness: number | null;
|
|
90
|
+
}[];
|
|
91
|
+
coverage: string;
|
|
92
|
+
};
|
|
93
|
+
enabled: boolean;
|
|
94
|
+
policy_issue: string | null;
|
|
95
|
+
observation: string;
|
|
96
|
+
episodes: number;
|
|
97
|
+
attempts: number;
|
|
98
|
+
first_attempts_done: number;
|
|
99
|
+
steps_with_outcomes: number;
|
|
100
|
+
artifacts: {
|
|
101
|
+
lessons: number;
|
|
102
|
+
skills: number;
|
|
103
|
+
quarantined: number;
|
|
104
|
+
};
|
|
105
|
+
instructions: {
|
|
106
|
+
active: number;
|
|
107
|
+
proposed: number;
|
|
108
|
+
};
|
|
109
|
+
served: number;
|
|
110
|
+
used: number;
|
|
111
|
+
helpful: number;
|
|
112
|
+
harmful: number;
|
|
113
|
+
weak_outcomes: {
|
|
114
|
+
done_first_attempt: number;
|
|
115
|
+
failed_same_signature: number;
|
|
116
|
+
other: number;
|
|
117
|
+
};
|
|
118
|
+
repeat_signatures: {
|
|
119
|
+
signature: string;
|
|
120
|
+
count: number;
|
|
121
|
+
}[];
|
|
122
|
+
distillation: {
|
|
123
|
+
submitted: number;
|
|
124
|
+
checked: number;
|
|
125
|
+
acceptance_rate: number | null;
|
|
126
|
+
demonstrated: number;
|
|
127
|
+
helpfulness_after_20_serves: {
|
|
128
|
+
id: unknown;
|
|
129
|
+
helpful: number;
|
|
130
|
+
harmful: number;
|
|
131
|
+
used: number;
|
|
132
|
+
}[];
|
|
133
|
+
};
|
|
134
|
+
limitations: string[];
|
|
135
|
+
};
|
|
136
|
+
export declare function exportLearning(root: string, to: string): Promise<{
|
|
137
|
+
files: string[];
|
|
138
|
+
unexported: string[];
|
|
139
|
+
}>;
|
|
140
|
+
/** Open the next useful proposal, never a coordinator or automatic approval. */
|
|
141
|
+
export declare function advanceLearning(root: string): Promise<void>;
|
|
142
|
+
//# sourceMappingURL=learning-controls.d.ts.map
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { buildLearningScorecard } from './learning-scorecard.js';
|
|
2
|
+
/** Explicit operator controls and reports over existing learning records. */
|
|
3
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { join, dirname, resolve } from 'node:path';
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
import { GoalRepository, defaultGoalBudgets } from './store/repositories/goal-repository.js';
|
|
7
|
+
import { amendTaskPlan, createTaskPlan } from './orchestrator-supervisor.js';
|
|
8
|
+
import { PlaybookRepository, withPlaybookTransaction } from './store/repositories/playbook-repository.js';
|
|
9
|
+
import { listLearnedArtifacts, saveLearnedArtifact, regenerateLearnedProjection, withLearnedStore } from './learned-artifacts.js';
|
|
10
|
+
import { withStoreWriteCoordinator } from './store/write-coordinator.js';
|
|
11
|
+
import { readLearningPolicy } from './learning-policy.js';
|
|
12
|
+
import { ContentSource } from './store/types.js';
|
|
13
|
+
import { listEpisodes } from './host-learning.js';
|
|
14
|
+
export function listLearning(root) { return { lessons: new PlaybookRepository(root).listBulletsReadOnly(), artifacts: listLearnedArtifacts(root) }; }
|
|
15
|
+
export function showLearning(root, id) { const all = listLearning(root); return all.lessons.find(b => b.bullet_id === id) ?? all.artifacts.find(a => a.id === id); }
|
|
16
|
+
export async function approveLearning(root, id) {
|
|
17
|
+
const a = listLearnedArtifacts(root).find(a => a.id === id);
|
|
18
|
+
if (!a || a.status !== 'proposed' || !['instruction', 'distiller'].includes(a.kind))
|
|
19
|
+
throw Error('Choose an existing proposed instruction or distiller overlay. Lessons and skills do not need operator approval.');
|
|
20
|
+
const sourceKey = a.key, canonical = a.kind === 'instruction' ? `knowledge/agents/${a.scope.role}/LEARNED.md` : 'knowledge/skills/learn-from-work/LEARNED.md';
|
|
21
|
+
await withLearnedStore(root, async (store) => {
|
|
22
|
+
const prior = await store.getBlob(canonical);
|
|
23
|
+
if (prior) {
|
|
24
|
+
const name = `references/history/${new Date().toISOString().replace(/[^0-9]/g, '')}.md`;
|
|
25
|
+
if (a.kind === 'instruction')
|
|
26
|
+
await store.setAgentInstruction(a.scope.role, name, prior, ContentSource.User);
|
|
27
|
+
else
|
|
28
|
+
await store.setSkillContent('learn-from-work', name, prior, ContentSource.User);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
if (sourceKey !== canonical)
|
|
32
|
+
await saveLearnedArtifact(root, { ...a, id: a.id + ':proposal-record', status: 'retired', retired_reason: 'Approved into ' + canonical });
|
|
33
|
+
a.status = 'active';
|
|
34
|
+
a.approved_at = new Date().toISOString();
|
|
35
|
+
a.approval = 'explicit ace learn approve';
|
|
36
|
+
a.key = canonical;
|
|
37
|
+
await saveLearnedArtifact(root, a);
|
|
38
|
+
await regenerateLearnedProjection(root);
|
|
39
|
+
}
|
|
40
|
+
export async function retireLearning(root, id, reason) {
|
|
41
|
+
if (!reason.trim())
|
|
42
|
+
throw Error('Retirement needs a reason.');
|
|
43
|
+
const a = listLearnedArtifacts(root).find(a => a.id === id);
|
|
44
|
+
if (a) {
|
|
45
|
+
await saveLearnedArtifact(root, { ...a, status: 'retired', retired_reason: reason });
|
|
46
|
+
}
|
|
47
|
+
else
|
|
48
|
+
await withPlaybookTransaction(root, repo => {
|
|
49
|
+
const b = repo.getBullet(id);
|
|
50
|
+
if (!b)
|
|
51
|
+
throw Error('Learning item not found: ' + id);
|
|
52
|
+
repo.saveBullet({ ...b, status: 'retired', retired_reason: reason });
|
|
53
|
+
});
|
|
54
|
+
await regenerateLearnedProjection(root);
|
|
55
|
+
}
|
|
56
|
+
export { approvedInstructionText, approvedDistillerText } from './learning-overlays.js';
|
|
57
|
+
export function learningReport(root) {
|
|
58
|
+
const all = listLearning(root), repo = new GoalRepository(root), steps = repo.listGoals().flatMap(g => (repo.getPlan(g.goal_id)?.steps ?? []).map(s => ({ goal: g.goal_id, step: s }))), episodes = listEpisodes(root);
|
|
59
|
+
const logs = steps.flatMap(({ goal, step }) => (step.attempts_log ?? []).map(a => ({ ...a, goal, step_id: step.step_id })));
|
|
60
|
+
const distill = steps.filter(({ step }) => step.learning_payload && step.learning_result), skills = all.artifacts.filter(a => a.kind === 'skill'), instructions = all.artifacts.filter(a => a.kind === 'instruction'), served = [...all.lessons, ...skills];
|
|
61
|
+
const signatureCounts = new Map();
|
|
62
|
+
for (const ep of episodes)
|
|
63
|
+
if (ep.failure_signature)
|
|
64
|
+
signatureCounts.set(ep.failure_signature, (signatureCounts.get(ep.failure_signature) ?? 0) + 1);
|
|
65
|
+
return { scorecard: buildLearningScorecard(steps, episodes, all.lessons, all.artifacts), enabled: readLearningPolicy(root).enabled, policy_issue: readLearningPolicy(root).issue ?? null, observation: 'unavailable unless a qualified Vericify adapter supplied Linked records', episodes: episodes.length, attempts: logs.length, first_attempts_done: logs.filter(a => a.attempt === 1 && a.status === 'done').length, steps_with_outcomes: new Set(logs.map(a => `${a.goal}:${a.step_id}`)).size, artifacts: { lessons: all.lessons.filter(b => b.status === 'active').length, skills: skills.filter(a => a.status === 'active').length, quarantined: all.lessons.filter(b => b.status === 'quarantined').length + all.artifacts.filter(a => a.status === 'quarantined').length }, instructions: { active: instructions.filter(a => a.status === 'active').length, proposed: instructions.filter(a => a.status === 'proposed').length }, served: served.reduce((sum, b) => sum + (b.served_count ?? 0), 0), used: served.reduce((sum, b) => sum + (b.used_count ?? 0), 0), helpful: served.reduce((sum, b) => sum + b.helpful_count, 0), harmful: served.reduce((sum, b) => sum + b.harmful_count, 0), weak_outcomes: served.reduce((sum, b) => ({ done_first_attempt: sum.done_first_attempt + (b.served_outcomes?.done_first_attempt ?? 0), failed_same_signature: sum.failed_same_signature + (b.served_outcomes?.failed_same_signature ?? 0), other: sum.other + (b.served_outcomes?.other ?? 0) }), { done_first_attempt: 0, failed_same_signature: 0, other: 0 }), repeat_signatures: [...signatureCounts.entries()].filter(([, count]) => count > 1).map(([signature, count]) => ({ signature, count })), distillation: { submitted: distill.length, checked: distill.filter(({ step }) => step.learning_result?.ok).length, acceptance_rate: distill.length ? distill.filter(({ step }) => step.learning_result?.ok).length / distill.length : null, demonstrated: all.artifacts.filter(a => a.label === 'demonstrated').length + all.lessons.filter(b => b.label === 'demonstrated').length, helpfulness_after_20_serves: served.filter(a => (a.served_count ?? 0) >= 20).map(a => ({ id: 'bullet_id' in a ? a.bullet_id : a.id, helpful: a.helpful_count, harmful: a.harmful_count, used: a.used_count ?? 0 })) }, limitations: ['Attempt context retains five entries; pinned citations preserve cited earlier episodes.', 'No provider quality, causal benefit or unavailable external observation is inferred from these counters.'] };
|
|
66
|
+
}
|
|
67
|
+
export async function exportLearning(root, to) {
|
|
68
|
+
const all = listLearning(root), files = [], unexported = [];
|
|
69
|
+
const target = resolve(root, to);
|
|
70
|
+
const emit = (path, text) => { const file = join(target, path); mkdirSync(dirname(file), { recursive: true }); const marker = '<!-- Generated by ace learn export; review changes before committing. -->\n'; const output = text.startsWith('---\n') ? text.replace(/\n---\n/, '\n---\n' + marker) : marker + text; writeFileSync(file, output.endsWith('\n') ? output : output + '\n'); files.push(file); };
|
|
71
|
+
const grouped = new Map();
|
|
72
|
+
for (const b of all.lessons.filter(b => b.status === 'active')) {
|
|
73
|
+
if (!b.evidence_snapshots?.length) {
|
|
74
|
+
unexported.push(`${b.bullet_id}: no ACE episode citation`);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const key = createHash('sha256').update(JSON.stringify(b.scope)).digest('hex').slice(0, 8);
|
|
78
|
+
grouped.set(key, [...(grouped.get(key) ?? []), b]);
|
|
79
|
+
}
|
|
80
|
+
for (const [key, items] of grouped)
|
|
81
|
+
emit(`lessons/${key}.md`, '# Learned lessons\n\n' + items.map(b => `## ${b.bullet_id} (${b.label ?? 'checked'})\n\n${b.content}\n\nEpisodes: ${b.evidence_snapshots.map(e => e.id).join(', ')}\n\nScope: ${JSON.stringify(b.scope)}`).join('\n\n'));
|
|
82
|
+
for (const a of all.artifacts.filter(a => a.status === 'active')) {
|
|
83
|
+
if (!a.episodes.length) {
|
|
84
|
+
unexported.push(`${a.id}: no episode citation`);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const name = a.kind === 'skill' ? `skills/${a.id.replace(/^learned\//, '')}/SKILL.md` : a.kind === 'instruction' ? `agents/${a.scope.role}/LEARNED.md` : 'skills/learn-from-work/LEARNED.md';
|
|
88
|
+
const front = a.kind === 'skill' ? `---\nname: ${JSON.stringify(a.id.replace(/^learned\//, ''))}\ndescription: ${JSON.stringify(a.description ?? 'Use for ' + JSON.stringify(a.scope))}\nscope: ${JSON.stringify(a.scope)}\norigin: learned\nlabel: ${a.label}\nepisodes: ${JSON.stringify(a.episodes)}\n---\n` : '';
|
|
89
|
+
emit(name, front + (a.kind === 'skill' ? a.body : `# ${a.id}\n\nLabel: ${a.label}\n\nEpisodes: ${a.episodes.join(', ')}\n\n${a.body}`));
|
|
90
|
+
for (const [name, body] of Object.entries(a.references ?? {}))
|
|
91
|
+
emit(`skills/${a.id.replace(/^learned\//, '')}/references/${name}`, `Episodes: ${a.episodes.join(', ')}\n\n${body}`);
|
|
92
|
+
if (a.kind === 'skill')
|
|
93
|
+
emit(`skills/${a.id.replace(/^learned\//, '')}/references/episodes.md`, a.evidence_snapshots.map(e => { const current = listEpisodes(root).find(item => item.id === e.id) ?? e; return `## ${e.id}\n\n${JSON.stringify(current, null, 2)}`; }).join('\n\n'));
|
|
94
|
+
}
|
|
95
|
+
return { files, unexported };
|
|
96
|
+
}
|
|
97
|
+
/** Open the next useful proposal, never a coordinator or automatic approval. */
|
|
98
|
+
export async function advanceLearning(root) {
|
|
99
|
+
const policy = readLearningPolicy(root);
|
|
100
|
+
if (!policy.enabled)
|
|
101
|
+
return;
|
|
102
|
+
const repo = new GoalRepository(root), catalog = listLearnedArtifacts(root), eligible = catalog.filter(a => a.kind === 'skill' && a.status === 'active' && a.label === 'demonstrated' && a.helpful_count >= 10 && a.harmful_count === 0 && (a.used_goals?.length ?? 0) >= 3);
|
|
103
|
+
const prior = repo.getPlan('G-learning');
|
|
104
|
+
if (prior?.steps.some(s => ['planned', 'running', 'blocked'].includes(s.status)))
|
|
105
|
+
return;
|
|
106
|
+
const priorBase = (prior?.steps ?? []).filter(s => s.learning_payload && s.learning_result && s.learning_kind !== 'meta').slice(-10);
|
|
107
|
+
if (!eligible.length && !(priorBase.length === 10 && priorBase.filter(s => s.learning_result.ok).length < 5))
|
|
108
|
+
return;
|
|
109
|
+
await withStoreWriteCoordinator(repo.planPath('G-learning'), async () => {
|
|
110
|
+
let g = repo.getGoal('G-learning'), p = repo.getPlan('G-learning');
|
|
111
|
+
if (p?.steps.some(s => ['planned', 'running', 'blocked'].includes(s.status)))
|
|
112
|
+
return;
|
|
113
|
+
const base = (p?.steps ?? []).filter(s => s.learning_payload && s.learning_result && s.learning_kind !== 'meta').slice(-10), trigger = base.map(s => s.step_id).join(',');
|
|
114
|
+
const coolingMeta = (p?.steps ?? []).some(s => s.learning_kind === 'meta' && Date.now() - Date.parse(s.learning_opened_at ?? new Date().toISOString()) < policy.distill_cooldown_days * 86400000);
|
|
115
|
+
const meta = !coolingMeta && base.length === 10 && base.filter(s => s.learning_result.ok).length < 5 && !(p?.steps ?? []).some(s => s.learning_kind === 'meta' && s.learning_trigger === trigger);
|
|
116
|
+
const candidate = eligible.find(a => !p?.steps.some(s => s.learning_kind === 'graduation' && s.learning_source === a.id));
|
|
117
|
+
const graduate = !!candidate;
|
|
118
|
+
if (!meta && !graduate)
|
|
119
|
+
return;
|
|
120
|
+
const now = new Date().toISOString();
|
|
121
|
+
const body = meta ? 'Propose one depth-one revision to the learn-from-work distillation skill. Use outcome.learned.skill with slug learn-from-work. Keep it proposed for operator approval. Never propose a revision about this overlay itself. Inspect the referenced rejected base payloads and acceptance errors.' : 'Propose the role-level rule this demonstrated skill implies, if any. Submit outcome.learned.instruction; it remains proposed until an operator approves it. Inspect the referenced demonstrated skill.';
|
|
122
|
+
const learning_inputs = meta ? { rejected_steps: base.map(s => s.step_id) } : { artifacts: [candidate.id] };
|
|
123
|
+
if (!p) {
|
|
124
|
+
p = createTaskPlan({ task: 'Keep this workspace\'s learned artifacts current', steps: [] });
|
|
125
|
+
g = { goal_id: 'G-learning', objective: p.task, status: 'executing', executor: 'host', plan_id: p.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: [] };
|
|
126
|
+
}
|
|
127
|
+
p = amendTaskPlan(p, { append_steps: [{ role: 'coders', task: body, learning_inputs, learning_scope: meta ? 'distiller-depth-1' : `graduate:${candidate.id}`, learning_kind: meta ? 'meta' : 'graduation', learning_source: meta ? undefined : candidate.id, learning_trigger: meta ? trigger : undefined, learning_opened_at: now }] });
|
|
128
|
+
if (g.status === 'done' || !g.started_at)
|
|
129
|
+
g.started_at = now;
|
|
130
|
+
g.status = 'executing';
|
|
131
|
+
repo.savePlan('G-learning', p);
|
|
132
|
+
repo.saveGoal(g);
|
|
133
|
+
}, { operation_label: 'learning_proposal' });
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=learning-controls.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Bounded derived context. The existing plans/knowledge retain every full input. */
|
|
2
|
+
import type { TaskStep } from './orchestrator-supervisor.js';
|
|
3
|
+
export interface LearningInputRefs {
|
|
4
|
+
episodes?: string[];
|
|
5
|
+
artifacts?: string[];
|
|
6
|
+
rejected_steps?: string[];
|
|
7
|
+
}
|
|
8
|
+
export declare function renderLearningTask(root: string, goalId: string, step: TaskStep): string;
|
|
9
|
+
//# sourceMappingURL=learning-input-context.d.ts.map
|