open-multi-agent-kit 0.97.0 → 0.98.0
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 +35 -0
- package/README.md +41 -9
- package/dist/cli/args.d.ts +2 -3
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +13 -189
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/help.d.ts +3 -0
- package/dist/cli/help.d.ts.map +1 -0
- package/dist/cli/help.js +194 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/initial-message.d.ts.map +1 -1
- package/dist/cli/initial-message.js +2 -1
- package/dist/cli/initial-message.js.map +1 -1
- package/dist/cli/resource-paths.d.ts +4 -0
- package/dist/cli/resource-paths.d.ts.map +1 -0
- package/dist/cli/resource-paths.js +59 -0
- package/dist/cli/resource-paths.js.map +1 -0
- package/dist/commands/adaptorch-doctor-cli.d.ts +13 -0
- package/dist/commands/adaptorch-doctor-cli.d.ts.map +1 -0
- package/dist/commands/adaptorch-doctor-cli.js +115 -0
- package/dist/commands/adaptorch-doctor-cli.js.map +1 -0
- package/dist/commands/resource-doctor-cli.d.ts +4 -0
- package/dist/commands/resource-doctor-cli.d.ts.map +1 -1
- package/dist/commands/resource-doctor-cli.js +35 -5
- package/dist/commands/resource-doctor-cli.js.map +1 -1
- package/dist/core/adaptorch-bridge.d.ts +11 -13
- package/dist/core/adaptorch-bridge.d.ts.map +1 -1
- package/dist/core/adaptorch-bridge.js +11 -13
- package/dist/core/adaptorch-bridge.js.map +1 -1
- package/dist/core/agent-session-snapshot.d.ts +16 -0
- package/dist/core/agent-session-snapshot.d.ts.map +1 -0
- package/dist/core/agent-session-snapshot.js +111 -0
- package/dist/core/agent-session-snapshot.js.map +1 -0
- package/dist/core/agent-session.d.ts +5 -9
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +27 -219
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/agent-tool-settings.d.ts +10 -0
- package/dist/core/agent-tool-settings.d.ts.map +1 -1
- package/dist/core/agent-tool-settings.js +10 -1
- package/dist/core/agent-tool-settings.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +7 -0
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +25 -61
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/compaction/index.d.ts +1 -0
- package/dist/core/compaction/index.d.ts.map +1 -1
- package/dist/core/compaction/index.js +1 -0
- package/dist/core/compaction/index.js.map +1 -1
- package/dist/core/compaction/knowledge-triage-format.d.ts +9 -0
- package/dist/core/compaction/knowledge-triage-format.d.ts.map +1 -0
- package/dist/core/compaction/knowledge-triage-format.js +71 -0
- package/dist/core/compaction/knowledge-triage-format.js.map +1 -0
- package/dist/core/compaction/knowledge-triage-types.d.ts +7 -0
- package/dist/core/compaction/knowledge-triage-types.d.ts.map +1 -0
- package/dist/core/compaction/knowledge-triage-types.js +2 -0
- package/dist/core/compaction/knowledge-triage-types.js.map +1 -0
- package/dist/core/compaction/knowledge-triage.d.ts +29 -0
- package/dist/core/compaction/knowledge-triage.d.ts.map +1 -0
- package/dist/core/compaction/knowledge-triage.js +189 -0
- package/dist/core/compaction/knowledge-triage.js.map +1 -0
- package/dist/core/compaction/transaction.d.ts.map +1 -1
- package/dist/core/compaction/transaction.js +1 -1
- package/dist/core/compaction/transaction.js.map +1 -1
- package/dist/core/compaction/utils.d.ts +4 -0
- package/dist/core/compaction/utils.d.ts.map +1 -1
- package/dist/core/compaction/utils.js +37 -0
- package/dist/core/compaction/utils.js.map +1 -1
- package/dist/core/completion-sound-io.d.ts +14 -0
- package/dist/core/completion-sound-io.d.ts.map +1 -0
- package/dist/core/completion-sound-io.js +64 -0
- package/dist/core/completion-sound-io.js.map +1 -0
- package/dist/core/completion-sound.d.ts +4 -10
- package/dist/core/completion-sound.d.ts.map +1 -1
- package/dist/core/completion-sound.js +28 -58
- package/dist/core/completion-sound.js.map +1 -1
- package/dist/core/context-budget-relevance.d.ts +15 -2
- package/dist/core/context-budget-relevance.d.ts.map +1 -1
- package/dist/core/context-budget-relevance.js +21 -9
- package/dist/core/context-budget-relevance.js.map +1 -1
- package/dist/core/context-budget-system-prompt-items.d.ts +1 -1
- package/dist/core/context-budget-system-prompt-items.d.ts.map +1 -1
- package/dist/core/context-budget-system-prompt-items.js.map +1 -1
- package/dist/core/context-budget-system-prompt.d.ts +1 -1
- package/dist/core/context-budget-system-prompt.d.ts.map +1 -1
- package/dist/core/context-budget-system-prompt.js.map +1 -1
- package/dist/core/context-file.d.ts +23 -0
- package/dist/core/context-file.d.ts.map +1 -0
- package/dist/core/context-file.js +17 -0
- package/dist/core/context-file.js.map +1 -0
- package/dist/core/keybindings.d.ts +1 -1
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +6 -1
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/model-prompt-policy.d.ts +1 -1
- package/dist/core/model-prompt-policy.d.ts.map +1 -1
- package/dist/core/model-prompt-policy.js.map +1 -1
- package/dist/core/prompt-settlement.d.ts +6 -3
- package/dist/core/prompt-settlement.d.ts.map +1 -1
- package/dist/core/prompt-settlement.js +10 -14
- package/dist/core/prompt-settlement.js.map +1 -1
- package/dist/core/reasoning-router-promotion.d.ts +41 -1
- package/dist/core/reasoning-router-promotion.d.ts.map +1 -1
- package/dist/core/reasoning-router-promotion.js +31 -2
- package/dist/core/reasoning-router-promotion.js.map +1 -1
- package/dist/core/reasoning-router-replay-stability.d.ts +65 -0
- package/dist/core/reasoning-router-replay-stability.d.ts.map +1 -0
- package/dist/core/reasoning-router-replay-stability.js +67 -0
- package/dist/core/reasoning-router-replay-stability.js.map +1 -0
- package/dist/core/reasoning-router-v4-weights.d.ts +6 -6
- package/dist/core/reasoning-router-v4-weights.d.ts.map +1 -1
- package/dist/core/reasoning-router-v4-weights.js +3 -3
- package/dist/core/reasoning-router-v4-weights.js.map +1 -1
- package/dist/core/reasoning-router-v4.d.ts.map +1 -1
- package/dist/core/reasoning-router-v4.js +6 -6
- package/dist/core/reasoning-router-v4.js.map +1 -1
- package/dist/core/redaction.d.ts.map +1 -1
- package/dist/core/redaction.js +1 -1
- package/dist/core/redaction.js.map +1 -1
- package/dist/core/resource-admission.d.ts +2 -1
- package/dist/core/resource-admission.d.ts.map +1 -1
- package/dist/core/resource-admission.js +11 -0
- package/dist/core/resource-admission.js.map +1 -1
- package/dist/core/resource-governor-format.d.ts +1 -1
- package/dist/core/resource-governor-format.d.ts.map +1 -1
- package/dist/core/resource-governor-format.js.map +1 -1
- package/dist/core/resource-governor-settings.d.ts +6 -12
- package/dist/core/resource-governor-settings.d.ts.map +1 -1
- package/dist/core/resource-governor-settings.js.map +1 -1
- package/dist/core/resource-governor-types.d.ts +24 -0
- package/dist/core/resource-governor-types.d.ts.map +1 -0
- package/dist/core/resource-governor-types.js +15 -0
- package/dist/core/resource-governor-types.js.map +1 -0
- package/dist/core/resource-loader.d.ts +1 -7
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/resource-observation-journal.d.ts +2 -1
- package/dist/core/resource-observation-journal.d.ts.map +1 -1
- package/dist/core/resource-observation-journal.js +4 -3
- package/dist/core/resource-observation-journal.js.map +1 -1
- package/dist/core/resource-observation-report-reader.d.ts +7 -0
- package/dist/core/resource-observation-report-reader.d.ts.map +1 -0
- package/dist/core/resource-observation-report-reader.js +157 -0
- package/dist/core/resource-observation-report-reader.js.map +1 -0
- package/dist/core/resource-observation-report.d.ts +24 -0
- package/dist/core/resource-observation-report.d.ts.map +1 -0
- package/dist/core/resource-observation-report.js +101 -0
- package/dist/core/resource-observation-report.js.map +1 -0
- package/dist/core/router-feedback-collector.d.ts +1 -1
- package/dist/core/router-feedback-collector.d.ts.map +1 -1
- package/dist/core/router-feedback-collector.js.map +1 -1
- package/dist/core/session-failure-cause.d.ts +33 -0
- package/dist/core/session-failure-cause.d.ts.map +1 -0
- package/dist/core/session-failure-cause.js +141 -0
- package/dist/core/session-failure-cause.js.map +1 -0
- package/dist/core/session-system-prompt.d.ts +1 -1
- package/dist/core/session-system-prompt.d.ts.map +1 -1
- package/dist/core/session-system-prompt.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +7 -7
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/workload-classifier.d.ts +2 -10
- package/dist/core/workload-classifier.d.ts.map +1 -1
- package/dist/core/workload-classifier.js +17 -0
- package/dist/core/workload-classifier.js.map +1 -1
- package/dist/core/workload-family-matcher.d.ts +1 -1
- package/dist/core/workload-family-matcher.d.ts.map +1 -1
- package/dist/core/workload-family-matcher.js.map +1 -1
- package/dist/core/workload-shard-execution-types.d.ts +40 -0
- package/dist/core/workload-shard-execution-types.d.ts.map +1 -0
- package/dist/core/workload-shard-execution-types.js +14 -0
- package/dist/core/workload-shard-execution-types.js.map +1 -0
- package/dist/core/workload-shard-executor.d.ts +3 -27
- package/dist/core/workload-shard-executor.d.ts.map +1 -1
- package/dist/core/workload-shard-executor.js.map +1 -1
- package/dist/core/workload-shard-runner.d.ts +1 -1
- package/dist/core/workload-shard-runner.d.ts.map +1 -1
- package/dist/core/workload-shard-runner.js.map +1 -1
- package/dist/core/workload-types.d.ts +24 -0
- package/dist/core/workload-types.d.ts.map +1 -0
- package/dist/core/workload-types.js +15 -0
- package/dist/core/workload-types.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +27 -47
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/theme/omk-control-panel.json +1 -1
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +42 -33
- package/dist/utils/clipboard-image.js.map +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +14 -2
- package/dist/utils/clipboard.js.map +1 -1
- package/docs/adaptorch-preview-spec.md +20 -18
- package/docs/adaptorch-preview.md +9 -9
- package/docs/compaction.md +24 -3
- package/docs/correctness-wall.md +10 -10
- package/docs/docs.json +12 -0
- package/docs/environment-variables.md +9 -2
- package/docs/index.md +3 -0
- package/docs/runtime-algorithms.md +359 -0
- package/docs/sdk.md +2 -2
- package/docs/settings.md +11 -10
- package/docs/usage.md +18 -2
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +18 -18
- package/package.json +6 -6
- package/dist/core/context-budget-governor.d.ts +0 -67
- package/dist/core/context-budget-governor.d.ts.map +0 -1
- package/dist/core/context-budget-governor.js +0 -224
- package/dist/core/context-budget-governor.js.map +0 -1
- package/dist/core/lean-ctx.d.ts +0 -99
- package/dist/core/lean-ctx.d.ts.map +0 -1
- package/dist/core/lean-ctx.js +0 -225
- package/dist/core/lean-ctx.js.map +0 -1
- package/dist/core/read-anchors.d.ts +0 -110
- package/dist/core/read-anchors.d.ts.map +0 -1
- package/dist/core/read-anchors.js +0 -193
- package/dist/core/read-anchors.js.map +0 -1
- package/dist/core/recovery-checkpoint.d.ts +0 -196
- package/dist/core/recovery-checkpoint.d.ts.map +0 -1
- package/dist/core/recovery-checkpoint.js +0 -382
- package/dist/core/recovery-checkpoint.js.map +0 -1
- package/dist/core/sandbox-policy-v2.d.ts +0 -49
- package/dist/core/sandbox-policy-v2.d.ts.map +0 -1
- package/dist/core/sandbox-policy-v2.js +0 -368
- package/dist/core/sandbox-policy-v2.js.map +0 -1
- package/dist/guardrails/audit-logger.d.ts +0 -4
- package/dist/guardrails/audit-logger.d.ts.map +0 -1
- package/dist/guardrails/audit-logger.js +0 -28
- package/dist/guardrails/audit-logger.js.map +0 -1
- package/dist/guardrails/freedomd-adapter.d.ts +0 -16
- package/dist/guardrails/freedomd-adapter.d.ts.map +0 -1
- package/dist/guardrails/freedomd-adapter.js +0 -87
- package/dist/guardrails/freedomd-adapter.js.map +0 -1
- package/dist/orchestration/lane-grant-auditor.d.ts +0 -4
- package/dist/orchestration/lane-grant-auditor.d.ts.map +0 -1
- package/dist/orchestration/lane-grant-auditor.js +0 -87
- package/dist/orchestration/lane-grant-auditor.js.map +0 -1
- package/dist/types/guardrails.d.ts +0 -61
- package/dist/types/guardrails.d.ts.map +0 -1
- package/dist/types/guardrails.js +0 -9
- package/dist/types/guardrails.js.map +0 -1
- package/dist/types/lane-grant.d.ts +0 -58
- package/dist/types/lane-grant.d.ts.map +0 -1
- package/dist/types/lane-grant.js +0 -9
- package/dist/types/lane-grant.js.map +0 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SessionTerminationKind } from "./session-termination.ts";
|
|
1
2
|
/**
|
|
2
3
|
* Prompt settlement coordinator (OMK v0.97.x roadmap §16, M4/PR6).
|
|
3
4
|
*
|
|
@@ -8,9 +9,9 @@
|
|
|
8
9
|
* M4 completion sound, notifications) must treat it as a UX signal, never a
|
|
9
10
|
* correctness signal (§3 non-goals).
|
|
10
11
|
*
|
|
11
|
-
* The reducer is a pure function over
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* The reducer is a pure function over immutable state (§16.5). Shard and child
|
|
13
|
+
* counters are reserved in the contract, but no production signal call sites
|
|
14
|
+
* wire them yet; any future live M5/M6 path must add those calls before use.
|
|
14
15
|
*/
|
|
15
16
|
export type PromptSettlementOutcome = "completed" | "failed" | "aborted";
|
|
16
17
|
/** §16.2 prompt run identity. */
|
|
@@ -65,6 +66,8 @@ export type PromptSettlementSignal = {
|
|
|
65
66
|
} | {
|
|
66
67
|
readonly kind: "emitted";
|
|
67
68
|
};
|
|
69
|
+
/** Map the final typed termination onto the prompt UX outcome. */
|
|
70
|
+
export declare function resolvePromptSettlementOutcome(fallback: PromptSettlementOutcome, terminationKind: SessionTerminationKind | undefined): PromptSettlementOutcome;
|
|
68
71
|
export declare function createPromptSettlementState(promptRunId: string, startedAtEpochMs: number): PromptSettlementState;
|
|
69
72
|
/** Pure reducer (§16.5). Counters clamp at zero; terminal and emitted are latching. */
|
|
70
73
|
export declare function reducePromptSettlement(state: PromptSettlementState, signal: PromptSettlementSignal): PromptSettlementState;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt-settlement.d.ts","sourceRoot":"","sources":["../../src/core/prompt-settlement.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Prompt settlement coordinator (OMK v0.97.x roadmap §16, M4/PR6).\n *\n * `agent_end` is NOT final (§16.1): provider retries, compaction retries,\n * durable-goal continuations, steering/follow-up queues, shards, and child\n * subagents may still run. `prompt_settled` fires only when the reducer's\n * §16.4 conditions all hold, exactly once per promptRunId. Consumers (the\n * M4 completion sound, notifications) must treat it as a UX signal, never a\n * correctness signal (§3 non-goals).\n *\n * The reducer is a pure function over
|
|
1
|
+
{"version":3,"file":"prompt-settlement.d.ts","sourceRoot":"","sources":["../../src/core/prompt-settlement.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SessionTerminationKind } from \"./session-termination.ts\";\n\n/**\n * Prompt settlement coordinator (OMK v0.97.x roadmap §16, M4/PR6).\n *\n * `agent_end` is NOT final (§16.1): provider retries, compaction retries,\n * durable-goal continuations, steering/follow-up queues, shards, and child\n * subagents may still run. `prompt_settled` fires only when the reducer's\n * §16.4 conditions all hold, exactly once per promptRunId. Consumers (the\n * M4 completion sound, notifications) must treat it as a UX signal, never a\n * correctness signal (§3 non-goals).\n *\n * The reducer is a pure function over immutable state (§16.5). Shard and child\n * counters are reserved in the contract, but no production signal call sites\n * wire them yet; any future live M5/M6 path must add those calls before use.\n */\n\nexport type PromptSettlementOutcome = \"completed\" | \"failed\" | \"aborted\";\n\n/** §16.2 prompt run identity. */\nexport interface PromptRunRef {\n\treadonly promptRunId: string;\n\treadonly sessionId: string;\n\treadonly startedAt: string;\n\treadonly lineageId?: string;\n}\n\n/** §16.3 settlement contract. */\nexport interface PromptSettledEvent {\n\treadonly type: \"prompt_settled\";\n\treadonly promptRunId: string;\n\treadonly outcome: PromptSettlementOutcome;\n\treadonly durationMs: number;\n\treadonly terminationKind?: string;\n}\n\n/** §16.5 coordinator state. */\nexport interface PromptSettlementState {\n\treadonly promptRunId: string;\n\treadonly startedAtEpochMs: number;\n\treadonly activeProviderAttempts: number;\n\treadonly activeTools: number;\n\treadonly activeShards: number;\n\treadonly activeChildren: number;\n\treadonly queuedContinuations: number;\n\treadonly terminal?: {\n\t\treadonly outcome: PromptSettlementOutcome;\n\t\treadonly terminationKind?: string;\n\t};\n\treadonly emitted: boolean;\n}\n\nexport type PromptSettlementSignal =\n\t| { readonly kind: \"provider_attempt\"; readonly delta: 1 | -1 }\n\t| { readonly kind: \"tool\"; readonly delta: 1 | -1 }\n\t| { readonly kind: \"shard\"; readonly delta: 1 | -1 }\n\t| { readonly kind: \"child\"; readonly delta: 1 | -1 }\n\t| { readonly kind: \"continuation\"; readonly delta: 1 | -1 }\n\t| { readonly kind: \"terminal\"; readonly outcome: PromptSettlementOutcome; readonly terminationKind?: string }\n\t| { readonly kind: \"emitted\" };\n\n/** Map the final typed termination onto the prompt UX outcome. */\nexport function resolvePromptSettlementOutcome(\n\tfallback: PromptSettlementOutcome,\n\tterminationKind: SessionTerminationKind | undefined,\n): PromptSettlementOutcome {\n\tif (terminationKind === \"user_abort\" || terminationKind === \"provider_abort\") return \"aborted\";\n\tif (fallback !== \"completed\") return fallback;\n\tif (terminationKind === undefined || terminationKind === \"completed\") return \"completed\";\n\treturn \"failed\";\n}\n\nexport function createPromptSettlementState(promptRunId: string, startedAtEpochMs: number): PromptSettlementState {\n\treturn {\n\t\tpromptRunId,\n\t\tstartedAtEpochMs,\n\t\tactiveProviderAttempts: 0,\n\t\tactiveTools: 0,\n\t\tactiveShards: 0,\n\t\tactiveChildren: 0,\n\t\tqueuedContinuations: 0,\n\t\temitted: false,\n\t};\n}\n\n/** Pure reducer (§16.5). Counters clamp at zero; terminal and emitted are latching. */\nexport function reducePromptSettlement(\n\tstate: PromptSettlementState,\n\tsignal: PromptSettlementSignal,\n): PromptSettlementState {\n\tswitch (signal.kind) {\n\t\tcase \"provider_attempt\":\n\t\t\treturn { ...state, activeProviderAttempts: clampCount(state.activeProviderAttempts + signal.delta) };\n\t\tcase \"tool\":\n\t\t\treturn { ...state, activeTools: clampCount(state.activeTools + signal.delta) };\n\t\tcase \"shard\":\n\t\t\treturn { ...state, activeShards: clampCount(state.activeShards + signal.delta) };\n\t\tcase \"child\":\n\t\t\treturn { ...state, activeChildren: clampCount(state.activeChildren + signal.delta) };\n\t\tcase \"continuation\":\n\t\t\treturn { ...state, queuedContinuations: clampCount(state.queuedContinuations + signal.delta) };\n\t\tcase \"terminal\":\n\t\t\t// First terminal outcome wins; later reclassification never flips it (§15.3).\n\t\t\tif (state.terminal !== undefined) {\n\t\t\t\treturn state;\n\t\t\t}\n\t\t\treturn { ...state, terminal: { outcome: signal.outcome, terminationKind: signal.terminationKind } };\n\t\tcase \"emitted\":\n\t\t\treturn { ...state, emitted: true };\n\t\tdefault: {\n\t\t\tconst exhaustive: never = signal;\n\t\t\treturn exhaustive;\n\t\t}\n\t}\n}\n\n/** §16.4: every condition must hold before `prompt_settled` may emit. */\nexport function shouldEmitPromptSettled(state: PromptSettlementState): boolean {\n\treturn (\n\t\tstate.terminal !== undefined &&\n\t\t!state.emitted &&\n\t\tstate.activeProviderAttempts === 0 &&\n\t\tstate.activeTools === 0 &&\n\t\tstate.activeShards === 0 &&\n\t\tstate.activeChildren === 0 &&\n\t\tstate.queuedContinuations === 0\n\t);\n}\n\n/**\n * Build the event when (and only when) §16.4 holds, marking the state\n * emitted so the same sequence can never yield a second event.\n */\nexport function settlePromptIfReady(\n\tstate: PromptSettlementState,\n\tnowEpochMs: number,\n): { readonly state: PromptSettlementState; readonly event: PromptSettledEvent | null } {\n\tif (!shouldEmitPromptSettled(state) || state.terminal === undefined) {\n\t\treturn { state, event: null };\n\t}\n\tconst event: PromptSettledEvent = {\n\t\ttype: \"prompt_settled\",\n\t\tpromptRunId: state.promptRunId,\n\t\toutcome: state.terminal.outcome,\n\t\tdurationMs: Math.max(0, nowEpochMs - state.startedAtEpochMs),\n\t\t...(state.terminal.terminationKind !== undefined ? { terminationKind: state.terminal.terminationKind } : {}),\n\t};\n\treturn { state: reducePromptSettlement(state, { kind: \"emitted\" }), event };\n}\n\nfunction clampCount(value: number): number {\n\treturn Math.max(0, value);\n}\n"]}
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* The reducer is a pure function over an immutable state (§16.5). Shard and
|
|
12
|
-
* child counters are wired by M5/M6; they participate in the contract now so
|
|
13
|
-
* those slices only add signal call sites.
|
|
14
|
-
*/
|
|
1
|
+
/** Map the final typed termination onto the prompt UX outcome. */
|
|
2
|
+
export function resolvePromptSettlementOutcome(fallback, terminationKind) {
|
|
3
|
+
if (terminationKind === "user_abort" || terminationKind === "provider_abort")
|
|
4
|
+
return "aborted";
|
|
5
|
+
if (fallback !== "completed")
|
|
6
|
+
return fallback;
|
|
7
|
+
if (terminationKind === undefined || terminationKind === "completed")
|
|
8
|
+
return "completed";
|
|
9
|
+
return "failed";
|
|
10
|
+
}
|
|
15
11
|
export function createPromptSettlementState(promptRunId, startedAtEpochMs) {
|
|
16
12
|
return {
|
|
17
13
|
promptRunId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt-settlement.js","sourceRoot":"","sources":["../../src/core/prompt-settlement.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"prompt-settlement.js","sourceRoot":"","sources":["../../src/core/prompt-settlement.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SessionTerminationKind } from \"./session-termination.ts\";\n\n/**\n * Prompt settlement coordinator (OMK v0.97.x roadmap §16, M4/PR6).\n *\n * `agent_end` is NOT final (§16.1): provider retries, compaction retries,\n * durable-goal continuations, steering/follow-up queues, shards, and child\n * subagents may still run. `prompt_settled` fires only when the reducer's\n * §16.4 conditions all hold, exactly once per promptRunId. Consumers (the\n * M4 completion sound, notifications) must treat it as a UX signal, never a\n * correctness signal (§3 non-goals).\n *\n * The reducer is a pure function over immutable state (§16.5). Shard and child\n * counters are reserved in the contract, but no production signal call sites\n * wire them yet; any future live M5/M6 path must add those calls before use.\n */\n\nexport type PromptSettlementOutcome = \"completed\" | \"failed\" | \"aborted\";\n\n/** §16.2 prompt run identity. */\nexport interface PromptRunRef {\n\treadonly promptRunId: string;\n\treadonly sessionId: string;\n\treadonly startedAt: string;\n\treadonly lineageId?: string;\n}\n\n/** §16.3 settlement contract. */\nexport interface PromptSettledEvent {\n\treadonly type: \"prompt_settled\";\n\treadonly promptRunId: string;\n\treadonly outcome: PromptSettlementOutcome;\n\treadonly durationMs: number;\n\treadonly terminationKind?: string;\n}\n\n/** §16.5 coordinator state. */\nexport interface PromptSettlementState {\n\treadonly promptRunId: string;\n\treadonly startedAtEpochMs: number;\n\treadonly activeProviderAttempts: number;\n\treadonly activeTools: number;\n\treadonly activeShards: number;\n\treadonly activeChildren: number;\n\treadonly queuedContinuations: number;\n\treadonly terminal?: {\n\t\treadonly outcome: PromptSettlementOutcome;\n\t\treadonly terminationKind?: string;\n\t};\n\treadonly emitted: boolean;\n}\n\nexport type PromptSettlementSignal =\n\t| { readonly kind: \"provider_attempt\"; readonly delta: 1 | -1 }\n\t| { readonly kind: \"tool\"; readonly delta: 1 | -1 }\n\t| { readonly kind: \"shard\"; readonly delta: 1 | -1 }\n\t| { readonly kind: \"child\"; readonly delta: 1 | -1 }\n\t| { readonly kind: \"continuation\"; readonly delta: 1 | -1 }\n\t| { readonly kind: \"terminal\"; readonly outcome: PromptSettlementOutcome; readonly terminationKind?: string }\n\t| { readonly kind: \"emitted\" };\n\n/** Map the final typed termination onto the prompt UX outcome. */\nexport function resolvePromptSettlementOutcome(\n\tfallback: PromptSettlementOutcome,\n\tterminationKind: SessionTerminationKind | undefined,\n): PromptSettlementOutcome {\n\tif (terminationKind === \"user_abort\" || terminationKind === \"provider_abort\") return \"aborted\";\n\tif (fallback !== \"completed\") return fallback;\n\tif (terminationKind === undefined || terminationKind === \"completed\") return \"completed\";\n\treturn \"failed\";\n}\n\nexport function createPromptSettlementState(promptRunId: string, startedAtEpochMs: number): PromptSettlementState {\n\treturn {\n\t\tpromptRunId,\n\t\tstartedAtEpochMs,\n\t\tactiveProviderAttempts: 0,\n\t\tactiveTools: 0,\n\t\tactiveShards: 0,\n\t\tactiveChildren: 0,\n\t\tqueuedContinuations: 0,\n\t\temitted: false,\n\t};\n}\n\n/** Pure reducer (§16.5). Counters clamp at zero; terminal and emitted are latching. */\nexport function reducePromptSettlement(\n\tstate: PromptSettlementState,\n\tsignal: PromptSettlementSignal,\n): PromptSettlementState {\n\tswitch (signal.kind) {\n\t\tcase \"provider_attempt\":\n\t\t\treturn { ...state, activeProviderAttempts: clampCount(state.activeProviderAttempts + signal.delta) };\n\t\tcase \"tool\":\n\t\t\treturn { ...state, activeTools: clampCount(state.activeTools + signal.delta) };\n\t\tcase \"shard\":\n\t\t\treturn { ...state, activeShards: clampCount(state.activeShards + signal.delta) };\n\t\tcase \"child\":\n\t\t\treturn { ...state, activeChildren: clampCount(state.activeChildren + signal.delta) };\n\t\tcase \"continuation\":\n\t\t\treturn { ...state, queuedContinuations: clampCount(state.queuedContinuations + signal.delta) };\n\t\tcase \"terminal\":\n\t\t\t// First terminal outcome wins; later reclassification never flips it (§15.3).\n\t\t\tif (state.terminal !== undefined) {\n\t\t\t\treturn state;\n\t\t\t}\n\t\t\treturn { ...state, terminal: { outcome: signal.outcome, terminationKind: signal.terminationKind } };\n\t\tcase \"emitted\":\n\t\t\treturn { ...state, emitted: true };\n\t\tdefault: {\n\t\t\tconst exhaustive: never = signal;\n\t\t\treturn exhaustive;\n\t\t}\n\t}\n}\n\n/** §16.4: every condition must hold before `prompt_settled` may emit. */\nexport function shouldEmitPromptSettled(state: PromptSettlementState): boolean {\n\treturn (\n\t\tstate.terminal !== undefined &&\n\t\t!state.emitted &&\n\t\tstate.activeProviderAttempts === 0 &&\n\t\tstate.activeTools === 0 &&\n\t\tstate.activeShards === 0 &&\n\t\tstate.activeChildren === 0 &&\n\t\tstate.queuedContinuations === 0\n\t);\n}\n\n/**\n * Build the event when (and only when) §16.4 holds, marking the state\n * emitted so the same sequence can never yield a second event.\n */\nexport function settlePromptIfReady(\n\tstate: PromptSettlementState,\n\tnowEpochMs: number,\n): { readonly state: PromptSettlementState; readonly event: PromptSettledEvent | null } {\n\tif (!shouldEmitPromptSettled(state) || state.terminal === undefined) {\n\t\treturn { state, event: null };\n\t}\n\tconst event: PromptSettledEvent = {\n\t\ttype: \"prompt_settled\",\n\t\tpromptRunId: state.promptRunId,\n\t\toutcome: state.terminal.outcome,\n\t\tdurationMs: Math.max(0, nowEpochMs - state.startedAtEpochMs),\n\t\t...(state.terminal.terminationKind !== undefined ? { terminationKind: state.terminal.terminationKind } : {}),\n\t};\n\treturn { state: reducePromptSettlement(state, { kind: \"emitted\" }), event };\n}\n\nfunction clampCount(value: number): number {\n\treturn Math.max(0, value);\n}\n"]}
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
* This module is that policy, as a pure total function. It deliberately refuses
|
|
11
11
|
* to promote on anything short of unanimous evidence:
|
|
12
12
|
*
|
|
13
|
+
* - outcomes that survived a two-run replay, so a flipped measurement is never
|
|
14
|
+
* credited as a win (see `reasoning-router-replay-stability.ts`);
|
|
15
|
+
* - a comparison against the frozen reference policy rather than an opponent
|
|
16
|
+
* the caller picked, which is what stops baseline shopping;
|
|
13
17
|
* - a statistically significant win on the held-in gold set (McNemar's exact
|
|
14
18
|
* test), computed by the caller and passed in;
|
|
15
19
|
* - enough discordant pairs for that test to mean anything;
|
|
@@ -37,10 +41,31 @@ export interface RouterHoldoutEvidence {
|
|
|
37
41
|
readonly candidateCorrect: number;
|
|
38
42
|
readonly total: number;
|
|
39
43
|
}
|
|
44
|
+
/** How much of the evidence survived repeated identical replays. */
|
|
45
|
+
export interface RouterStabilityEvidence {
|
|
46
|
+
/** Rows replayed under both policies, whatever bucket they landed in. */
|
|
47
|
+
readonly evaluated: number;
|
|
48
|
+
/** Rows withheld because repeated observations disagreed. */
|
|
49
|
+
readonly unstable: number;
|
|
50
|
+
/** Independent observations per row behind the credited outcomes. */
|
|
51
|
+
readonly replays: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* What the candidate was measured against.
|
|
55
|
+
*
|
|
56
|
+
* `frozen_reference` is the shipped policy that learning never updates. A
|
|
57
|
+
* candidate that only beats an opponent the caller chose has demonstrated
|
|
58
|
+
* nothing, so the gate refuses to reason about `ad_hoc` comparisons.
|
|
59
|
+
*/
|
|
60
|
+
export type RouterBaselineKind = "frozen_reference" | "ad_hoc";
|
|
40
61
|
/** Everything the gate is allowed to consider. */
|
|
41
62
|
export interface RouterPromotionEvidence {
|
|
42
63
|
readonly heldIn: RouterHeldInEvidence;
|
|
43
64
|
readonly holdout: RouterHoldoutEvidence;
|
|
65
|
+
/** Replay agreement behind every credited outcome. */
|
|
66
|
+
readonly stability: RouterStabilityEvidence;
|
|
67
|
+
/** Which opponent produced `heldIn` and `holdout`. */
|
|
68
|
+
readonly baselineKind: RouterBaselineKind;
|
|
44
69
|
/** Golden-diff changes still unreviewed at decision time. */
|
|
45
70
|
readonly goldenChanges: number;
|
|
46
71
|
/** True only when a human explicitly approved this exact candidate. */
|
|
@@ -54,9 +79,15 @@ export interface RouterPromotionPolicy {
|
|
|
54
79
|
readonly minHoldout: number;
|
|
55
80
|
/** Maximum unreviewed golden-diff changes tolerated. */
|
|
56
81
|
readonly maxGoldenChanges: number;
|
|
82
|
+
/** Independent replays required per row before any outcome carries credit. */
|
|
83
|
+
readonly minReplays: number;
|
|
84
|
+
/** Maximum rows allowed to disagree across replays. */
|
|
85
|
+
readonly maxUnstable: number;
|
|
86
|
+
/** Require the frozen reference policy as the comparison opponent. */
|
|
87
|
+
readonly requireFrozenBaseline: boolean;
|
|
57
88
|
readonly requireHumanApproval: boolean;
|
|
58
89
|
}
|
|
59
|
-
export type RouterPromotionBlocker = "malformed_evidence" | "insufficient_discordant_pairs" | "mcnemar_not_significant" | "candidate_not_favored" | "holdout_regression" | "insufficient_holdout" | "unreviewed_behavior_changes" | "human_approval_missing";
|
|
90
|
+
export type RouterPromotionBlocker = "malformed_evidence" | "insufficient_replays" | "unstable_evidence" | "baseline_not_frozen" | "insufficient_discordant_pairs" | "mcnemar_not_significant" | "candidate_not_favored" | "holdout_regression" | "insufficient_holdout" | "unreviewed_behavior_changes" | "human_approval_missing";
|
|
60
91
|
export interface RouterPromotionVerdict {
|
|
61
92
|
readonly promote: boolean;
|
|
62
93
|
/** Every independent reason promotion was refused, in evaluation order. */
|
|
@@ -70,6 +101,11 @@ export interface RouterPromotionVerdict {
|
|
|
70
101
|
* `minHoldout` is 40 because the router gold set freezes 42 holdout rows (6 per
|
|
71
102
|
* class × 7 classes). A higher bar would be unsatisfiable rather than strict,
|
|
72
103
|
* and a gate that can never pass is a gate nobody runs.
|
|
104
|
+
*
|
|
105
|
+
* `minReplays` of 2 is the two-run rule: one observation cannot be checked for
|
|
106
|
+
* stability at all. `maxUnstable` of 0 follows from what instability means here
|
|
107
|
+
* — the routing path is deterministic, so a single disagreeing row is a defect
|
|
108
|
+
* in the measurement, not an acceptable error rate to average over.
|
|
73
109
|
*/
|
|
74
110
|
export declare const DEFAULT_ROUTER_PROMOTION_POLICY: RouterPromotionPolicy;
|
|
75
111
|
/**
|
|
@@ -79,6 +115,10 @@ export declare const DEFAULT_ROUTER_PROMOTION_POLICY: RouterPromotionPolicy;
|
|
|
79
115
|
* run reports the full distance to promotion instead of revealing it one
|
|
80
116
|
* rejection at a time. Malformed evidence short-circuits: the gate never
|
|
81
117
|
* reasons about numbers it cannot trust.
|
|
118
|
+
*
|
|
119
|
+
* Evidence-integrity blockers are reported ahead of statistical ones. A p-value
|
|
120
|
+
* computed over unstable rows or against the wrong opponent is not a weaker
|
|
121
|
+
* result, it is a result about nothing, and the report should say so first.
|
|
82
122
|
*/
|
|
83
123
|
export declare function evaluateRouterPromotion(evidence: RouterPromotionEvidence, policy?: RouterPromotionPolicy): RouterPromotionVerdict;
|
|
84
124
|
//# sourceMappingURL=reasoning-router-promotion.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reasoning-router-promotion.d.ts","sourceRoot":"","sources":["../../src/core/reasoning-router-promotion.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,0EAA0E;AAC1E,MAAM,WAAW,oBAAoB;IACpC,0EAA0E;IAC1E,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,0EAA0E;IAC1E,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC9B;AAED,qDAAqD;AACrD,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED,kDAAkD;AAClD,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,qBAAqB,CAAC;IACxC,6DAA6D;IAC7D,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,uEAAuE;IACvE,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CAChC;AAED,4EAA4E;AAC5E,MAAM,WAAW,qBAAqB;IACrC,oEAAoE;IACpE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,kEAAkE;IAClE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,wDAAwD;IACxD,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC;CACvC;AAED,MAAM,MAAM,sBAAsB,GAC/B,oBAAoB,GACpB,+BAA+B,GAC/B,yBAAyB,GACzB,uBAAuB,GACvB,oBAAoB,GACpB,sBAAsB,GACtB,6BAA6B,GAC7B,wBAAwB,CAAC;AAE5B,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,SAAS,sBAAsB,EAAE,CAAC;CACrD;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,+BAA+B,EAAE,qBAK7C,CAAC;AAeF;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACtC,QAAQ,EAAE,uBAAuB,EACjC,MAAM,GAAE,qBAAuD,GAC7D,sBAAsB,CA+BxB","sourcesContent":["/**\n * Promotion gate for learned reasoning-router weights.\n *\n * The router already produces a feedback ledger (`router-feedback-collector.ts`)\n * and an offline calibration stack (`scripts/reasoning-router/`: calibration,\n * held-out evaluation, golden diffing, and McNemar's exact test). What was\n * missing is the decision policy that makes automatic promotion trustworthy:\n * without it the loop is open and every weight change is a judgement call.\n *\n * This module is that policy, as a pure total function. It deliberately refuses\n * to promote on anything short of unanimous evidence:\n *\n * - a statistically significant win on the held-in gold set (McNemar's exact\n * test), computed by the caller and passed in;\n * - enough discordant pairs for that test to mean anything;\n * - the candidate — not the baseline — winning those pairs;\n * - no accuracy regression on a held-out split the calibration never saw;\n * - no unreviewed behavior changes surfaced by golden diffing; and\n * - an explicit human approval.\n *\n * \"The agent tried again\" is not the same as \"the harness improved\", so a\n * candidate that merely fails to be worse is rejected, and malformed evidence\n * fails closed rather than defaulting to promotion.\n */\n\n/** Held-in McNemar outcome. Wins are counted in discordant pairs only. */\nexport interface RouterHeldInEvidence {\n\t/** Rows the baseline classified correctly and the candidate got wrong. */\n\treadonly baselineWins: number;\n\t/** Rows the candidate classified correctly and the baseline got wrong. */\n\treadonly candidateWins: number;\n\treadonly pValue: number;\n\treadonly significant: boolean;\n}\n\n/** Accuracy on a split withheld from calibration. */\nexport interface RouterHoldoutEvidence {\n\treadonly baselineCorrect: number;\n\treadonly candidateCorrect: number;\n\treadonly total: number;\n}\n\n/** Everything the gate is allowed to consider. */\nexport interface RouterPromotionEvidence {\n\treadonly heldIn: RouterHeldInEvidence;\n\treadonly holdout: RouterHoldoutEvidence;\n\t/** Golden-diff changes still unreviewed at decision time. */\n\treadonly goldenChanges: number;\n\t/** True only when a human explicitly approved this exact candidate. */\n\treadonly humanApproved: boolean;\n}\n\n/** Tunable thresholds. Loosening these can never authorize a regression. */\nexport interface RouterPromotionPolicy {\n\t/** Minimum discordant pairs before McNemar's verdict is trusted. */\n\treadonly minDiscordant: number;\n\t/** Minimum held-out rows before \"no regression\" is meaningful. */\n\treadonly minHoldout: number;\n\t/** Maximum unreviewed golden-diff changes tolerated. */\n\treadonly maxGoldenChanges: number;\n\treadonly requireHumanApproval: boolean;\n}\n\nexport type RouterPromotionBlocker =\n\t| \"malformed_evidence\"\n\t| \"insufficient_discordant_pairs\"\n\t| \"mcnemar_not_significant\"\n\t| \"candidate_not_favored\"\n\t| \"holdout_regression\"\n\t| \"insufficient_holdout\"\n\t| \"unreviewed_behavior_changes\"\n\t| \"human_approval_missing\";\n\nexport interface RouterPromotionVerdict {\n\treadonly promote: boolean;\n\t/** Every independent reason promotion was refused, in evaluation order. */\n\treadonly blockers: readonly RouterPromotionBlocker[];\n}\n\n/**\n * Conservative defaults. `minDiscordant` of 20 keeps McNemar from ruling on a\n * handful of pairs; `maxGoldenChanges` of 0 means a reviewer accepts behavior\n * changes by regenerating the golden set, never by tolerating drift.\n *\n * `minHoldout` is 40 because the router gold set freezes 42 holdout rows (6 per\n * class × 7 classes). A higher bar would be unsatisfiable rather than strict,\n * and a gate that can never pass is a gate nobody runs.\n */\nexport const DEFAULT_ROUTER_PROMOTION_POLICY: RouterPromotionPolicy = {\n\tmaxGoldenChanges: 0,\n\tminDiscordant: 20,\n\tminHoldout: 40,\n\trequireHumanApproval: true,\n};\n\nfunction isCount(value: number): boolean {\n\treturn Number.isFinite(value) && value >= 0;\n}\n\nfunction isMalformed(evidence: RouterPromotionEvidence): boolean {\n\tconst { goldenChanges, heldIn, holdout } = evidence;\n\tif (!isCount(heldIn.baselineWins) || !isCount(heldIn.candidateWins)) return true;\n\tif (!Number.isFinite(heldIn.pValue) || heldIn.pValue < 0 || heldIn.pValue > 1) return true;\n\tif (!isCount(holdout.baselineCorrect) || !isCount(holdout.candidateCorrect) || !isCount(holdout.total)) return true;\n\tif (holdout.baselineCorrect > holdout.total || holdout.candidateCorrect > holdout.total) return true;\n\treturn !isCount(goldenChanges);\n}\n\n/**\n * Decide whether a candidate weight set may replace the active one.\n *\n * Returns every independent blocker rather than the first, so one calibration\n * run reports the full distance to promotion instead of revealing it one\n * rejection at a time. Malformed evidence short-circuits: the gate never\n * reasons about numbers it cannot trust.\n */\nexport function evaluateRouterPromotion(\n\tevidence: RouterPromotionEvidence,\n\tpolicy: RouterPromotionPolicy = DEFAULT_ROUTER_PROMOTION_POLICY,\n): RouterPromotionVerdict {\n\tif (isMalformed(evidence)) {\n\t\treturn { blockers: [\"malformed_evidence\"], promote: false };\n\t}\n\n\tconst { goldenChanges, heldIn, holdout, humanApproved } = evidence;\n\tconst blockers: RouterPromotionBlocker[] = [];\n\n\tif (heldIn.baselineWins + heldIn.candidateWins < policy.minDiscordant) {\n\t\tblockers.push(\"insufficient_discordant_pairs\");\n\t}\n\tif (!heldIn.significant) {\n\t\tblockers.push(\"mcnemar_not_significant\");\n\t}\n\tif (heldIn.candidateWins <= heldIn.baselineWins) {\n\t\tblockers.push(\"candidate_not_favored\");\n\t}\n\tif (holdout.total < policy.minHoldout) {\n\t\tblockers.push(\"insufficient_holdout\");\n\t}\n\tif (holdout.candidateCorrect < holdout.baselineCorrect) {\n\t\tblockers.push(\"holdout_regression\");\n\t}\n\tif (goldenChanges > policy.maxGoldenChanges) {\n\t\tblockers.push(\"unreviewed_behavior_changes\");\n\t}\n\tif (policy.requireHumanApproval && !humanApproved) {\n\t\tblockers.push(\"human_approval_missing\");\n\t}\n\n\treturn { blockers, promote: blockers.length === 0 };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"reasoning-router-promotion.d.ts","sourceRoot":"","sources":["../../src/core/reasoning-router-promotion.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,0EAA0E;AAC1E,MAAM,WAAW,oBAAoB;IACpC,0EAA0E;IAC1E,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,0EAA0E;IAC1E,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC9B;AAED,qDAAqD;AACrD,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED,oEAAoE;AACpE,MAAM,WAAW,uBAAuB;IACvC,yEAAyE;IACzE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAAG,kBAAkB,GAAG,QAAQ,CAAC;AAE/D,kDAAkD;AAClD,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,qBAAqB,CAAC;IACxC,sDAAsD;IACtD,QAAQ,CAAC,SAAS,EAAE,uBAAuB,CAAC;IAC5C,sDAAsD;IACtD,QAAQ,CAAC,YAAY,EAAE,kBAAkB,CAAC;IAC1C,6DAA6D;IAC7D,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,uEAAuE;IACvE,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;CAChC;AAED,4EAA4E;AAC5E,MAAM,WAAW,qBAAqB;IACrC,oEAAoE;IACpE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,kEAAkE;IAClE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,wDAAwD;IACxD,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,uDAAuD;IACvD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,sEAAsE;IACtE,QAAQ,CAAC,qBAAqB,EAAE,OAAO,CAAC;IACxC,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC;CACvC;AAED,MAAM,MAAM,sBAAsB,GAC/B,oBAAoB,GACpB,sBAAsB,GACtB,mBAAmB,GACnB,qBAAqB,GACrB,+BAA+B,GAC/B,yBAAyB,GACzB,uBAAuB,GACvB,oBAAoB,GACpB,sBAAsB,GACtB,6BAA6B,GAC7B,wBAAwB,CAAC;AAE5B,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,SAAS,sBAAsB,EAAE,CAAC;CACrD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,+BAA+B,EAAE,qBAQ7C,CAAC;AAiBF;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACtC,QAAQ,EAAE,uBAAuB,EACjC,MAAM,GAAE,qBAAuD,GAC7D,sBAAsB,CAwCxB","sourcesContent":["/**\n * Promotion gate for learned reasoning-router weights.\n *\n * The router already produces a feedback ledger (`router-feedback-collector.ts`)\n * and an offline calibration stack (`scripts/reasoning-router/`: calibration,\n * held-out evaluation, golden diffing, and McNemar's exact test). What was\n * missing is the decision policy that makes automatic promotion trustworthy:\n * without it the loop is open and every weight change is a judgement call.\n *\n * This module is that policy, as a pure total function. It deliberately refuses\n * to promote on anything short of unanimous evidence:\n *\n * - outcomes that survived a two-run replay, so a flipped measurement is never\n * credited as a win (see `reasoning-router-replay-stability.ts`);\n * - a comparison against the frozen reference policy rather than an opponent\n * the caller picked, which is what stops baseline shopping;\n * - a statistically significant win on the held-in gold set (McNemar's exact\n * test), computed by the caller and passed in;\n * - enough discordant pairs for that test to mean anything;\n * - the candidate — not the baseline — winning those pairs;\n * - no accuracy regression on a held-out split the calibration never saw;\n * - no unreviewed behavior changes surfaced by golden diffing; and\n * - an explicit human approval.\n *\n * \"The agent tried again\" is not the same as \"the harness improved\", so a\n * candidate that merely fails to be worse is rejected, and malformed evidence\n * fails closed rather than defaulting to promotion.\n */\n\n/** Held-in McNemar outcome. Wins are counted in discordant pairs only. */\nexport interface RouterHeldInEvidence {\n\t/** Rows the baseline classified correctly and the candidate got wrong. */\n\treadonly baselineWins: number;\n\t/** Rows the candidate classified correctly and the baseline got wrong. */\n\treadonly candidateWins: number;\n\treadonly pValue: number;\n\treadonly significant: boolean;\n}\n\n/** Accuracy on a split withheld from calibration. */\nexport interface RouterHoldoutEvidence {\n\treadonly baselineCorrect: number;\n\treadonly candidateCorrect: number;\n\treadonly total: number;\n}\n\n/** How much of the evidence survived repeated identical replays. */\nexport interface RouterStabilityEvidence {\n\t/** Rows replayed under both policies, whatever bucket they landed in. */\n\treadonly evaluated: number;\n\t/** Rows withheld because repeated observations disagreed. */\n\treadonly unstable: number;\n\t/** Independent observations per row behind the credited outcomes. */\n\treadonly replays: number;\n}\n\n/**\n * What the candidate was measured against.\n *\n * `frozen_reference` is the shipped policy that learning never updates. A\n * candidate that only beats an opponent the caller chose has demonstrated\n * nothing, so the gate refuses to reason about `ad_hoc` comparisons.\n */\nexport type RouterBaselineKind = \"frozen_reference\" | \"ad_hoc\";\n\n/** Everything the gate is allowed to consider. */\nexport interface RouterPromotionEvidence {\n\treadonly heldIn: RouterHeldInEvidence;\n\treadonly holdout: RouterHoldoutEvidence;\n\t/** Replay agreement behind every credited outcome. */\n\treadonly stability: RouterStabilityEvidence;\n\t/** Which opponent produced `heldIn` and `holdout`. */\n\treadonly baselineKind: RouterBaselineKind;\n\t/** Golden-diff changes still unreviewed at decision time. */\n\treadonly goldenChanges: number;\n\t/** True only when a human explicitly approved this exact candidate. */\n\treadonly humanApproved: boolean;\n}\n\n/** Tunable thresholds. Loosening these can never authorize a regression. */\nexport interface RouterPromotionPolicy {\n\t/** Minimum discordant pairs before McNemar's verdict is trusted. */\n\treadonly minDiscordant: number;\n\t/** Minimum held-out rows before \"no regression\" is meaningful. */\n\treadonly minHoldout: number;\n\t/** Maximum unreviewed golden-diff changes tolerated. */\n\treadonly maxGoldenChanges: number;\n\t/** Independent replays required per row before any outcome carries credit. */\n\treadonly minReplays: number;\n\t/** Maximum rows allowed to disagree across replays. */\n\treadonly maxUnstable: number;\n\t/** Require the frozen reference policy as the comparison opponent. */\n\treadonly requireFrozenBaseline: boolean;\n\treadonly requireHumanApproval: boolean;\n}\n\nexport type RouterPromotionBlocker =\n\t| \"malformed_evidence\"\n\t| \"insufficient_replays\"\n\t| \"unstable_evidence\"\n\t| \"baseline_not_frozen\"\n\t| \"insufficient_discordant_pairs\"\n\t| \"mcnemar_not_significant\"\n\t| \"candidate_not_favored\"\n\t| \"holdout_regression\"\n\t| \"insufficient_holdout\"\n\t| \"unreviewed_behavior_changes\"\n\t| \"human_approval_missing\";\n\nexport interface RouterPromotionVerdict {\n\treadonly promote: boolean;\n\t/** Every independent reason promotion was refused, in evaluation order. */\n\treadonly blockers: readonly RouterPromotionBlocker[];\n}\n\n/**\n * Conservative defaults. `minDiscordant` of 20 keeps McNemar from ruling on a\n * handful of pairs; `maxGoldenChanges` of 0 means a reviewer accepts behavior\n * changes by regenerating the golden set, never by tolerating drift.\n *\n * `minHoldout` is 40 because the router gold set freezes 42 holdout rows (6 per\n * class × 7 classes). A higher bar would be unsatisfiable rather than strict,\n * and a gate that can never pass is a gate nobody runs.\n *\n * `minReplays` of 2 is the two-run rule: one observation cannot be checked for\n * stability at all. `maxUnstable` of 0 follows from what instability means here\n * — the routing path is deterministic, so a single disagreeing row is a defect\n * in the measurement, not an acceptable error rate to average over.\n */\nexport const DEFAULT_ROUTER_PROMOTION_POLICY: RouterPromotionPolicy = {\n\tmaxGoldenChanges: 0,\n\tmaxUnstable: 0,\n\tminDiscordant: 20,\n\tminHoldout: 40,\n\tminReplays: 2,\n\trequireFrozenBaseline: true,\n\trequireHumanApproval: true,\n};\n\nfunction isCount(value: number): boolean {\n\treturn Number.isFinite(value) && value >= 0;\n}\n\nfunction isMalformed(evidence: RouterPromotionEvidence): boolean {\n\tconst { goldenChanges, heldIn, holdout, stability } = evidence;\n\tif (!isCount(heldIn.baselineWins) || !isCount(heldIn.candidateWins)) return true;\n\tif (!Number.isFinite(heldIn.pValue) || heldIn.pValue < 0 || heldIn.pValue > 1) return true;\n\tif (!isCount(holdout.baselineCorrect) || !isCount(holdout.candidateCorrect) || !isCount(holdout.total)) return true;\n\tif (holdout.baselineCorrect > holdout.total || holdout.candidateCorrect > holdout.total) return true;\n\tif (!isCount(stability.evaluated) || !isCount(stability.unstable) || !isCount(stability.replays)) return true;\n\tif (stability.unstable > stability.evaluated) return true;\n\treturn !isCount(goldenChanges);\n}\n\n/**\n * Decide whether a candidate weight set may replace the active one.\n *\n * Returns every independent blocker rather than the first, so one calibration\n * run reports the full distance to promotion instead of revealing it one\n * rejection at a time. Malformed evidence short-circuits: the gate never\n * reasons about numbers it cannot trust.\n *\n * Evidence-integrity blockers are reported ahead of statistical ones. A p-value\n * computed over unstable rows or against the wrong opponent is not a weaker\n * result, it is a result about nothing, and the report should say so first.\n */\nexport function evaluateRouterPromotion(\n\tevidence: RouterPromotionEvidence,\n\tpolicy: RouterPromotionPolicy = DEFAULT_ROUTER_PROMOTION_POLICY,\n): RouterPromotionVerdict {\n\tif (isMalformed(evidence)) {\n\t\treturn { blockers: [\"malformed_evidence\"], promote: false };\n\t}\n\n\tconst { baselineKind, goldenChanges, heldIn, holdout, humanApproved, stability } = evidence;\n\tconst blockers: RouterPromotionBlocker[] = [];\n\n\tif (stability.replays < policy.minReplays) {\n\t\tblockers.push(\"insufficient_replays\");\n\t}\n\tif (stability.unstable > policy.maxUnstable) {\n\t\tblockers.push(\"unstable_evidence\");\n\t}\n\tif (policy.requireFrozenBaseline && baselineKind !== \"frozen_reference\") {\n\t\tblockers.push(\"baseline_not_frozen\");\n\t}\n\tif (heldIn.baselineWins + heldIn.candidateWins < policy.minDiscordant) {\n\t\tblockers.push(\"insufficient_discordant_pairs\");\n\t}\n\tif (!heldIn.significant) {\n\t\tblockers.push(\"mcnemar_not_significant\");\n\t}\n\tif (heldIn.candidateWins <= heldIn.baselineWins) {\n\t\tblockers.push(\"candidate_not_favored\");\n\t}\n\tif (holdout.total < policy.minHoldout) {\n\t\tblockers.push(\"insufficient_holdout\");\n\t}\n\tif (holdout.candidateCorrect < holdout.baselineCorrect) {\n\t\tblockers.push(\"holdout_regression\");\n\t}\n\tif (goldenChanges > policy.maxGoldenChanges) {\n\t\tblockers.push(\"unreviewed_behavior_changes\");\n\t}\n\tif (policy.requireHumanApproval && !humanApproved) {\n\t\tblockers.push(\"human_approval_missing\");\n\t}\n\n\treturn { blockers, promote: blockers.length === 0 };\n}\n"]}
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
* This module is that policy, as a pure total function. It deliberately refuses
|
|
11
11
|
* to promote on anything short of unanimous evidence:
|
|
12
12
|
*
|
|
13
|
+
* - outcomes that survived a two-run replay, so a flipped measurement is never
|
|
14
|
+
* credited as a win (see `reasoning-router-replay-stability.ts`);
|
|
15
|
+
* - a comparison against the frozen reference policy rather than an opponent
|
|
16
|
+
* the caller picked, which is what stops baseline shopping;
|
|
13
17
|
* - a statistically significant win on the held-in gold set (McNemar's exact
|
|
14
18
|
* test), computed by the caller and passed in;
|
|
15
19
|
* - enough discordant pairs for that test to mean anything;
|
|
@@ -30,18 +34,26 @@
|
|
|
30
34
|
* `minHoldout` is 40 because the router gold set freezes 42 holdout rows (6 per
|
|
31
35
|
* class × 7 classes). A higher bar would be unsatisfiable rather than strict,
|
|
32
36
|
* and a gate that can never pass is a gate nobody runs.
|
|
37
|
+
*
|
|
38
|
+
* `minReplays` of 2 is the two-run rule: one observation cannot be checked for
|
|
39
|
+
* stability at all. `maxUnstable` of 0 follows from what instability means here
|
|
40
|
+
* — the routing path is deterministic, so a single disagreeing row is a defect
|
|
41
|
+
* in the measurement, not an acceptable error rate to average over.
|
|
33
42
|
*/
|
|
34
43
|
export const DEFAULT_ROUTER_PROMOTION_POLICY = {
|
|
35
44
|
maxGoldenChanges: 0,
|
|
45
|
+
maxUnstable: 0,
|
|
36
46
|
minDiscordant: 20,
|
|
37
47
|
minHoldout: 40,
|
|
48
|
+
minReplays: 2,
|
|
49
|
+
requireFrozenBaseline: true,
|
|
38
50
|
requireHumanApproval: true,
|
|
39
51
|
};
|
|
40
52
|
function isCount(value) {
|
|
41
53
|
return Number.isFinite(value) && value >= 0;
|
|
42
54
|
}
|
|
43
55
|
function isMalformed(evidence) {
|
|
44
|
-
const { goldenChanges, heldIn, holdout } = evidence;
|
|
56
|
+
const { goldenChanges, heldIn, holdout, stability } = evidence;
|
|
45
57
|
if (!isCount(heldIn.baselineWins) || !isCount(heldIn.candidateWins))
|
|
46
58
|
return true;
|
|
47
59
|
if (!Number.isFinite(heldIn.pValue) || heldIn.pValue < 0 || heldIn.pValue > 1)
|
|
@@ -50,6 +62,10 @@ function isMalformed(evidence) {
|
|
|
50
62
|
return true;
|
|
51
63
|
if (holdout.baselineCorrect > holdout.total || holdout.candidateCorrect > holdout.total)
|
|
52
64
|
return true;
|
|
65
|
+
if (!isCount(stability.evaluated) || !isCount(stability.unstable) || !isCount(stability.replays))
|
|
66
|
+
return true;
|
|
67
|
+
if (stability.unstable > stability.evaluated)
|
|
68
|
+
return true;
|
|
53
69
|
return !isCount(goldenChanges);
|
|
54
70
|
}
|
|
55
71
|
/**
|
|
@@ -59,13 +75,26 @@ function isMalformed(evidence) {
|
|
|
59
75
|
* run reports the full distance to promotion instead of revealing it one
|
|
60
76
|
* rejection at a time. Malformed evidence short-circuits: the gate never
|
|
61
77
|
* reasons about numbers it cannot trust.
|
|
78
|
+
*
|
|
79
|
+
* Evidence-integrity blockers are reported ahead of statistical ones. A p-value
|
|
80
|
+
* computed over unstable rows or against the wrong opponent is not a weaker
|
|
81
|
+
* result, it is a result about nothing, and the report should say so first.
|
|
62
82
|
*/
|
|
63
83
|
export function evaluateRouterPromotion(evidence, policy = DEFAULT_ROUTER_PROMOTION_POLICY) {
|
|
64
84
|
if (isMalformed(evidence)) {
|
|
65
85
|
return { blockers: ["malformed_evidence"], promote: false };
|
|
66
86
|
}
|
|
67
|
-
const { goldenChanges, heldIn, holdout, humanApproved } = evidence;
|
|
87
|
+
const { baselineKind, goldenChanges, heldIn, holdout, humanApproved, stability } = evidence;
|
|
68
88
|
const blockers = [];
|
|
89
|
+
if (stability.replays < policy.minReplays) {
|
|
90
|
+
blockers.push("insufficient_replays");
|
|
91
|
+
}
|
|
92
|
+
if (stability.unstable > policy.maxUnstable) {
|
|
93
|
+
blockers.push("unstable_evidence");
|
|
94
|
+
}
|
|
95
|
+
if (policy.requireFrozenBaseline && baselineKind !== "frozen_reference") {
|
|
96
|
+
blockers.push("baseline_not_frozen");
|
|
97
|
+
}
|
|
69
98
|
if (heldIn.baselineWins + heldIn.candidateWins < policy.minDiscordant) {
|
|
70
99
|
blockers.push("insufficient_discordant_pairs");
|
|
71
100
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reasoning-router-promotion.js","sourceRoot":"","sources":["../../src/core/reasoning-router-promotion.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Promotion gate for learned reasoning-router weights.\n *\n * The router already produces a feedback ledger (`router-feedback-collector.ts`)\n * and an offline calibration stack (`scripts/reasoning-router/`: calibration,\n * held-out evaluation, golden diffing, and McNemar's exact test). What was\n * missing is the decision policy that makes automatic promotion trustworthy:\n * without it the loop is open and every weight change is a judgement call.\n *\n * This module is that policy, as a pure total function. It deliberately refuses\n * to promote on anything short of unanimous evidence:\n *\n * - a statistically significant win on the held-in gold set (McNemar's exact\n * test), computed by the caller and passed in;\n * - enough discordant pairs for that test to mean anything;\n * - the candidate — not the baseline — winning those pairs;\n * - no accuracy regression on a held-out split the calibration never saw;\n * - no unreviewed behavior changes surfaced by golden diffing; and\n * - an explicit human approval.\n *\n * \"The agent tried again\" is not the same as \"the harness improved\", so a\n * candidate that merely fails to be worse is rejected, and malformed evidence\n * fails closed rather than defaulting to promotion.\n */\n\n/** Held-in McNemar outcome. Wins are counted in discordant pairs only. */\nexport interface RouterHeldInEvidence {\n\t/** Rows the baseline classified correctly and the candidate got wrong. */\n\treadonly baselineWins: number;\n\t/** Rows the candidate classified correctly and the baseline got wrong. */\n\treadonly candidateWins: number;\n\treadonly pValue: number;\n\treadonly significant: boolean;\n}\n\n/** Accuracy on a split withheld from calibration. */\nexport interface RouterHoldoutEvidence {\n\treadonly baselineCorrect: number;\n\treadonly candidateCorrect: number;\n\treadonly total: number;\n}\n\n/** Everything the gate is allowed to consider. */\nexport interface RouterPromotionEvidence {\n\treadonly heldIn: RouterHeldInEvidence;\n\treadonly holdout: RouterHoldoutEvidence;\n\t/** Golden-diff changes still unreviewed at decision time. */\n\treadonly goldenChanges: number;\n\t/** True only when a human explicitly approved this exact candidate. */\n\treadonly humanApproved: boolean;\n}\n\n/** Tunable thresholds. Loosening these can never authorize a regression. */\nexport interface RouterPromotionPolicy {\n\t/** Minimum discordant pairs before McNemar's verdict is trusted. */\n\treadonly minDiscordant: number;\n\t/** Minimum held-out rows before \"no regression\" is meaningful. */\n\treadonly minHoldout: number;\n\t/** Maximum unreviewed golden-diff changes tolerated. */\n\treadonly maxGoldenChanges: number;\n\treadonly requireHumanApproval: boolean;\n}\n\nexport type RouterPromotionBlocker =\n\t| \"malformed_evidence\"\n\t| \"insufficient_discordant_pairs\"\n\t| \"mcnemar_not_significant\"\n\t| \"candidate_not_favored\"\n\t| \"holdout_regression\"\n\t| \"insufficient_holdout\"\n\t| \"unreviewed_behavior_changes\"\n\t| \"human_approval_missing\";\n\nexport interface RouterPromotionVerdict {\n\treadonly promote: boolean;\n\t/** Every independent reason promotion was refused, in evaluation order. */\n\treadonly blockers: readonly RouterPromotionBlocker[];\n}\n\n/**\n * Conservative defaults. `minDiscordant` of 20 keeps McNemar from ruling on a\n * handful of pairs; `maxGoldenChanges` of 0 means a reviewer accepts behavior\n * changes by regenerating the golden set, never by tolerating drift.\n *\n * `minHoldout` is 40 because the router gold set freezes 42 holdout rows (6 per\n * class × 7 classes). A higher bar would be unsatisfiable rather than strict,\n * and a gate that can never pass is a gate nobody runs.\n */\nexport const DEFAULT_ROUTER_PROMOTION_POLICY: RouterPromotionPolicy = {\n\tmaxGoldenChanges: 0,\n\tminDiscordant: 20,\n\tminHoldout: 40,\n\trequireHumanApproval: true,\n};\n\nfunction isCount(value: number): boolean {\n\treturn Number.isFinite(value) && value >= 0;\n}\n\nfunction isMalformed(evidence: RouterPromotionEvidence): boolean {\n\tconst { goldenChanges, heldIn, holdout } = evidence;\n\tif (!isCount(heldIn.baselineWins) || !isCount(heldIn.candidateWins)) return true;\n\tif (!Number.isFinite(heldIn.pValue) || heldIn.pValue < 0 || heldIn.pValue > 1) return true;\n\tif (!isCount(holdout.baselineCorrect) || !isCount(holdout.candidateCorrect) || !isCount(holdout.total)) return true;\n\tif (holdout.baselineCorrect > holdout.total || holdout.candidateCorrect > holdout.total) return true;\n\treturn !isCount(goldenChanges);\n}\n\n/**\n * Decide whether a candidate weight set may replace the active one.\n *\n * Returns every independent blocker rather than the first, so one calibration\n * run reports the full distance to promotion instead of revealing it one\n * rejection at a time. Malformed evidence short-circuits: the gate never\n * reasons about numbers it cannot trust.\n */\nexport function evaluateRouterPromotion(\n\tevidence: RouterPromotionEvidence,\n\tpolicy: RouterPromotionPolicy = DEFAULT_ROUTER_PROMOTION_POLICY,\n): RouterPromotionVerdict {\n\tif (isMalformed(evidence)) {\n\t\treturn { blockers: [\"malformed_evidence\"], promote: false };\n\t}\n\n\tconst { goldenChanges, heldIn, holdout, humanApproved } = evidence;\n\tconst blockers: RouterPromotionBlocker[] = [];\n\n\tif (heldIn.baselineWins + heldIn.candidateWins < policy.minDiscordant) {\n\t\tblockers.push(\"insufficient_discordant_pairs\");\n\t}\n\tif (!heldIn.significant) {\n\t\tblockers.push(\"mcnemar_not_significant\");\n\t}\n\tif (heldIn.candidateWins <= heldIn.baselineWins) {\n\t\tblockers.push(\"candidate_not_favored\");\n\t}\n\tif (holdout.total < policy.minHoldout) {\n\t\tblockers.push(\"insufficient_holdout\");\n\t}\n\tif (holdout.candidateCorrect < holdout.baselineCorrect) {\n\t\tblockers.push(\"holdout_regression\");\n\t}\n\tif (goldenChanges > policy.maxGoldenChanges) {\n\t\tblockers.push(\"unreviewed_behavior_changes\");\n\t}\n\tif (policy.requireHumanApproval && !humanApproved) {\n\t\tblockers.push(\"human_approval_missing\");\n\t}\n\n\treturn { blockers, promote: blockers.length === 0 };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"reasoning-router-promotion.js","sourceRoot":"","sources":["../../src/core/reasoning-router-promotion.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Promotion gate for learned reasoning-router weights.\n *\n * The router already produces a feedback ledger (`router-feedback-collector.ts`)\n * and an offline calibration stack (`scripts/reasoning-router/`: calibration,\n * held-out evaluation, golden diffing, and McNemar's exact test). What was\n * missing is the decision policy that makes automatic promotion trustworthy:\n * without it the loop is open and every weight change is a judgement call.\n *\n * This module is that policy, as a pure total function. It deliberately refuses\n * to promote on anything short of unanimous evidence:\n *\n * - outcomes that survived a two-run replay, so a flipped measurement is never\n * credited as a win (see `reasoning-router-replay-stability.ts`);\n * - a comparison against the frozen reference policy rather than an opponent\n * the caller picked, which is what stops baseline shopping;\n * - a statistically significant win on the held-in gold set (McNemar's exact\n * test), computed by the caller and passed in;\n * - enough discordant pairs for that test to mean anything;\n * - the candidate — not the baseline — winning those pairs;\n * - no accuracy regression on a held-out split the calibration never saw;\n * - no unreviewed behavior changes surfaced by golden diffing; and\n * - an explicit human approval.\n *\n * \"The agent tried again\" is not the same as \"the harness improved\", so a\n * candidate that merely fails to be worse is rejected, and malformed evidence\n * fails closed rather than defaulting to promotion.\n */\n\n/** Held-in McNemar outcome. Wins are counted in discordant pairs only. */\nexport interface RouterHeldInEvidence {\n\t/** Rows the baseline classified correctly and the candidate got wrong. */\n\treadonly baselineWins: number;\n\t/** Rows the candidate classified correctly and the baseline got wrong. */\n\treadonly candidateWins: number;\n\treadonly pValue: number;\n\treadonly significant: boolean;\n}\n\n/** Accuracy on a split withheld from calibration. */\nexport interface RouterHoldoutEvidence {\n\treadonly baselineCorrect: number;\n\treadonly candidateCorrect: number;\n\treadonly total: number;\n}\n\n/** How much of the evidence survived repeated identical replays. */\nexport interface RouterStabilityEvidence {\n\t/** Rows replayed under both policies, whatever bucket they landed in. */\n\treadonly evaluated: number;\n\t/** Rows withheld because repeated observations disagreed. */\n\treadonly unstable: number;\n\t/** Independent observations per row behind the credited outcomes. */\n\treadonly replays: number;\n}\n\n/**\n * What the candidate was measured against.\n *\n * `frozen_reference` is the shipped policy that learning never updates. A\n * candidate that only beats an opponent the caller chose has demonstrated\n * nothing, so the gate refuses to reason about `ad_hoc` comparisons.\n */\nexport type RouterBaselineKind = \"frozen_reference\" | \"ad_hoc\";\n\n/** Everything the gate is allowed to consider. */\nexport interface RouterPromotionEvidence {\n\treadonly heldIn: RouterHeldInEvidence;\n\treadonly holdout: RouterHoldoutEvidence;\n\t/** Replay agreement behind every credited outcome. */\n\treadonly stability: RouterStabilityEvidence;\n\t/** Which opponent produced `heldIn` and `holdout`. */\n\treadonly baselineKind: RouterBaselineKind;\n\t/** Golden-diff changes still unreviewed at decision time. */\n\treadonly goldenChanges: number;\n\t/** True only when a human explicitly approved this exact candidate. */\n\treadonly humanApproved: boolean;\n}\n\n/** Tunable thresholds. Loosening these can never authorize a regression. */\nexport interface RouterPromotionPolicy {\n\t/** Minimum discordant pairs before McNemar's verdict is trusted. */\n\treadonly minDiscordant: number;\n\t/** Minimum held-out rows before \"no regression\" is meaningful. */\n\treadonly minHoldout: number;\n\t/** Maximum unreviewed golden-diff changes tolerated. */\n\treadonly maxGoldenChanges: number;\n\t/** Independent replays required per row before any outcome carries credit. */\n\treadonly minReplays: number;\n\t/** Maximum rows allowed to disagree across replays. */\n\treadonly maxUnstable: number;\n\t/** Require the frozen reference policy as the comparison opponent. */\n\treadonly requireFrozenBaseline: boolean;\n\treadonly requireHumanApproval: boolean;\n}\n\nexport type RouterPromotionBlocker =\n\t| \"malformed_evidence\"\n\t| \"insufficient_replays\"\n\t| \"unstable_evidence\"\n\t| \"baseline_not_frozen\"\n\t| \"insufficient_discordant_pairs\"\n\t| \"mcnemar_not_significant\"\n\t| \"candidate_not_favored\"\n\t| \"holdout_regression\"\n\t| \"insufficient_holdout\"\n\t| \"unreviewed_behavior_changes\"\n\t| \"human_approval_missing\";\n\nexport interface RouterPromotionVerdict {\n\treadonly promote: boolean;\n\t/** Every independent reason promotion was refused, in evaluation order. */\n\treadonly blockers: readonly RouterPromotionBlocker[];\n}\n\n/**\n * Conservative defaults. `minDiscordant` of 20 keeps McNemar from ruling on a\n * handful of pairs; `maxGoldenChanges` of 0 means a reviewer accepts behavior\n * changes by regenerating the golden set, never by tolerating drift.\n *\n * `minHoldout` is 40 because the router gold set freezes 42 holdout rows (6 per\n * class × 7 classes). A higher bar would be unsatisfiable rather than strict,\n * and a gate that can never pass is a gate nobody runs.\n *\n * `minReplays` of 2 is the two-run rule: one observation cannot be checked for\n * stability at all. `maxUnstable` of 0 follows from what instability means here\n * — the routing path is deterministic, so a single disagreeing row is a defect\n * in the measurement, not an acceptable error rate to average over.\n */\nexport const DEFAULT_ROUTER_PROMOTION_POLICY: RouterPromotionPolicy = {\n\tmaxGoldenChanges: 0,\n\tmaxUnstable: 0,\n\tminDiscordant: 20,\n\tminHoldout: 40,\n\tminReplays: 2,\n\trequireFrozenBaseline: true,\n\trequireHumanApproval: true,\n};\n\nfunction isCount(value: number): boolean {\n\treturn Number.isFinite(value) && value >= 0;\n}\n\nfunction isMalformed(evidence: RouterPromotionEvidence): boolean {\n\tconst { goldenChanges, heldIn, holdout, stability } = evidence;\n\tif (!isCount(heldIn.baselineWins) || !isCount(heldIn.candidateWins)) return true;\n\tif (!Number.isFinite(heldIn.pValue) || heldIn.pValue < 0 || heldIn.pValue > 1) return true;\n\tif (!isCount(holdout.baselineCorrect) || !isCount(holdout.candidateCorrect) || !isCount(holdout.total)) return true;\n\tif (holdout.baselineCorrect > holdout.total || holdout.candidateCorrect > holdout.total) return true;\n\tif (!isCount(stability.evaluated) || !isCount(stability.unstable) || !isCount(stability.replays)) return true;\n\tif (stability.unstable > stability.evaluated) return true;\n\treturn !isCount(goldenChanges);\n}\n\n/**\n * Decide whether a candidate weight set may replace the active one.\n *\n * Returns every independent blocker rather than the first, so one calibration\n * run reports the full distance to promotion instead of revealing it one\n * rejection at a time. Malformed evidence short-circuits: the gate never\n * reasons about numbers it cannot trust.\n *\n * Evidence-integrity blockers are reported ahead of statistical ones. A p-value\n * computed over unstable rows or against the wrong opponent is not a weaker\n * result, it is a result about nothing, and the report should say so first.\n */\nexport function evaluateRouterPromotion(\n\tevidence: RouterPromotionEvidence,\n\tpolicy: RouterPromotionPolicy = DEFAULT_ROUTER_PROMOTION_POLICY,\n): RouterPromotionVerdict {\n\tif (isMalformed(evidence)) {\n\t\treturn { blockers: [\"malformed_evidence\"], promote: false };\n\t}\n\n\tconst { baselineKind, goldenChanges, heldIn, holdout, humanApproved, stability } = evidence;\n\tconst blockers: RouterPromotionBlocker[] = [];\n\n\tif (stability.replays < policy.minReplays) {\n\t\tblockers.push(\"insufficient_replays\");\n\t}\n\tif (stability.unstable > policy.maxUnstable) {\n\t\tblockers.push(\"unstable_evidence\");\n\t}\n\tif (policy.requireFrozenBaseline && baselineKind !== \"frozen_reference\") {\n\t\tblockers.push(\"baseline_not_frozen\");\n\t}\n\tif (heldIn.baselineWins + heldIn.candidateWins < policy.minDiscordant) {\n\t\tblockers.push(\"insufficient_discordant_pairs\");\n\t}\n\tif (!heldIn.significant) {\n\t\tblockers.push(\"mcnemar_not_significant\");\n\t}\n\tif (heldIn.candidateWins <= heldIn.baselineWins) {\n\t\tblockers.push(\"candidate_not_favored\");\n\t}\n\tif (holdout.total < policy.minHoldout) {\n\t\tblockers.push(\"insufficient_holdout\");\n\t}\n\tif (holdout.candidateCorrect < holdout.baselineCorrect) {\n\t\tblockers.push(\"holdout_regression\");\n\t}\n\tif (goldenChanges > policy.maxGoldenChanges) {\n\t\tblockers.push(\"unreviewed_behavior_changes\");\n\t}\n\tif (policy.requireHumanApproval && !humanApproved) {\n\t\tblockers.push(\"human_approval_missing\");\n\t}\n\n\treturn { blockers, promote: blockers.length === 0 };\n}\n"]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two-run stability filter for reasoning-router promotion evidence.
|
|
3
|
+
*
|
|
4
|
+
* A promotion gate is only as trustworthy as the outcomes it credits. Routing
|
|
5
|
+
* evaluations are paired comparisons, and paired comparisons are cheap to fool:
|
|
6
|
+
* if the measured outcome of a row can flip between two identical replays, then
|
|
7
|
+
* a "win" on that row may be measurement noise rather than a better policy.
|
|
8
|
+
* Published routing evaluations report exactly this — a non-trivial share of
|
|
9
|
+
* model/question pairs score differently when the identical matrix is re-run —
|
|
10
|
+
* so a credit rule that ignores rerun disagreement will happily promote noise.
|
|
11
|
+
*
|
|
12
|
+
* This module implements the rule that closes that hole: a row carries credit
|
|
13
|
+
* only when it was observed at least `minReplays` times under each policy and
|
|
14
|
+
* every one of those observations agreed. Rows that disagree are withheld, not
|
|
15
|
+
* majority-voted — a flipped observation means the measurement is unreliable,
|
|
16
|
+
* not that the majority is the truth.
|
|
17
|
+
*
|
|
18
|
+
* For a deterministic classifier every row is stable by construction, so the
|
|
19
|
+
* filter is also a determinism attestation: if nondeterminism ever leaks into
|
|
20
|
+
* the routing path (iteration order, clock, randomness), rows start landing in
|
|
21
|
+
* the unstable bucket and the gate refuses to promote instead of crediting a
|
|
22
|
+
* coin flip.
|
|
23
|
+
*
|
|
24
|
+
* Pure and total: no I/O, no global state, no prompt text.
|
|
25
|
+
*/
|
|
26
|
+
/** Independent observations required per side before a row may carry credit. */
|
|
27
|
+
export declare const DEFAULT_REPLAY_MIN = 2;
|
|
28
|
+
/** One evaluated row, replayed independently under each policy. */
|
|
29
|
+
export interface RouterReplayRow {
|
|
30
|
+
readonly rowId: string;
|
|
31
|
+
/** True when the row belongs to the frozen held-out split. */
|
|
32
|
+
readonly holdout: boolean;
|
|
33
|
+
/** Correctness observed on each independent replay under the baseline. */
|
|
34
|
+
readonly baselineReplays: readonly boolean[];
|
|
35
|
+
/** Correctness observed on each independent replay under the candidate. */
|
|
36
|
+
readonly candidateReplays: readonly boolean[];
|
|
37
|
+
}
|
|
38
|
+
/** A row whose replays agreed, so its outcome may carry promotion credit. */
|
|
39
|
+
export interface RouterStableOutcome {
|
|
40
|
+
readonly rowId: string;
|
|
41
|
+
readonly holdout: boolean;
|
|
42
|
+
readonly baselineCorrect: boolean;
|
|
43
|
+
readonly candidateCorrect: boolean;
|
|
44
|
+
}
|
|
45
|
+
/** Credit-eligible outcomes plus why every other row was withheld. */
|
|
46
|
+
export interface RouterReplayStability {
|
|
47
|
+
readonly stable: readonly RouterStableOutcome[];
|
|
48
|
+
/** Rows considered, whatever bucket they landed in. */
|
|
49
|
+
readonly evaluated: number;
|
|
50
|
+
/** Rows withheld because repeated observations disagreed. */
|
|
51
|
+
readonly unstable: number;
|
|
52
|
+
/** Rows withheld because one side was observed too few times to judge. */
|
|
53
|
+
readonly insufficientReplays: number;
|
|
54
|
+
readonly minReplays: number;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Partition replayed rows into credit-eligible outcomes and withheld rows.
|
|
58
|
+
*
|
|
59
|
+
* Insufficient replays outrank disagreement: a row observed once is not stable
|
|
60
|
+
* evidence that happens to agree with itself, it is evidence that was never
|
|
61
|
+
* tested for stability at all. Reporting the two buckets separately keeps that
|
|
62
|
+
* distinction visible to the gate, which fails closed on either.
|
|
63
|
+
*/
|
|
64
|
+
export declare function summarizeReplayStability(rows: readonly RouterReplayRow[], minReplays?: number): RouterReplayStability;
|
|
65
|
+
//# sourceMappingURL=reasoning-router-replay-stability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reasoning-router-replay-stability.d.ts","sourceRoot":"","sources":["../../src/core/reasoning-router-replay-stability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,gFAAgF;AAChF,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAEpC,mEAAmE;AACnE,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,0EAA0E;IAC1E,QAAQ,CAAC,eAAe,EAAE,SAAS,OAAO,EAAE,CAAC;IAC7C,2EAA2E;IAC3E,QAAQ,CAAC,gBAAgB,EAAE,SAAS,OAAO,EAAE,CAAC;CAC9C;AAED,6EAA6E;AAC7E,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;CACnC;AAED,sEAAsE;AACtE,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,MAAM,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAChD,uDAAuD;IACvD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,0EAA0E;IAC1E,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC5B;AAgBD;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACvC,IAAI,EAAE,SAAS,eAAe,EAAE,EAChC,UAAU,GAAE,MAA2B,GACrC,qBAAqB,CA2BvB","sourcesContent":["/**\n * Two-run stability filter for reasoning-router promotion evidence.\n *\n * A promotion gate is only as trustworthy as the outcomes it credits. Routing\n * evaluations are paired comparisons, and paired comparisons are cheap to fool:\n * if the measured outcome of a row can flip between two identical replays, then\n * a \"win\" on that row may be measurement noise rather than a better policy.\n * Published routing evaluations report exactly this — a non-trivial share of\n * model/question pairs score differently when the identical matrix is re-run —\n * so a credit rule that ignores rerun disagreement will happily promote noise.\n *\n * This module implements the rule that closes that hole: a row carries credit\n * only when it was observed at least `minReplays` times under each policy and\n * every one of those observations agreed. Rows that disagree are withheld, not\n * majority-voted — a flipped observation means the measurement is unreliable,\n * not that the majority is the truth.\n *\n * For a deterministic classifier every row is stable by construction, so the\n * filter is also a determinism attestation: if nondeterminism ever leaks into\n * the routing path (iteration order, clock, randomness), rows start landing in\n * the unstable bucket and the gate refuses to promote instead of crediting a\n * coin flip.\n *\n * Pure and total: no I/O, no global state, no prompt text.\n */\n\n/** Independent observations required per side before a row may carry credit. */\nexport const DEFAULT_REPLAY_MIN = 2;\n\n/** One evaluated row, replayed independently under each policy. */\nexport interface RouterReplayRow {\n\treadonly rowId: string;\n\t/** True when the row belongs to the frozen held-out split. */\n\treadonly holdout: boolean;\n\t/** Correctness observed on each independent replay under the baseline. */\n\treadonly baselineReplays: readonly boolean[];\n\t/** Correctness observed on each independent replay under the candidate. */\n\treadonly candidateReplays: readonly boolean[];\n}\n\n/** A row whose replays agreed, so its outcome may carry promotion credit. */\nexport interface RouterStableOutcome {\n\treadonly rowId: string;\n\treadonly holdout: boolean;\n\treadonly baselineCorrect: boolean;\n\treadonly candidateCorrect: boolean;\n}\n\n/** Credit-eligible outcomes plus why every other row was withheld. */\nexport interface RouterReplayStability {\n\treadonly stable: readonly RouterStableOutcome[];\n\t/** Rows considered, whatever bucket they landed in. */\n\treadonly evaluated: number;\n\t/** Rows withheld because repeated observations disagreed. */\n\treadonly unstable: number;\n\t/** Rows withheld because one side was observed too few times to judge. */\n\treadonly insufficientReplays: number;\n\treadonly minReplays: number;\n}\n\n/** Verdict for one side of one row. */\ntype ReplayVerdict =\n\t| { readonly kind: \"agreed\"; readonly correct: boolean }\n\t| { readonly kind: \"disagreed\" }\n\t| { readonly kind: \"insufficient\" };\n\nfunction judgeReplays(replays: readonly boolean[], minReplays: number): ReplayVerdict {\n\tconst [first, ...rest] = replays;\n\tif (first === undefined || replays.length < minReplays) {\n\t\treturn { kind: \"insufficient\" };\n\t}\n\treturn rest.every((replay) => replay === first) ? { correct: first, kind: \"agreed\" } : { kind: \"disagreed\" };\n}\n\n/**\n * Partition replayed rows into credit-eligible outcomes and withheld rows.\n *\n * Insufficient replays outrank disagreement: a row observed once is not stable\n * evidence that happens to agree with itself, it is evidence that was never\n * tested for stability at all. Reporting the two buckets separately keeps that\n * distinction visible to the gate, which fails closed on either.\n */\nexport function summarizeReplayStability(\n\trows: readonly RouterReplayRow[],\n\tminReplays: number = DEFAULT_REPLAY_MIN,\n): RouterReplayStability {\n\tconst stable: RouterStableOutcome[] = [];\n\tlet unstable = 0;\n\tlet insufficientReplays = 0;\n\n\tfor (const row of rows) {\n\t\tconst baseline = judgeReplays(row.baselineReplays, minReplays);\n\t\tconst candidate = judgeReplays(row.candidateReplays, minReplays);\n\n\t\tif (baseline.kind === \"insufficient\" || candidate.kind === \"insufficient\") {\n\t\t\tinsufficientReplays++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (baseline.kind === \"disagreed\" || candidate.kind === \"disagreed\") {\n\t\t\tunstable++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tstable.push({\n\t\t\tbaselineCorrect: baseline.correct,\n\t\t\tcandidateCorrect: candidate.correct,\n\t\t\tholdout: row.holdout,\n\t\t\trowId: row.rowId,\n\t\t});\n\t}\n\n\treturn { evaluated: rows.length, insufficientReplays, minReplays, stable, unstable };\n}\n"]}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two-run stability filter for reasoning-router promotion evidence.
|
|
3
|
+
*
|
|
4
|
+
* A promotion gate is only as trustworthy as the outcomes it credits. Routing
|
|
5
|
+
* evaluations are paired comparisons, and paired comparisons are cheap to fool:
|
|
6
|
+
* if the measured outcome of a row can flip between two identical replays, then
|
|
7
|
+
* a "win" on that row may be measurement noise rather than a better policy.
|
|
8
|
+
* Published routing evaluations report exactly this — a non-trivial share of
|
|
9
|
+
* model/question pairs score differently when the identical matrix is re-run —
|
|
10
|
+
* so a credit rule that ignores rerun disagreement will happily promote noise.
|
|
11
|
+
*
|
|
12
|
+
* This module implements the rule that closes that hole: a row carries credit
|
|
13
|
+
* only when it was observed at least `minReplays` times under each policy and
|
|
14
|
+
* every one of those observations agreed. Rows that disagree are withheld, not
|
|
15
|
+
* majority-voted — a flipped observation means the measurement is unreliable,
|
|
16
|
+
* not that the majority is the truth.
|
|
17
|
+
*
|
|
18
|
+
* For a deterministic classifier every row is stable by construction, so the
|
|
19
|
+
* filter is also a determinism attestation: if nondeterminism ever leaks into
|
|
20
|
+
* the routing path (iteration order, clock, randomness), rows start landing in
|
|
21
|
+
* the unstable bucket and the gate refuses to promote instead of crediting a
|
|
22
|
+
* coin flip.
|
|
23
|
+
*
|
|
24
|
+
* Pure and total: no I/O, no global state, no prompt text.
|
|
25
|
+
*/
|
|
26
|
+
/** Independent observations required per side before a row may carry credit. */
|
|
27
|
+
export const DEFAULT_REPLAY_MIN = 2;
|
|
28
|
+
function judgeReplays(replays, minReplays) {
|
|
29
|
+
const [first, ...rest] = replays;
|
|
30
|
+
if (first === undefined || replays.length < minReplays) {
|
|
31
|
+
return { kind: "insufficient" };
|
|
32
|
+
}
|
|
33
|
+
return rest.every((replay) => replay === first) ? { correct: first, kind: "agreed" } : { kind: "disagreed" };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Partition replayed rows into credit-eligible outcomes and withheld rows.
|
|
37
|
+
*
|
|
38
|
+
* Insufficient replays outrank disagreement: a row observed once is not stable
|
|
39
|
+
* evidence that happens to agree with itself, it is evidence that was never
|
|
40
|
+
* tested for stability at all. Reporting the two buckets separately keeps that
|
|
41
|
+
* distinction visible to the gate, which fails closed on either.
|
|
42
|
+
*/
|
|
43
|
+
export function summarizeReplayStability(rows, minReplays = DEFAULT_REPLAY_MIN) {
|
|
44
|
+
const stable = [];
|
|
45
|
+
let unstable = 0;
|
|
46
|
+
let insufficientReplays = 0;
|
|
47
|
+
for (const row of rows) {
|
|
48
|
+
const baseline = judgeReplays(row.baselineReplays, minReplays);
|
|
49
|
+
const candidate = judgeReplays(row.candidateReplays, minReplays);
|
|
50
|
+
if (baseline.kind === "insufficient" || candidate.kind === "insufficient") {
|
|
51
|
+
insufficientReplays++;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (baseline.kind === "disagreed" || candidate.kind === "disagreed") {
|
|
55
|
+
unstable++;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
stable.push({
|
|
59
|
+
baselineCorrect: baseline.correct,
|
|
60
|
+
candidateCorrect: candidate.correct,
|
|
61
|
+
holdout: row.holdout,
|
|
62
|
+
rowId: row.rowId,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return { evaluated: rows.length, insufficientReplays, minReplays, stable, unstable };
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=reasoning-router-replay-stability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reasoning-router-replay-stability.js","sourceRoot":"","sources":["../../src/core/reasoning-router-replay-stability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,gFAAgF;AAChF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAuCpC,SAAS,YAAY,CAAC,OAA2B,EAAE,UAAkB,EAAiB;IACrF,MAAM,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC;IACjC,IAAI,KAAK,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,GAAG,UAAU,EAAE,CAAC;QACxD,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;IACjC,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;AAAA,CAC7G;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,wBAAwB,CACvC,IAAgC,EAChC,UAAU,GAAW,kBAAkB,EACf;IACxB,MAAM,MAAM,GAA0B,EAAE,CAAC;IACzC,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,mBAAmB,GAAG,CAAC,CAAC;IAE5B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACxB,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;QAC/D,MAAM,SAAS,GAAG,YAAY,CAAC,GAAG,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAAC;QAEjE,IAAI,QAAQ,CAAC,IAAI,KAAK,cAAc,IAAI,SAAS,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;YAC3E,mBAAmB,EAAE,CAAC;YACtB,SAAS;QACV,CAAC;QACD,IAAI,QAAQ,CAAC,IAAI,KAAK,WAAW,IAAI,SAAS,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YACrE,QAAQ,EAAE,CAAC;YACX,SAAS;QACV,CAAC;QAED,MAAM,CAAC,IAAI,CAAC;YACX,eAAe,EAAE,QAAQ,CAAC,OAAO;YACjC,gBAAgB,EAAE,SAAS,CAAC,OAAO;YACnC,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,KAAK,EAAE,GAAG,CAAC,KAAK;SAChB,CAAC,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,MAAM,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAAA,CACrF","sourcesContent":["/**\n * Two-run stability filter for reasoning-router promotion evidence.\n *\n * A promotion gate is only as trustworthy as the outcomes it credits. Routing\n * evaluations are paired comparisons, and paired comparisons are cheap to fool:\n * if the measured outcome of a row can flip between two identical replays, then\n * a \"win\" on that row may be measurement noise rather than a better policy.\n * Published routing evaluations report exactly this — a non-trivial share of\n * model/question pairs score differently when the identical matrix is re-run —\n * so a credit rule that ignores rerun disagreement will happily promote noise.\n *\n * This module implements the rule that closes that hole: a row carries credit\n * only when it was observed at least `minReplays` times under each policy and\n * every one of those observations agreed. Rows that disagree are withheld, not\n * majority-voted — a flipped observation means the measurement is unreliable,\n * not that the majority is the truth.\n *\n * For a deterministic classifier every row is stable by construction, so the\n * filter is also a determinism attestation: if nondeterminism ever leaks into\n * the routing path (iteration order, clock, randomness), rows start landing in\n * the unstable bucket and the gate refuses to promote instead of crediting a\n * coin flip.\n *\n * Pure and total: no I/O, no global state, no prompt text.\n */\n\n/** Independent observations required per side before a row may carry credit. */\nexport const DEFAULT_REPLAY_MIN = 2;\n\n/** One evaluated row, replayed independently under each policy. */\nexport interface RouterReplayRow {\n\treadonly rowId: string;\n\t/** True when the row belongs to the frozen held-out split. */\n\treadonly holdout: boolean;\n\t/** Correctness observed on each independent replay under the baseline. */\n\treadonly baselineReplays: readonly boolean[];\n\t/** Correctness observed on each independent replay under the candidate. */\n\treadonly candidateReplays: readonly boolean[];\n}\n\n/** A row whose replays agreed, so its outcome may carry promotion credit. */\nexport interface RouterStableOutcome {\n\treadonly rowId: string;\n\treadonly holdout: boolean;\n\treadonly baselineCorrect: boolean;\n\treadonly candidateCorrect: boolean;\n}\n\n/** Credit-eligible outcomes plus why every other row was withheld. */\nexport interface RouterReplayStability {\n\treadonly stable: readonly RouterStableOutcome[];\n\t/** Rows considered, whatever bucket they landed in. */\n\treadonly evaluated: number;\n\t/** Rows withheld because repeated observations disagreed. */\n\treadonly unstable: number;\n\t/** Rows withheld because one side was observed too few times to judge. */\n\treadonly insufficientReplays: number;\n\treadonly minReplays: number;\n}\n\n/** Verdict for one side of one row. */\ntype ReplayVerdict =\n\t| { readonly kind: \"agreed\"; readonly correct: boolean }\n\t| { readonly kind: \"disagreed\" }\n\t| { readonly kind: \"insufficient\" };\n\nfunction judgeReplays(replays: readonly boolean[], minReplays: number): ReplayVerdict {\n\tconst [first, ...rest] = replays;\n\tif (first === undefined || replays.length < minReplays) {\n\t\treturn { kind: \"insufficient\" };\n\t}\n\treturn rest.every((replay) => replay === first) ? { correct: first, kind: \"agreed\" } : { kind: \"disagreed\" };\n}\n\n/**\n * Partition replayed rows into credit-eligible outcomes and withheld rows.\n *\n * Insufficient replays outrank disagreement: a row observed once is not stable\n * evidence that happens to agree with itself, it is evidence that was never\n * tested for stability at all. Reporting the two buckets separately keeps that\n * distinction visible to the gate, which fails closed on either.\n */\nexport function summarizeReplayStability(\n\trows: readonly RouterReplayRow[],\n\tminReplays: number = DEFAULT_REPLAY_MIN,\n): RouterReplayStability {\n\tconst stable: RouterStableOutcome[] = [];\n\tlet unstable = 0;\n\tlet insufficientReplays = 0;\n\n\tfor (const row of rows) {\n\t\tconst baseline = judgeReplays(row.baselineReplays, minReplays);\n\t\tconst candidate = judgeReplays(row.candidateReplays, minReplays);\n\n\t\tif (baseline.kind === \"insufficient\" || candidate.kind === \"insufficient\") {\n\t\t\tinsufficientReplays++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (baseline.kind === \"disagreed\" || candidate.kind === \"disagreed\") {\n\t\t\tunstable++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tstable.push({\n\t\t\tbaselineCorrect: baseline.correct,\n\t\t\tcandidateCorrect: candidate.correct,\n\t\t\tholdout: row.holdout,\n\t\t\trowId: row.rowId,\n\t\t});\n\t}\n\n\treturn { evaluated: rows.length, insufficientReplays, minReplays, stable, unstable };\n}\n"]}
|
|
@@ -64,11 +64,11 @@ export interface RouterWeightsV4 {
|
|
|
64
64
|
readonly intentSkeleton: number;
|
|
65
65
|
/** Bump to "code-gen" for a bare "add" keyword, gated by !localEdit. */
|
|
66
66
|
readonly addKeyword: number;
|
|
67
|
-
/** Bump to the class matching `history[0]`, if supplied (
|
|
67
|
+
/** Bump to the class matching `history[0]`, if supplied (default 2; gated on existing prompt evidence). */
|
|
68
68
|
readonly multiTurnPrior: number;
|
|
69
|
-
/** Linear per-bucket bump applied to debug/review/plan under context pressure (
|
|
69
|
+
/** Linear per-bucket bump applied to debug/review/plan under context pressure (default 1). */
|
|
70
70
|
readonly pressureBucket: number;
|
|
71
|
-
/** Bump to
|
|
71
|
+
/** Bump to an externally supplied judge vote, if any (default 2; gated on existing prompt evidence). */
|
|
72
72
|
readonly judgeVote: number;
|
|
73
73
|
/** Bounded look-back window (characters) for negation-cue gating, never crossing a .,;!? boundary. */
|
|
74
74
|
readonly negationWindowChars: number;
|
|
@@ -80,9 +80,9 @@ export interface RouterWeightsV4 {
|
|
|
80
80
|
/**
|
|
81
81
|
* Calibrated default preset. The named weights below are the production v4
|
|
82
82
|
* scorer configuration covered by
|
|
83
|
-
* test/suite/regressions/013-reasoning-router-v4-accuracy.test.ts.
|
|
84
|
-
* fields (`multiTurnPrior`, `pressureBucket`, `judgeVote`) are
|
|
85
|
-
*
|
|
83
|
+
* test/suite/regressions/013-reasoning-router-v4-accuracy.test.ts. The bounded
|
|
84
|
+
* extension fields (`multiTurnPrior`, `pressureBucket`, `judgeVote`) are active
|
|
85
|
+
* only after the base scorer finds prompt evidence; zero-score fallbacks ignore them.
|
|
86
86
|
*/
|
|
87
87
|
export declare const DEFAULT_WEIGHTS_V4: RouterWeightsV4;
|
|
88
88
|
//# sourceMappingURL=reasoning-router-v4-weights.d.ts.map
|