@voybio/ace-swarm 2.4.4 → 2.4.6
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 +69 -0
- package/README.md +72 -532
- package/assets/eval-battery/battery.json +85 -0
- package/assets/eval-battery/fixtures/edit-target.js +3 -0
- package/assets/scripts/render-mcp-configs.sh +12 -0
- package/dist/ace-context.d.ts +18 -0
- package/dist/ace-context.js +161 -16
- package/dist/ace-server-instructions.js +36 -9
- package/dist/bridge-trace.d.ts +17 -0
- package/dist/bridge-trace.js +23 -0
- package/dist/cli.d.ts +9 -1
- package/dist/cli.js +619 -10
- package/dist/discovery-runtime-wrappers.d.ts +16 -1
- package/dist/discovery-runtime-wrappers.js +65 -9
- package/dist/eval-harness.d.ts +110 -0
- package/dist/eval-harness.js +254 -0
- package/dist/experience-reflector.d.ts +13 -0
- package/dist/experience-reflector.js +62 -0
- package/dist/goal-engine.d.ts +155 -0
- package/dist/goal-engine.js +795 -0
- package/dist/helpers/store-resolution.js +40 -39
- package/dist/hermes/session-manager.d.ts +10 -0
- package/dist/hermes/session-manager.js +130 -27
- package/dist/job-scheduler.d.ts +58 -0
- package/dist/job-scheduler.js +202 -2
- package/dist/job-worker.d.ts +87 -0
- package/dist/job-worker.js +182 -0
- package/dist/local-model-policy.d.ts +8 -2
- package/dist/local-model-policy.js +31 -17
- package/dist/local-model-runtime.d.ts +6 -0
- package/dist/local-model-runtime.js +130 -2
- package/dist/measured-classing.d.ts +66 -0
- package/dist/measured-classing.js +165 -0
- package/dist/model-bridge.d.ts +45 -0
- package/dist/model-bridge.js +502 -44
- package/dist/orchestrator-supervisor.js +1 -1
- package/dist/plan-proposal.d.ts +7 -0
- package/dist/plan-proposal.js +74 -4
- package/dist/playbook-adaptation.d.ts +37 -0
- package/dist/playbook-adaptation.js +159 -0
- package/dist/playbook-curator.d.ts +16 -0
- package/dist/playbook-curator.js +106 -0
- package/dist/playbook-llm-reflector.d.ts +26 -0
- package/dist/playbook-llm-reflector.js +157 -0
- package/dist/public-surface.d.ts +2 -0
- package/dist/public-surface.js +26 -14
- package/dist/safe-edit.d.ts +20 -0
- package/dist/safe-edit.js +355 -78
- package/dist/server.d.ts +11 -2
- package/dist/server.js +55 -5
- package/dist/shared.d.ts +12 -0
- package/dist/shared.js +13 -1
- package/dist/source-mutation-coordinator.d.ts +54 -0
- package/dist/source-mutation-coordinator.js +259 -0
- package/dist/status-events.d.ts +4 -1
- package/dist/status-events.js +41 -6
- package/dist/store/ace-packed-store.d.ts +28 -9
- package/dist/store/ace-packed-store.js +324 -57
- package/dist/store/bgzf.d.ts +4 -0
- package/dist/store/bgzf.js +94 -0
- package/dist/store/bootstrap-store.d.ts +4 -1
- package/dist/store/bootstrap-store.js +57 -3
- package/dist/store/cache-workspace.d.ts +8 -0
- package/dist/store/cache-workspace.js +67 -1
- package/dist/store/crc32.d.ts +2 -0
- package/dist/store/crc32.js +16 -0
- package/dist/store/fsck.d.ts +16 -0
- package/dist/store/fsck.js +240 -0
- package/dist/store/materializers/context-snapshot-materializer.d.ts +1 -0
- package/dist/store/materializers/context-snapshot-materializer.js +4 -1
- package/dist/store/materializers/projection-manager.d.ts +7 -0
- package/dist/store/materializers/projection-manager.js +37 -0
- package/dist/store/materializers/scheduler-projection-materializer.js +10 -3
- package/dist/store/repositories/goal-repository.d.ts +112 -0
- package/dist/store/repositories/goal-repository.js +157 -0
- package/dist/store/repositories/playbook-repository.d.ts +107 -0
- package/dist/store/repositories/playbook-repository.js +314 -0
- package/dist/store/store-artifacts.js +44 -2
- package/dist/store/store-authority-audit.js +1 -0
- package/dist/store/store-cli.d.ts +30 -0
- package/dist/store/store-cli.js +242 -0
- package/dist/store/store-snapshot.d.ts +12 -0
- package/dist/store/store-snapshot.js +74 -11
- package/dist/store/types.d.ts +1 -0
- package/dist/store/write-coordinator.d.ts +7 -0
- package/dist/store/write-coordinator.js +11 -0
- package/dist/todo-state.d.ts +21 -0
- package/dist/todo-state.js +150 -53
- package/dist/tools-agent.js +86 -22
- package/dist/tools-discovery.js +5 -3
- package/dist/tools-files.js +39 -18
- package/dist/tools-framework.js +31 -3
- package/dist/tools-goal.d.ts +11 -0
- package/dist/tools-goal.js +108 -0
- package/dist/tools-handoff.js +7 -11
- package/dist/tools-lifecycle.js +2 -0
- package/dist/tools-scheduler.js +44 -1
- package/dist/tools-skills.js +3 -2
- package/dist/tools-todo.js +64 -3
- package/dist/tools.js +2 -0
- package/dist/tui/chat.js +4 -0
- package/dist/tui/index.js +4 -1
- package/dist/tui/ollama.d.ts +2 -0
- package/dist/tui/openai-compatible.d.ts +14 -0
- package/dist/tui/openai-compatible.js +15 -2
- package/dist/tui/provider-discovery.d.ts +1 -0
- package/dist/tui/provider-discovery.js +2 -2
- package/docs/cli-reference.md +125 -0
- package/docs/development.md +46 -0
- package/docs/evaluation-safety.md +27 -0
- package/docs/experience-playbook.md +35 -0
- package/docs/hermes.md +129 -0
- package/docs/library-api.md +31 -0
- package/docs/mcp-tool-reference.md +290 -0
- package/docs/provider-security.md +85 -0
- package/docs/state-model.md +113 -0
- package/docs/troubleshooting.md +95 -0
- package/docs/tui-operator-guide.md +75 -0
- package/package.json +3 -1
- package/scripts/dev/run_hermes_crispr_live.mjs +212 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACE Goal Engine tool registrations (WS-F).
|
|
3
|
+
*
|
|
4
|
+
* These let a premium host delegate a long-running objective to ACE and poll
|
|
5
|
+
* it. `start_goal`/`resume_goal` never block the MCP server for the life of the
|
|
6
|
+
* goal: they compile/prepare, then hand execution to a detached `ace goal run`
|
|
7
|
+
* child and return immediately. The host polls `get_goal_status`.
|
|
8
|
+
*/
|
|
9
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
|
+
export declare function registerGoalTools(server: McpServer): void;
|
|
11
|
+
//# sourceMappingURL=tools-goal.d.ts.map
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACE Goal Engine tool registrations (WS-F).
|
|
3
|
+
*
|
|
4
|
+
* These let a premium host delegate a long-running objective to ACE and poll
|
|
5
|
+
* it. `start_goal`/`resume_goal` never block the MCP server for the life of the
|
|
6
|
+
* goal: they compile/prepare, then hand execution to a detached `ace goal run`
|
|
7
|
+
* child and return immediately. The host polls `get_goal_status`.
|
|
8
|
+
*/
|
|
9
|
+
import { spawn } from "node:child_process";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import { getGoalStatus, listGoals, startGoal, stopGoal, } from "./goal-engine.js";
|
|
13
|
+
function asJsonBlock(value) {
|
|
14
|
+
return `\`\`\`json\n${JSON.stringify(value, null, 2)}\n\`\`\``;
|
|
15
|
+
}
|
|
16
|
+
function spawnDetachedRunner(goalId, verb) {
|
|
17
|
+
try {
|
|
18
|
+
// dist/cli.js is a sibling of this compiled module.
|
|
19
|
+
const cliPath = fileURLToPath(new URL("./cli.js", import.meta.url));
|
|
20
|
+
const child = spawn(process.execPath, [cliPath, "goal", verb, goalId], {
|
|
21
|
+
detached: true,
|
|
22
|
+
stdio: "ignore",
|
|
23
|
+
});
|
|
24
|
+
child.unref();
|
|
25
|
+
return child.pid;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function registerGoalTools(server) {
|
|
32
|
+
server.tool("start_goal", "Compile and launch a long-running autonomous goal (ACE plans it, executes step-by-step under evidence-gated completion, and can never declare success by prose). Returns immediately; poll with get_goal_status.", {
|
|
33
|
+
objective: z.string().min(1).describe("What the goal should accomplish"),
|
|
34
|
+
budget_hours: z.number().positive().max(24).optional().describe("Wall-clock budget (default 4h)"),
|
|
35
|
+
max_replans: z.number().int().min(0).max(20).optional().describe("Max plan-level replans (default 3)"),
|
|
36
|
+
plan_only: z.boolean().optional().describe("Compile + validate the plan but do not execute"),
|
|
37
|
+
detach: z.boolean().optional().describe("Run in a detached background child (default true when not plan_only)"),
|
|
38
|
+
}, async ({ objective, budget_hours, max_replans, plan_only, detach }) => {
|
|
39
|
+
const started = await startGoal(objective, { budget_hours, max_replans, plan_only });
|
|
40
|
+
let runnerPid;
|
|
41
|
+
if (started.ok && !plan_only && detach !== false) {
|
|
42
|
+
runnerPid = spawnDetachedRunner(started.goal.goal_id, "run");
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
content: [
|
|
46
|
+
{
|
|
47
|
+
type: "text",
|
|
48
|
+
text: [
|
|
49
|
+
started.ok ? "✅ Goal started" : "❌ Goal not started",
|
|
50
|
+
`goal_id: ${started.goal.goal_id}`,
|
|
51
|
+
`status: ${started.goal.status}`,
|
|
52
|
+
started.reason_code ? `reason: ${started.reason_code}` : "",
|
|
53
|
+
runnerPid ? `detached runner pid: ${runnerPid}` : "",
|
|
54
|
+
asJsonBlock({ ...started, runner_pid: runnerPid }),
|
|
55
|
+
]
|
|
56
|
+
.filter(Boolean)
|
|
57
|
+
.join("\n"),
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
server.tool("get_goal_status", "Get a goal's status (tick count, current step, last heartbeat, escalations, plan status), or list all goals when no goal_id is given.", {
|
|
63
|
+
goal_id: z.string().optional().describe("Goal id; omit to list all goals"),
|
|
64
|
+
}, async ({ goal_id }) => {
|
|
65
|
+
if (!goal_id) {
|
|
66
|
+
return {
|
|
67
|
+
content: [{ type: "text", text: asJsonBlock({ goals: listGoals() }) }],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const view = getGoalStatus(goal_id);
|
|
71
|
+
if (!view) {
|
|
72
|
+
return { content: [{ type: "text", text: `❌ Unknown goal: ${goal_id}` }] };
|
|
73
|
+
}
|
|
74
|
+
return { content: [{ type: "text", text: asJsonBlock(view) }] };
|
|
75
|
+
});
|
|
76
|
+
server.tool("stop_goal", "Request that a running goal stop at its next tick boundary.", { goal_id: z.string().describe("Goal id to stop") }, async ({ goal_id }) => {
|
|
77
|
+
try {
|
|
78
|
+
const goal = stopGoal(goal_id);
|
|
79
|
+
return {
|
|
80
|
+
content: [{ type: "text", text: `🛑 Stop requested for ${goal.goal_id} (status ${goal.status}).` }],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
return {
|
|
85
|
+
content: [{ type: "text", text: `❌ ${err instanceof Error ? err.message : String(err)}` }],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
server.tool("resume_goal", "Resume a blocked/stopped goal: reset any step left running mid-crash and continue execution in a detached runner.", { goal_id: z.string().describe("Goal id to resume") }, async ({ goal_id }) => {
|
|
90
|
+
const view = getGoalStatus(goal_id);
|
|
91
|
+
if (!view) {
|
|
92
|
+
return { content: [{ type: "text", text: `❌ Unknown goal: ${goal_id}` }] };
|
|
93
|
+
}
|
|
94
|
+
const runnerPid = spawnDetachedRunner(goal_id, "resume");
|
|
95
|
+
return {
|
|
96
|
+
content: [
|
|
97
|
+
{
|
|
98
|
+
type: "text",
|
|
99
|
+
text: [
|
|
100
|
+
`▶️ Resuming ${goal_id}`,
|
|
101
|
+
runnerPid ? `detached runner pid: ${runnerPid}` : "runner spawn failed; run `ace goal resume` manually",
|
|
102
|
+
].join("\n"),
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=tools-goal.js.map
|
package/dist/tools-handoff.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
-
import { ACE_TASKS_ROOT_REL,
|
|
6
|
+
import { ACE_TASKS_ROOT_REL, readTaskArtifact, WORKSPACE_ROOT, } from "./helpers.js";
|
|
7
|
+
import { writeStoreBlobQueued } from "./store/store-artifacts.js";
|
|
7
8
|
import { ROLE_ENUM, HANDOFF_VALIDATION_MODE, HANDOFF_LIFECYCLE_STATUS_ENUM, } from "./shared.js";
|
|
8
9
|
import { validateHandoffPayload, validateSwarmHandoffPayload, lintHandoffPayload, } from "./schemas.js";
|
|
9
10
|
import { acknowledgeHandoffSafe, getHandoffRegistryPath, readHandoffRegistry, registerHandoffSafe, } from "./handoff-registry.js";
|
|
@@ -241,13 +242,8 @@ export function registerHandoffTools(server) {
|
|
|
241
242
|
],
|
|
242
243
|
};
|
|
243
244
|
}
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
.replace(/[-:]/g, "")
|
|
247
|
-
.replace("T", "-")
|
|
248
|
-
.slice(0, 15);
|
|
249
|
-
const filePath = wsPath("tasks", `SWARM_HANDOFF.${from}_to_${to}_${timeStr}_${shortId}.json`);
|
|
250
|
-
await safeWriteAsync(filePath, JSON.stringify(handoff, null, 2));
|
|
245
|
+
const payloadStoreKey = `state/handoffs/payloads/${handoff.handoff_id}.json`;
|
|
246
|
+
const payloadPath = await writeStoreBlobQueued(WORKSPACE_ROOT, payloadStoreKey, JSON.stringify(handoff, null, 2), { operation_label: "create_handoff.payload" });
|
|
251
247
|
const registry = await registerHandoffSafe({
|
|
252
248
|
handoff_id: handoff.handoff_id,
|
|
253
249
|
from: handoff.router.from,
|
|
@@ -255,14 +251,14 @@ export function registerHandoffTools(server) {
|
|
|
255
251
|
title: handoff.task.title,
|
|
256
252
|
task_type: handoff.task.type,
|
|
257
253
|
priority: handoff.task.priority,
|
|
258
|
-
source_file:
|
|
254
|
+
source_file: payloadPath,
|
|
259
255
|
});
|
|
260
256
|
const runLedger = await appendRunLedgerEntrySafe({
|
|
261
257
|
tool: "create_handoff",
|
|
262
258
|
category: "major_update",
|
|
263
259
|
message: `Created handoff ${handoff.handoff_id} (${handoff.router.from} -> ${handoff.router.to})`,
|
|
264
260
|
artifacts: [
|
|
265
|
-
|
|
261
|
+
payloadPath,
|
|
266
262
|
"agent-state/handoff-registry.json",
|
|
267
263
|
],
|
|
268
264
|
metadata: {
|
|
@@ -282,7 +278,7 @@ export function registerHandoffTools(server) {
|
|
|
282
278
|
"# Handoff Created",
|
|
283
279
|
"",
|
|
284
280
|
`Schema: ${validation.schema}`,
|
|
285
|
-
`Saved to: ${
|
|
281
|
+
`Saved to: ${payloadPath}`,
|
|
286
282
|
`Handoff registry: ${registry.path}`,
|
|
287
283
|
`Run ledger: ${runLedger.path} (${runLedger.entry.id})`,
|
|
288
284
|
`Scheduler job: ${schedulerSync.job_id} (${schedulerSync.enqueued ? "enqueued" : "already_exists"})`,
|
package/dist/tools-lifecycle.js
CHANGED
|
@@ -194,6 +194,8 @@ export function registerLifecycleTools(server) {
|
|
|
194
194
|
"",
|
|
195
195
|
`JSON: ${snapshot.json_path}`,
|
|
196
196
|
`HTML: ${snapshot.html_path}`,
|
|
197
|
+
`Read: ace read agent-state/kanban.json`,
|
|
198
|
+
`Open: ace read agent-state/kanban.html --out /tmp/ace-kanban.html && open /tmp/ace-kanban.html`,
|
|
197
199
|
`Updated: ${snapshot.generated_at}`,
|
|
198
200
|
`Counts: backlog=${snapshot.counts.backlog}, blocked=${snapshot.counts.blocked}, in_progress=${snapshot.counts.in_progress}, gated=${snapshot.counts.gated}, done=${snapshot.counts.done}`,
|
|
199
201
|
].join("\n"),
|
package/dist/tools-scheduler.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Job scheduler tool registrations.
|
|
3
3
|
*/
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import { acknowledgeJob, completeJob, dispatchJobNow, dispatchJobs, enqueueJob, getJobLockTablePath, getJobQueuePath, getSchedulerLeasePath, readJobQueue, readJobLockTable, readSchedulerLease, } from "./job-scheduler.js";
|
|
5
|
+
import { acknowledgeJob, completeJob, dispatchJobNow, dispatchJobs, enqueueJob, getJobLockTablePath, getJobQueuePath, getSchedulerLeasePath, heartbeatJob, reapStaleRunningJobsTick, readJobQueue, readJobLockTable, readSchedulerLease, } from "./job-scheduler.js";
|
|
6
6
|
// Event emission handled in core job-scheduler.ts — no wrapper duplication.
|
|
7
7
|
const PRIORITY_BAND_ENUM = z.enum(["P0", "P1", "P2", "P3"]);
|
|
8
8
|
const JOB_STATUS_ENUM = z.enum([
|
|
@@ -196,6 +196,49 @@ export function registerSchedulerTools(server) {
|
|
|
196
196
|
],
|
|
197
197
|
};
|
|
198
198
|
});
|
|
199
|
+
server.tool("heartbeat_job", "Prove liveness for a running job so the heartbeat watchdog does not reap it; renews the owner lease", {
|
|
200
|
+
job_id: z.string(),
|
|
201
|
+
owner: z.string().optional().describe("Lease owner identity (defaults to capability-ops)"),
|
|
202
|
+
lease_ttl_seconds: z.number().int().min(5).max(3600).optional(),
|
|
203
|
+
now_iso: z.string().optional().describe("Optional deterministic clock override"),
|
|
204
|
+
}, async ({ job_id, owner, lease_ttl_seconds, now_iso }) => {
|
|
205
|
+
const result = await heartbeatJob({ job_id, owner, lease_ttl_seconds, now_iso });
|
|
206
|
+
return {
|
|
207
|
+
content: [
|
|
208
|
+
{
|
|
209
|
+
type: "text",
|
|
210
|
+
text: result.ok
|
|
211
|
+
? ["✅ Job heartbeat recorded", asJsonBlock(result)].join("\n")
|
|
212
|
+
: ["❌ heartbeat_job failed", result.error ?? "Unknown error", asJsonBlock(result)].join("\n"),
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
};
|
|
216
|
+
});
|
|
217
|
+
server.tool("reap_stale_jobs", "Run the heartbeat watchdog once: move running jobs with a stale worker heartbeat into unknown_recovery and release their locks", {
|
|
218
|
+
heartbeat_stale_seconds: z
|
|
219
|
+
.number()
|
|
220
|
+
.int()
|
|
221
|
+
.min(1)
|
|
222
|
+
.max(86_400)
|
|
223
|
+
.optional()
|
|
224
|
+
.describe("Staleness window in seconds (default 120)"),
|
|
225
|
+
now_iso: z.string().optional().describe("Optional deterministic clock override"),
|
|
226
|
+
}, async ({ heartbeat_stale_seconds, now_iso }) => {
|
|
227
|
+
const result = await reapStaleRunningJobsTick({ heartbeat_stale_seconds, now_iso });
|
|
228
|
+
return {
|
|
229
|
+
content: [
|
|
230
|
+
{
|
|
231
|
+
type: "text",
|
|
232
|
+
text: [
|
|
233
|
+
result.reaped_jobs > 0
|
|
234
|
+
? `🩺 Watchdog reaped ${result.reaped_jobs} stale job(s)`
|
|
235
|
+
: "🩺 Watchdog found no stale running jobs",
|
|
236
|
+
asJsonBlock(result),
|
|
237
|
+
].join("\n"),
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
};
|
|
241
|
+
});
|
|
199
242
|
server.tool("get_lock_table", "Read the scheduler lock table", {}, async () => ({
|
|
200
243
|
content: [
|
|
201
244
|
{
|
package/dist/tools-skills.js
CHANGED
|
@@ -49,9 +49,10 @@ export function registerSkillTools(server) {
|
|
|
49
49
|
.describe("Optional workspace-relative path for the audit report"),
|
|
50
50
|
}, async ({ write_artifact, artifact_path }) => {
|
|
51
51
|
const result = auditSkillCatalog();
|
|
52
|
+
const artifactPath = artifact_path || "agent-state/SKILL_AUDIT_REPORT.md";
|
|
52
53
|
const writtenPath = write_artifact === false
|
|
53
54
|
? undefined
|
|
54
|
-
: writeSkillAuditReport(result,
|
|
55
|
+
: writeSkillAuditReport(result, artifactPath);
|
|
55
56
|
const failCount = result.entries.filter((entry) => entry.status === "fail").length;
|
|
56
57
|
const warnCount = result.entries.filter((entry) => entry.status === "warn").length;
|
|
57
58
|
return {
|
|
@@ -62,7 +63,7 @@ export function registerSkillTools(server) {
|
|
|
62
63
|
`Portable-contract-ready skills: ${result.summary.portable_contract_ready_skills}/${result.summary.total_skills}`,
|
|
63
64
|
`Fail/Warn: ${failCount}/${warnCount}`,
|
|
64
65
|
...(result.rollout_backlog.slice(0, 5).map((line) => `- ${line}`)),
|
|
65
|
-
...(writtenPath ? [`- Artifact
|
|
66
|
+
...(writtenPath ? [`- Artifact: ${artifactPath}`, `- Read: ace read ${artifactPath}`, `- Store path: ${writtenPath}`] : []),
|
|
66
67
|
].join("\n"),
|
|
67
68
|
},
|
|
68
69
|
],
|
package/dist/tools-todo.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { ACE_TASKS_ROOT_REL, readTaskArtifact, resolveWritableTaskPath, safeWriteAsync, } from "./helpers.js";
|
|
6
6
|
import { TODO_STATUS_ENUM } from "./shared.js";
|
|
7
|
-
import { getTodoStatePath, readTodoState, syncTodoStateSafe, transitionTodoNodeSafe, } from "./todo-state.js";
|
|
7
|
+
import { appendTodoItemSafe, getTodoStatePath, readTodoState, syncTodoStateSafe, transitionTodoNodeSafe, } from "./todo-state.js";
|
|
8
8
|
import { appendRunLedgerEntrySafe, getRunLedgerPath, readRunLedger, } from "./run-ledger.js";
|
|
9
9
|
import { refreshKanbanArtifacts } from "./kanban.js";
|
|
10
10
|
export function registerTodoTools(server) {
|
|
@@ -19,7 +19,7 @@ export function registerTodoTools(server) {
|
|
|
19
19
|
},
|
|
20
20
|
],
|
|
21
21
|
}));
|
|
22
|
-
server.tool("update_todo", "Replace TODO board content or transition a TODO node status with gate validation", {
|
|
22
|
+
server.tool("update_todo", "Replace TODO board content, append a new TODO item, or transition a TODO node status with gate validation", {
|
|
23
23
|
new_content: z
|
|
24
24
|
.string()
|
|
25
25
|
.optional()
|
|
@@ -30,7 +30,68 @@ export function registerTodoTools(server) {
|
|
|
30
30
|
.describe("Todo node id to transition (transition mode)"),
|
|
31
31
|
status: TODO_STATUS_ENUM.optional().describe("Target status for transition mode"),
|
|
32
32
|
note: z.string().optional().describe("Optional transition context note"),
|
|
33
|
-
|
|
33
|
+
append_title: z
|
|
34
|
+
.string()
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Title for a new TODO item to append atomically (append mode). Prefer this over " +
|
|
37
|
+
"new_content when adding a single item — it is safe under concurrent callers, " +
|
|
38
|
+
"unlike a read-then-full-replace round trip."),
|
|
39
|
+
append_section: z
|
|
40
|
+
.string()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("Section heading to append the new item under (default: Active Work)"),
|
|
43
|
+
append_depends_on: z
|
|
44
|
+
.array(z.string())
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("Node ids the new append-mode item depends on"),
|
|
47
|
+
append_id: z
|
|
48
|
+
.string()
|
|
49
|
+
.optional()
|
|
50
|
+
.describe("Optional explicit node id for the new append-mode item"),
|
|
51
|
+
}, async ({ new_content, node_id, status, note, append_title, append_section, append_depends_on, append_id, }) => {
|
|
52
|
+
if (typeof append_title === "string") {
|
|
53
|
+
const appended = await appendTodoItemSafe(append_title, {
|
|
54
|
+
section: append_section,
|
|
55
|
+
depends_on: append_depends_on,
|
|
56
|
+
id: append_id,
|
|
57
|
+
});
|
|
58
|
+
if (!appended.ok) {
|
|
59
|
+
return {
|
|
60
|
+
content: [
|
|
61
|
+
{ type: "text", text: `❌ ${appended.error ?? "Failed to append TODO item."}` },
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const runLedger = await appendRunLedgerEntrySafe({
|
|
66
|
+
tool: "update_todo",
|
|
67
|
+
category: "info",
|
|
68
|
+
message: `Appended TODO item ${appended.node_id}`,
|
|
69
|
+
artifacts: [`${ACE_TASKS_ROOT_REL}/todo.md`, "agent-state/todo-state.json"],
|
|
70
|
+
metadata: {
|
|
71
|
+
mode: "append",
|
|
72
|
+
node_id: appended.node_id,
|
|
73
|
+
section: appended.section,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
const kanban = refreshKanbanArtifacts("tool:update_todo");
|
|
77
|
+
const counts = kanban.counts;
|
|
78
|
+
return {
|
|
79
|
+
content: [
|
|
80
|
+
{
|
|
81
|
+
type: "text",
|
|
82
|
+
text: [
|
|
83
|
+
`✅ TODO item appended: ${appended.node_id}`,
|
|
84
|
+
`Section: ${appended.section}`,
|
|
85
|
+
`TODO state: ${appended.path}`,
|
|
86
|
+
`TODO markdown: ${appended.todo_path}`,
|
|
87
|
+
`Run ledger: ${runLedger.path} (${runLedger.entry.id})`,
|
|
88
|
+
`Kanban updated: ${kanban.html_path}`,
|
|
89
|
+
`Kanban counts: backlog=${counts.backlog}, blocked=${counts.blocked}, in_progress=${counts.in_progress}, gated=${counts.gated}, done=${counts.done}`,
|
|
90
|
+
].join("\n"),
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
34
95
|
if (typeof new_content === "string") {
|
|
35
96
|
const synced = await syncTodoStateSafe(new_content);
|
|
36
97
|
const path = await safeWriteAsync(resolveWritableTaskPath("todo"), new_content);
|
package/dist/tools.js
CHANGED
|
@@ -15,6 +15,7 @@ import { registerGitTools } from "./tools-git.js";
|
|
|
15
15
|
import { registerMemoryTools } from "./tools-memory.js";
|
|
16
16
|
import { registerDriftTools } from "./tools-drift.js";
|
|
17
17
|
import { registerSchedulerTools } from "./tools-scheduler.js";
|
|
18
|
+
import { registerGoalTools } from "./tools-goal.js";
|
|
18
19
|
import { registerSkillTools } from "./tools-skills.js";
|
|
19
20
|
const TOOL_REGISTRARS = [
|
|
20
21
|
registerAgentTools,
|
|
@@ -28,6 +29,7 @@ const TOOL_REGISTRARS = [
|
|
|
28
29
|
registerMemoryTools,
|
|
29
30
|
registerDriftTools,
|
|
30
31
|
registerSchedulerTools,
|
|
32
|
+
registerGoalTools,
|
|
31
33
|
registerSkillTools,
|
|
32
34
|
];
|
|
33
35
|
function createAllowlistedServer(server, allowed) {
|
package/dist/tui/chat.js
CHANGED
|
@@ -243,6 +243,10 @@ export class ChatSession extends EventEmitter {
|
|
|
243
243
|
const activeProvider = this.activeStreamProvider ?? this.provider;
|
|
244
244
|
this.streaming = false;
|
|
245
245
|
if (this.activeAceBridge) {
|
|
246
|
+
if (this.executionEngine === "hermes_local") {
|
|
247
|
+
this.hermesExecutor?.interrupt?.();
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
246
250
|
this.aceBridge.interrupt();
|
|
247
251
|
return;
|
|
248
252
|
}
|
package/dist/tui/index.js
CHANGED
|
@@ -190,7 +190,7 @@ export class AceTui {
|
|
|
190
190
|
});
|
|
191
191
|
// Handle signals
|
|
192
192
|
process.on("SIGINT", () => {
|
|
193
|
-
// Ctrl+C
|
|
193
|
+
// Ctrl+C interrupts an active stream; while idle it is deliberately a no-op.
|
|
194
194
|
if (this.activeChatSession?.isStreaming()) {
|
|
195
195
|
this.activeChatSession.interrupt();
|
|
196
196
|
}
|
|
@@ -332,6 +332,9 @@ export class AceTui {
|
|
|
332
332
|
case "w":
|
|
333
333
|
this.closeCurrentTab();
|
|
334
334
|
return;
|
|
335
|
+
case "z":
|
|
336
|
+
this.suspend();
|
|
337
|
+
return;
|
|
335
338
|
}
|
|
336
339
|
}
|
|
337
340
|
// Alt+number → switch tab
|
package/dist/tui/ollama.d.ts
CHANGED
|
@@ -36,6 +36,8 @@ export interface ChatRequest {
|
|
|
36
36
|
model: string;
|
|
37
37
|
messages: ChatMessage[];
|
|
38
38
|
stream?: boolean;
|
|
39
|
+
/** "json" for generic JSON mode; a JSON Schema object for schema-constrained generation (Ollama ≥ 0.5). */
|
|
40
|
+
format?: "json" | Record<string, unknown> | string;
|
|
39
41
|
options?: {
|
|
40
42
|
temperature?: number;
|
|
41
43
|
top_p?: number;
|
|
@@ -15,6 +15,19 @@ export interface OpenAICompatibleChatRequest {
|
|
|
15
15
|
messages: OpenAICompatibleChatMessage[];
|
|
16
16
|
temperature?: number;
|
|
17
17
|
topP?: number;
|
|
18
|
+
maxTokens?: number;
|
|
19
|
+
responseFormat?: {
|
|
20
|
+
type: "json_object";
|
|
21
|
+
} | {
|
|
22
|
+
type: "json_schema";
|
|
23
|
+
json_schema: {
|
|
24
|
+
name: string;
|
|
25
|
+
schema: Record<string, unknown>;
|
|
26
|
+
strict?: boolean;
|
|
27
|
+
};
|
|
28
|
+
} | {
|
|
29
|
+
type: string;
|
|
30
|
+
};
|
|
18
31
|
onProviderEvent?: (event: OpenAICompatibleProviderEvent) => void;
|
|
19
32
|
}
|
|
20
33
|
export interface OpenAICompatibleChatChunk {
|
|
@@ -66,6 +79,7 @@ export declare class OpenAICompatibleClient {
|
|
|
66
79
|
private resolveProviderConfig;
|
|
67
80
|
private streamSseData;
|
|
68
81
|
}
|
|
82
|
+
export declare const ANTHROPIC_OPENAI_COMPATIBLE_BASE_URL = "https://api.anthropic.com/v1";
|
|
69
83
|
/**
|
|
70
84
|
* Resolve a GitHub / Copilot token from the environment or `gh` CLI.
|
|
71
85
|
* Checks: GITHUB_TOKEN → GH_TOKEN → `gh auth token` (sync exec).
|
|
@@ -172,6 +172,8 @@ export class OpenAICompatibleClient {
|
|
|
172
172
|
stream: true,
|
|
173
173
|
temperature: request.temperature,
|
|
174
174
|
top_p: request.topP,
|
|
175
|
+
max_tokens: request.maxTokens,
|
|
176
|
+
...(request.responseFormat ? { response_format: request.responseFormat } : {}),
|
|
175
177
|
};
|
|
176
178
|
const streamRes = await fetch(`${config.baseUrl}/chat/completions`, {
|
|
177
179
|
method: "POST",
|
|
@@ -263,6 +265,8 @@ export class OpenAICompatibleClient {
|
|
|
263
265
|
stream: false,
|
|
264
266
|
temperature: request.temperature,
|
|
265
267
|
top_p: request.topP,
|
|
268
|
+
max_tokens: request.maxTokens,
|
|
269
|
+
...(request.responseFormat ? { response_format: request.responseFormat } : {}),
|
|
266
270
|
};
|
|
267
271
|
const res = await fetch(`${config.baseUrl}/chat/completions`, {
|
|
268
272
|
method: "POST",
|
|
@@ -348,6 +352,8 @@ export class OpenAICompatibleClient {
|
|
|
348
352
|
stream: true,
|
|
349
353
|
temperature: request.temperature,
|
|
350
354
|
top_p: request.topP,
|
|
355
|
+
max_tokens: request.maxTokens,
|
|
356
|
+
...(request.responseFormat ? { response_format: request.responseFormat } : {}),
|
|
351
357
|
};
|
|
352
358
|
// llama.cpp rejects custom stream_options; only include for non-llama providers
|
|
353
359
|
if (provider !== 'llama.cpp') {
|
|
@@ -485,7 +491,10 @@ export class OpenAICompatibleClient {
|
|
|
485
491
|
const apiKey = override?.apiKey || providerKey || genericKey || process.env.CODEX_API_KEY?.trim();
|
|
486
492
|
return { provider, baseUrl, apiKey, headers: override?.headers };
|
|
487
493
|
}
|
|
488
|
-
const resolvedBaseUrl = override?.baseUrl ||
|
|
494
|
+
const resolvedBaseUrl = override?.baseUrl ||
|
|
495
|
+
providerBase ||
|
|
496
|
+
genericBase ||
|
|
497
|
+
(provider === "claude" ? ANTHROPIC_OPENAI_COMPATIBLE_BASE_URL : "");
|
|
489
498
|
const baseUrl = provider === "llama.cpp"
|
|
490
499
|
? buildOpenAiCompatibleBaseUrl(resolvedBaseUrl)
|
|
491
500
|
: stripTrailingSlash(resolvedBaseUrl);
|
|
@@ -559,6 +568,7 @@ function providerEnvPrefix(provider) {
|
|
|
559
568
|
return provider === "llama.cpp" ? "LLAMA_CPP" : provider.toUpperCase();
|
|
560
569
|
}
|
|
561
570
|
const GITHUB_MODELS_BASE_URL = "https://models.github.com/v1";
|
|
571
|
+
export const ANTHROPIC_OPENAI_COMPATIBLE_BASE_URL = "https://api.anthropic.com/v1";
|
|
562
572
|
/**
|
|
563
573
|
* Resolve a GitHub / Copilot token from the environment or `gh` CLI.
|
|
564
574
|
* Checks: GITHUB_TOKEN → GH_TOKEN → `gh auth token` (sync exec).
|
|
@@ -612,7 +622,10 @@ export function diagnoseProviderRuntimeConfig(providerInput, override) {
|
|
|
612
622
|
message: "Missing API key for provider 'codex'. Set CODEX_API_KEY or OPENAI_API_KEY before sending chat messages.",
|
|
613
623
|
};
|
|
614
624
|
}
|
|
615
|
-
const baseUrl = stripTrailingSlash(override?.baseUrl ||
|
|
625
|
+
const baseUrl = stripTrailingSlash(override?.baseUrl ||
|
|
626
|
+
providerBase ||
|
|
627
|
+
genericBase ||
|
|
628
|
+
(provider === "claude" ? ANTHROPIC_OPENAI_COMPATIBLE_BASE_URL : ""));
|
|
616
629
|
const apiKey = override?.apiKey || providerKey || genericKey || "";
|
|
617
630
|
const missing = [];
|
|
618
631
|
if (!baseUrl) {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
export declare const DEFAULT_OLLAMA_MODEL = "llama3.1:8b";
|
|
16
16
|
export declare const DEFAULT_LLAMA_CPP_MODEL = "local-model";
|
|
17
|
+
export declare const DEFAULT_HOSTED_MODELS: Readonly<Record<"codex" | "claude" | "gemini" | "copilot", string>>;
|
|
17
18
|
export declare const LOCAL_LLM_PROVIDERS: readonly ["ollama", "llama.cpp"];
|
|
18
19
|
export type LocalLlmProvider = (typeof LOCAL_LLM_PROVIDERS)[number];
|
|
19
20
|
export interface ProviderDiscoveryOptions {
|
|
@@ -18,9 +18,9 @@ import { join, resolve } from "node:path";
|
|
|
18
18
|
import { readStoreJsonSync } from "../store/store-snapshot.js";
|
|
19
19
|
export const DEFAULT_OLLAMA_MODEL = "llama3.1:8b";
|
|
20
20
|
export const DEFAULT_LLAMA_CPP_MODEL = "local-model";
|
|
21
|
-
const DEFAULT_HOSTED_MODELS = {
|
|
21
|
+
export const DEFAULT_HOSTED_MODELS = {
|
|
22
22
|
codex: "gpt-5",
|
|
23
|
-
claude: "claude-
|
|
23
|
+
claude: "claude-opus-4-8",
|
|
24
24
|
gemini: "gemini-2.5-pro",
|
|
25
25
|
copilot: "copilot/gpt-5-mini",
|
|
26
26
|
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# CLI Reference
|
|
2
|
+
|
|
3
|
+
> Companion to the [Quick Start in the README](../README.md#quick-start).
|
|
4
|
+
|
|
5
|
+
The bare `ace` commands below assume a global install (`npm install -g @voybio/ace-swarm`); without one, prefix each with `npx -y @voybio/ace-swarm` — e.g. `npx -y @voybio/ace-swarm mcp`.
|
|
6
|
+
|
|
7
|
+
## Commands
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
+--------------------------------------------+-------------------------------------------------------+
|
|
11
|
+
| Command | Purpose |
|
|
12
|
+
+--------------------------------------------+-------------------------------------------------------+
|
|
13
|
+
| `ace mcp [--profile host-min|full]` / `ace serve` | Start MCP server over stdio |
|
|
14
|
+
| `ace mcp-shadow --tools <csv>` | Start filtered shadow MCP server (Hermes use) |
|
|
15
|
+
| `ace tui [options]` | Open the terminal operator surface |
|
|
16
|
+
| `ace init [options]` | Seed the ACE store and optional host stubs |
|
|
17
|
+
| `ace connect [provider] [options]` | Register a live local runtime or hosted provider |
|
|
18
|
+
| `ace turnkey [options]` | Project store-backed host surface stubs |
|
|
19
|
+
| `ace doctor [options]` | Validate ACE runtime + MCP readiness |
|
|
20
|
+
| `ace goal <start|status|run|resume|stop>` | Drive a self-paced, evidence-gated goal loop |
|
|
21
|
+
| `ace ls [prefix] [--keys] [--json]` | List store-backed artifacts by logical path |
|
|
22
|
+
| `ace read <path> [--out f] [--source] [--json] [--prefer disk|store]` | Read a store/workspace file (aliases: get, cat) |
|
|
23
|
+
| `ace fsck [--json] [--repair]` | Validate ACEPACK structure, CRCs, orphans |
|
|
24
|
+
| `ace cache [options]` | DEPRECATED legacy ingest/clean command; it is not an alias for compaction |
|
|
25
|
+
| `ace compact [--no-sweep]` | Compact the store and sweep loose generated artifacts |
|
|
26
|
+
| `ace migrate [store-path]` | Import a legacy file workspace into the store |
|
|
27
|
+
| `ace repair` | Re-materialize host files and repair store assets |
|
|
28
|
+
| `ace add skills [name|all]` | Install packaged skill packs into the store |
|
|
29
|
+
| `ace playbook <list|show|add|tag|gc>` | Inspect and curate the experience playbook |
|
|
30
|
+
| `ace eval [--adapt] [options]` | Run the eval harness / adaptation cycle |
|
|
31
|
+
| `ace mcp-config [--client <name>|--all]` | Print baked client snippets for optional install |
|
|
32
|
+
| `ace preconfig` | Write .mcp-config/ bundle for all clients |
|
|
33
|
+
| `ace paths` | Show resolved package and workspace paths |
|
|
34
|
+
| `ace help` | Show help |
|
|
35
|
+
+--------------------------------------------+-------------------------------------------------------+
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Examples
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Bootstrap a workspace with a hosted provider
|
|
42
|
+
ace init --project "My App" --llm claude --client-config-bundle
|
|
43
|
+
|
|
44
|
+
# Bootstrap with a local Ollama model and write VS Code MCP config
|
|
45
|
+
ace init --project "My App" --llm ollama --model qwen3:8b \
|
|
46
|
+
--base-url http://localhost:11434 --mcp-config
|
|
47
|
+
|
|
48
|
+
# Connect to a running llama.cpp server from a Hugging Face model
|
|
49
|
+
ace connect llama-server --hf bartowski/gemma-3-4b-it-GGUF \
|
|
50
|
+
--base-url http://localhost:8080
|
|
51
|
+
|
|
52
|
+
# Run doctor checks with auto-scan of common local endpoints
|
|
53
|
+
ace doctor --scan
|
|
54
|
+
|
|
55
|
+
# Run doctor checks for a specific provider + model
|
|
56
|
+
ace doctor --llm ollama --model qwen3:8b --base-url http://localhost:11434
|
|
57
|
+
|
|
58
|
+
# Run optional Hermes checks with user-supplied Hermes/Python
|
|
59
|
+
ACE_HERMES_ROOT=/path/to/hermes-agent HERMES_PYTHON=/path/to/python \
|
|
60
|
+
ace doctor --llm llama.cpp --model gemma-3-4b-it --base-url http://localhost:8080 --hermes
|
|
61
|
+
|
|
62
|
+
# Repair a missing Ollama model and re-probe runtime readiness
|
|
63
|
+
ace doctor --llm ollama --model qwen3:8b --repair-ollama
|
|
64
|
+
|
|
65
|
+
# Print VS Code MCP config snippet to stdout
|
|
66
|
+
ace mcp-config --client vscode
|
|
67
|
+
|
|
68
|
+
# Write config bundles for every supported client at workspace root
|
|
69
|
+
ace preconfig
|
|
70
|
+
|
|
71
|
+
# Launch TUI with optional Hermes engine against a local llama.cpp server
|
|
72
|
+
ACE_HERMES_ROOT=/path/to/hermes-agent HERMES_PYTHON=/path/to/python \
|
|
73
|
+
ace tui --provider llama.cpp --base-url http://localhost:8080 --model gemma-3-4b-it --hermes
|
|
74
|
+
|
|
75
|
+
# Compact the store and sweep loose generated artifacts
|
|
76
|
+
ace compact
|
|
77
|
+
ace compact --no-sweep
|
|
78
|
+
|
|
79
|
+
# List and read store-backed artifacts a human otherwise can't see on disk
|
|
80
|
+
ace ls agent-state
|
|
81
|
+
ace read agent-state/kanban.json
|
|
82
|
+
ace read agent-state/STORE_AUTHORITY_AUDIT.md --out /tmp/audit.md
|
|
83
|
+
|
|
84
|
+
# Validate ACEPACK structure/CRCs/orphans, optionally repairing safe issues
|
|
85
|
+
ace fsck
|
|
86
|
+
ace fsck --repair
|
|
87
|
+
|
|
88
|
+
# Start a self-paced goal, watch it, stop it if needed.
|
|
89
|
+
# The objective needs at least one machine-checkable obligation -- a declared
|
|
90
|
+
# artifact or a `verify: <command>` criterion -- or `start` refuses it with
|
|
91
|
+
# `contract_uncheckable` rather than completing on the model's own say-so.
|
|
92
|
+
ace goal start "Ship the auth module. verify: npm test" --budget-hours 2 --detach
|
|
93
|
+
ace goal status
|
|
94
|
+
ace goal stop <goal_id>
|
|
95
|
+
|
|
96
|
+
# Experimental: list and inspect the learned experience playbook
|
|
97
|
+
ace playbook list
|
|
98
|
+
ace playbook show pb-1234abcd
|
|
99
|
+
ace playbook add --section verification --content "Require evidence refs before completion."
|
|
100
|
+
ace playbook add --section failure_modes --reason-code false_completion_no_evidence \
|
|
101
|
+
--content "Require evidence refs before completion."
|
|
102
|
+
ace playbook tag pb-1234abcd helpful
|
|
103
|
+
|
|
104
|
+
# Experimental: run an eval adaptation cycle
|
|
105
|
+
ace eval --adapt --models qwen3:8b,gemma3:4b --epochs 2 \
|
|
106
|
+
--battery ./my-battery.json --out agent-state/eval-reports
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Playbook sections are `strategies`, `tool_usage`, `failure_modes`, `verification`,
|
|
110
|
+
and `env_facts`; list statuses are `active`, `quarantined`, and `retired`.
|
|
111
|
+
Lessons from every producer without evidence are stored as `quarantined` and
|
|
112
|
+
are not prompt-injected.
|
|
113
|
+
|
|
114
|
+
## Selected options
|
|
115
|
+
|
|
116
|
+
- `ace mcp --profile <host-min|full>` selects the visible tool schema; `full` is the default.
|
|
117
|
+
- `ace turnkey --no-client-config-bundle` skips the host-facing config stubs created by default.
|
|
118
|
+
- `ace playbook add --reason-code <code>` scopes a lesson to a matching failure class.
|
|
119
|
+
- `ace eval` accepts `--model`/`--models`, `--battery <path>`, `--out <dir>`, and `--adapt`; `--epochs <n>` applies only to adaptation runs.
|
|
120
|
+
- `ace read` accepts `--source` and `--json` in addition to `--out` and `--prefer`.
|
|
121
|
+
|
|
122
|
+
Eval is non-destructive by default: every task, model, baseline, and adapted
|
|
123
|
+
epoch runs in a new disposable workspace seeded only from the battery's adjacent
|
|
124
|
+
`fixtures/` directory. Expected artifacts are scored inside that workspace and
|
|
125
|
+
recorded by SHA-256 in the scorecard, so stale project files cannot pass a run.
|