@voybio/ace-swarm 2.4.3 → 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.
Files changed (144) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +72 -532
  3. package/assets/agent-state/INTERFACE_REGISTRY.md +2 -2
  4. package/assets/agent-state/MODULES/gates/gate-autonomy.json +4 -2
  5. package/assets/agent-state/MODULES/gates/gate-completeness.json +4 -2
  6. package/assets/agent-state/MODULES/gates/gate-operability.json +4 -2
  7. package/assets/agent-state/MODULES/gates/gate-security.json +3 -1
  8. package/assets/agent-state/MODULES/registry.json +1 -4
  9. package/assets/agent-state/MODULES/roles/capability-build.json +1 -2
  10. package/assets/agent-state/MODULES/roles/capability-eval.json +1 -1
  11. package/assets/agent-state/MODULES/roles/capability-qa.json +1 -2
  12. package/assets/agent-state/QUALITY_GATES.md +25 -9
  13. package/assets/agent-state/TEAL_CONFIG.md +2 -11
  14. package/assets/eval-battery/battery.json +85 -0
  15. package/assets/eval-battery/fixtures/edit-target.js +3 -0
  16. package/assets/scripts/render-mcp-configs.sh +12 -0
  17. package/dist/ace-context.d.ts +18 -0
  18. package/dist/ace-context.js +161 -16
  19. package/dist/ace-server-instructions.js +36 -9
  20. package/dist/astgrep-index.d.ts +7 -1
  21. package/dist/astgrep-index.js +66 -39
  22. package/dist/bridge-trace.d.ts +17 -0
  23. package/dist/bridge-trace.js +23 -0
  24. package/dist/cli.d.ts +9 -1
  25. package/dist/cli.js +619 -10
  26. package/dist/discovery-runtime-wrappers.d.ts +16 -1
  27. package/dist/discovery-runtime-wrappers.js +65 -9
  28. package/dist/eval-harness.d.ts +110 -0
  29. package/dist/eval-harness.js +254 -0
  30. package/dist/experience-reflector.d.ts +13 -0
  31. package/dist/experience-reflector.js +62 -0
  32. package/dist/gate-contract.d.ts +63 -0
  33. package/dist/gate-contract.js +178 -0
  34. package/dist/goal-engine.d.ts +155 -0
  35. package/dist/goal-engine.js +795 -0
  36. package/dist/helpers/store-resolution.js +40 -39
  37. package/dist/hermes/session-manager.d.ts +10 -0
  38. package/dist/hermes/session-manager.js +130 -27
  39. package/dist/job-scheduler.d.ts +58 -0
  40. package/dist/job-scheduler.js +202 -2
  41. package/dist/job-worker.d.ts +87 -0
  42. package/dist/job-worker.js +182 -0
  43. package/dist/local-model-policy.d.ts +8 -2
  44. package/dist/local-model-policy.js +31 -17
  45. package/dist/local-model-runtime.d.ts +6 -0
  46. package/dist/local-model-runtime.js +130 -2
  47. package/dist/measured-classing.d.ts +66 -0
  48. package/dist/measured-classing.js +165 -0
  49. package/dist/model-bridge.d.ts +45 -0
  50. package/dist/model-bridge.js +502 -44
  51. package/dist/orchestrator-supervisor.js +1 -1
  52. package/dist/plan-proposal.d.ts +7 -0
  53. package/dist/plan-proposal.js +74 -4
  54. package/dist/playbook-adaptation.d.ts +37 -0
  55. package/dist/playbook-adaptation.js +159 -0
  56. package/dist/playbook-curator.d.ts +16 -0
  57. package/dist/playbook-curator.js +106 -0
  58. package/dist/playbook-llm-reflector.d.ts +26 -0
  59. package/dist/playbook-llm-reflector.js +157 -0
  60. package/dist/public-surface.d.ts +2 -0
  61. package/dist/public-surface.js +26 -14
  62. package/dist/runtime-executor.js +45 -2
  63. package/dist/runtime-tool-specs.d.ts +8 -1
  64. package/dist/runtime-tool-specs.js +19 -1
  65. package/dist/safe-edit.d.ts +20 -0
  66. package/dist/safe-edit.js +355 -78
  67. package/dist/schemas.js +16 -15
  68. package/dist/server.d.ts +11 -2
  69. package/dist/server.js +55 -5
  70. package/dist/shared.d.ts +12 -0
  71. package/dist/shared.js +13 -1
  72. package/dist/source-mutation-coordinator.d.ts +54 -0
  73. package/dist/source-mutation-coordinator.js +259 -0
  74. package/dist/status-events.d.ts +4 -1
  75. package/dist/status-events.js +41 -6
  76. package/dist/store/ace-packed-store.d.ts +28 -9
  77. package/dist/store/ace-packed-store.js +324 -57
  78. package/dist/store/bgzf.d.ts +4 -0
  79. package/dist/store/bgzf.js +94 -0
  80. package/dist/store/bootstrap-store.d.ts +4 -1
  81. package/dist/store/bootstrap-store.js +87 -9
  82. package/dist/store/cache-workspace.d.ts +8 -0
  83. package/dist/store/cache-workspace.js +67 -1
  84. package/dist/store/crc32.d.ts +2 -0
  85. package/dist/store/crc32.js +16 -0
  86. package/dist/store/fsck.d.ts +16 -0
  87. package/dist/store/fsck.js +240 -0
  88. package/dist/store/gate-contract-migration.d.ts +10 -0
  89. package/dist/store/gate-contract-migration.js +413 -0
  90. package/dist/store/materializers/context-snapshot-materializer.d.ts +1 -0
  91. package/dist/store/materializers/context-snapshot-materializer.js +4 -1
  92. package/dist/store/materializers/projection-manager.d.ts +7 -0
  93. package/dist/store/materializers/projection-manager.js +37 -0
  94. package/dist/store/materializers/scheduler-projection-materializer.js +10 -3
  95. package/dist/store/repositories/goal-repository.d.ts +112 -0
  96. package/dist/store/repositories/goal-repository.js +157 -0
  97. package/dist/store/repositories/playbook-repository.d.ts +107 -0
  98. package/dist/store/repositories/playbook-repository.js +314 -0
  99. package/dist/store/store-artifacts.js +44 -2
  100. package/dist/store/store-authority-audit.js +1 -0
  101. package/dist/store/store-cli.d.ts +30 -0
  102. package/dist/store/store-cli.js +242 -0
  103. package/dist/store/store-snapshot.d.ts +12 -0
  104. package/dist/store/store-snapshot.js +74 -11
  105. package/dist/store/types.d.ts +1 -0
  106. package/dist/store/write-coordinator.d.ts +7 -0
  107. package/dist/store/write-coordinator.js +11 -0
  108. package/dist/todo-state.d.ts +21 -0
  109. package/dist/todo-state.js +150 -53
  110. package/dist/tools-agent.js +86 -22
  111. package/dist/tools-discovery.js +5 -3
  112. package/dist/tools-files.js +107 -23
  113. package/dist/tools-framework.js +146 -40
  114. package/dist/tools-goal.d.ts +11 -0
  115. package/dist/tools-goal.js +108 -0
  116. package/dist/tools-handoff.js +7 -11
  117. package/dist/tools-lifecycle.js +2 -0
  118. package/dist/tools-scheduler.js +44 -1
  119. package/dist/tools-skills.js +3 -2
  120. package/dist/tools-todo.js +64 -3
  121. package/dist/tools.js +2 -0
  122. package/dist/tui/chat.js +4 -0
  123. package/dist/tui/index.js +4 -1
  124. package/dist/tui/ollama.d.ts +2 -0
  125. package/dist/tui/openai-compatible.d.ts +14 -0
  126. package/dist/tui/openai-compatible.js +15 -2
  127. package/dist/tui/provider-discovery.d.ts +1 -0
  128. package/dist/tui/provider-discovery.js +2 -2
  129. package/docs/cli-reference.md +125 -0
  130. package/docs/development.md +46 -0
  131. package/docs/evaluation-safety.md +27 -0
  132. package/docs/experience-playbook.md +35 -0
  133. package/docs/hermes.md +129 -0
  134. package/docs/library-api.md +31 -0
  135. package/docs/mcp-tool-reference.md +290 -0
  136. package/docs/provider-security.md +85 -0
  137. package/docs/state-model.md +113 -0
  138. package/docs/troubleshooting.md +95 -0
  139. package/docs/tui-operator-guide.md +75 -0
  140. package/package.json +5 -1
  141. package/scripts/dev/run_hermes_crispr_live.mjs +212 -0
  142. package/assets/agent-state/MODULES/gates/gate-correctness.json +0 -7
  143. package/assets/agent-state/MODULES/gates/gate-evaluation.json +0 -7
  144. package/assets/agent-state/MODULES/gates/gate-typescript-public-surface.json +0 -7
