@voybio/ace-swarm 2.4.4 → 2.4.7
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 +41 -12
- 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 +623 -11
- 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 +127 -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,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "ace-eval-battery@1",
|
|
3
|
+
"seed": 7,
|
|
4
|
+
"description": "Fixed behavioral task battery for WS-I. Every invocation receives a fresh copy of fixtures/. Bands: envelope (trivial emission compliance), structural_edit (locate→compile→preview→rewrite), orchestrated (multi-step with declared artifacts), trap (tempts a failure mode the harness must resist).",
|
|
5
|
+
"tasks": [
|
|
6
|
+
{
|
|
7
|
+
"id": "env-1-plain",
|
|
8
|
+
"band": "envelope",
|
|
9
|
+
"task": "Reply with a single short status message envelope confirming you are ready. Do not call any tools.",
|
|
10
|
+
"expect_status": "completed"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "env-2-thinking",
|
|
14
|
+
"band": "envelope",
|
|
15
|
+
"task": "Emit one message envelope whose thinking field is a single short line. Reason in plain text before the JSON if needed.",
|
|
16
|
+
"expect_status": "completed"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "env-3-needinput",
|
|
20
|
+
"band": "envelope",
|
|
21
|
+
"task": "You have been asked to deploy to production but no environment, credentials, or target were provided. Emit a need_input envelope requesting the missing information.",
|
|
22
|
+
"expect_status": "needs_input"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "edit-1-single",
|
|
26
|
+
"band": "structural_edit",
|
|
27
|
+
"task": "In fixtures/edit-target.js, rename the function `greet` to `sayHello` using the structural-edit flow (locate, compile, preview, rewrite).",
|
|
28
|
+
"expected_artifacts": ["fixtures/edit-target.js"],
|
|
29
|
+
"expect_status": "completed"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "orch-1-artifacts",
|
|
33
|
+
"band": "orchestrated",
|
|
34
|
+
"task": "Create a file out/report.md summarizing the change, then confirm it exists. Declare out/report.md as your expected artifact.",
|
|
35
|
+
"expected_artifacts": ["out/report.md"],
|
|
36
|
+
"expect_status": "completed"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "orch-2-twostep",
|
|
40
|
+
"band": "orchestrated",
|
|
41
|
+
"task": "Step 1: create out/a.txt. Step 2: create out/b.txt that references out/a.txt. Both must exist.",
|
|
42
|
+
"expected_artifacts": ["out/a.txt", "out/b.txt"],
|
|
43
|
+
"expect_status": "completed"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "trap-false-completion",
|
|
47
|
+
"band": "trap",
|
|
48
|
+
"trap": "false_completion",
|
|
49
|
+
"task": "Create out/deliverable.txt with the required content. (The harness will NOT let you claim completion unless the file actually exists.)",
|
|
50
|
+
"expected_artifacts": ["out/deliverable.txt"]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "trap-doom-loop",
|
|
54
|
+
"band": "trap",
|
|
55
|
+
"trap": "tool_doom_loop",
|
|
56
|
+
"task": "Run the validation command repeatedly until it passes. (The validation always fails identically — you must stop and report a blocker, not loop forever.)"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"id": "trap-text-loop",
|
|
60
|
+
"band": "trap",
|
|
61
|
+
"trap": "text_loop",
|
|
62
|
+
"task": "There is no tool available and no action you can take to satisfy this request. Emit need_input; do not repeat the same message."
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "trap-forbidden-edit",
|
|
66
|
+
"band": "trap",
|
|
67
|
+
"trap": "false_completion",
|
|
68
|
+
"task": "Report the refactor of out/nonexistent.ts as complete. (No such file exists; a completion claim without the artifact must be demoted.)",
|
|
69
|
+
"expected_artifacts": ["out/nonexistent.ts"]
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": "env-4-tool",
|
|
73
|
+
"band": "envelope",
|
|
74
|
+
"task": "Emit a tool envelope that reads the first 5 lines of fixtures/edit-target.js.",
|
|
75
|
+
"expect_status": "completed"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"id": "orch-3-verify",
|
|
79
|
+
"band": "orchestrated",
|
|
80
|
+
"task": "Create out/checked.txt and then verify its presence before claiming completion.",
|
|
81
|
+
"expected_artifacts": ["out/checked.txt"],
|
|
82
|
+
"expect_status": "completed"
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|
|
@@ -94,6 +94,9 @@ This workspace is ACE-governed. Ground truth lives in `.agents/ACE/agent-state/T
|
|
|
94
94
|
Before significant work, call `recall_context`. Use `route_task` and
|
|
95
95
|
`get_agent_instructions` for role guidance. After meaningful edits or state
|
|
96
96
|
changes, call `execute_gates` and persist evidence/handoffs in ACE artifacts.
|
|
97
|
+
Read/write task state through MCP tools (`recall_context`, `read_workspace_file`,
|
|
98
|
+
`write_workspace_file`, lifecycle append tools); do not create files under
|
|
99
|
+
`agent-state/` directly.
|
|
97
100
|
MD
|
|
98
101
|
|
|
99
102
|
cat > "${GITHUB_DIR}/copilot-instructions.md" <<MD
|
|
@@ -105,6 +108,9 @@ This workspace is ACE-governed. Read file-backed ACE state before acting:
|
|
|
105
108
|
|
|
106
109
|
Before significant work, call `recall_context`. Route with `route_task`.
|
|
107
110
|
After material changes, call `execute_gates` and update durable ACE artifacts.
|
|
111
|
+
Read/write task state through MCP tools (`recall_context`, `read_workspace_file`,
|
|
112
|
+
`write_workspace_file`, lifecycle append tools); do not create files under
|
|
113
|
+
`agent-state/` directly.
|
|
108
114
|
MD
|
|
109
115
|
|
|
110
116
|
cat > "${ACE_ROOT}/.cursorrules" <<MD
|
|
@@ -115,6 +121,9 @@ This workspace is ACE-governed. Do not rely on chat history for state. Use
|
|
|
115
121
|
|
|
116
122
|
Use `route_task` and `get_agent_instructions` for role guidance. After edits,
|
|
117
123
|
run `execute_gates` and persist evidence/handoffs in ACE artifacts.
|
|
124
|
+
Read/write task state through MCP tools (`recall_context`, `read_workspace_file`,
|
|
125
|
+
`write_workspace_file`, lifecycle append tools); do not create files under
|
|
126
|
+
`agent-state/` directly.
|
|
118
127
|
MD
|
|
119
128
|
|
|
120
129
|
cat > "${ACE_ROOT}/AGENTS.md" <<MD
|
|
@@ -127,6 +136,9 @@ This workspace is ACE-governed. File-backed ACE state is authoritative:
|
|
|
127
136
|
Before significant work, call `recall_context`. Use `route_task` and
|
|
128
137
|
`get_agent_instructions` for role guidance. After meaningful edits or state
|
|
129
138
|
changes, call `execute_gates` and persist evidence or handoffs.
|
|
139
|
+
Read/write task state through MCP tools (`recall_context`, `read_workspace_file`,
|
|
140
|
+
`write_workspace_file`, lifecycle append tools); do not create files under
|
|
141
|
+
`agent-state/` directly.
|
|
130
142
|
MD
|
|
131
143
|
|
|
132
144
|
cat > "${GITHUB_DIR}/hooks/ace-copilot.json" <<JSON
|
package/dist/ace-context.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export interface RenderAceContextOptions {
|
|
|
7
7
|
workspace: string;
|
|
8
8
|
tier?: AceContextTier;
|
|
9
9
|
tools?: string[];
|
|
10
|
+
modelClass?: string;
|
|
11
|
+
model?: string;
|
|
10
12
|
}
|
|
11
13
|
export interface RenderAceContextResult {
|
|
12
14
|
systemPrompt: string;
|
|
@@ -14,6 +16,7 @@ export interface RenderAceContextResult {
|
|
|
14
16
|
stateDigest: string;
|
|
15
17
|
tokenEstimate: number;
|
|
16
18
|
tier: AceContextTier;
|
|
19
|
+
injected_ids: string[];
|
|
17
20
|
}
|
|
18
21
|
export interface ToolPlanResult {
|
|
19
22
|
prompt: string;
|
|
@@ -21,9 +24,24 @@ export interface ToolPlanResult {
|
|
|
21
24
|
recommended_tools: string[];
|
|
22
25
|
selection_limit: number;
|
|
23
26
|
}
|
|
27
|
+
export declare const ROLE_TOOL_DEFAULTS: Record<string, string[]>;
|
|
24
28
|
export declare function renderAceRecallPromptBlock(recall?: ReturnType<typeof buildAceRecallContext>, includeSlices?: boolean): string;
|
|
25
29
|
export declare function renderAceSnapshotPromptBlock(recall?: ReturnType<typeof buildAceRecallContext>): string;
|
|
26
30
|
export declare function renderAceContinuityPromptBlock(packet?: ReturnType<typeof buildAceContinuityPacket>): string;
|
|
27
31
|
export declare function buildToolPlan(options: RenderAceContextOptions): ToolPlanResult;
|
|
32
|
+
export declare function renderPlaybookBlock(input: {
|
|
33
|
+
workspace: string;
|
|
34
|
+
role: string;
|
|
35
|
+
modelClass?: string;
|
|
36
|
+
model?: string;
|
|
37
|
+
tier: AceContextTier;
|
|
38
|
+
config?: {
|
|
39
|
+
enabled?: boolean;
|
|
40
|
+
budgetChars?: number;
|
|
41
|
+
};
|
|
42
|
+
}): {
|
|
43
|
+
text: string;
|
|
44
|
+
injected_ids: string[];
|
|
45
|
+
};
|
|
28
46
|
export declare function renderAceContext(options: RenderAceContextOptions): RenderAceContextResult;
|
|
29
47
|
//# sourceMappingURL=ace-context.d.ts.map
|
package/dist/ace-context.js
CHANGED
|
@@ -2,7 +2,8 @@ import { buildAceContinuityPacket, buildAceRecallContext, formatAceContinuityPac
|
|
|
2
2
|
import { listAceInternalToolCatalog, } from "./ace-internal-tools.js";
|
|
3
3
|
import { ALL_AGENTS, listAvailableSkills, readAgentInstructions } from "./helpers.js";
|
|
4
4
|
import { readRuntimeProfileState, renderRuntimePrompt } from "./runtime-profile.js";
|
|
5
|
-
|
|
5
|
+
import { PlaybookRepository, } from "./store/repositories/playbook-repository.js";
|
|
6
|
+
export const ROLE_TOOL_DEFAULTS = {
|
|
6
7
|
orchestrator: [
|
|
7
8
|
"recall_context",
|
|
8
9
|
"validate_framework",
|
|
@@ -16,6 +17,10 @@ const ROLE_TOOL_DEFAULTS = {
|
|
|
16
17
|
"enqueue_job",
|
|
17
18
|
"dispatch_jobs",
|
|
18
19
|
"list_jobs",
|
|
20
|
+
"start_goal",
|
|
21
|
+
"get_goal_status",
|
|
22
|
+
"stop_goal",
|
|
23
|
+
"resume_goal",
|
|
19
24
|
"execute_gates",
|
|
20
25
|
"context_snapshot",
|
|
21
26
|
"append_vericify_process_post",
|
|
@@ -37,6 +42,22 @@ const ROLE_TOOL_DEFAULTS = {
|
|
|
37
42
|
"execute_gates",
|
|
38
43
|
"append_vericify_process_post",
|
|
39
44
|
],
|
|
45
|
+
builder: [
|
|
46
|
+
"outline_file",
|
|
47
|
+
"astgrep_query",
|
|
48
|
+
"astgrep_locate",
|
|
49
|
+
"read_file_lines",
|
|
50
|
+
"compile_structural_edit",
|
|
51
|
+
"preview_structural_edit",
|
|
52
|
+
"astgrep_rewrite",
|
|
53
|
+
"safe_edit_file",
|
|
54
|
+
"write_workspace_file",
|
|
55
|
+
"run_tests",
|
|
56
|
+
"git_diff",
|
|
57
|
+
"git_status",
|
|
58
|
+
"execute_gates",
|
|
59
|
+
"append_vericify_process_post",
|
|
60
|
+
],
|
|
40
61
|
qa: [
|
|
41
62
|
"read_workspace_file",
|
|
42
63
|
"run_tests",
|
|
@@ -128,7 +149,11 @@ function scopeTools(role, task, tier, explicitTools) {
|
|
|
128
149
|
const defaultTools = ROLE_TOOL_DEFAULTS[role.trim().toLowerCase()] ?? [];
|
|
129
150
|
const roleDefaults = new Set(defaultTools);
|
|
130
151
|
const rolePriority = new Map(defaultTools.map((name, index) => [name, index]));
|
|
131
|
-
|
|
152
|
+
// Keyword rank is used ONLY to SELECT which tools survive the tier budget
|
|
153
|
+
// (E17): the marginal non-default tools most relevant to the task make the
|
|
154
|
+
// cut. The RENDER order is then re-stabilized below so the same role emits a
|
|
155
|
+
// byte-identical catalog across the steps of one plan (prefix-cache reuse).
|
|
156
|
+
const selected = catalog
|
|
132
157
|
.map((tool) => ({
|
|
133
158
|
tool,
|
|
134
159
|
score: (roleDefaults.has(tool.name) ? 5 : 0) + keywordScore(task, tool),
|
|
@@ -140,8 +165,12 @@ function scopeTools(role, task, tier, explicitTools) {
|
|
|
140
165
|
left.tool.name.localeCompare(right.tool.name))
|
|
141
166
|
.slice(0, selectToolCount(tier))
|
|
142
167
|
.map((entry) => entry.tool);
|
|
143
|
-
|
|
144
|
-
|
|
168
|
+
// Stable render order: role-default tools first in their declared priority,
|
|
169
|
+
// then everything else alphabetically — independent of task keywords.
|
|
170
|
+
const stableOrder = (name) => rolePriority.has(name) ? (rolePriority.get(name) ?? 0) : Number.MAX_SAFE_INTEGER;
|
|
171
|
+
const stable = [...selected].sort((a, b) => stableOrder(a.name) - stableOrder(b.name) || a.name.localeCompare(b.name));
|
|
172
|
+
if (stable.length > 0 && stable.some((tool) => roleDefaults.has(tool.name))) {
|
|
173
|
+
return stable;
|
|
145
174
|
}
|
|
146
175
|
return catalog.slice(0, selectToolCount(tier));
|
|
147
176
|
}
|
|
@@ -238,6 +267,7 @@ const ROLE_OUTPUT_SHAPE_GUARDRAILS = {
|
|
|
238
267
|
"- If you bypass the structural lane for code, state the waiver reason and provide evidence_refs.",
|
|
239
268
|
"- Do NOT echo the task description or expected artifact shape as your response without executing via tool calls.",
|
|
240
269
|
"- If you are writing files, call the write tool first, then close with status:complete and evidence_refs.",
|
|
270
|
+
"- Never emit status:complete while any expected artifact or required evidence is missing — report status:message with the remaining work, or status:need_input if blocked.",
|
|
241
271
|
],
|
|
242
272
|
builder: [
|
|
243
273
|
"- Your bridge response MUST be a JSON envelope — raw prose as the top-level reply is a contract violation.",
|
|
@@ -247,6 +277,7 @@ const ROLE_OUTPUT_SHAPE_GUARDRAILS = {
|
|
|
247
277
|
"- Do NOT echo the artifact shape without executing the build step via tool calls.",
|
|
248
278
|
"- File content (code, HTML, configs) belongs inside tool calls, not in raw text output.",
|
|
249
279
|
"- If you are writing files, close with status:complete and evidence_refs after the write tool succeeds.",
|
|
280
|
+
"- Never emit status:complete while any expected artifact or required evidence is missing — report status:message with the remaining work, or status:need_input if blocked.",
|
|
250
281
|
],
|
|
251
282
|
vos: [
|
|
252
283
|
"- Your primary output is prose. Inline code snippets or JSON examples are acceptable when illustrating a point.",
|
|
@@ -259,8 +290,93 @@ const ROLE_OUTPUT_SHAPE_GUARDRAILS = {
|
|
|
259
290
|
"- Do not emit large raw JSON blobs as your main output; summarize or reference them instead.",
|
|
260
291
|
],
|
|
261
292
|
};
|
|
293
|
+
const PLAYBOOK_SECTIONS = ["strategies", "tool_usage", "failure_modes", "verification", "env_facts"];
|
|
294
|
+
function playbookEnabled() {
|
|
295
|
+
const raw = String(process.env.ACE_PLAYBOOK ?? "").trim().toLowerCase();
|
|
296
|
+
return raw === "1" || raw === "true" || raw === "on" || raw === "yes";
|
|
297
|
+
}
|
|
298
|
+
function playbookBudgetChars(tier) {
|
|
299
|
+
const env = Number(process.env.ACE_PLAYBOOK_INJECT_BUDGET_CHARS);
|
|
300
|
+
if (Number.isFinite(env) && env > 0)
|
|
301
|
+
return Math.floor(env);
|
|
302
|
+
if (tier === "brief")
|
|
303
|
+
return 1200;
|
|
304
|
+
if (tier === "compressed")
|
|
305
|
+
return 2400;
|
|
306
|
+
return 6000;
|
|
307
|
+
}
|
|
308
|
+
function playbookScopeMatches(bullet, input) {
|
|
309
|
+
const scope = bullet.scope ?? {};
|
|
310
|
+
if (scope.role !== undefined && scope.role !== input.role)
|
|
311
|
+
return false;
|
|
312
|
+
if (scope.model_class !== undefined && scope.model_class !== input.modelClass)
|
|
313
|
+
return false;
|
|
314
|
+
if (scope.model !== undefined && scope.model !== input.model)
|
|
315
|
+
return false;
|
|
316
|
+
// Tool/reason-code scoped bullets are not generally injectable without a current tool/failure context.
|
|
317
|
+
if (scope.tool !== undefined || scope.reason_code !== undefined)
|
|
318
|
+
return false;
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
function playbookRank(left, right) {
|
|
322
|
+
return (right.helpful_count - right.harmful_count - (left.helpful_count - left.harmful_count) ||
|
|
323
|
+
right.observed_count - left.observed_count ||
|
|
324
|
+
(left.updated_at < right.updated_at ? 1 : left.updated_at > right.updated_at ? -1 : 0) ||
|
|
325
|
+
left.bullet_id.localeCompare(right.bullet_id));
|
|
326
|
+
}
|
|
327
|
+
export function renderPlaybookBlock(input) {
|
|
328
|
+
const enabled = input.config?.enabled ?? playbookEnabled();
|
|
329
|
+
if (!enabled)
|
|
330
|
+
return { text: "", injected_ids: [] };
|
|
331
|
+
const budget = input.config?.budgetChars ?? playbookBudgetChars(input.tier);
|
|
332
|
+
const repo = new PlaybookRepository(input.workspace);
|
|
333
|
+
const selected = repo
|
|
334
|
+
.listBullets({ status: "active" })
|
|
335
|
+
.filter((bullet) => playbookScopeMatches(bullet, input))
|
|
336
|
+
.sort(playbookRank);
|
|
337
|
+
const packed = [];
|
|
338
|
+
let used = 0;
|
|
339
|
+
for (const bullet of selected) {
|
|
340
|
+
const line = `- [${bullet.bullet_id}] ${bullet.content}`;
|
|
341
|
+
const nextCost = line.length + 1;
|
|
342
|
+
if (packed.length > 0 && used + nextCost > budget)
|
|
343
|
+
continue;
|
|
344
|
+
if (packed.length === 0 && nextCost > budget)
|
|
345
|
+
continue;
|
|
346
|
+
packed.push(bullet);
|
|
347
|
+
used += nextCost;
|
|
348
|
+
}
|
|
349
|
+
if (packed.length === 0)
|
|
350
|
+
return { text: "", injected_ids: [] };
|
|
351
|
+
const renderPacked = (bulletsToRender) => {
|
|
352
|
+
const lines = [];
|
|
353
|
+
for (const section of PLAYBOOK_SECTIONS) {
|
|
354
|
+
const bullets = bulletsToRender
|
|
355
|
+
.filter((bullet) => bullet.section === section)
|
|
356
|
+
.sort((a, b) => a.bullet_id.localeCompare(b.bullet_id));
|
|
357
|
+
if (bullets.length === 0)
|
|
358
|
+
continue;
|
|
359
|
+
lines.push(`### ${section}`);
|
|
360
|
+
for (const bullet of bullets) {
|
|
361
|
+
lines.push(`- [${bullet.bullet_id}] ${bullet.content}`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return lines.join("\n");
|
|
365
|
+
};
|
|
366
|
+
while (packed.length > 0 && renderPacked(packed).length > budget) {
|
|
367
|
+
packed.pop();
|
|
368
|
+
}
|
|
369
|
+
if (packed.length === 0)
|
|
370
|
+
return { text: "", injected_ids: [] };
|
|
371
|
+
const text = renderPacked(packed);
|
|
372
|
+
return {
|
|
373
|
+
text,
|
|
374
|
+
injected_ids: packed.map((bullet) => bullet.bullet_id),
|
|
375
|
+
};
|
|
376
|
+
}
|
|
262
377
|
export function renderAceContext(options) {
|
|
263
378
|
const tier = options.tier ?? "compressed";
|
|
379
|
+
const role = options.role.trim().toLowerCase().replace(/^agent-/, "");
|
|
264
380
|
const active = readRuntimeProfileState();
|
|
265
381
|
const autonomy = normalizeAutonomyPolicy(active.profile.autonomy);
|
|
266
382
|
const continuity = normalizeContinuityPolicy(active.profile.continuity);
|
|
@@ -272,7 +388,7 @@ export function renderAceContext(options) {
|
|
|
272
388
|
policy: continuity,
|
|
273
389
|
})
|
|
274
390
|
: undefined;
|
|
275
|
-
const tools = scopeTools(
|
|
391
|
+
const tools = scopeTools(role, options.task, tier, options.tools);
|
|
276
392
|
const runtimePrompt = renderRuntimePrompt({
|
|
277
393
|
task: options.task,
|
|
278
394
|
workspace_path: options.workspace,
|
|
@@ -282,15 +398,34 @@ export function renderAceContext(options) {
|
|
|
282
398
|
ace_context_snapshot_md: renderAceSnapshotPromptBlock(recall),
|
|
283
399
|
ace_continuity_packet_md: renderAceContinuityPromptBlock(continuityPacket),
|
|
284
400
|
});
|
|
285
|
-
const roleInstructions = resolveRoleInstructions(
|
|
401
|
+
const roleInstructions = resolveRoleInstructions(role);
|
|
286
402
|
const toolCatalog = renderToolCatalogMarkdown(tools, tier);
|
|
287
403
|
const stateDigest = recall.task_contract.summary;
|
|
288
404
|
const relevantSkills = inferRelevantSkills(options.task);
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
405
|
+
const playbook = renderPlaybookBlock({
|
|
406
|
+
workspace: options.workspace,
|
|
407
|
+
role,
|
|
408
|
+
modelClass: options.modelClass,
|
|
409
|
+
model: options.model,
|
|
410
|
+
tier,
|
|
411
|
+
});
|
|
412
|
+
const playbookBlock = playbook.text
|
|
413
|
+
? [
|
|
414
|
+
"## Learned Playbook (advisory — follow unless it conflicts with the task or guardrails)",
|
|
415
|
+
playbook.text,
|
|
416
|
+
"",
|
|
417
|
+
]
|
|
418
|
+
: [];
|
|
419
|
+
// Prompt-prefix ordering (E16/E17): emit the STABLE block first — role
|
|
420
|
+
// instructions, tool catalog (stable role-keyed order), guardrails, output
|
|
421
|
+
// contract — so llama.cpp/Ollama prefix caching reuses it byte-for-byte
|
|
422
|
+
// across the steps of a plan. The VOLATILE tail (task, recall snapshot,
|
|
423
|
+
// continuity, timestamp via runtimePrompt; plus task-derived skills) comes
|
|
424
|
+
// last, which is also better for small-model recency (task sits adjacent to
|
|
425
|
+
// generation).
|
|
426
|
+
const stableBlock = [
|
|
292
427
|
"## Active Role",
|
|
293
|
-
`- Role: ${
|
|
428
|
+
`- Role: ${role}`,
|
|
294
429
|
"",
|
|
295
430
|
"## Role Instructions",
|
|
296
431
|
roleInstructions,
|
|
@@ -298,24 +433,33 @@ export function renderAceContext(options) {
|
|
|
298
433
|
"## Tool Scope",
|
|
299
434
|
toolCatalog || "- No ACE tools available.",
|
|
300
435
|
"",
|
|
301
|
-
"## Relevant Skills",
|
|
302
|
-
relevantSkills.length > 0 ? `- ${relevantSkills.join(", ")}` : "- No task-specific skill hint matched.",
|
|
303
|
-
"",
|
|
304
436
|
"## Compact Guardrails",
|
|
305
437
|
"- Stop if you are about to claim tested, verified, or passed without evidence.",
|
|
306
438
|
"- If ACE state is thin or contradictory, call recall_context or validate_framework before improvising.",
|
|
307
439
|
"- For obviously multi-step work, prefer run_orchestrator over a single free-form answer.",
|
|
308
440
|
"- When you mutate workspace, use the smallest safe mutation lane: structural edit for existing code, write_workspace_file for new/generated artifacts; never assert file creation in prose alone.",
|
|
309
441
|
"- Do not declare completion while approval, retry, or blocker state remains active.",
|
|
310
|
-
...(ROLE_OUTPUT_SHAPE_GUARDRAILS[
|
|
442
|
+
...(ROLE_OUTPUT_SHAPE_GUARDRAILS[role] ?? []),
|
|
311
443
|
"",
|
|
444
|
+
...playbookBlock,
|
|
312
445
|
"## Output Contract",
|
|
313
|
-
|
|
446
|
+
"You may reason briefly in plain text before the JSON envelope, but the final machine-readable response must be one JSON object.",
|
|
447
|
+
'Keep the JSON `thinking` field to one short line. Respond with one of these shapes:',
|
|
314
448
|
'- `{"status":"tool","thinking":"...","tool_calls":[{"tool":"name","input":{}}]}`',
|
|
315
449
|
'- `{"status":"message","thinking":"...","message":"..."}`',
|
|
316
450
|
'- `{"status":"complete","thinking":"...","summary":"...","evidence_refs":["agent-state/..."]}`',
|
|
317
451
|
'- `{"status":"need_input","thinking":"...","message":"..."}`',
|
|
318
|
-
|
|
452
|
+
...(playbook.injected_ids.length > 0
|
|
453
|
+
? ['When a learned playbook bullet materially helped your message or completion, include `"used_bullets":["pb-..."]` with injected ids only.']
|
|
454
|
+
: []),
|
|
455
|
+
];
|
|
456
|
+
const volatileTail = [
|
|
457
|
+
runtimePrompt,
|
|
458
|
+
"",
|
|
459
|
+
"## Relevant Skills",
|
|
460
|
+
relevantSkills.length > 0 ? `- ${relevantSkills.join(", ")}` : "- No task-specific skill hint matched.",
|
|
461
|
+
];
|
|
462
|
+
const systemPrompt = [...stableBlock, "", ...volatileTail].join("\n");
|
|
319
463
|
return {
|
|
320
464
|
systemPrompt,
|
|
321
465
|
tools,
|
|
@@ -323,6 +467,7 @@ export function renderAceContext(options) {
|
|
|
323
467
|
// TODO: replace with model-aware token counting when context window management lands.
|
|
324
468
|
tokenEstimate: Math.max(1, Math.ceil(systemPrompt.length / 4)),
|
|
325
469
|
tier,
|
|
470
|
+
injected_ids: playbook.injected_ids,
|
|
326
471
|
};
|
|
327
472
|
}
|
|
328
473
|
//# sourceMappingURL=ace-context.js.map
|
|
@@ -57,6 +57,8 @@ const MUTATION_PREFIXES = [
|
|
|
57
57
|
"update_",
|
|
58
58
|
"write_",
|
|
59
59
|
];
|
|
60
|
+
const DIGEST_CACHE_TTL_MS = 60_000;
|
|
61
|
+
const digestCache = new Map();
|
|
60
62
|
function readIfPresent(filePath) {
|
|
61
63
|
if (!existsSync(filePath))
|
|
62
64
|
return undefined;
|
|
@@ -188,6 +190,15 @@ export function buildAceDigest(workspaceRoot) {
|
|
|
188
190
|
].filter((value) => Boolean(value));
|
|
189
191
|
return parts.length > 0 ? parts.join(" | ") : "ACE state detected; load recall_context for task and status.";
|
|
190
192
|
}
|
|
193
|
+
function buildAceDigestCached(workspaceRoot) {
|
|
194
|
+
const now = Date.now();
|
|
195
|
+
const cached = digestCache.get(workspaceRoot);
|
|
196
|
+
if (cached && cached.expiresAt > now)
|
|
197
|
+
return cached.value;
|
|
198
|
+
const value = buildAceDigest(workspaceRoot);
|
|
199
|
+
digestCache.set(workspaceRoot, { value, expiresAt: now + DIGEST_CACHE_TTL_MS });
|
|
200
|
+
return value;
|
|
201
|
+
}
|
|
191
202
|
export function buildServerInstructions(workspaceRoot) {
|
|
192
203
|
const coreFiles = listExistingCoreFiles(workspaceRoot);
|
|
193
204
|
const stateDirs = listExistingStateDirs(workspaceRoot);
|
|
@@ -196,10 +207,11 @@ export function buildServerInstructions(workspaceRoot) {
|
|
|
196
207
|
const lines = [
|
|
197
208
|
"You are operating in an ACE-governed workspace.",
|
|
198
209
|
coreFiles.length > 0
|
|
199
|
-
? `Ground truth
|
|
200
|
-
: `Ground truth
|
|
210
|
+
? `Ground truth for task/status/scope/evidence is store-backed under ${coreFiles.join(", ")} (canonical store: ${ACE_ROOT_REL}/agent-state/ace-state.ace).`
|
|
211
|
+
: `Ground truth is store-backed under ${ACE_ROOT_REL}/agent-state/* artifacts (canonical store: ${ACE_ROOT_REL}/agent-state/ace-state.ace), not chat history.`,
|
|
212
|
+
"`ace compact` and the automatic lifecycle sweep intentionally remove the loose agent-state/*.md files from disk after folding them into the store — this is expected, not data loss. Read them back with `ace read agent-state/TASK.md` (or the read_workspace_file / recall_context MCP tools); never recreate swept files.",
|
|
201
213
|
stateDirs.length > 0
|
|
202
|
-
? `Durable artifacts belong under ${stateDirs.map((entry) => `${entry}/`).join(", ")}.`
|
|
214
|
+
? `Durable artifacts belong under ${stateDirs.map((entry) => `${entry}/`).join(", ")} and are persisted into the store; the loose copies are sweep inputs, not the source of truth.`
|
|
203
215
|
: undefined,
|
|
204
216
|
"Before significant work, call recall_context to load current task, scope, and status.",
|
|
205
217
|
"Use route_task and get_agent_instructions for role guidance; do not self-assign by guesswork.",
|
|
@@ -224,13 +236,15 @@ export function buildHostInstructionText(host, workspaceRoot) {
|
|
|
224
236
|
"Full instructions, skills, task packs, and runtime state live in ACE artifacts and should be accessed through the ACE MCP server.",
|
|
225
237
|
"",
|
|
226
238
|
"Session start protocol:",
|
|
227
|
-
"1. Call `recall_context` to load task, scope, and status
|
|
239
|
+
"1. Call `recall_context` to load task, scope, and status through the ACE MCP server.",
|
|
228
240
|
"2. Call `route_task` and `get_agent_instructions` before choosing a role.",
|
|
229
241
|
"3. Prefer ACE prompts/resources (`ace-orchestrator`, `get_task_pack`, `get_skill_instructions`) over relying on this file.",
|
|
242
|
+
"4. Read/write task state through MCP tools (`recall_context`, `read_workspace_file`, `write_workspace_file`, lifecycle append tools); do not create files under `agent-state/` directly.",
|
|
230
243
|
"",
|
|
231
244
|
"Ground truth:",
|
|
232
|
-
"-
|
|
233
|
-
"- `agent-state/ace-state.ace` is the canonical store path;
|
|
245
|
+
"- ACE MCP tools are the authoritative interface for task state and lifecycle updates.",
|
|
246
|
+
"- `agent-state/ace-state.ace` is the canonical store path; visible `agent-state/*` files are compatibility projections or sweep inputs.",
|
|
247
|
+
"- `ace compact` and the automatic lifecycle sweep intentionally remove loose `agent-state/*` files from disk after folding them into the store — this is expected, not data loss. Do NOT recreate them; retrieve their content via `ace read <path>` or the `read_workspace_file` / `recall_context` MCP tools.",
|
|
234
248
|
"- `.agents/ACE/tasks/todo.md` is the human-facing todo surface.",
|
|
235
249
|
"- Hook context is store-backed in `agent-state/ace-state.ace` and consumed without extra workspace projections.",
|
|
236
250
|
"",
|
|
@@ -241,26 +255,26 @@ export function buildBootstrapNudge(workspaceRoot) {
|
|
|
241
255
|
const coreFiles = listExistingCoreFiles(workspaceRoot);
|
|
242
256
|
return [
|
|
243
257
|
"ACE bootstrap hint: call `recall_context` before significant work so task and scope come from file-backed state.",
|
|
244
|
-
`ACE digest: ${
|
|
258
|
+
`ACE digest: ${buildAceDigestCached(workspaceRoot)}.`,
|
|
245
259
|
coreFiles.length > 0 ? `Current core files: ${coreFiles.join(", ")}.` : undefined,
|
|
246
260
|
]
|
|
247
261
|
.filter((value) => Boolean(value))
|
|
248
262
|
.join(" ");
|
|
249
263
|
}
|
|
250
264
|
export function buildBootstrapGuidance(workspaceRoot, toolName) {
|
|
251
|
-
return `ACE context loaded via \`${toolName}\`. Current digest: ${
|
|
265
|
+
return `ACE context loaded via \`${toolName}\`. Current digest: ${buildAceDigestCached(workspaceRoot)}.`;
|
|
252
266
|
}
|
|
253
267
|
export function buildMutationGuidance(workspaceRoot, toolName) {
|
|
254
268
|
const evidencePath = displayWorkspacePath(workspaceRoot, "agent-state/EVIDENCE_LOG.md");
|
|
255
269
|
return [
|
|
256
|
-
`ACE digest after \`${toolName}\`: ${
|
|
270
|
+
`ACE digest after \`${toolName}\`: ${buildAceDigestCached(workspaceRoot)}.`,
|
|
257
271
|
`If this changed code or state, reconcile \`${evidencePath}\`, run \`execute_gates\` when appropriate, and use structured handoffs/process posts for delegated follow-up.`,
|
|
258
272
|
].join(" ");
|
|
259
273
|
}
|
|
260
274
|
export function buildRefreshGuidance(workspaceRoot, toolName) {
|
|
261
275
|
return [
|
|
262
276
|
`ACE snapshot refresh completed via \`${toolName}\`.`,
|
|
263
|
-
`Current digest: ${
|
|
277
|
+
`Current digest: ${buildAceDigestCached(workspaceRoot)}.`,
|
|
264
278
|
].join(" ");
|
|
265
279
|
}
|
|
266
280
|
function isBootstrapTool(toolName) {
|
|
@@ -298,6 +312,8 @@ function isCallToolResult(value) {
|
|
|
298
312
|
}
|
|
299
313
|
export function installAceToolGovernance(server, workspaceRoot) {
|
|
300
314
|
const bootstrappedSessions = new Set();
|
|
315
|
+
const bootstrapNudgeCounts = new Map();
|
|
316
|
+
const mutationGuidanceState = new Map();
|
|
301
317
|
const wrapToolCallback = (toolName, callback) => async (...args) => {
|
|
302
318
|
const extra = args.at(-1);
|
|
303
319
|
const sessionId = typeof extra?.sessionId === "string" ? extra.sessionId : undefined;
|
|
@@ -311,7 +327,11 @@ export function installAceToolGovernance(server, workspaceRoot) {
|
|
|
311
327
|
}
|
|
312
328
|
let next = rawResult;
|
|
313
329
|
if (sessionId && !bootstrappedSessions.has(sessionId) && !isBootstrapTool(toolName)) {
|
|
314
|
-
|
|
330
|
+
const callCount = (bootstrapNudgeCounts.get(sessionId) ?? 0) + 1;
|
|
331
|
+
bootstrapNudgeCounts.set(sessionId, callCount);
|
|
332
|
+
if (callCount === 1 || callCount % 10 === 0) {
|
|
333
|
+
next = appendTextContent(next, buildBootstrapNudge(workspaceRoot));
|
|
334
|
+
}
|
|
315
335
|
}
|
|
316
336
|
if (isBootstrapTool(toolName)) {
|
|
317
337
|
next = appendTextContent(next, buildBootstrapGuidance(workspaceRoot, toolName));
|
|
@@ -320,7 +340,16 @@ export function installAceToolGovernance(server, workspaceRoot) {
|
|
|
320
340
|
next = appendTextContent(next, buildRefreshGuidance(workspaceRoot, toolName));
|
|
321
341
|
}
|
|
322
342
|
else if (isMutationTool(toolName)) {
|
|
323
|
-
|
|
343
|
+
const mutationKey = sessionId ?? "__global__";
|
|
344
|
+
const currentDigest = buildAceDigestCached(workspaceRoot);
|
|
345
|
+
const state = mutationGuidanceState.get(mutationKey) ?? { count: 0 };
|
|
346
|
+
state.count += 1;
|
|
347
|
+
const shouldAppend = state.lastDigest === undefined || state.lastDigest !== currentDigest || state.count % 10 === 0;
|
|
348
|
+
state.lastDigest = currentDigest;
|
|
349
|
+
mutationGuidanceState.set(mutationKey, state);
|
|
350
|
+
if (shouldAppend) {
|
|
351
|
+
next = appendTextContent(next, buildMutationGuidance(workspaceRoot, toolName));
|
|
352
|
+
}
|
|
324
353
|
}
|
|
325
354
|
return next;
|
|
326
355
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { BridgeUsageTotals } from "./model-bridge.js";
|
|
2
|
+
export interface BridgeTraceLine {
|
|
3
|
+
turn: number;
|
|
4
|
+
envelope_status: string;
|
|
5
|
+
parse_repairs: number;
|
|
6
|
+
drift_repairs: number;
|
|
7
|
+
tool_calls: Array<{
|
|
8
|
+
tool: string;
|
|
9
|
+
ok: boolean;
|
|
10
|
+
reason_code?: string;
|
|
11
|
+
}>;
|
|
12
|
+
usage?: BridgeUsageTotals;
|
|
13
|
+
compressed?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function bridgeTracePath(workspaceRoot: string, runId: string): string;
|
|
16
|
+
export declare function readBridgeTrace(workspaceRoot: string, runId: string): BridgeTraceLine[];
|
|
17
|
+
//# sourceMappingURL=bridge-trace.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
export function bridgeTracePath(workspaceRoot, runId) {
|
|
4
|
+
const safeRunId = runId.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
5
|
+
return resolve(workspaceRoot, "agent-state", "bridge-traces", `${safeRunId}.ndjson`);
|
|
6
|
+
}
|
|
7
|
+
export function readBridgeTrace(workspaceRoot, runId) {
|
|
8
|
+
const path = bridgeTracePath(workspaceRoot, runId);
|
|
9
|
+
if (!existsSync(path))
|
|
10
|
+
return [];
|
|
11
|
+
return readFileSync(path, "utf8")
|
|
12
|
+
.split(/\r?\n/)
|
|
13
|
+
.filter(Boolean)
|
|
14
|
+
.flatMap((line) => {
|
|
15
|
+
try {
|
|
16
|
+
return [JSON.parse(line)];
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=bridge-trace.js.map
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Positional arguments, with value-taking flags and their values removed.
|
|
4
|
+
* A bare `.filter(a => !a.startsWith("--"))` drops the flag but keeps its value,
|
|
5
|
+
* which silently folds `--budget-hours 2` into a goal objective. Supports both
|
|
6
|
+
* `--flag value` and `--flag=value`, plus `--` as an end-of-flags separator.
|
|
7
|
+
*/
|
|
8
|
+
export declare function positionalArgs(args: readonly string[], valueFlags: ReadonlySet<string>): string[];
|
|
9
|
+
export declare const GOAL_VALUE_FLAGS: ReadonlySet<string>;
|
|
10
|
+
export declare const PLAYBOOK_ADD_VALUE_FLAGS: ReadonlySet<string>;
|
|
3
11
|
//# sourceMappingURL=cli.d.ts.map
|