@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,62 @@
|
|
|
1
|
+
import { GoalRepository } from './store/repositories/goal-repository.js';
|
|
2
|
+
import { PlaybookRepository } from './store/repositories/playbook-repository.js';
|
|
3
|
+
import { listEpisodes } from './host-learning.js';
|
|
4
|
+
import { listLearnedArtifacts } from './learned-artifacts.js';
|
|
5
|
+
import { utf8Prefix } from './task-plan-context.js';
|
|
6
|
+
const MAX_CONTEXT_BYTES = 8000;
|
|
7
|
+
export function renderLearningTask(root, goalId, step) {
|
|
8
|
+
if (!step.learning_scope)
|
|
9
|
+
return step.task;
|
|
10
|
+
const source = `agent-state/goals/${goalId}.plan.json#${step.step_id}`;
|
|
11
|
+
const refs = step.learning_inputs;
|
|
12
|
+
if (!refs) {
|
|
13
|
+
if (Buffer.byteLength(step.task) <= MAX_CONTEXT_BYTES)
|
|
14
|
+
return step.task;
|
|
15
|
+
return `${utf8Prefix(step.task, 7400)}\n[Legacy learning input preview; full retained text is at the source below.]\nFull learning inputs: ${source}.task`;
|
|
16
|
+
}
|
|
17
|
+
const rows = [];
|
|
18
|
+
const episodes = new Map(listEpisodes(root).map(e => [e.id, e]));
|
|
19
|
+
const groups = new Map();
|
|
20
|
+
for (const id of refs.episodes ?? []) {
|
|
21
|
+
const group = episodes.get(id)?.goal_id ?? 'unavailable';
|
|
22
|
+
groups.set(group, [...(groups.get(group) ?? []), id]);
|
|
23
|
+
}
|
|
24
|
+
const selected = [];
|
|
25
|
+
while (selected.length < 8 && [...groups.values()].some(ids => ids.length)) {
|
|
26
|
+
for (const ids of groups.values()) {
|
|
27
|
+
if (ids.length && selected.length < 8)
|
|
28
|
+
selected.push(ids.shift());
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
for (const id of selected) {
|
|
32
|
+
const e = episodes.get(id);
|
|
33
|
+
rows.push({ id, text: e ? `${e.status}: ${utf8Prefix(e.failure_excerpt ?? '', 220)}\nSummary: ${utf8Prefix(e.summary, 300)}\nEvidence: ${e.evidence_refs.slice(0, 2).join(', ')}` : 'Source unavailable; retain this identity and inspect the referenced plan or pinned knowledge.' });
|
|
34
|
+
}
|
|
35
|
+
const artifacts = new Map([
|
|
36
|
+
...new PlaybookRepository(root).listBulletsReadOnly().map(b => [b.bullet_id, b]),
|
|
37
|
+
...listLearnedArtifacts(root).map(a => [a.id, a]),
|
|
38
|
+
]);
|
|
39
|
+
for (const id of (refs.artifacts ?? []).slice(0, 4)) {
|
|
40
|
+
const a = artifacts.get(id);
|
|
41
|
+
rows.push({ id, text: a ? `${a.status}: ${a.content ?? a.body ?? ''}` : 'Artifact unavailable; preserve its reference.' });
|
|
42
|
+
}
|
|
43
|
+
const plan = new GoalRepository(root).getPlan(goalId);
|
|
44
|
+
for (const id of (refs.rejected_steps ?? []).slice(-10)) {
|
|
45
|
+
const s = plan?.steps.find(s => s.step_id === id);
|
|
46
|
+
rows.push({ id: `${goalId}/${id}`, text: s ? `Acceptance errors: ${(s.learning_result?.errors ?? []).join('; ')}\nPayload preview: ${JSON.stringify(s.learning_payload)}` : 'Rejected work record unavailable; inspect its retained source.' });
|
|
47
|
+
}
|
|
48
|
+
const count = (refs.episodes?.length ?? 0) + (refs.artifacts?.length ?? 0) + (refs.rejected_steps?.length ?? 0);
|
|
49
|
+
const footer = `\nInput selection: ${rows.length} of ${count} records previewed. Summaries are excerpts; no source record is deleted.\nFull learning inputs: ${source}.learning_inputs\nRead the referenced goal plans, pinned episodes and knowledge with the host's tools or existing ACE read tools when more detail is needed.`;
|
|
50
|
+
const prefix = `${step.task}\n\nRetained input previews:\n`;
|
|
51
|
+
let remaining = MAX_CONTEXT_BYTES - Buffer.byteLength(prefix + footer);
|
|
52
|
+
const blocks = rows.map((row, index) => {
|
|
53
|
+
const budget = Math.max(0, Math.floor(remaining / (rows.length - index)));
|
|
54
|
+
const header = `[${row.id}]\n`;
|
|
55
|
+
const text = utf8Prefix(row.text, Math.max(0, budget - Buffer.byteLength(header) - 45));
|
|
56
|
+
const block = `${header}${text}${text.length < row.text.length ? '\n[excerpt; full source referenced below]' : ''}\n`;
|
|
57
|
+
remaining -= Buffer.byteLength(block);
|
|
58
|
+
return block;
|
|
59
|
+
});
|
|
60
|
+
return prefix + blocks.join('\n') + footer;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=learning-input-context.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** State transition dates on the artifact itself; no separate activity log. */
|
|
2
|
+
export interface LearningLifecycle {
|
|
3
|
+
status: string;
|
|
4
|
+
ever_active?: boolean;
|
|
5
|
+
activated_at?: string;
|
|
6
|
+
last_active_at?: string;
|
|
7
|
+
retired_at?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function learningLifecycle(next: LearningLifecycle, previous: LearningLifecycle | undefined, at: string): Omit<LearningLifecycle, 'status'>;
|
|
10
|
+
//# sourceMappingURL=learning-lifecycle.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function learningLifecycle(next, previous, at) {
|
|
2
|
+
const active = next.status === 'active';
|
|
3
|
+
const ever = active || previous?.status === 'active' ? true : next.ever_active ?? previous?.ever_active ?? (previous ? undefined : false);
|
|
4
|
+
return {
|
|
5
|
+
ever_active: ever,
|
|
6
|
+
activated_at: next.activated_at ?? previous?.activated_at ?? (active && previous?.status !== 'active' ? at : undefined),
|
|
7
|
+
last_active_at: active || previous?.status === 'active' ? at : next.last_active_at ?? previous?.last_active_at,
|
|
8
|
+
retired_at: next.status === 'retired' ? next.retired_at ?? (previous?.status !== 'retired' ? at : previous?.retired_at) : undefined,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=learning-lifecycle.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Read approved canonical overlays without importing execution/runtime owners. */
|
|
2
|
+
import { readLearningPolicy } from './learning-policy.js';
|
|
3
|
+
import { readStoreBlobSync } from './store/store-snapshot.js';
|
|
4
|
+
import { decodeLearned } from './learning-records.js';
|
|
5
|
+
export function approvedInstructionText(root, role) {
|
|
6
|
+
if (!readLearningPolicy(root).enabled)
|
|
7
|
+
return '';
|
|
8
|
+
const key = `knowledge/agents/${role}/LEARNED.md`, a = decodeLearned(readStoreBlobSync(root, key) ?? '', key);
|
|
9
|
+
return a?.kind === 'instruction' && a.status === 'active' && a.scope.role === role ? `\n\n## Learned in this workspace\n[${a.id}] ${a.body}\nEpisodes: ${a.episodes.join(', ')}` : '';
|
|
10
|
+
}
|
|
11
|
+
export function approvedDistillerText(root) {
|
|
12
|
+
if (!readLearningPolicy(root).enabled)
|
|
13
|
+
return '';
|
|
14
|
+
const key = 'knowledge/skills/learn-from-work/LEARNED.md', a = decodeLearned(readStoreBlobSync(root, key) ?? '', key);
|
|
15
|
+
return a?.kind === 'distiller' && a.status === 'active' ? `\n\n## Approved workspace distillation guidance\n[${a.id}] ${a.body}\nEpisodes: ${a.episodes.join(', ')}` : '';
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=learning-overlays.js.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** The learning block of the existing ACE_WORKFLOW profile. No load events on reads. */
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export declare const DEFAULT_LEARNING_POLICY: {
|
|
4
|
+
enabled: boolean;
|
|
5
|
+
distill_min_items: number;
|
|
6
|
+
distill_min_goals: number;
|
|
7
|
+
max_open_learning_steps: 1;
|
|
8
|
+
distill_cooldown_days: number;
|
|
9
|
+
max_learned_skills: number;
|
|
10
|
+
serve_max_lessons: number;
|
|
11
|
+
serve_max_skills: number;
|
|
12
|
+
serve_max_tokens: number;
|
|
13
|
+
retire_unhelpful_after_serves: number;
|
|
14
|
+
instructions_require_approval: true;
|
|
15
|
+
};
|
|
16
|
+
export declare const learningPolicySchema: z.ZodObject<{
|
|
17
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
18
|
+
distill_min_items: z.ZodDefault<z.ZodNumber>;
|
|
19
|
+
distill_min_goals: z.ZodDefault<z.ZodNumber>;
|
|
20
|
+
max_open_learning_steps: z.ZodDefault<z.ZodLiteral<1>>;
|
|
21
|
+
distill_cooldown_days: z.ZodDefault<z.ZodNumber>;
|
|
22
|
+
max_learned_skills: z.ZodDefault<z.ZodNumber>;
|
|
23
|
+
serve_max_lessons: z.ZodDefault<z.ZodNumber>;
|
|
24
|
+
serve_max_skills: z.ZodDefault<z.ZodNumber>;
|
|
25
|
+
serve_max_tokens: z.ZodDefault<z.ZodNumber>;
|
|
26
|
+
retire_unhelpful_after_serves: z.ZodDefault<z.ZodNumber>;
|
|
27
|
+
instructions_require_approval: z.ZodDefault<z.ZodLiteral<true>>;
|
|
28
|
+
}, z.core.$strict>;
|
|
29
|
+
export type LearningPolicy = z.infer<typeof learningPolicySchema> & {
|
|
30
|
+
issue?: string;
|
|
31
|
+
};
|
|
32
|
+
export declare function readLearningPolicy(root: string): LearningPolicy;
|
|
33
|
+
/** One identity for threshold and re-distillation cooldown decisions. */
|
|
34
|
+
export declare function learningScopeKey(scope: {
|
|
35
|
+
role?: string;
|
|
36
|
+
paths?: string[];
|
|
37
|
+
task_tags?: string[];
|
|
38
|
+
}): string;
|
|
39
|
+
export declare function canonicalLearningScopeKey(value: string): string;
|
|
40
|
+
//# sourceMappingURL=learning-policy.d.ts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** The learning block of the existing ACE_WORKFLOW profile. No load events on reads. */
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { readStoreBlobSync } from './store/store-snapshot.js';
|
|
6
|
+
import { extractFrontMatter, parseYamlFrontMatter } from './runtime-profile-syntax.js';
|
|
7
|
+
export const DEFAULT_LEARNING_POLICY = { enabled: true, distill_min_items: 3, distill_min_goals: 2, max_open_learning_steps: 1, distill_cooldown_days: 7, max_learned_skills: 30, serve_max_lessons: 3, serve_max_skills: 2, serve_max_tokens: 600, retire_unhelpful_after_serves: 40, instructions_require_approval: true };
|
|
8
|
+
export const learningPolicySchema = z.object({ enabled: z.boolean().default(true), distill_min_items: z.number().int().positive().default(3), distill_min_goals: z.number().int().min(2).default(2), max_open_learning_steps: z.literal(1).default(1), distill_cooldown_days: z.number().positive().default(7), max_learned_skills: z.number().int().min(1).max(30).default(30), serve_max_lessons: z.number().int().min(0).max(3).default(3), serve_max_skills: z.number().int().min(0).max(2).default(2), serve_max_tokens: z.number().int().min(1).max(600).default(600), retire_unhelpful_after_serves: z.number().int().positive().default(40), instructions_require_approval: z.literal(true).default(true) }).strict();
|
|
9
|
+
export function readLearningPolicy(root) {
|
|
10
|
+
let result = { ...DEFAULT_LEARNING_POLICY };
|
|
11
|
+
const file = join(root, 'agent-state/ACE_WORKFLOW.md');
|
|
12
|
+
const raw = readStoreBlobSync(root, 'knowledge/runtime/ACE_WORKFLOW.md') ?? readStoreBlobSync(root, 'state/artifacts/agent-state/ACE_WORKFLOW.md') ?? (existsSync(file) ? readFileSync(file, 'utf8') : undefined);
|
|
13
|
+
if (raw)
|
|
14
|
+
try {
|
|
15
|
+
const front = parseYamlFrontMatter(extractFrontMatter(raw).front_matter);
|
|
16
|
+
result = learningPolicySchema.parse(front.learning ?? {});
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
result = { ...result, enabled: false, issue: `Learning profile invalid: ${String(error)}` };
|
|
20
|
+
}
|
|
21
|
+
if (process.env.ACE_LEARN === '0')
|
|
22
|
+
result.enabled = false;
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
/** One identity for threshold and re-distillation cooldown decisions. */
|
|
26
|
+
export function learningScopeKey(scope) {
|
|
27
|
+
const path = (scope.paths?.[0] ?? '').replace(/\\/g, '/').replace(/^\.\//, '').split('/').slice(0, 2).join('/');
|
|
28
|
+
return `${scope.role ?? ''}|${path}|${scope.task_tags?.[0] ?? ''}`;
|
|
29
|
+
}
|
|
30
|
+
export function canonicalLearningScopeKey(value) {
|
|
31
|
+
try {
|
|
32
|
+
if (value.startsWith('{'))
|
|
33
|
+
return learningScopeKey(JSON.parse(value));
|
|
34
|
+
}
|
|
35
|
+
catch { }
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=learning-policy.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Episode } from './host-learning.js';
|
|
2
|
+
import type { PlaybookScope } from './store/repositories/playbook-repository.js';
|
|
3
|
+
export interface LearnedArtifact {
|
|
4
|
+
id: string;
|
|
5
|
+
key: string;
|
|
6
|
+
kind: 'skill' | 'instruction' | 'distiller';
|
|
7
|
+
status: 'active' | 'quarantined' | 'proposed' | 'retired';
|
|
8
|
+
label: 'checked' | 'demonstrated';
|
|
9
|
+
episodes: string[];
|
|
10
|
+
evidence_snapshots: Episode[];
|
|
11
|
+
scope: PlaybookScope;
|
|
12
|
+
body: string;
|
|
13
|
+
acceptance_errors: string[];
|
|
14
|
+
checked_at: string;
|
|
15
|
+
proposal_id: string;
|
|
16
|
+
helpful_count: number;
|
|
17
|
+
harmful_count: number;
|
|
18
|
+
references?: Record<string, string>;
|
|
19
|
+
retired_reason?: string;
|
|
20
|
+
ever_active?: boolean;
|
|
21
|
+
activated_at?: string;
|
|
22
|
+
last_active_at?: string;
|
|
23
|
+
retired_at?: string;
|
|
24
|
+
used_goals?: string[];
|
|
25
|
+
served_count?: number;
|
|
26
|
+
used_count?: number;
|
|
27
|
+
served_outcomes?: {
|
|
28
|
+
done_first_attempt: number;
|
|
29
|
+
failed_same_signature: number;
|
|
30
|
+
other: number;
|
|
31
|
+
};
|
|
32
|
+
[key: string]: unknown;
|
|
33
|
+
}
|
|
34
|
+
export declare function decodeLearned(text: string, key: string): LearnedArtifact | undefined;
|
|
35
|
+
//# sourceMappingURL=learning-records.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Shared learned-knowledge metadata codec; no runtime, helper or goal imports. */
|
|
2
|
+
import { parse } from 'yaml';
|
|
3
|
+
export function decodeLearned(text, key) {
|
|
4
|
+
try {
|
|
5
|
+
const m = text.match(/^---\n([\s\S]*?)\n---\n/);
|
|
6
|
+
if (!m)
|
|
7
|
+
return;
|
|
8
|
+
const front = parse(m[1]);
|
|
9
|
+
if (!front.ace_learning)
|
|
10
|
+
return;
|
|
11
|
+
return { ...front.ace_learning, key, body: text.slice(m[0].length), description: front.description };
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=learning-records.js.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** The declared scorecard, derived from retained work and artifact state. */
|
|
2
|
+
import type { TaskStep } from './orchestrator-supervisor.js';
|
|
3
|
+
import type { Episode } from './host-learning.js';
|
|
4
|
+
import type { PlaybookBullet } from './store/repositories/playbook-repository.js';
|
|
5
|
+
import type { LearnedArtifact } from './learning-records.js';
|
|
6
|
+
type Work = {
|
|
7
|
+
goal: string;
|
|
8
|
+
step: TaskStep;
|
|
9
|
+
};
|
|
10
|
+
declare const phase: () => {
|
|
11
|
+
done: number;
|
|
12
|
+
steps: number;
|
|
13
|
+
rate: number | null;
|
|
14
|
+
};
|
|
15
|
+
export declare function buildLearningScorecard(steps: Work[], episodes: Episode[], lessons: PlaybookBullet[], artifacts: LearnedArtifact[], now?: number): {
|
|
16
|
+
active_learned_artifacts: {
|
|
17
|
+
lessons: number;
|
|
18
|
+
skills: number;
|
|
19
|
+
approved_instructions: number;
|
|
20
|
+
};
|
|
21
|
+
repeat_failure_rate: {
|
|
22
|
+
signatures_served: number;
|
|
23
|
+
signatures_recurred: number;
|
|
24
|
+
rate: number | null;
|
|
25
|
+
missing_context_attempts: number;
|
|
26
|
+
interpretation: string;
|
|
27
|
+
};
|
|
28
|
+
first_attempt_success_by_scope: {
|
|
29
|
+
scope: string;
|
|
30
|
+
before: ReturnType<typeof phase>;
|
|
31
|
+
after: ReturnType<typeof phase>;
|
|
32
|
+
unclassified: ReturnType<typeof phase>;
|
|
33
|
+
unexposed: ReturnType<typeof phase>;
|
|
34
|
+
}[];
|
|
35
|
+
first_attempt_coverage: {
|
|
36
|
+
missing_first_attempts: number;
|
|
37
|
+
reconstructed_scopes: number;
|
|
38
|
+
interpretation: string;
|
|
39
|
+
};
|
|
40
|
+
served_helpfulness: {
|
|
41
|
+
helpful: number;
|
|
42
|
+
harmful: number;
|
|
43
|
+
ratio: number | null;
|
|
44
|
+
counter_scope: string;
|
|
45
|
+
};
|
|
46
|
+
distill_acceptance: {
|
|
47
|
+
checked: number;
|
|
48
|
+
submitted: number;
|
|
49
|
+
rate: number | null;
|
|
50
|
+
demonstrated_current_artifacts: number;
|
|
51
|
+
};
|
|
52
|
+
learning_overhead: {
|
|
53
|
+
learning_steps: number;
|
|
54
|
+
all_steps: number;
|
|
55
|
+
ratio: number | null;
|
|
56
|
+
opportunistic_payload_steps: number;
|
|
57
|
+
assignments: {
|
|
58
|
+
episode_id: string;
|
|
59
|
+
utf8_bytes: number;
|
|
60
|
+
token_upper_bound: number;
|
|
61
|
+
}[];
|
|
62
|
+
max_served_token_upper_bound: number | null;
|
|
63
|
+
token_method: string;
|
|
64
|
+
};
|
|
65
|
+
retirement_health: {
|
|
66
|
+
window_days: number;
|
|
67
|
+
retired: number;
|
|
68
|
+
ever_active: number;
|
|
69
|
+
ratio: number | null;
|
|
70
|
+
unknown_history: number;
|
|
71
|
+
};
|
|
72
|
+
distillation_steps: {
|
|
73
|
+
goal_id: string;
|
|
74
|
+
step_id: string;
|
|
75
|
+
checked: boolean;
|
|
76
|
+
items_after_20_serves: number;
|
|
77
|
+
mean_strong_helpfulness: number | null;
|
|
78
|
+
}[];
|
|
79
|
+
coverage: string;
|
|
80
|
+
};
|
|
81
|
+
export {};
|
|
82
|
+
//# sourceMappingURL=learning-scorecard.d.ts.map
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { learningScopeKey } from './learning-policy.js';
|
|
2
|
+
const ratio = (n, d) => d ? n / d : null;
|
|
3
|
+
const phase = () => ({ done: 0, steps: 0, rate: null });
|
|
4
|
+
function match(scope, e) {
|
|
5
|
+
return (scope.role === e.role ? 2 : 0) +
|
|
6
|
+
(scope.paths?.some(p => e.paths.some(q => q === p || q.startsWith(p + '/') || p.startsWith(q + '/'))) ? 3 : 0) +
|
|
7
|
+
(scope.task_tags?.filter(t => e.tags.includes(t)).length ?? 0) +
|
|
8
|
+
(scope.failure_signature && scope.failure_signature === e.failure_signature ? 5 : 0);
|
|
9
|
+
}
|
|
10
|
+
export function buildLearningScorecard(steps, episodes, lessons, artifacts, now = Date.now()) {
|
|
11
|
+
const skills = artifacts.filter(a => a.kind === 'skill');
|
|
12
|
+
const known = [...lessons, ...skills];
|
|
13
|
+
const byId = new Map(known.map(a => ['bullet_id' in a ? a.bullet_id : a.id, a]));
|
|
14
|
+
const work = new Map(steps.map(s => [`${s.goal}:${s.step.step_id}`, s.step]));
|
|
15
|
+
const signatures = new Set(), recurring = new Set();
|
|
16
|
+
const servedSizes = [];
|
|
17
|
+
let missingContext = 0;
|
|
18
|
+
for (const ep of episodes) {
|
|
19
|
+
const step = work.get(`${ep.goal_id}:${ep.step_id}`);
|
|
20
|
+
const attempt = ep.attempt ?? Number(ep.id.split(':').at(-1));
|
|
21
|
+
const fallback = step && step.attempt === attempt && step.served_learning ? {
|
|
22
|
+
items: step.served_learning.items,
|
|
23
|
+
rendered_bytes: Buffer.byteLength(step.served_learning.text),
|
|
24
|
+
} : undefined;
|
|
25
|
+
const context = ep.served_context ?? fallback;
|
|
26
|
+
if (!context) {
|
|
27
|
+
if (ep.served?.length)
|
|
28
|
+
missingContext++;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (context.items.length)
|
|
32
|
+
servedSizes.push({ episode_id: ep.id, utf8_bytes: context.rendered_bytes, token_upper_bound: context.rendered_bytes });
|
|
33
|
+
for (const item of context.items.filter(i => i.kind === 'lesson' && i.scope.failure_signature)) {
|
|
34
|
+
signatures.add(item.scope.failure_signature);
|
|
35
|
+
if (ep.failure_signature === item.scope.failure_signature)
|
|
36
|
+
recurring.add(item.scope.failure_signature);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const scopes = new Map();
|
|
40
|
+
let missingFirst = 0, reconstructedScopes = 0;
|
|
41
|
+
const episodeMap = new Map(episodes.map(e => [`${e.goal_id}:${e.step_id}:${e.attempt ?? Number(e.id.split(':').at(-1))}`, e]));
|
|
42
|
+
for (const { goal, step } of steps) {
|
|
43
|
+
const first = episodeMap.get(`${goal}:${step.step_id}:1`);
|
|
44
|
+
if (!first) {
|
|
45
|
+
missingFirst++;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (!first.scope_snapshot)
|
|
49
|
+
reconstructedScopes++;
|
|
50
|
+
const key = learningScopeKey({ role: first.role, paths: first.paths, task_tags: first.tags });
|
|
51
|
+
const group = scopes.get(key) ?? { scope: key, before: phase(), after: phase(), unclassified: phase(), unexposed: phase() };
|
|
52
|
+
const relevant = known.filter(a => a.ever_active !== false && (a.status === 'active' || a.ever_active) && match(a.scope, first) >= 3);
|
|
53
|
+
const dates = relevant.flatMap(a => a.activated_at && Number.isFinite(Date.parse(a.activated_at)) ? [Date.parse(a.activated_at)] : []);
|
|
54
|
+
const kind = !Number.isFinite(Date.parse(first.at)) ? 'unclassified' : !relevant.length ? 'unexposed' : dates.length !== relevant.length ? 'unclassified' : Date.parse(first.at) < Math.min(...dates) ? 'before' : 'after';
|
|
55
|
+
group[kind].steps++;
|
|
56
|
+
if (first.status === 'done')
|
|
57
|
+
group[kind].done++;
|
|
58
|
+
for (const p of [group.before, group.after, group.unclassified, group.unexposed])
|
|
59
|
+
p.rate = ratio(p.done, p.steps);
|
|
60
|
+
scopes.set(key, group);
|
|
61
|
+
}
|
|
62
|
+
const learning = steps.filter(({ step }) => !!step.learning_scope);
|
|
63
|
+
const submissions = steps.filter(({ step }) => !!step.learning_payload && !!step.learning_result);
|
|
64
|
+
const checked = submissions.filter(({ step }) => step.learning_result.ok);
|
|
65
|
+
const helpful = known.reduce((n, a) => n + a.helpful_count, 0), harmful = known.reduce((n, a) => n + a.harmful_count, 0);
|
|
66
|
+
const since = now - 30 * 86400000;
|
|
67
|
+
const recentActive = known.filter(a => a.status === 'active' || (a.ever_active && !!a.last_active_at && Date.parse(a.last_active_at) >= since));
|
|
68
|
+
const retired = recentActive.filter(a => a.status === 'retired' && !!a.retired_at && Date.parse(a.retired_at) >= since);
|
|
69
|
+
const unknownHistory = known.filter(a => a.status !== 'active' && (a.ever_active === undefined || (a.ever_active && !a.last_active_at)));
|
|
70
|
+
return {
|
|
71
|
+
active_learned_artifacts: { lessons: lessons.filter(a => a.status === 'active').length, skills: skills.filter(a => a.status === 'active').length, approved_instructions: artifacts.filter(a => a.kind === 'instruction' && a.status === 'active').length },
|
|
72
|
+
repeat_failure_rate: { signatures_served: signatures.size, signatures_recurred: recurring.size, rate: ratio(recurring.size, signatures.size), missing_context_attempts: missingContext, interpretation: 'Recurrence after serving is correlational; it is not proof the item was used or caused the outcome.' },
|
|
73
|
+
first_attempt_success_by_scope: [...scopes.values()],
|
|
74
|
+
first_attempt_coverage: { missing_first_attempts: missingFirst, reconstructed_scopes: reconstructedScopes, interpretation: 'Before/after uses known activation timestamps and retained first attempts. Unknown activation history stays unclassified.' },
|
|
75
|
+
served_helpfulness: { helpful, harmful, ratio: ratio(helpful, helpful + harmful), counter_scope: 'Existing artifact counters; legacy producer history is not retrospectively re-attributed.' },
|
|
76
|
+
distill_acceptance: { checked: checked.length, submitted: submissions.length, rate: ratio(checked.length, submissions.length), demonstrated_current_artifacts: known.filter(a => a.status === 'active' && a.label === 'demonstrated').length },
|
|
77
|
+
learning_overhead: { learning_steps: learning.length, all_steps: steps.length, ratio: ratio(learning.length, steps.length), opportunistic_payload_steps: submissions.filter(({ step }) => !step.learning_scope).length, assignments: servedSizes, max_served_token_upper_bound: servedSizes.length ? Math.max(...servedSizes.map(s => s.token_upper_bound)) : null, token_method: 'UTF-8 byte count is a conservative upper bound; no provider tokenizer count is claimed.' },
|
|
78
|
+
retirement_health: { window_days: 30, retired: retired.length, ever_active: recentActive.length, ratio: ratio(retired.length, recentActive.length), unknown_history: unknownHistory.length },
|
|
79
|
+
distillation_steps: submissions.map(({ goal, step }) => {
|
|
80
|
+
const eligible = (step.learning_result.ids ?? []).flatMap(id => { const a = byId.get(id); return a && (a.served_count ?? 0) >= 20 && a.helpful_count + a.harmful_count > 0 ? [a.helpful_count / (a.helpful_count + a.harmful_count)] : []; });
|
|
81
|
+
return { goal_id: goal, step_id: step.step_id, checked: step.learning_result.ok, items_after_20_serves: eligible.length, mean_strong_helpfulness: eligible.length ? eligible.reduce((sum, v) => sum + v, 0) / eligible.length : null };
|
|
82
|
+
}),
|
|
83
|
+
coverage: 'Retained plans, pinned episodes and existing artifacts only; missing history and insufficient samples are not treated as success.',
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=learning-scorecard.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Match learning at assignment time; score only consequence with evidence of use. */
|
|
2
|
+
import type { TaskPlan, TaskStep } from './orchestrator-supervisor.js';
|
|
3
|
+
import type { GoalRecord } from './store/repositories/goal-repository.js';
|
|
4
|
+
import { type PlaybookScope } from './store/repositories/playbook-repository.js';
|
|
5
|
+
import type { StepOutcome } from './host-goal-engine.js';
|
|
6
|
+
export interface ServedLearningItem {
|
|
7
|
+
id: string;
|
|
8
|
+
kind: 'lesson' | 'skill' | 'packaged';
|
|
9
|
+
scope: PlaybookScope;
|
|
10
|
+
text: string;
|
|
11
|
+
check?: string;
|
|
12
|
+
checks?: string[];
|
|
13
|
+
helpful: number;
|
|
14
|
+
harmful: number;
|
|
15
|
+
score: number;
|
|
16
|
+
}
|
|
17
|
+
export interface ServedLearning {
|
|
18
|
+
text: string;
|
|
19
|
+
items: ServedLearningItem[];
|
|
20
|
+
}
|
|
21
|
+
export declare function selectLearning(root: string, plan: TaskPlan, step: TaskStep): ServedLearning;
|
|
22
|
+
export declare function recordSkillUse(root: string, ref: {
|
|
23
|
+
goal_id: string;
|
|
24
|
+
step_id: string;
|
|
25
|
+
attempt: number;
|
|
26
|
+
agent: string;
|
|
27
|
+
}, id: string): Promise<void>;
|
|
28
|
+
export declare function scoreLearning(root: string, goal: GoalRecord, step: TaskStep, outcome: StepOutcome): Promise<void>;
|
|
29
|
+
//# sourceMappingURL=learning-serving.d.ts.map
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { readLearningPolicy } from './learning-policy.js';
|
|
2
|
+
import { GoalRepository } from './store/repositories/goal-repository.js';
|
|
3
|
+
import { PlaybookRepository, withPlaybookTransaction } from './store/repositories/playbook-repository.js';
|
|
4
|
+
import { listLearnedArtifacts, updateLearnedArtifact, ensureLearningStep } from './learned-artifacts.js';
|
|
5
|
+
import { listSkillCatalogEntries } from './skill-catalog.js';
|
|
6
|
+
import { withWorkspaceRoot } from './helpers/workspace-root.js';
|
|
7
|
+
import { withStoreWriteCoordinator } from './store/write-coordinator.js';
|
|
8
|
+
import { failureSignature, learningTaskText } from './experience-reflector.js';
|
|
9
|
+
import { curate } from './playbook-curator.js';
|
|
10
|
+
const tags = ['test', 'build', 'store', 'todo', 'handoff', 'scheduler', 'migration', 'docs', 'release', 'mcp', 'hook', 'schema'];
|
|
11
|
+
export function selectLearning(root, plan, step) {
|
|
12
|
+
const policy = readLearningPolicy(root);
|
|
13
|
+
if (!policy.enabled || step.learning_scope)
|
|
14
|
+
return { text: '', items: [] };
|
|
15
|
+
const all = [step, ...plan.steps.filter(s => step.depends_on?.includes(s.step_id))];
|
|
16
|
+
const signatures = new Set(all.flatMap(s => s.attempts_log?.flatMap(a => a.failure_signature ? [a.failure_signature] : []) ?? []));
|
|
17
|
+
const paths = all.flatMap(s => [...(s.expected_artifacts ?? []).map(a => a.path), ...(s.evidence_refs ?? [])]).map(p => p.replace(/\\/g, '/').replace(/^\.\//, ''));
|
|
18
|
+
const taskTags = tags.filter(tag => learningTaskText(step).toLowerCase().includes(tag));
|
|
19
|
+
const score = (scope, helpful, harmful) => (scope.failure_signature && signatures.has(scope.failure_signature) ? 5 : 0) + (scope.paths?.some(p => paths.some(q => q === p || q.startsWith(p + '/') || p.startsWith(q + '/'))) ? 3 : 0) + (scope.role === step.role ? 2 : 0) + (scope.task_tags?.filter(t => taskTags.includes(t)).length ?? 0) - (harmful > helpful ? 3 : 0);
|
|
20
|
+
const candidates = new PlaybookRepository(root).listBulletsReadOnly({ status: 'active' }).map(b => ({ id: b.bullet_id, kind: 'lesson', scope: b.scope, text: `[${b.bullet_id}] (learned, ${b.label ?? 'checked'}) ${b.content} (helped ${b.helpful_count}×; harmful ${b.harmful_count}×)`, check: b.content.match(/Check:\s*(.+)$/)?.[1]?.trim(), helpful: b.helpful_count, harmful: b.harmful_count, score: score(b.scope, b.helpful_count, b.harmful_count) }));
|
|
21
|
+
for (const a of listLearnedArtifacts(root).filter(a => a.kind === 'skill' && a.status === 'active'))
|
|
22
|
+
candidates.push({ id: a.id, kind: 'skill', scope: a.scope, text: `[${a.id}] (${a.label}) ${typeof a.description === 'string' ? a.description : 'Use for ' + JSON.stringify(a.scope)}`, checks: [...(a.body.match(/^## Validation[^\n]*\n([\s\S]*?)(?=^## |$(?![\s\S]))/m)?.[1] ?? '').matchAll(/```bash\s*\n([\s\S]*?)```/g)].map(m => m[1].replace(/\n$/, '')), helpful: a.helpful_count, harmful: a.harmful_count, score: score(a.scope, a.helpful_count, a.harmful_count) });
|
|
23
|
+
const packaged = withWorkspaceRoot(root, () => listSkillCatalogEntries()).filter(s => s.source === 'package-defaults');
|
|
24
|
+
for (const skill of packaged) {
|
|
25
|
+
const scope = { task_tags: tags.filter(t => skill.description.toLowerCase().includes(t)) };
|
|
26
|
+
candidates.push({ id: skill.name, kind: 'packaged', scope, text: `[${skill.name}] ${skill.description} — load with get_skill_instructions`, helpful: 0, harmful: 0, score: score(scope, 0, 0) });
|
|
27
|
+
}
|
|
28
|
+
const picked = [];
|
|
29
|
+
let text = '';
|
|
30
|
+
for (const item of candidates.filter(c => c.score >= 3).sort((a, b) => b.score - a.score || b.helpful - a.helpful || a.id.localeCompare(b.id))) {
|
|
31
|
+
if (picked.filter(i => i.kind === 'lesson').length >= policy.serve_max_lessons && item.kind === 'lesson')
|
|
32
|
+
continue;
|
|
33
|
+
if (picked.filter(i => i.kind !== 'lesson').length >= policy.serve_max_skills && item.kind !== 'lesson')
|
|
34
|
+
continue;
|
|
35
|
+
const rendered = text + (text ? '\n' : 'Known pitfalls and relevant skills (load when useful):\n') + item.text;
|
|
36
|
+
if (Buffer.byteLength(rendered, 'utf8') > policy.serve_max_tokens)
|
|
37
|
+
continue;
|
|
38
|
+
picked.push(item);
|
|
39
|
+
text = rendered;
|
|
40
|
+
}
|
|
41
|
+
return { text, items: picked };
|
|
42
|
+
}
|
|
43
|
+
export async function recordSkillUse(root, ref, id) {
|
|
44
|
+
if (!readLearningPolicy(root).enabled)
|
|
45
|
+
return;
|
|
46
|
+
const repo = new GoalRepository(root);
|
|
47
|
+
await withStoreWriteCoordinator(repo.planPath(ref.goal_id), async () => {
|
|
48
|
+
const p = repo.getPlan(ref.goal_id), s = p?.steps.find(s => s.step_id === ref.step_id);
|
|
49
|
+
if (!p || !s || s.status !== 'running' || s.attempt !== ref.attempt || s.assignee !== ref.agent || !s.served_learning?.items.some(i => i.id === id && i.kind !== 'lesson'))
|
|
50
|
+
return;
|
|
51
|
+
s.loaded_learning ??= [];
|
|
52
|
+
if (!s.loaded_learning.some(i => i.id === id)) {
|
|
53
|
+
s.loaded_learning.push({ id, at: new Date().toISOString() });
|
|
54
|
+
repo.savePlan(ref.goal_id, p);
|
|
55
|
+
}
|
|
56
|
+
}, { operation_label: 'served_skill_use' });
|
|
57
|
+
}
|
|
58
|
+
export async function scoreLearning(root, goal, step, outcome) {
|
|
59
|
+
const policy = readLearningPolicy(root);
|
|
60
|
+
if (!policy.enabled || !step.served_learning?.items.length)
|
|
61
|
+
return;
|
|
62
|
+
const episode = step.attempts_log?.at(-1);
|
|
63
|
+
if (!episode || episode.attempt !== step.attempt)
|
|
64
|
+
return;
|
|
65
|
+
const key = `${goal.goal_id}:${step.step_id}:${episode.attempt}`, runs = episode.observed_runs ?? [], forces = [];
|
|
66
|
+
for (const served of step.served_learning.items) {
|
|
67
|
+
if (served.kind === 'packaged')
|
|
68
|
+
continue;
|
|
69
|
+
const feedback = step.learning_feedback?.find(f => f.id === served.id);
|
|
70
|
+
const checkRuns = runs.filter(r => r.attribution === 'Linked' && (r.command === served.check || served.checks?.includes(r.command)));
|
|
71
|
+
const loaded = step.loaded_learning?.find(i => i.id === served.id);
|
|
72
|
+
const used = feedback !== undefined || !!loaded || (served.kind === 'lesson' && checkRuns.length > 0);
|
|
73
|
+
const usedAt = loaded?.at ?? checkRuns[0]?.at;
|
|
74
|
+
const recurrence = !!served.scope.failure_signature && (runs.some(r => r.attribution === 'Linked' && r.outcome === 'failed' && failureSignature(r.output_tail ?? '') === served.scope.failure_signature && (!usedAt || r.at >= usedAt)) || (!runs.length && episode.status !== 'done' && episode.failure_signature === served.scope.failure_signature));
|
|
75
|
+
const commands = served.check ? [served.check] : (served.checks ?? []);
|
|
76
|
+
const passed = episode.status === 'done' && commands.length > 0 && commands.every(command => checkRuns.some(r => r.command === command && r.outcome === 'ok' && r.exit_code === 0));
|
|
77
|
+
const tag = feedback ? (feedback.helpful ? 'helpful' : 'harmful') : used && recurrence ? 'harmful' : used && passed ? 'helpful' : undefined;
|
|
78
|
+
const weak = !used ? (episode.status === 'done' && episode.attempt === 1 ? 'done_first_attempt' : recurrence ? 'failed_same_signature' : 'other') : undefined;
|
|
79
|
+
if (served.kind === 'lesson') {
|
|
80
|
+
await withPlaybookTransaction(root, async (repo) => {
|
|
81
|
+
let b = repo.getBullet(served.id);
|
|
82
|
+
if (!b || b.scored_attempts?.includes(key))
|
|
83
|
+
return;
|
|
84
|
+
if (tag) {
|
|
85
|
+
await curate(repo, [{ type: 'TAG', bullet_id: b.bullet_id, tag }], { quarantine_harmful_min: 2, emitEvent: false });
|
|
86
|
+
b = repo.getBullet(served.id);
|
|
87
|
+
}
|
|
88
|
+
const counts = b.served_outcomes ?? { done_first_attempt: 0, failed_same_signature: 0, other: 0 };
|
|
89
|
+
if (weak)
|
|
90
|
+
counts[weak]++;
|
|
91
|
+
const next = { ...b, served_count: (b.served_count ?? 0) + 1, used_count: (b.used_count ?? 0) + (used ? 1 : 0), served_outcomes: counts, scored_attempts: [...(b.scored_attempts ?? []), key], used_goals: used ? [...new Set([...(b.used_goals ?? []), goal.goal_id])] : b.used_goals };
|
|
92
|
+
if (next.served_count >= policy.retire_unhelpful_after_serves && !next.used_count) {
|
|
93
|
+
next.status = 'retired';
|
|
94
|
+
next.retired_reason = 'Served at least 40 times without evidence of use.';
|
|
95
|
+
}
|
|
96
|
+
repo.saveBullet(next);
|
|
97
|
+
if (tag === 'harmful' && recurrence)
|
|
98
|
+
forces.push(b.scope);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
const found = listLearnedArtifacts(root).find(a => a.id === served.id && a.kind === 'skill');
|
|
103
|
+
if (!found)
|
|
104
|
+
continue;
|
|
105
|
+
await updateLearnedArtifact(root, found.key, a => {
|
|
106
|
+
if (a.scored_attempts?.includes(key))
|
|
107
|
+
return;
|
|
108
|
+
a.served_count = (a.served_count ?? 0) + 1;
|
|
109
|
+
a.used_count = (a.used_count ?? 0) + (used ? 1 : 0);
|
|
110
|
+
a.scored_attempts = [...(a.scored_attempts ?? []), key];
|
|
111
|
+
a.used_goals = used ? [...new Set([...(a.used_goals ?? []), goal.goal_id])] : a.used_goals;
|
|
112
|
+
if (tag === 'helpful')
|
|
113
|
+
a.helpful_count++;
|
|
114
|
+
if (tag === 'harmful')
|
|
115
|
+
a.harmful_count++;
|
|
116
|
+
a.served_outcomes ??= { done_first_attempt: 0, failed_same_signature: 0, other: 0 };
|
|
117
|
+
if (weak)
|
|
118
|
+
a.served_outcomes[weak]++;
|
|
119
|
+
if (a.harmful_count >= 2 && a.harmful_count > a.helpful_count)
|
|
120
|
+
a.status = 'quarantined';
|
|
121
|
+
if (a.served_count >= policy.retire_unhelpful_after_serves && !a.used_count) {
|
|
122
|
+
a.status = 'retired';
|
|
123
|
+
a.retired_reason = 'Served at least 40 times without evidence of use.';
|
|
124
|
+
}
|
|
125
|
+
if (tag === 'harmful' && recurrence)
|
|
126
|
+
forces.push(a.scope);
|
|
127
|
+
return a;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
for (const scope of forces)
|
|
132
|
+
await ensureLearningStep(root, { scope, reason: 'An applied artifact\'s own failure recurred. Compare the served attempts and repair the advice.' });
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=learning-serving.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TodoNode } from './store/repositories/todo-repository.js';
|
|
2
|
+
import type { TaskPlan } from './orchestrator-supervisor.js';
|
|
3
|
+
export declare function deriveLinkedTodo<T extends TodoNode>(todo: T, root: string, snapshot?: {
|
|
4
|
+
goal: {
|
|
5
|
+
goal_id: string;
|
|
6
|
+
};
|
|
7
|
+
plan: TaskPlan;
|
|
8
|
+
}): T;
|
|
9
|
+
//# sourceMappingURL=linked-work.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Read-only derivation: a linked todo is a view of its existing goal step. */
|
|
2
|
+
import { GoalRepository } from './store/repositories/goal-repository.js';
|
|
3
|
+
export function deriveLinkedTodo(todo, root, snapshot) {
|
|
4
|
+
const repo = new GoalRepository(root);
|
|
5
|
+
let ref = todo.step_ref;
|
|
6
|
+
if (!ref) {
|
|
7
|
+
const current = snapshot?.plan.steps.find(s => s.todo_id === todo.id);
|
|
8
|
+
if (current)
|
|
9
|
+
ref = { goal_id: snapshot.goal.goal_id, step_id: current.step_id };
|
|
10
|
+
else
|
|
11
|
+
for (const goal of repo.listGoals()) {
|
|
12
|
+
const linked = repo.getPlan(goal.goal_id)?.steps.find(s => s.todo_id === todo.id);
|
|
13
|
+
if (linked) {
|
|
14
|
+
ref = { goal_id: goal.goal_id, step_id: linked.step_id };
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (!ref)
|
|
20
|
+
return todo;
|
|
21
|
+
const plan = snapshot?.goal.goal_id === ref.goal_id ? snapshot.plan : repo.getPlan(ref.goal_id);
|
|
22
|
+
const step = plan?.steps.find(s => s.step_id === ref.step_id);
|
|
23
|
+
if (!step)
|
|
24
|
+
return todo;
|
|
25
|
+
const status = step.status === 'running' ? 'in_progress' : step.status === 'done' || step.status === 'skipped' ? 'done' : step.status === 'failed' || step.status === 'blocked' ? 'blocked' : 'pending';
|
|
26
|
+
return { ...todo, step_ref: ref, status, ...(step.assignee ? { assigned_to: step.assignee } : {}) };
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=linked-work.js.map
|
|
@@ -159,7 +159,7 @@ export async function runLocalModelTask(options) {
|
|
|
159
159
|
requested_tier: options.tier,
|
|
160
160
|
requested_model_class: options.modelClass,
|
|
161
161
|
});
|
|
162
|
-
const maxTurns = options.maxTurns ?? defaultTurnsForPolicy(role, policy);
|
|
162
|
+
const maxTurns = options.maxTurns ?? (runtime.executionEngine === 'hermes_local' ? defaultTurnsForPolicy(role, policy) : 0);
|
|
163
163
|
const requestedToolScope = options.toolScope ?? defaultToolScopeForPolicy(role, policy);
|
|
164
164
|
const filteredToolCatalog = buildFilteredToolCatalog(policy.model_class, requestedToolScope, {
|
|
165
165
|
mutation_lane: policy.mutation_lane,
|