@@ -1,15 +1,24 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import { resolve } from "node:path";
2
3
  import { ROLE_ENUM } from "./shared.js";
3
4
  import { resolveWorkspaceRoot } from "./helpers.js";
4
5
  import { ALL_EXECUTION_ENGINES } from "./helpers/constants.js";
5
6
  import { executeAceInternalTool } from "./ace-internal-tools.js";
6
- import { ModelBridge } from "./model-bridge.js";
7
+ import { isMutationIntent, ModelBridge, verifyCompletionArtifacts } from "./model-bridge.js";
8
+ import { readBridgeTrace } from "./bridge-trace.js";
9
+ import { reflectDeterministic } from "./experience-reflector.js";
10
+ import { curate } from "./playbook-curator.js";
11
+ import { PlaybookRepository } from "./store/repositories/playbook-repository.js";
12
+ import { appendStatusEventSafe } from "./status-events.js";
7
13
  import { OllamaClient } from "./tui/ollama.js";
8
14
  import { OpenAICompatibleClient, diagnoseChatRuntimeConfig, } from "./tui/openai-compatible.js";
9
15
  import { discoverProviderContext } from "./tui/provider-discovery.js";
10
16
  import { defaultToolScopeForPolicy, defaultTurnsForPolicy, resolveLocalModelExecutionPolicy, } from "./local-model-policy.js";
