create-quorum-router 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,7 +17,7 @@ deno task intake
17
17
  deno task supabase:status
18
18
  ```
19
19
 
20
- Current package version: `create-quorum-router@0.1.8`. Releases are published
20
+ Current package version: `create-quorum-router@0.1.10`. Releases are published
21
21
  from an immutable Git tag through GitHub Actions OIDC Trusted Publishing.
22
22
 
23
23
  ## What the generated project supports
@@ -27,7 +27,9 @@ API.
27
27
 
28
28
  `deno task calibration:demo` runs the bundled calibration-by-task API against
29
29
  deterministic local observations. The report is advisory-only and is not
30
- connected to routing weights, provider eligibility, or execution.
30
+ connected to routing weights, provider eligibility, or execution. The command
31
+ does not call provider APIs; on a new Deno installation, its first run resolves
32
+ the pinned Zod dependency before execution.
31
33
 
32
34
  `deno task intake` is the first real setup command. It detects local provider
33
35
  wrappers, checks OAuth/session status, runs safe list-only model inventory where
@@ -54,9 +56,15 @@ RUN_EXTERNAL_MODEL_DOGFOOD=1 deno task route:once --prompt "https://github.com/s
54
56
  # GitHub URL prompts fetch bounded repository context before invoking the selected provider.
55
57
  # Only use this with repositories you are allowed to send to that provider.
56
58
  RUN_EXTERNAL_MODEL_DOGFOOD=1 deno task best-route --prompt "Choose the safest launch copy."
57
- RUN_EXTERNAL_MODEL_DOGFOOD=1 RUN_EXPERIMENTAL_AGENT_CHAT=1 deno task agent-chat --prompt "Review this launch plan."
59
+ RUN_EXTERNAL_MODEL_DOGFOOD=1 RUN_AGENT_CHAT=1 deno task agent-chat --prompt "Review this launch plan."
58
60
  ```
59
61
 
62
+ `route:once` and `best-route` also accept
63
+ `--calibration-evidence ./calibration-evidence.json`. The generated CLI
64
+ caller-attested outcomes before invoking providers and stores the advisory
65
+ metrics with hashed task/source identifiers in the route trace without changing
66
+ routing authority.
67
+
60
68
  `auth:login` does not ask for API keys as the primary path. If OAuth/browser
61
69
  login is not wired in the generated scaffold, it fails closed and tells the user
62
70
  to use an installed provider CLI login, then rerun `deno task intake`.
@@ -3,7 +3,7 @@ const fs = require("fs");
3
3
  const path = require("path");
4
4
  const { spawnSync } = require("child_process");
5
5
 
6
- const VERSION = "0.1.8";
6
+ const VERSION = "0.1.10";
7
7
  const SUPPORTED_TEMPLATES = new Set(["basic"]);
8
8
 