11
17
  import { HermesSubprocessExecutor, } from "./hermes/session-manager.js";
12
18
  import { resolveHermesLaunchProfile, } from "./hermes/launch-profile.js";
19
+ import { buildFilteredToolCatalog } from "./runtime-tool-specs.js";
20
+ import { withLocalModelRuntimeRepository, } from "./store/repositories/local-model-runtime-repository.js";
21
+ import { storeExistsSync } from "./store/store-snapshot.js";
13
22
  export function parseExecutionEngine(input) {
14
23
  if (!input)
15
24
  return undefined;
@@ -151,8 +160,40 @@ export async function runLocalModelTask(options) {
151
160
  requested_model_class: options.modelClass,
152
161
  });
153
162
  const maxTurns = options.maxTurns ?? defaultTurnsForPolicy(role, policy);
154
- const toolScope = options.toolScope ?? defaultToolScopeForPolicy(role, policy);
163
+ const requestedToolScope = options.toolScope ?? defaultToolScopeForPolicy(role, policy);
164
+ const filteredToolCatalog = buildFilteredToolCatalog(policy.model_class, requestedToolScope, {
165
+ mutation_lane: policy.mutation_lane,
166
+ });
167
+ const filteredToolScope = filteredToolCatalog.entries.map((entry) => entry.name);
168
+ const toolScope = filteredToolScope.length > 0 ? filteredToolScope : requestedToolScope;
155
169
  if (runtime.executionEngine === "hermes_local") {
170
+ if (toolScope.length === 0) {
171
+ throw new Error("hermes_local requires a non-empty tool scope (reason_code=hermes_empty_tool_scope)");
172
+ }
173
+ const capabilitySnapshotId = randomUUID();
174
+ if (storeExistsSync(runtime.workspaceRoot)) {
175
+ await withLocalModelRuntimeRepository(runtime.workspaceRoot, async (repo) => {
176
+ await repo.saveCapabilitySnapshot({
177
+ snapshot_id: capabilitySnapshotId,
178
+ session_id: `hermes-local-${capabilitySnapshotId}`,
179
+ turn_number: 1,
180
+ captured_at: Date.now(),
181
+ model_class: policy.model_class,
182
+ allowed_tools: toolScope,
183
+ unavailable_tools: filteredToolCatalog.unavailable_tools,
184
+ tool_cost_class: filteredToolCatalog.tool_cost_class,
185
+ });
186
+ await repo.appendTransitionRecord({
187
+ session_id: `hermes-local-${capabilitySnapshotId}`,
188
+ subject_kind: "capability",
189
+ subject_id: `hermes_local/${capabilitySnapshotId}`,
190
+ from: "unscoped",
191
+ to: `model_class:${policy.model_class}`,
192
+ reason: "Capability snapshot captured for Hermes-local turn (surface_kind=hermes_local)",
193
+ evidence_refs: [capabilitySnapshotId],
194
+ });
195
+ });
196
+ }
156
197
  const launchProfile = options.hermesLaunchProfile ?? resolveHermesLaunchProfile({ workspaceRoot: runtime.workspaceRoot });
157
198
  const hermesExecutor = options.hermesExecutor ??
158
199
  new HermesSubprocessExecutor({
@@ -165,6 +206,24 @@ export async function runLocalModelTask(options) {
165
206
  policy,
166
207
  toolScope,
167
208
  maxTurns,
209
+ timeoutMs: options.timeoutMs,
210
+ });
211
+ hermes.result = await verifyCompletionArtifacts(hermes.result, {
212
+ task: options.task,
213
+ role,
214
+ workspace: runtime.workspaceRoot,
215
+ touchedPaths: hermes.metadata.touched_paths ?? [],
216
+ expectedArtifacts: options.expectedArtifacts,
217
+ requiredEvidenceRefs: options.requiredEvidenceRefs,
218
+ forceCompletionVerification: (role === "coders" || role === "builder") &&
219
+ isMutationIntent(options.task),
220
+ });
221
+ await reflectRunIntoPlaybook({
222
+ workspaceRoot: runtime.workspaceRoot,
223
+ result: hermes.result,
224
+ role,
225
+ model: runtime.model,
226
+ modelClass: policy.model_class,
168
227
  });
169
228
  return {
170
229
  runtime,
@@ -183,8 +242,16 @@ export async function runLocalModelTask(options) {
183
242
  maxTurns,
184
243
  provider: runtime.provider,
185
244
  model: runtime.model,
245
+ modelClass: policy.model_class,
186
246
  toolScope,
187
247
  });
248
+ await reflectRunIntoPlaybook({
249
+ workspaceRoot: runtime.workspaceRoot,
250
+ result,
251
+ role,
252
+ model: runtime.model,
253
+ modelClass: policy.model_class,
254
+ });
188
255
  return {
189
256
  runtime,
190
257
  role,
@@ -193,5 +260,66 @@ export async function runLocalModelTask(options) {
193
260
  result,
194
261
  };
195
262
  }
263
+ async function reflectRunIntoPlaybook(input) {
264
+ if (process.env.ACE_PLAYBOOK_REFLECT === "0" || process.env.ACE_PLAYBOOK_REFLECT === "false") {
265
+ return;
266
+ }
267
+ try {
268
+ const trace = readBridgeTrace(input.workspaceRoot, input.result.bridge_id);
269
+ const repo = new PlaybookRepository(input.workspaceRoot);
270
+ const ops = reflectDeterministic({
271
+ trace,
272
+ result: input.result,
273
+ role: input.role,
274
+ model: input.model,
275
+ modelClass: input.modelClass,
276
+ });
277
+ if (ops.length > 0) {
278
+ await curate(repo, ops);
279
+ }
280
+ const attributionOps = buildImplicitAttributionOps(repo, input.result);
281
+ if (attributionOps.length > 0) {
282
+ await curate(repo, attributionOps);
283
+ }
284
+ }
285
+ catch (error) {
286
+ await appendStatusEventSafe({
287
+ source_module: "capability-build",
288
+ event_type: "PLAYBOOK_REFLECT_FAILED",
289
+ status: "fail",
290
+ summary: `Playbook reflection failed: ${error instanceof Error ? error.message : String(error)}`,
291
+ payload: {
292
+ bridge_id: input.result.bridge_id,
293
+ role: input.role,
294
+ model_class: input.modelClass,
295
+ },
296
+ }).catch(() => undefined);
297
+ }
298
+ }
299
+ function buildImplicitAttributionOps(repo, result) {
300
+ const injected = result.injected_bullets ?? [];
301
+ if (injected.length === 0)
302
+ return [];
303
+ if ((result.used_bullets ?? []).length > 0) {
304
+ const injectedSet = new Set(injected);
305
+ return [...new Set(result.used_bullets ?? [])]
306
+ .filter((bullet_id) => injectedSet.has(bullet_id))
307
+ .map((bullet_id) => ({ type: "TAG", bullet_id, tag: "helpful" }));
308
+ }
309
+ const completedWithEvidence = result.status === "completed" &&
310
+ ((result.evidence_refs ?? []).length > 0 || (result.tool_calls ?? []).some((tool) => tool.ok));
311
+ if (completedWithEvidence) {
312
+ return injected.map((bullet_id) => ({ type: "TAG", bullet_id, tag: "helpful" }));
313
+ }
314
+ if (result.status === "blocked" && result.reason_code) {
315
+ return injected.flatMap((bullet_id) => {
316
+ const bullet = repo.getBullet(bullet_id);
317
+ return bullet?.scope.reason_code === result.reason_code
318
+ ? [{ type: "TAG", bullet_id, tag: "harmful" }]
319
+ : [];
320
+ });
321
+ }
322
+ return [];
323
+ }
196
324
  export { resolveLocalModelExecutionPolicy, resolveLocalModelClass } from "./local-model-policy.js";
197
325
  //# sourceMappingURL=local-model-runtime.js.map
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Measured model classing (E10 / D8).
3
+ *
4
+ * Name-regex classing misfires (gpt-oss→frontier, qwen2.5-coder→small). The
5
+ * question ACE actually cares about is "can this model hold the envelope
6
+ * contract?" — which is directly measurable with a one-shot canary probe at
7
+ * `ace connect`/`doctor`: ask for a fixed envelope and score compliance. This
8
+ * module compiles a {json_compliance, measured_ctx, param_count_b} measurement
9
+ * into a class, persists it per (provider, model), and lets the policy consult
10
+ * measurements first and the regex only as a fallback.
11
+ *
12
+ * The probe's model call is injected, so scoring/classing/persistence are all
13
+ * unit-testable without a live model.
14
+ */
15
+ import type { RuntimeModelClass } from "./runtime-profile.js";
16
+ /** Fixed canary: the smallest possible well-formed envelope request. */
17
+ export declare const CANARY_PROMPT: string;
18
+ export interface CapabilityMeasurement {
19
+ provider: string;
20
+ model: string;
21
+ /** 0..1 — how well the model emitted the requested envelope. */
22
+ json_compliance: number;
23
+ /** Measured/declared context window in tokens, if probed. */
24
+ measured_ctx?: number;
25
+ /** Parameter count in billions, if probed from provider metadata. */
26
+ param_count_b?: number;
27
+ measured_at: string;
28
+ }
29
+ /**
30
+ * Score a raw model output against the canary contract. Full credit for a
31
+ * clean parse of the exact envelope; partial credit for a JSON object with the
32
+ * right status field embedded in prose (the parser tolerates leading text);
33
+ * zero for no recoverable JSON.
34
+ */
35
+ export declare function scoreCanaryCompliance(raw: string): number;
36
+ /**
37
+ * Compile a measurement into a class. Compliance dominates: a model that cannot
38
+ * hold the envelope is `small_local` regardless of its parameter count (that is
39
+ * the whole point — measured behavior over metadata). Otherwise parameter count
40
+ * decides, with the context window as a tie-break toward `mid`.
41
+ */
42
+ export declare function classFromMeasurement(m: Pick<CapabilityMeasurement, "json_compliance" | "measured_ctx" | "param_count_b">): RuntimeModelClass | undefined;
43
+ export interface ProbeCanaryInput {
44
+ provider: string;
45
+ model: string;
46
+ /** Injected model call: returns the raw text the model produced for a prompt. */
47
+ call: (prompt: string) => Promise<string>;
48
+ measured_ctx?: number;
49
+ param_count_b?: number;
50
+ }
51
+ /** Run the canary probe and produce a measurement (does not persist). */
52
+ export declare function probeModelCanary(input: ProbeCanaryInput): Promise<CapabilityMeasurement>;
53
+ export declare function saveMeasurement(workspaceRoot: string, measurement: CapabilityMeasurement): void;
54
+ export declare function getMeasurement(workspaceRoot: string, provider: string, model: string): CapabilityMeasurement | undefined;
55
+ /**
56
+ * Class for a (provider, model), consulting a persisted measurement first and
57
+ * the name regex only as a fallback. This is the E10 entry point callers use
58
+ * instead of `resolveLocalModelClass` directly when a workspace is available.
59
+ */
60
+ export declare function resolveClassMeasuredFirst(input: {
61
+ provider: string;
62
+ model: string;
63
+ workspaceRoot: string;
64
+ requested_model_class?: RuntimeModelClass;
65
+ }): Promise<RuntimeModelClass>;
66
+ //# sourceMappingURL=measured-classing.d.ts.map
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Measured model classing (E10 / D8).
3
+ *
4
+ * Name-regex classing misfires (gpt-oss→frontier, qwen2.5-coder→small). The
5
+ * question ACE actually cares about is "can this model hold the envelope
6
+ * contract?" — which is directly measurable with a one-shot canary probe at
7
+ * `ace connect`/`doctor`: ask for a fixed envelope and score compliance. This
8
+ * module compiles a {json_compliance, measured_ctx, param_count_b} measurement
9
+ * into a class, persists it per (provider, model), and lets the policy consult
10
+ * measurements first and the regex only as a fallback.
11
+ *
12
+ * The probe's model call is injected, so scoring/classing/persistence are all
13
+ * unit-testable without a live model.
14
+ */
15
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
16
+ import { join } from "node:path";
17
+ import { randomUUID } from "node:crypto";
18
+ /** Fixed canary: the smallest possible well-formed envelope request. */
19
+ export const CANARY_PROMPT = 'Respond with exactly one JSON object and nothing else: ' +
20
+ '{"status":"message","thinking":"ok","message":"canary"}';
21
+ /**
22
+ * Score a raw model output against the canary contract. Full credit for a
23
+ * clean parse of the exact envelope; partial credit for a JSON object with the
24
+ * right status field embedded in prose (the parser tolerates leading text);
25
+ * zero for no recoverable JSON.
26
+ */
27
+ export function scoreCanaryCompliance(raw) {
28
+ const text = (raw ?? "").trim();
29
+ if (!text)
30
+ return 0;
31
+ // Exact-ish: the whole output is one JSON object with status:"message".
32
+ try {
33
+ const parsed = JSON.parse(text);
34
+ if (parsed && typeof parsed === "object" && parsed.status === "message") {
35
+ return 1;
36
+ }
37
+ if (parsed && typeof parsed === "object" && typeof parsed.status === "string") {
38
+ return 0.75;
39
+ }
40
+ }
41
+ catch {
42
+ // fall through to brace-scan
43
+ }
44
+ // Tolerant: a JSON object is embedded in surrounding prose.
45
+ const start = text.indexOf("{");
46
+ const end = text.lastIndexOf("}");
47
+ if (start >= 0 && end > start) {
48
+ try {
49
+ const parsed = JSON.parse(text.slice(start, end + 1));
50
+ if (parsed && typeof parsed === "object" && typeof parsed.status === "string") {
51
+ return 0.5;
52
+ }
53
+ return 0.3;
54
+ }
55
+ catch {
56
+ return 0.1;
57
+ }
58
+ }
59
+ return 0;
60
+ }
61
+ /**
62
+ * Compile a measurement into a class. Compliance dominates: a model that cannot
63
+ * hold the envelope is `small_local` regardless of its parameter count (that is
64
+ * the whole point — measured behavior over metadata). Otherwise parameter count
65
+ * decides, with the context window as a tie-break toward `mid`.
66
+ */
67
+ export function classFromMeasurement(m) {
68
+ const compliance = clamp01(m.json_compliance);
69
+ // Below half-compliance the model cannot be trusted with the tool envelope.
70
+ if (compliance < 0.5)
71
+ return "small_local";
72
+ const params = m.param_count_b;
73
+ if (typeof params === "number" && Number.isFinite(params) && params > 0) {
74
+ if (params >= 30 && compliance >= 0.8)
75
+ return "frontier";
76
+ if (params >= 13)
77
+ return "frontier";
78
+ if (params >= 7)
79
+ return "mid";
80
+ return "small_local";
81
+ }
82
+ // No parameter metadata: fall back on compliance + context.
83
+ if (compliance >= 0.9 && (m.measured_ctx ?? 0) >= 32_768)
84
+ return "frontier";
85
+ if (compliance >= 0.75)
86
+ return "mid";
87
+ return "small_local";
88
+ }
89
+ /** Run the canary probe and produce a measurement (does not persist). */
90
+ export async function probeModelCanary(input) {
91
+ let raw = "";
92
+ try {
93
+ raw = await input.call(CANARY_PROMPT);
94
+ }
95
+ catch {
96
+ raw = "";
97
+ }
98
+ return {
99
+ provider: input.provider,
100
+ model: input.model,
101
+ json_compliance: scoreCanaryCompliance(raw),
102
+ measured_ctx: input.measured_ctx,
103
+ param_count_b: input.param_count_b,
104
+ measured_at: new Date().toISOString(),
105
+ };
106
+ }
107
+ function measurementPath(workspaceRoot) {
108
+ return join(workspaceRoot, "agent-state", "model-measurements.json");
109
+ }
110
+ function measurementKey(provider, model) {
111
+ return `${provider.trim().toLowerCase()}::${model.trim().toLowerCase()}`;
112
+ }
113
+ function readMeasurementFile(workspaceRoot) {
114
+ const path = measurementPath(workspaceRoot);
115
+ if (!existsSync(path))
116
+ return { version: 1, updated_at: new Date().toISOString(), measurements: [] };
117
+ try {
118
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
119
+ if (parsed.version === 1 && Array.isArray(parsed.measurements))
120
+ return parsed;
121
+ }
122
+ catch {
123
+ // fall through to default
124
+ }
125
+ return { version: 1, updated_at: new Date().toISOString(), measurements: [] };
126
+ }
127
+ export function saveMeasurement(workspaceRoot, measurement) {
128
+ const dir = join(workspaceRoot, "agent-state");
129
+ if (!existsSync(dir))
130
+ mkdirSync(dir, { recursive: true });
131
+ const file = readMeasurementFile(workspaceRoot);
132
+ const key = measurementKey(measurement.provider, measurement.model);
133
+ const next = file.measurements.filter((m) => measurementKey(m.provider, m.model) !== key);
134
+ next.push(measurement);
135
+ const path = measurementPath(workspaceRoot);
136
+ const tmp = `${path}.${process.pid}.${randomUUID().slice(0, 8)}.tmp`;
137
+ writeFileSync(tmp, JSON.stringify({ version: 1, updated_at: new Date().toISOString(), measurements: next }, null, 2));
138
+ renameSync(tmp, path);
139
+ }
140
+ export function getMeasurement(workspaceRoot, provider, model) {
141
+ const key = measurementKey(provider, model);
142
+ return readMeasurementFile(workspaceRoot).measurements.find((m) => measurementKey(m.provider, m.model) === key);
143
+ }
144
+ /**
145
+ * Class for a (provider, model), consulting a persisted measurement first and
146
+ * the name regex only as a fallback. This is the E10 entry point callers use
147
+ * instead of `resolveLocalModelClass` directly when a workspace is available.
148
+ */
149
+ export async function resolveClassMeasuredFirst(input) {
150
+ const { resolveLocalModelClass } = await import("./local-model-policy.js");
151
+ const measurement = getMeasurement(input.workspaceRoot, input.provider, input.model);
152
+ const measured = measurement ? classFromMeasurement(measurement) : undefined;
153
+ return resolveLocalModelClass({
154
+ provider: input.provider,
155
+ model: input.model,
156
+ requested_model_class: input.requested_model_class,
157
+ measured_model_class: measured,
158
+ });
159
+ }
160
+ function clamp01(n) {
161
+ if (!Number.isFinite(n))
162
+ return 0;
163
+ return Math.max(0, Math.min(1, n));
164
+ }
165
+ //# sourceMappingURL=measured-classing.js.map
@@ -10,6 +10,12 @@ export interface BridgeToolResult {
10
10
  ok: boolean;
11
11
  summary: string;
12
12
  isError: boolean;
13
+ /** Set when a tool fails for a classified reason (e.g. "tool_timeout"). */
14
+ reason_code?: string;
15
+ }
16
+ export interface BridgeUsageTotals {
17
+ prompt_tokens: number;
18
+ completion_tokens: number;
13
19
  }
14
20
  export interface BridgeProgressEvent {
15
21
  kind: "model_chunk" | "tool_start" | "tool_finish" | "process_post" | "output" | "thinking";
@@ -26,11 +32,16 @@ export interface BridgeResult {
26
32
  child_results: BridgeResult[];
27
33
  evidence_refs?: string[];
28
34
  reason_code?: string;
35
+ usage_totals?: BridgeUsageTotals;
36
+ injected_bullets?: string[];
37
+ used_bullets?: string[];
29
38
  }
30
39
  export interface ModelBridgeClients {
31
40
  ollama: Pick<OllamaClient, "chat" | "abort">;
32
41
  openai: Pick<OpenAICompatibleClient, "chat" | "abort">;
33
42
  }
43
+ /** Keep completion-evidence policy identical across direct and Hermes execution. */
44
+ export declare function isMutationIntent(task: string): boolean;
34
45
  export interface ModelBridgeRunOptions {
35
46
  task: string;
36
47
  role: string;
@@ -40,11 +51,13 @@ export interface ModelBridgeRunOptions {
40
51
  maxTurns: number;
41
52
  provider: string;
42
53
  model: string;
54
+ modelClass?: string;
43
55
  toolScope?: string[];
44
56
  expectedArtifacts?: {
45
57
  path: string;
46
58
  required?: boolean;
47
59
  }[];
60
+ requiredEvidenceRefs?: string[];
48
61
  parentBridge?: string;
49
62
  onToolCall?: (tool: string, args: Record<string, unknown>) => void;
50
63
  onToolResult?: (tool: string, result: BridgeToolResult) => void;
@@ -52,6 +65,38 @@ export interface ModelBridgeRunOptions {
52
65
  onThinking?: (text: string) => void;
53
66
  onProgress?: (event: BridgeProgressEvent) => void;
54
67
  }
68
+ /**
69
+ * JSON Schema for the ACE response envelope. Passed as `format` (Ollama ≥ 0.5) or
70
+ * `response_format.json_schema` (OpenAI-compatible) to enable schema-constrained
71
+ * generation. Falls back to plain json-mode if the provider rejects it with a 400.
72
+ */
73
+ export declare const ACE_ENVELOPE_JSON_SCHEMA: Record<string, unknown>;
74
+ export declare function verifyCompletionArtifacts(result: BridgeResult, context: {
75
+ task: string;
76
+ role: string;
77
+ workspace: string;
78
+ touchedPaths: string[];
79
+ expectedArtifacts?: {
80
+ path: string;
81
+ required?: boolean;
82
+ }[];
83
+ requiredEvidenceRefs?: string[];
84
+ forceCompletionVerification?: boolean;
85
+ }): Promise<BridgeResult>;
86
+ /**
87
+ * Per-tool-call deadline (E12). A hung validation/test subprocess must never
88
+ * wedge a turn indefinitely — and under Promise.all one stuck call also holds
89
+ * every parallel sibling's feedback hostage. On timeout we resolve a classified
90
+ * `tool_timeout` failure that then feeds the WS-C doom-loop brake.
91
+ */
92
+ export declare function executeToolWithDeadline(tool: string, args: Record<string, unknown>, sessionId: string, opts: {
93
+ workspace_path: string;
94
+ }, deadlineMs: number, exec?: (tool: string, args: Record<string, unknown>, sessionId: string, opts: {
95
+ workspace_path: string;
96
+ }) => Promise<unknown>): Promise<{
97
+ rawToolResult: unknown;
98
+ timedOut: boolean;
99
+ }>;
55
100
  export declare class ModelBridge {
56
101
  private clients;
57
102
  private activeProviderClient;