9
9
  function usage() {
@@ -150,7 +150,7 @@ function main() {
150
150
  console.log("Then verify with: deno --version");
151
151
  }
152
152
  console.log(
153
- " # Optional one-shot real provider dogfood after intake reports a usable OAuth/session/wrapper provider:",
153
+ " # Optional one-shot real provider dogfood after live probe verifies authentication:",
154
154
  );
155
155
  console.log(
156
156
  ' RUN_EXTERNAL_MODEL_DOGFOOD=1 deno task route:once --prompt "Review this README for risky claims."',
@@ -163,7 +163,7 @@ function main() {
163
163
  " # Optional conversation-only Agent Chat stays explicit opt-in:",
164
164
  );
165
165
  console.log(
166
- ' RUN_EXTERNAL_MODEL_DOGFOOD=1 RUN_EXPERIMENTAL_AGENT_CHAT=1 deno task agent-chat --prompt "Review this launch plan."',
166
+ ' RUN_EXTERNAL_MODEL_DOGFOOD=1 RUN_AGENT_CHAT=1 deno task agent-chat --prompt "Review this launch plan."',
167
167
  );
168
168
  console.log("");
169
169
  console.log(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-quorum-router",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Create a local QuorumRouter project.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  # QuorumRouter generated workspace
2
2
 
3
3
  This generated workspace contains the MIT-licensed QuorumRouter current release.
4
- npm latest targets v0.1.8.
4
+ npm latest targets v0.1.10.
5
5
 
6
6
  QuorumRouter is **MIT**. It is **open source**. Commercial and production use
7
7
  are permitted under the MIT License.
@@ -51,7 +51,8 @@ deno task supabase:status
51
51
  `calibration:demo` exercises the bundled calibration-by-task API with local
52
52
  fixture observations. Calibration reports are advisory-only: the scaffold does
53
53
  not use them to change routing weights, ranks, provider eligibility, quorum, or
54
- execution.
54
+ execution. The command does not call provider APIs; on a new Deno installation,
55
+ its first run resolves the pinned Zod dependency before execution.
55
56
 
56
57
  `intake` detects local provider wrappers, checks OAuth/session status, runs safe
57
58
  model inventory/list-only probes where possible, writes local health traces
@@ -93,20 +94,24 @@ enabled by this audit integration.
93
94
 
94
95
  ## Real provider dogfood commands
95
96
 
96
- Run only after `intake` reports a usable OAuth/session/wrapper provider:
97
+ Run only after a live probe verifies provider authentication:
97
98
 
98
99
  ```bash
99
100
  RUN_EXTERNAL_MODEL_DOGFOOD=1 deno task route:once --prompt "Review this README for risky claims."
100
101
  RUN_EXTERNAL_MODEL_DOGFOOD=1 deno task best-route --prompt "Choose the safest launch copy."
101
- RUN_EXTERNAL_MODEL_DOGFOOD=1 RUN_EXPERIMENTAL_AGENT_CHAT=1 deno task agent-chat --prompt "Review this launch plan."
102
+ RUN_EXTERNAL_MODEL_DOGFOOD=1 RUN_AGENT_CHAT=1 deno task agent-chat --prompt "Review this launch plan."
102
103
  ```
103
104
 
104
105
  Behavior:
105
106
 
106
107
  - `route:once` requires `RUN_EXTERNAL_MODEL_DOGFOOD=1`.
107
108
  - `best-route` requires `RUN_EXTERNAL_MODEL_DOGFOOD=1`.
109
+ - `route:once` and `best-route` accept
110
+ `--calibration-evidence ./calibration-evidence.json`; evidence is validated
111
+ before provider invocation. The trace stores advisory metrics with hashed task
112
+ and source identifiers, not raw labels.
108
113
  - `agent-chat` requires both `RUN_EXTERNAL_MODEL_DOGFOOD=1` and
109
- `RUN_EXPERIMENTAL_AGENT_CHAT=1`.
114
+ `RUN_AGENT_CHAT=1`.
110
115
  - Live Agent Chat requires at least two distinct working provider/model
111
116
  identities. It passes the bounded transcript to alternating models, prints
112
117
  each response and `replying to` lineage as it arrives, and stores turns in
@@ -168,10 +173,9 @@ RUN_EXTERNAL_MODEL_DOGFOOD=1 \
168
173
  ```
169
174
 
170
175
  Supported provider aliases include `grok-cli`, `grok`, `xai`, `xAI`, `OpenAI`,
171
- `codex-cli`, `claude-code`, `gemini-cli`, `devin-cli`, and `qwen-cli`. Wrapper
172
- invocations use argv arrays, closed stdin, timeout guards, and sanitized
173
- stdout/stderr; CLI banners or auth/runtime errors are not accepted as valid
174
- model answers.
176
+ `codex-cli`, `claude-code`, `gemini-cli`, and `devin-cli`. Wrapper invocations
177
+ use argv arrays, closed stdin, timeout guards, and sanitized stdout/stderr; CLI
178
+ banners or auth/runtime errors are not accepted as valid model answers.
175
179
 
176
180
  ## Auth and inventory
177
181
 
@@ -18,8 +18,8 @@
18
18
  },
19
19
  "runtime_boundaries": {
20
20
  "best_route_direct": "production-ready best-answer routing",
21
- "agent_chat": "experimental explicit opt-in only",
22
- "production_autonomous_runtime": false,
21
+ "agent_chat": "explicit opt-in conversation mode; no mutation authority",
22
+ "safeloop_execution": "production-capable only with explicit execution authority and approval",
23
23
  "live_supabase_agent_bus_writes": false,
24
24
  "service_role_runtime": false
25
25
  }
@@ -18,9 +18,11 @@ export async function runAuthStatus(): Promise<void> {
18
18
  console.log("Credential values printed: false");
19
19
  console.log(`Env fallback configured: ${envFallbackConfigured()}`);
20
20
  printInventoryTable(inventory);
21
- const usableEntries = invokableEntries(inventory);
22
- if (usableEntries.length === 0) {
23
- console.log("Status: missing usable OAuth/session/wrapper provider");
21
+ const invokable = invokableEntries(inventory);
22
+ if (invokable.length === 0) {
23
+ console.log(
24
+ "Status: no invokable OAuth/session/wrapper provider discovered",
25
+ );
24
26
  console.log("Next action: deno task auth:login");
25
27
  } else if (authMode === "env") {
26
28
  console.log("Status: explicit private env fallback is configured");
@@ -29,7 +31,7 @@ export async function runAuthStatus(): Promise<void> {
29
31
  );
30
32
  } else {
31
33
  console.log(
32
- "Status: at least one OAuth/session/wrapper provider appears usable",
34
+ "Status: at least one provider command is invokable; live authentication is not verified by auth:status",
33
35
  );
34
36
  console.log(
35
37
  'Next action: RUN_EXTERNAL_MODEL_DOGFOOD=1 deno task route:once --prompt "Review this README for risky claims."',
@@ -22,6 +22,7 @@ import { buildTrace, score, writeTrace } from "./trace.ts";
22
22
  import { callWrapper } from "./wrapper_client.ts";
23
23
  import { preparePromptWithContext } from "./context.ts";
24
24
  import { selectCandidatesWithinBudget } from "./cost_aware.ts";
25
+ import type { TaskCalibrationReport } from "./calibration.ts";
25
26
 
26
27
  function hasExplicitSelection(request: ProviderSelectionRequest): boolean {
27
28
  return Boolean(
@@ -180,6 +181,7 @@ async function discoverCandidates(authMode: ReturnType<typeof parseAuthMode>) {
180
181
 
181
182
  export async function invokeSelected(
182
183
  prompt: string,
184
+ calibration?: TaskCalibrationReport,
183
185
  ): Promise<
184
186
  { results: ProviderResult[]; tracePath: string; trace: DogfoodTrace }
185
187
  > {
@@ -188,7 +190,7 @@ export async function invokeSelected(
188
190
  const { request, candidates } = await discoverCandidates(authMode);
189
191
  if (candidates.length === 0) {
190
192
  throw new Error(
191
- "OAuth/session-first provider unavailable. No usable OAuth/session/wrapper provider is available yet. Next: deno task auth:login",
193
+ "OAuth/session-first provider unavailable. No discovered provider has passed live authentication yet. Next: deno task auth:login",
192
194
  );
193
195
  }
194
196
  const prepared = await preparePromptWithContext(prompt);
@@ -250,6 +252,7 @@ export async function invokeSelected(
250
252
  requestedModel: request.model,
251
253
  providerSelectionHonored: selectionHonored(request, result),
252
254
  fallbackUsed: selectedIndex > 0 || selected.source === "env_fallback",
255
+ calibration,
253
256
  });
254
257
  const tracePath = await writeTrace("route-once-trace", trace);
255
258
  return { results: [result], tracePath, trace };
@@ -257,6 +260,7 @@ export async function invokeSelected(
257
260
 
258
261
  export async function runBestRoute(
259
262
  prompt: string,
263
+ calibration?: TaskCalibrationReport,
260
264
  ): Promise<
261
265
  { results: ProviderResult[]; tracePath: string; trace: DogfoodTrace }
262
266
  > {
@@ -270,7 +274,7 @@ export async function runBestRoute(
270
274
  const candidates = costAwareDecision.candidates;
271
275
  if (candidates.length === 0) {
272
276
  throw new Error(
273
- "OAuth/session-first provider unavailable. best-route has no usable OAuth/session/wrapper provider yet. Next: deno task auth:login",
277
+ "OAuth/session-first provider unavailable. best-route has no provider with verified live authentication yet. Next: deno task auth:login",
274
278
  );
275
279
  }
276
280
  const prepared = await preparePromptWithContext(prompt);
@@ -325,6 +329,7 @@ export async function runBestRoute(
325
329
  costAware: costAwareDecision.cost.enabled
326
330
  ? costAwareDecision.cost
327
331
  : undefined,
332
+ calibration,
328
333
  });
329
334
  const tracePath = await writeTrace("best-route-trace", trace);
330
335
  return { results, tracePath, trace };
@@ -0,0 +1,31 @@
1
+ import {
2
+ aggregateTaskCalibration,
3
+ type TaskCalibrationOptions,
4
+ type TaskCalibrationReport,
5
+ } from "./calibration.ts";
6
+
7
+ export async function loadCalibrationEvidence(
8
+ path: string | undefined,
9
+ ): Promise<TaskCalibrationReport | undefined> {
10
+ if (path === undefined) return undefined;
11
+ const normalizedPath = path.trim();
12
+ if (!normalizedPath) {
13
+ throw new Error("--calibration-evidence requires a local JSON file path");
14
+ }
15
+ const parsed: unknown = JSON.parse(
16
+ await Deno.readTextFile(normalizedPath),
17
+ );
18
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
19
+ throw new Error(
20
+ "calibration evidence must be an object with observations and optional options",
21
+ );
22
+ }
23
+ const record = parsed as Record<string, unknown>;
24
+ if (!Array.isArray(record.observations)) {
25
+ throw new Error("calibration evidence observations must be an array");
26
+ }
27
+ return aggregateTaskCalibration(
28
+ record.observations,
29
+ record.options as TaskCalibrationOptions | undefined,
30
+ );
31
+ }
@@ -17,6 +17,7 @@ import {
17
17
  runSupabaseStatus,
18
18
  } from "./supabase.ts";
19
19
  import { buildTrace, writeTrace } from "./trace.ts";
20
+ import { loadCalibrationEvidence } from "./calibration_route.ts";
20
21
 
21
22
  const DEFAULT_PROMPT = "Review this README for risky claims.";
22
23
 
@@ -61,6 +62,21 @@ function promptFromArgs(): string {
61
62
  return DEFAULT_PROMPT;
62
63
  }
63
64
 
65
+ async function calibrationFromArgs() {
66
+ const indices = Deno.args.flatMap((arg, index) =>
67
+ arg === "--calibration-evidence" ? [index] : []
68
+ );
69
+ if (indices.length === 0) return await loadCalibrationEvidence(undefined);
70
+ if (indices.length !== 1) {
71
+ throw new Error("--calibration-evidence may be specified only once");
72
+ }
73
+ const path = Deno.args[indices[0] + 1];
74
+ if (path === undefined || path.startsWith("--")) {
75
+ throw new Error("--calibration-evidence requires a local JSON file path");
76
+ }
77
+ return await loadCalibrationEvidence(path);
78
+ }
79
+
64
80
  async function runModelsList(): Promise<void> {
65
81
  const inventory = await inventoryCommand();
66
82
  console.log("QuorumRouter model inventory");
@@ -92,7 +108,8 @@ async function runHealth(): Promise<void> {
92
108
  )}`,
93
109
  );
94
110
  console.log(`Model inventory entries: ${inventory.entries.length}`);
95
- console.log(`Usable providers: ${invokable.length}`);
111
+ console.log(`Discovered invokable providers: ${invokable.length}`);
112
+ console.log("Live provider authentication verified: false");
96
113
  console.log("Provider request sent: false");
97
114
  console.log(`Trace: ${tracePath}`);
98
115
  console.log(
@@ -110,9 +127,11 @@ try {
110
127
  else if (command === "health") await runHealth();
111
128
  else if (command === "supabase:status") await runSupabaseStatus();
112
129
  else if (command === "route:once") {
130
+ const calibration = await calibrationFromArgs();
113
131
  await preflightRequiredSupabaseAudit();
114
132
  const { results, tracePath, trace } = await invokeSelected(
115
133
  promptFromArgs(),
134
+ calibration,
116
135
  );
117
136
  await auditRouteOutcome(trace);
118
137
  console.log("QuorumRouter route:once");
@@ -126,8 +145,12 @@ try {
126
145
  console.log(`final: ${summarize(results[0].response_summary, 500)}`);
127
146
  console.log(`trace: ${tracePath}`);
128
147
  } else if (command === "best-route") {
148
+ const calibration = await calibrationFromArgs();
129
149
  await preflightRequiredSupabaseAudit();
130
- const { results, tracePath, trace } = await runBestRoute(promptFromArgs());
150
+ const { results, tracePath, trace } = await runBestRoute(
151
+ promptFromArgs(),
152
+ calibration,
153
+ );
131
154
  await auditRouteOutcome(trace);
132
155
  console.log("QuorumRouter best-route");
133
156
  console.log(`models_called: ${results.length}`);
@@ -8,7 +8,7 @@ import { buildTrace, writeTrace } from "./trace.ts";
8
8
  export async function runIntake(): Promise<void> {
9
9
  const inventory = await discoverInventoryWithModelListing();
10
10
  await writeInventory(inventory);
11
- const usable = invokableEntries(inventory);
11
+ const invokable = invokableEntries(inventory);
12
12
  console.log("QuorumRouter intake");
13
13
  console.log("");
14
14
  console.log("Step 1 — Detect local providers");
@@ -16,7 +16,10 @@ export async function runIntake(): Promise<void> {
16
16
  printInventoryTable(inventory);
17
17
  console.log("");
18
18
  console.log("Step 2 — OAuth/session status");
19
- console.log(`Usable OAuth/session/wrapper providers: ${usable.length}`);
19
+ console.log(
20
+ `Discovered invokable OAuth/session/wrapper providers: ${invokable.length}`,
21
+ );
22
+ console.log("Live provider authentication verified: false");
20
23
  console.log("Provider request sent: false");
21
24
  console.log("Credential values printed: false");
22
25
  console.log("");
@@ -35,8 +38,8 @@ export async function runIntake(): Promise<void> {
35
38
  command: "intake-health",
36
39
  mode: "health",
37
40
  authMode: inventory.auth_mode,
38
- errors: usable.length === 0
39
- ? ["no usable OAuth/session/wrapper provider is available yet"]
41
+ errors: invokable.length === 0
42
+ ? ["no invokable OAuth/session/wrapper provider was discovered"]
40
43
  : [],
41
44
  });
42
45
  const tracePath = await writeTrace("intake-health-trace", trace);
@@ -44,10 +47,10 @@ export async function runIntake(): Promise<void> {
44
47
  console.log("");
45
48
  console.log("Step 6 — Recommended next action");
46
49
  console.log("");
47
- if (usable.length === 0) {
50
+ if (invokable.length === 0) {
48
51
  console.log("QuorumRouter intake blocked");
49
52
  console.log("");
50
- console.log("No usable OAuth/session/wrapper provider is available yet.");
53
+ console.log("No invokable OAuth/session/wrapper provider was discovered.");
51
54
  console.log("");
52
55
  console.log("Next:");
53
56
  console.log(" deno task auth:login");
@@ -46,7 +46,7 @@ export async function inventoryCommand(): Promise<ModelInventory> {
46
46
  export function printInventoryTable(inventory: ModelInventory): void {
47
47
  console.log("Provider Status Auth Models");
48
48
  for (const entry of inventory.entries) {
49
- const status = entry.available ? "ready" : "blocked";
49
+ const status = entry.available ? "discovered" : "blocked";
50
50
  const models = entry.listed_models?.length
51
51
  ? entry.listed_models.join(", ")
52
52
  : entry.model;
@@ -56,4 +56,7 @@ export function printInventoryTable(inventory: ModelInventory): void {
56
56
  } ${models}`,
57
57
  );
58
58
  }
59
+ console.log(
60
+ "Status note: discovered means command/config present; live authentication is not verified.",
61
+ );
59
62
  }
@@ -61,7 +61,6 @@ export function providerAliasesForCommand(
61
61
  claude: ["anthropic", "claude", "claude-code"],
62
62
  gemini: ["google", "gemini", "gemini-cli"],
63
63
  devin: ["cognition", "devin", "devin-cli"],
64
- qwen: ["alibaba", "qwen", "qwen-cli"],
65
64
  };
66
65
  const providerAliases: Record<string, string[]> = {
67
66
  xai: ["grok", "grok-cli", "xai", "xai-cli"],
@@ -69,7 +68,6 @@ export function providerAliasesForCommand(
69
68
  anthropic: ["anthropic", "claude", "claude-code"],
70
69
  google: ["google", "gemini", "gemini-cli"],
71
70
  cognition: ["cognition", "devin", "devin-cli"],
72
- alibaba: ["alibaba", "qwen", "qwen-cli"],
73
71
  };
74
72
  const normalizedProvider = normalizeSelectionValue(provider);
75
73
  return aliasSet([
@@ -220,19 +218,6 @@ export const LOCAL_PROVIDER_SPECS: ProviderSpec[] = [
220
218
  "Devin CLI does not expose a models subcommand in this environment.",
221
219
  notes: ["Uses existing Devin CLI session."],
222
220
  },
223
- {
224
- provider: "Alibaba",
225
- model: "qwen-cli",
226
- model_id: "alibaba/qwen-cli",
227
- auth_mode: "session",
228
- source: "local_cli",
229
- command: "qwen",
230
- args_template: ["-p", ""],
231
- prompt_transport: "stdin",
232
- list_blocked_reason:
233
- "Qwen CLI model listing path is stdin/API-key dependent in this environment.",
234
- notes: ["Uses existing local Qwen CLI session."],
235
- },
236
221
  ];
237
222
 
238
223
  export function envFallbackEntry(configured: boolean): ModelInventoryEntry {
@@ -168,6 +168,23 @@ export type CostAwareTrace = {
168
168
  }>;
169
169
  };
170
170
 
171
+ export type CalibrationTraceSummary = {
172
+ schema_version: "quorum-router.calibration-trace.v1";
173
+ advisory_only: true;
174
+ minimum_sample_count: number;
175
+ group_count: number;
176
+ groups: Array<{
177
+ task_type_sha256: string;
178
+ source_sha256: string;
179
+ sample_count: number;
180
+ accuracy: number;
181
+ mean_confidence: number;
182
+ brier_score: number;
183
+ mean_calibration_bias: number;
184
+ sample_status: "insufficient" | "sufficient";
185
+ }>;
186
+ };
187
+
171
188
  export type DogfoodTrace = {
172
189
  run_id: string;
173
190
  timestamp: string;
@@ -186,6 +203,7 @@ export type DogfoodTrace = {
186
203
  prompt_summary?: string;
187
204
  prompt_context?: PromptContextTrace;
188
205
  cost_aware?: CostAwareTrace;
206
+ calibration?: CalibrationTraceSummary;
189
207
  response_summary?: string;
190
208
  schema_valid: boolean;
191
209
  redaction_ok: boolean;
@@ -217,9 +235,12 @@ export function assertOptIn(): void {
217
235
  }
218
236
 
219
237
  export function assertAgentChatOptIn(): void {
220
- if (Deno.env.get("RUN_EXPERIMENTAL_AGENT_CHAT") !== "1") {
238
+ if (
239
+ Deno.env.get("RUN_AGENT_CHAT") !== "1" &&
240
+ Deno.env.get("RUN_EXPERIMENTAL_AGENT_CHAT") !== "1"
241
+ ) {
221
242
  throw new Error(
222
- "agent_chat blocked: set RUN_EXPERIMENTAL_AGENT_CHAT=1; live multi-model dialogue is explicit opt-in",
243
+ "agent_chat blocked: set RUN_AGENT_CHAT=1; live multi-model dialogue is explicit opt-in and has no mutation authority",
223
244
  );
224
245
  }
225
246
  }
@@ -1,12 +1,14 @@
1
1
  import type {
2
2
  AgentChatTurn,
3
3
  AuthMode,
4
+ CalibrationTraceSummary,
4
5
  CostAwareTrace,
5
6
  DogfoodTrace,
6
7
  PromptContextTrace,
7
8
  ProviderResult,
8
9
  ScoreRow,
9
10
  } from "./schema.ts";
11
+ import type { TaskCalibrationReport } from "./calibration.ts";
10
12
  import { redactionOk, summarize } from "./redact.ts";
11
13
 
12
14
  export const OUT_DIR = "out";
@@ -35,6 +37,29 @@ export async function promptHash(prompt: string): Promise<string> {
35
37
  ).join("");
36
38
  }
37
39
 
40
+ async function calibrationTraceSummary(
41
+ report: TaskCalibrationReport,
42
+ ): Promise<CalibrationTraceSummary> {
43
+ return {
44
+ schema_version: "quorum-router.calibration-trace.v1",
45
+ advisory_only: true,
46
+ minimum_sample_count: report.minimum_sample_count,
47
+ group_count: report.groups.length,
48
+ groups: await Promise.all(report.groups.map(async (group) => ({
49
+ task_type_sha256: await promptHash(group.task_type),
50
+ source_sha256: await promptHash(
51
+ `${group.source.provider}\u0000${group.source.model}`,
52
+ ),
53
+ sample_count: group.sample_count,
54
+ accuracy: group.accuracy,
55
+ mean_confidence: group.mean_confidence,
56
+ brier_score: group.brier_score,
57
+ mean_calibration_bias: group.mean_calibration_bias,
58
+ sample_status: group.sample_status,
59
+ }))),
60
+ };
61
+ }
62
+
38
63
  export function score(result: ProviderResult): ScoreRow {
39
64
  const text = result.response_summary;
40
65
  const clarity = text.length > 20 ? 4 : 2;
@@ -77,7 +102,11 @@ export async function buildTrace(args: {
77
102
  fallbackUsed?: boolean;
78
103
  costAware?: CostAwareTrace;
79
104
  agentChatTurns?: AgentChatTurn[];
105
+ calibration?: TaskCalibrationReport;
80
106
  }): Promise<DogfoodTrace> {
107
+ const calibration = args.calibration
108
+ ? await calibrationTraceSummary(args.calibration)
109
+ : undefined;
81
110
  const responseSummary = args.results?.map((r) =>
82
111
  `[${r.provider}/${r.model}] ${r.response_summary}`
83
112
  ).join("\n");
@@ -96,6 +125,7 @@ export async function buildTrace(args: {
96
125
  provider_selection_honored: args.providerSelectionHonored ?? true,
97
126
  fallback_used: args.fallbackUsed ?? false,
98
127
  cost_aware: args.costAware,
128
+ ...(calibration ? { calibration } : {}),
99
129
  prompt_hash: args.prompt ? await promptHash(args.prompt) : undefined,
100
130
  prompt_summary: args.prompt ? summarize(args.prompt, 160) : undefined,
101
131
  prompt_context: args.promptContext,