open-multi-agent-kit 0.90.4 → 0.90.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +54 -0
- package/README.md +9 -9
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/codexbar-cli.d.ts +39 -0
- package/dist/codexbar-cli.d.ts.map +1 -0
- package/dist/codexbar-cli.js +230 -0
- package/dist/codexbar-cli.js.map +1 -0
- package/dist/commands/doctor-provider.d.ts.map +1 -1
- package/dist/commands/doctor-provider.js +16 -3
- package/dist/commands/doctor-provider.js.map +1 -1
- package/dist/core/adaptorch-bridge.d.ts +14 -21
- package/dist/core/adaptorch-bridge.d.ts.map +1 -1
- package/dist/core/adaptorch-bridge.js +11 -18
- package/dist/core/adaptorch-bridge.js.map +1 -1
- package/dist/core/agent-session.d.ts +6 -39
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +37 -107
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/codexbar-adapter.d.ts +44 -0
- package/dist/core/codexbar-adapter.d.ts.map +1 -0
- package/dist/core/codexbar-adapter.js +211 -0
- package/dist/core/codexbar-adapter.js.map +1 -0
- package/dist/core/compaction/index.d.ts +2 -0
- package/dist/core/compaction/index.d.ts.map +1 -1
- package/dist/core/compaction/index.js +2 -0
- package/dist/core/compaction/index.js.map +1 -1
- package/dist/core/compaction/model-policy.d.ts +9 -0
- package/dist/core/compaction/model-policy.d.ts.map +1 -0
- package/dist/core/compaction/model-policy.js +37 -0
- package/dist/core/compaction/model-policy.js.map +1 -0
- package/dist/core/compaction/resume-policy.d.ts +6 -0
- package/dist/core/compaction/resume-policy.d.ts.map +1 -0
- package/dist/core/compaction/resume-policy.js +8 -0
- package/dist/core/compaction/resume-policy.js.map +1 -0
- package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
- package/dist/core/context-budget-v2-planner.js +10 -2
- package/dist/core/context-budget-v2-planner.js.map +1 -1
- package/dist/core/context-budget-v2-types.d.ts +7 -1
- package/dist/core/context-budget-v2-types.d.ts.map +1 -1
- package/dist/core/context-budget-v2-types.js.map +1 -1
- package/dist/core/domain-loadouts.d.ts.map +1 -1
- package/dist/core/domain-loadouts.js +68 -1
- package/dist/core/domain-loadouts.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +12 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +3 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +15 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/grok-harness-dispatch.d.ts +22 -0
- package/dist/core/grok-harness-dispatch.d.ts.map +1 -0
- package/dist/core/grok-harness-dispatch.js +44 -0
- package/dist/core/grok-harness-dispatch.js.map +1 -0
- package/dist/core/grok-harness.d.ts +25 -0
- package/dist/core/grok-harness.d.ts.map +1 -0
- package/dist/core/grok-harness.js +61 -0
- package/dist/core/grok-harness.js.map +1 -0
- package/dist/core/grok-playbook.d.ts +7 -0
- package/dist/core/grok-playbook.d.ts.map +1 -0
- package/dist/core/grok-playbook.js +34 -0
- package/dist/core/grok-playbook.js.map +1 -0
- package/dist/core/reasoning-router-bias.d.ts +11 -23
- package/dist/core/reasoning-router-bias.d.ts.map +1 -1
- package/dist/core/reasoning-router-bias.js +9 -18
- package/dist/core/reasoning-router-bias.js.map +1 -1
- package/dist/core/reasoning-router-resolver.d.ts +44 -0
- package/dist/core/reasoning-router-resolver.d.ts.map +1 -0
- package/dist/core/reasoning-router-resolver.js +79 -0
- package/dist/core/reasoning-router-resolver.js.map +1 -0
- package/dist/core/reasoning-router-v4-weights.d.ts +28 -72
- package/dist/core/reasoning-router-v4-weights.d.ts.map +1 -1
- package/dist/core/reasoning-router-v4-weights.js +16 -60
- package/dist/core/reasoning-router-v4-weights.js.map +1 -1
- package/dist/core/reasoning-router-v4.d.ts +25 -70
- package/dist/core/reasoning-router-v4.d.ts.map +1 -1
- package/dist/core/reasoning-router-v4.js +46 -144
- package/dist/core/reasoning-router-v4.js.map +1 -1
- package/dist/core/router-feedback-collector.d.ts +13 -16
- package/dist/core/router-feedback-collector.d.ts.map +1 -1
- package/dist/core/router-feedback-collector.js +5 -8
- package/dist/core/router-feedback-collector.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +31 -12
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +4 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +16 -98
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/utils/tools-manager.d.ts +3 -2
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +42 -17
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/correctness-wall.md +117 -0
- package/docs/grok-harness.md +63 -0
- package/docs/loadout-domains/README.md +2 -1
- package/docs/loadout-domains/ai-agent-ops.md +8 -1
- package/docs/loadout-domains/grok-harness.md +74 -0
- package/docs/providers.md +4 -0
- package/docs/usage.md +11 -12
- package/examples/extensions/correctness-wall/LOADOUT.md +87 -0
- package/examples/extensions/correctness-wall/README.md +111 -0
- package/examples/extensions/correctness-wall/adjudication-fixture.ts +151 -0
- package/examples/extensions/correctness-wall/index.ts +470 -0
- package/examples/extensions/correctness-wall/repair-state.ts +77 -0
- package/examples/extensions/correctness-wall/wall-cache.ts +81 -0
- 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/subagent/agent-capability-router.test.ts +123 -0
- package/examples/extensions/subagent/agent-capability-router.ts +274 -0
- package/examples/extensions/subagent/agents/planner.md +3 -0
- package/examples/extensions/subagent/agents/reviewer.md +3 -0
- package/examples/extensions/subagent/agents/scout.md +3 -0
- package/examples/extensions/subagent/agents/worker.md +2 -0
- package/examples/extensions/subagent/agents.ts +14 -0
- package/examples/extensions/subagent/capabilities.ts +368 -0
- package/examples/extensions/subagent/domain-profiles.ts +1089 -0
- package/examples/extensions/subagent/index.ts +71 -3
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/12-full-control.ts +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +5 -5
- package/dist/core/compactor.d.ts +0 -42
- package/dist/core/compactor.d.ts.map +0 -1
- package/dist/core/compactor.js +0 -128
- package/dist/core/compactor.js.map +0 -1
- package/dist/core/reasoning-router-v2.d.ts +0 -94
- package/dist/core/reasoning-router-v2.d.ts.map +0 -1
- package/dist/core/reasoning-router-v2.js +0 -288
- package/dist/core/reasoning-router-v2.js.map +0 -1
- package/dist/core/reasoning-router-v3.d.ts +0 -15
- package/dist/core/reasoning-router-v3.d.ts.map +0 -1
- package/dist/core/reasoning-router-v3.js +0 -193
- package/dist/core/reasoning-router-v3.js.map +0 -1
- package/dist/core/reasoning-router-weights.d.ts +0 -155
- package/dist/core/reasoning-router-weights.d.ts.map +0 -1
- package/dist/core/reasoning-router-weights.js +0 -204
- package/dist/core/reasoning-router-weights.js.map +0 -1
- package/dist/core/reasoning-router.d.ts +0 -44
- package/dist/core/reasoning-router.d.ts.map +0 -1
- package/dist/core/reasoning-router.js +0 -138
- package/dist/core/reasoning-router.js.map +0 -1
- package/dist/core/token-optimizer.d.ts +0 -58
- package/dist/core/token-optimizer.d.ts.map +0 -1
- package/dist/core/token-optimizer.js +0 -179
- package/dist/core/token-optimizer.js.map +0 -1
|
@@ -1,26 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Reasoning-router bias compiler —
|
|
2
|
+
* Reasoning-router bias compiler — privacy-safe learning ledger.
|
|
3
3
|
*
|
|
4
|
-
* Pure, deterministic compiler from
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* agent-session.ts by this lane.
|
|
11
|
-
*
|
|
12
|
-
* Goal 010 Lane I addition: `isRouterBiasCell` / `isRouterBiasSnapshot` /
|
|
13
|
-
* `parseRouterBiasSnapshot` / `getDefaultRouterBiasSnapshotPath` below let a
|
|
14
|
-
* (now opt-in) caller safely load a snapshot this module previously only
|
|
15
|
-
* wrote. `parseRouterBiasSnapshot` performs no filesystem access itself — it
|
|
16
|
-
* only validates already-read text — so this module's zero-I/O determinism
|
|
17
|
-
* guarantee for `compileBiasSnapshot`/`getBiasStepsForCell` is unchanged; the
|
|
18
|
-
* actual disk read lives in agent-session.ts.
|
|
4
|
+
* Pure, deterministic compiler from v4 feedback records (see
|
|
5
|
+
* router-feedback-collector.ts) to a bounded, per-cell reasoning-level bias
|
|
6
|
+
* snapshot. No clock, randomness, network, or model calls: the same input array
|
|
7
|
+
* always produces the same output (`JSON.stringify` byte-identical), regardless
|
|
8
|
+
* of input record order. `parseRouterBiasSnapshot` performs no filesystem
|
|
9
|
+
* access itself; the actual disk read lives in agent-session.ts.
|
|
19
10
|
*/
|
|
20
11
|
import { type RouterFeedbackLaneType, type RouterFeedbackLenBucket, type RouterFeedbackTaskClass, type RouterFeedbackVersion } from "./router-feedback-collector.ts";
|
|
21
12
|
/** Minimum non-neutral ("strong") record count per cell before any bias applies. */
|
|
22
13
|
export declare const BIAS_STRONG_THRESHOLD = 5;
|
|
23
|
-
/** Bias magnitude bound, matching the resolver's
|
|
14
|
+
/** Bias magnitude bound, matching the resolver's bias clamp. */
|
|
24
15
|
export declare const BIAS_MAX_STEPS = 2;
|
|
25
16
|
/** Bounded per-cell bias, in ladder steps. Never outside [-2, 2]. */
|
|
26
17
|
export type RouterBiasSteps = -2 | -1 | 0 | 1 | 2;
|
|
@@ -52,12 +43,9 @@ export interface RouterBiasSnapshot {
|
|
|
52
43
|
}
|
|
53
44
|
export interface CompileBiasSnapshotOptions {
|
|
54
45
|
/**
|
|
55
|
-
* Restrict compilation to one router version
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* counted in `droppedCount`. Defaults to "v3" for API compatibility; the
|
|
59
|
-
* offline CLI now defaults to "v4", matching the only auto-router wired to
|
|
60
|
-
* consume a learning-bias snapshot at runtime.
|
|
46
|
+
* Restrict compilation to one router version. The only supported runtime
|
|
47
|
+
* router version is v4; records tagged with any other value are invalid and
|
|
48
|
+
* counted in `droppedCount`.
|
|
61
49
|
*/
|
|
62
50
|
readonly routerVersion?: RouterFeedbackVersion;
|
|
63
51
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reasoning-router-bias.d.ts","sourceRoot":"","sources":["../../src/core/reasoning-router-bias.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH,OAAO,EAKN,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,EAE5B,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,MAAM,gCAAgC,CAAC;AAExC,oFAAoF;AACpF,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAEvC,2GAA2G;AAC3G,eAAO,MAAM,cAAc,IAAI,CAAC;AAEhC,qEAAqE;AACrE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAElD,8GAA4G;AAC5G,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,cAAc,EAAE,uBAAuB,CAAC;IACjD,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,uBAAuB,CAAC;IAC5C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,cAAe,SAAQ,iBAAiB;IACxD,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,wFAAwF;IACxF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,aAAa,EAAE,yBAAyB,CAAC;IAClD,oGAAoG;IACpG,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,0EAA0E;IAC1E,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,iGAAiG;IACjG,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,iHAAiH;IACjH,QAAQ,CAAC,SAAS,EAAE,SAAS,cAAc,EAAE,CAAC;CAC9C;AAED,MAAM,WAAW,0BAA0B;IAC1C;;;;;;;OAOG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,qBAAqB,CAAC;CAC/C;AAgDD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAClC,OAAO,EAAE,SAAS,OAAO,EAAE,EAC3B,OAAO,GAAE,0BAA+B,GACtC,kBAAkB,CA0DpB;AAED,6GAA6G;AAC7G,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,kBAAkB,EAAE,GAAG,EAAE,iBAAiB,GAAG,eAAe,CAMzG;AAwDD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,cAAc,CAaxE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,kBAAkB,CAYhF;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAQ9E;AAED;;;;;;;;GAQG;AACH,wBAAgB,gCAAgC,CAAC,OAAO,GAAE,qBAA4B,GAAG,MAAM,CAE9F","sourcesContent":["/**\n * Reasoning-router bias compiler — Goal 009 Wave 1 Lane L (privacy-safe learning ledger).\n *\n * Pure, deterministic compiler from a set of privacy-safe feedback records\n * (see router-feedback-collector.ts) to a bounded, per-cell reasoning-level\n * bias snapshot. No clock, randomness, network, or model calls: the same\n * input array always produces the same output (`JSON.stringify` byte-\n * identical), regardless of input record order. This module is a standalone,\n * pure utility — nothing here is wired into a router resolver or\n * agent-session.ts by this lane.\n *\n * Goal 010 Lane I addition: `isRouterBiasCell` / `isRouterBiasSnapshot` /\n * `parseRouterBiasSnapshot` / `getDefaultRouterBiasSnapshotPath` below let a\n * (now opt-in) caller safely load a snapshot this module previously only\n * wrote. `parseRouterBiasSnapshot` performs no filesystem access itself — it\n * only validates already-read text — so this module's zero-I/O determinism\n * guarantee for `compileBiasSnapshot`/`getBiasStepsForCell` is unchanged; the\n * actual disk read lives in agent-session.ts.\n */\n\nimport { createHash } from \"crypto\";\nimport { join } from \"path\";\nimport { getAgentDir } from \"../config.ts\";\nimport {\n\tisRouterFeedbackRecord,\n\tROUTER_FEEDBACK_LANE_TYPES,\n\tROUTER_FEEDBACK_LEN_BUCKETS,\n\tROUTER_FEEDBACK_TASK_CLASSES,\n\ttype RouterFeedbackLaneType,\n\ttype RouterFeedbackLenBucket,\n\ttype RouterFeedbackRecord,\n\ttype RouterFeedbackTaskClass,\n\ttype RouterFeedbackVersion,\n} from \"./router-feedback-collector.ts\";\n\n/** Minimum non-neutral (\"strong\") record count per cell before any bias applies. */\nexport const BIAS_STRONG_THRESHOLD = 5;\n\n/** Bias magnitude bound, matching the resolver's existing bias clamp (reasoning-router-v2.ts BIAS_MAX). */\nexport const BIAS_MAX_STEPS = 2;\n\n/** Bounded per-cell bias, in ladder steps. Never outside [-2, 2]. */\nexport type RouterBiasSteps = -2 | -1 | 0 | 1 | 2;\n\n/** The feature tuple a bias cell is keyed on — identical dimensions the router already computes locally. */\nexport interface RouterBiasCellKey {\n\treadonly predictedClass: RouterFeedbackTaskClass;\n\treadonly laneType: RouterFeedbackLaneType;\n\treadonly lenBucket: RouterFeedbackLenBucket;\n\treadonly hadFence: boolean;\n\treadonly hadDiff: boolean;\n}\n\nexport interface RouterBiasCell extends RouterBiasCellKey {\n\treadonly biasSteps: RouterBiasSteps;\n\t/** Non-neutral (up/down-signaling) record count for this cell. */\n\treadonly nStrong: number;\n\t/** Total record count for this cell, including neutral (accepted/same/pass) records. */\n\treadonly nTotal: number;\n}\n\nexport interface RouterBiasSnapshot {\n\treadonly schemaVersion: \"router-bias-snapshot/v1\";\n\t/** sha256 hex digest of the canonicalized valid-record set actually compiled; not a prompt hash. */\n\treadonly sourceRecordDigest: string;\n\t/** Count of records that were validated and included in this snapshot. */\n\treadonly consideredCount: number;\n\t/** Count of records excluded from this snapshot (schema-invalid, or router-version mismatch). */\n\treadonly droppedCount: number;\n\t/** Bias cells, sorted deterministically. Empty when there is no ledger data or no cell reaches the threshold. */\n\treadonly biasCells: readonly RouterBiasCell[];\n}\n\nexport interface CompileBiasSnapshotOptions {\n\t/**\n\t * Restrict compilation to one router version — `compileBiasSnapshot` never\n\t * mixes records from different router versions into one snapshot. Records\n\t * tagged with any other `routerVersion` are excluded from this snapshot and\n\t * counted in `droppedCount`. Defaults to \"v3\" for API compatibility; the\n\t * offline CLI now defaults to \"v4\", matching the only auto-router wired to\n\t * consume a learning-bias snapshot at runtime.\n\t */\n\treadonly routerVersion?: RouterFeedbackVersion;\n}\n\nfunction cellKeyString(key: RouterBiasCellKey): string {\n\treturn [key.predictedClass, key.laneType, String(key.lenBucket), String(key.hadFence), String(key.hadDiff)].join(\n\t\t\"|\",\n\t);\n}\n\n/** +1 \"needed more effort\", -1 \"needed less effort\", 0 neutral (no directional bias contribution). */\nfunction outcomeDirection(outcome: RouterFeedbackRecord[\"outcome\"]): -1 | 0 | 1 {\n\tif (outcome === \"up\" || outcome === \"fail\" || outcome === \"debug-follow-up\") return 1;\n\tif (outcome === \"down\") return -1;\n\treturn 0;\n}\n\n/** Fixed-key-order JSON so identical records always canonicalize to identical bytes. */\nfunction canonicalRecordJson(record: RouterFeedbackRecord): string {\n\treturn JSON.stringify({\n\t\trouterVersion: record.routerVersion,\n\t\tlaneType: record.laneType,\n\t\tpredictedClass: record.predictedClass,\n\t\tresolvedLevel: record.resolvedLevel,\n\t\tacceptedLevel: record.acceptedLevel,\n\t\tsignal: record.signal,\n\t\toutcome: record.outcome,\n\t\tlenBucket: record.lenBucket,\n\t\thadFence: record.hadFence,\n\t\thadDiff: record.hadDiff,\n\t});\n}\n\ninterface CellAccumulator extends RouterBiasCellKey {\n\tnTotal: number;\n\tupVotes: number;\n\tdownVotes: number;\n}\n\nfunction computeBiasSteps(upVotes: number, downVotes: number, nStrong: number): RouterBiasSteps {\n\tif (nStrong < BIAS_STRONG_THRESHOLD) return 0;\n\tconst majority = upVotes - downVotes;\n\tif (majority === 0) return 0;\n\n\tconst dominance = Math.abs(majority) / nStrong;\n\tconst magnitude = dominance >= 0.75 ? BIAS_MAX_STEPS : 1;\n\tconst signedMagnitude = majority > 0 ? magnitude : -magnitude;\n\treturn Math.max(-BIAS_MAX_STEPS, Math.min(BIAS_MAX_STEPS, signedMagnitude)) as RouterBiasSteps;\n}\n\n/**\n * Deterministically compile a bounded bias snapshot from raw (possibly\n * malformed) ledger entries. Unknown-shaped entries — including ones with\n * extra keys, missing keys, or out-of-range enum values — are dropped and\n * counted, never thrown. Records tagged with a `routerVersion` other than the\n * target version are also dropped and counted (never merged across router\n * versions into one snapshot). An empty or all-dropped input compiles to an\n * empty-bias snapshot (`biasCells: []`), which is exactly the shipped\n * no-learning behavior — this makes \"empty ledger -> zero bias\" true by\n * construction, not by a special case.\n *\n * Pure: same `records` (any order) -> same `RouterBiasSnapshot` (byte-\n * identical `JSON.stringify` output), because cells are grouped by an\n * order-independent key and the emitted cell/record ordering is always\n * sorted before serialization.\n */\nexport function compileBiasSnapshot(\n\trecords: readonly unknown[],\n\toptions: CompileBiasSnapshotOptions = {},\n): RouterBiasSnapshot {\n\tconst routerVersion = options.routerVersion ?? \"v3\";\n\tconst valid: RouterFeedbackRecord[] = [];\n\tlet droppedCount = 0;\n\n\tfor (const entry of records) {\n\t\tif (isRouterFeedbackRecord(entry) && entry.routerVersion === routerVersion) {\n\t\t\tvalid.push(entry);\n\t\t} else {\n\t\t\tdroppedCount += 1;\n\t\t}\n\t}\n\n\tconst cells = new Map<string, CellAccumulator>();\n\tfor (const record of valid) {\n\t\tconst key: RouterBiasCellKey = {\n\t\t\tpredictedClass: record.predictedClass,\n\t\t\tlaneType: record.laneType,\n\t\t\tlenBucket: record.lenBucket,\n\t\t\thadFence: record.hadFence,\n\t\t\thadDiff: record.hadDiff,\n\t\t};\n\t\tconst mapKey = cellKeyString(key);\n\t\tconst existing = cells.get(mapKey) ?? { ...key, nTotal: 0, upVotes: 0, downVotes: 0 };\n\t\texisting.nTotal += 1;\n\t\tconst direction = outcomeDirection(record.outcome);\n\t\tif (direction === 1) existing.upVotes += 1;\n\t\telse if (direction === -1) existing.downVotes += 1;\n\t\tcells.set(mapKey, existing);\n\t}\n\n\tconst biasCells: RouterBiasCell[] = [];\n\tfor (const mapKey of [...cells.keys()].sort()) {\n\t\tconst acc = cells.get(mapKey);\n\t\tif (!acc) continue;\n\t\tconst nStrong = acc.upVotes + acc.downVotes;\n\t\tbiasCells.push({\n\t\t\tpredictedClass: acc.predictedClass,\n\t\t\tlaneType: acc.laneType,\n\t\t\tlenBucket: acc.lenBucket,\n\t\t\thadFence: acc.hadFence,\n\t\t\thadDiff: acc.hadDiff,\n\t\t\tbiasSteps: computeBiasSteps(acc.upVotes, acc.downVotes, nStrong),\n\t\t\tnStrong,\n\t\t\tnTotal: acc.nTotal,\n\t\t});\n\t}\n\n\tconst canonicalValid = valid.map(canonicalRecordJson).sort();\n\tconst sourceRecordDigest = createHash(\"sha256\").update(canonicalValid.join(\"\\n\")).digest(\"hex\");\n\n\treturn {\n\t\tschemaVersion: \"router-bias-snapshot/v1\",\n\t\tsourceRecordDigest,\n\t\tconsideredCount: valid.length,\n\t\tdroppedCount,\n\t\tbiasCells,\n\t};\n}\n\n/** Look up the compiled bias for one feature cell; 0 (no bias) when the cell is absent from the snapshot. */\nexport function getBiasStepsForCell(snapshot: RouterBiasSnapshot, key: RouterBiasCellKey): RouterBiasSteps {\n\tconst wantKey = cellKeyString(key);\n\tfor (const cell of snapshot.biasCells) {\n\t\tif (cellKeyString(cell) === wantKey) return cell.biasSteps;\n\t}\n\treturn 0;\n}\n\n// ============================================================================\n// Snapshot parser / loader (Goal 010 Lane I)\n// ============================================================================\n//\n// Everything below lets a caller safely turn untrusted on-disk JSON text into\n// a validated RouterBiasSnapshot. Same posture as isRouterFeedbackRecord in\n// router-feedback-collector.ts: an exact positive key-set allowlist (not a\n// denylist), bounded per-field checks reusing the ledger's own closed enums,\n// and \"never throw, return null/false instead\" on any malformed input.\n\n/** Bounded bias-step values a compiled cell may carry; mirrors RouterBiasSteps. */\nconst ROUTER_BIAS_STEPS_VALUES: readonly RouterBiasSteps[] = [-2, -1, 0, 1, 2];\n\n/** Exact allowed key set for one compiled `RouterBiasCell`, sorted for canonical set-equality comparison. */\nconst ROUTER_BIAS_CELL_KEYS: readonly string[] = [\n\t\"predictedClass\",\n\t\"laneType\",\n\t\"lenBucket\",\n\t\"hadFence\",\n\t\"hadDiff\",\n\t\"biasSteps\",\n\t\"nStrong\",\n\t\"nTotal\",\n].sort();\n\n/** Exact allowed key set for a `RouterBiasSnapshot`, sorted for canonical set-equality comparison. */\nconst ROUTER_BIAS_SNAPSHOT_KEYS: readonly string[] = [\n\t\"schemaVersion\",\n\t\"sourceRecordDigest\",\n\t\"consideredCount\",\n\t\"droppedCount\",\n\t\"biasCells\",\n].sort();\n\n/** sha256 hex digest shape check for `sourceRecordDigest` (never trusts the value is actually a real digest of anything, only that it has the right shape). */\nconst SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction hasExactKeySet(value: Record<string, unknown>, allowedSortedKeys: readonly string[]): boolean {\n\tconst keys = Object.keys(value).sort();\n\tif (keys.length !== allowedSortedKeys.length) return false;\n\tfor (let i = 0; i < keys.length; i++) {\n\t\tif (keys[i] !== allowedSortedKeys[i]) return false;\n\t}\n\treturn true;\n}\n\nfunction isBoundedNonNegativeInteger(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isInteger(value) && value >= 0;\n}\n\n/**\n * Strict runtime validator for one compiled bias cell. Exact eight-key\n * allowlist plus bounded per-field checks, reusing the same closed enums as\n * `isRouterFeedbackRecord` (`ROUTER_FEEDBACK_TASK_CLASSES`,\n * `ROUTER_FEEDBACK_LANE_TYPES`, `ROUTER_FEEDBACK_LEN_BUCKETS`) so this\n * validator cannot silently drift from the ledger's own schema. `nStrong` is\n * additionally required to never exceed `nTotal` (true by construction for\n * every cell `compileBiasSnapshot` can produce; a cheap sanity bound against\n * a hand-tampered file).\n */\nexport function isRouterBiasCell(value: unknown): value is RouterBiasCell {\n\tif (!isPlainObject(value)) return false;\n\tif (!hasExactKeySet(value, ROUTER_BIAS_CELL_KEYS)) return false;\n\tif (!ROUTER_FEEDBACK_TASK_CLASSES.includes(value.predictedClass as RouterFeedbackTaskClass)) return false;\n\tif (!ROUTER_FEEDBACK_LANE_TYPES.includes(value.laneType as RouterFeedbackLaneType)) return false;\n\tif (!ROUTER_FEEDBACK_LEN_BUCKETS.includes(value.lenBucket as RouterFeedbackLenBucket)) return false;\n\tif (typeof value.hadFence !== \"boolean\") return false;\n\tif (typeof value.hadDiff !== \"boolean\") return false;\n\tif (!ROUTER_BIAS_STEPS_VALUES.includes(value.biasSteps as RouterBiasSteps)) return false;\n\tif (!isBoundedNonNegativeInteger(value.nStrong)) return false;\n\tif (!isBoundedNonNegativeInteger(value.nTotal)) return false;\n\tif ((value.nStrong as number) > (value.nTotal as number)) return false;\n\treturn true;\n}\n\n/**\n * Strict runtime validator for a compiled bias snapshot. Exact five-key\n * allowlist; `biasCells` must be an array whose every element passes\n * `isRouterBiasCell`. Never trusts on-disk content: any unexpected shape,\n * extra key, or out-of-range value returns `false` rather than throwing.\n */\nexport function isRouterBiasSnapshot(value: unknown): value is RouterBiasSnapshot {\n\tif (!isPlainObject(value)) return false;\n\tif (!hasExactKeySet(value, ROUTER_BIAS_SNAPSHOT_KEYS)) return false;\n\tif (value.schemaVersion !== \"router-bias-snapshot/v1\") return false;\n\tif (typeof value.sourceRecordDigest !== \"string\" || !SHA256_HEX_PATTERN.test(value.sourceRecordDigest)) return false;\n\tif (!isBoundedNonNegativeInteger(value.consideredCount)) return false;\n\tif (!isBoundedNonNegativeInteger(value.droppedCount)) return false;\n\tif (!Array.isArray(value.biasCells)) return false;\n\tfor (const cell of value.biasCells) {\n\t\tif (!isRouterBiasCell(cell)) return false;\n\t}\n\treturn true;\n}\n\n/**\n * Safe parse of raw (untrusted, e.g. on-disk) JSON text into a validated\n * `RouterBiasSnapshot`. Never throws: malformed JSON or any schema violation\n * returns `null`. Performs no filesystem access itself — callers own the\n * actual read — so this module's zero-I/O determinism guarantee for\n * `compileBiasSnapshot`/`getBiasStepsForCell` is unaffected.\n */\nexport function parseRouterBiasSnapshot(raw: string): RouterBiasSnapshot | null {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch {\n\t\treturn null;\n\t}\n\treturn isRouterBiasSnapshot(parsed) ? parsed : null;\n}\n\n/**\n * Default compiled-bias-snapshot path:\n * `<agentDir>/router-feedback/weights/router-bias-snapshot.<version>.json` —\n * matches `compile-bias-snapshot.ts`'s own default `--out` naming exactly, so\n * a snapshot compiled offline for `version` is found here with no extra\n * configuration. Owner-only agent dir, never repo-local (mirrors\n * `getDefaultRouterFeedbackLedgerPath`). Defaults to \"v4\", the router version\n * this lane wires bias *consumption* for.\n */\nexport function getDefaultRouterBiasSnapshotPath(version: RouterFeedbackVersion = \"v4\"): string {\n\treturn join(getAgentDir(), \"router-feedback\", \"weights\", `router-bias-snapshot.${version}.json`);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"reasoning-router-bias.d.ts","sourceRoot":"","sources":["../../src/core/reasoning-router-bias.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,EAKN,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,EAE5B,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,MAAM,gCAAgC,CAAC;AAExC,oFAAoF;AACpF,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAEvC,gEAAgE;AAChE,eAAO,MAAM,cAAc,IAAI,CAAC;AAEhC,qEAAqE;AACrE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAElD,8GAA4G;AAC5G,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,cAAc,EAAE,uBAAuB,CAAC;IACjD,QAAQ,CAAC,QAAQ,EAAE,sBAAsB,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,uBAAuB,CAAC;IAC5C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,cAAe,SAAQ,iBAAiB;IACxD,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,wFAAwF;IACxF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,aAAa,EAAE,yBAAyB,CAAC;IAClD,oGAAoG;IACpG,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,0EAA0E;IAC1E,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,iGAAiG;IACjG,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,iHAAiH;IACjH,QAAQ,CAAC,SAAS,EAAE,SAAS,cAAc,EAAE,CAAC;CAC9C;AAED,MAAM,WAAW,0BAA0B;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,qBAAqB,CAAC;CAC/C;AAgDD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAClC,OAAO,EAAE,SAAS,OAAO,EAAE,EAC3B,OAAO,GAAE,0BAA+B,GACtC,kBAAkB,CA0DpB;AAED,6GAA6G;AAC7G,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,kBAAkB,EAAE,GAAG,EAAE,iBAAiB,GAAG,eAAe,CAMzG;AAwDD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,cAAc,CAaxE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,kBAAkB,CAYhF;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAQ9E;AAED;;;;;;;;GAQG;AACH,wBAAgB,gCAAgC,CAAC,OAAO,GAAE,qBAA4B,GAAG,MAAM,CAE9F","sourcesContent":["/**\n * Reasoning-router bias compiler — privacy-safe learning ledger.\n *\n * Pure, deterministic compiler from v4 feedback records (see\n * router-feedback-collector.ts) to a bounded, per-cell reasoning-level bias\n * snapshot. No clock, randomness, network, or model calls: the same input array\n * always produces the same output (`JSON.stringify` byte-identical), regardless\n * of input record order. `parseRouterBiasSnapshot` performs no filesystem\n * access itself; the actual disk read lives in agent-session.ts.\n */\n\nimport { createHash } from \"crypto\";\nimport { join } from \"path\";\nimport { getAgentDir } from \"../config.ts\";\nimport {\n\tisRouterFeedbackRecord,\n\tROUTER_FEEDBACK_LANE_TYPES,\n\tROUTER_FEEDBACK_LEN_BUCKETS,\n\tROUTER_FEEDBACK_TASK_CLASSES,\n\ttype RouterFeedbackLaneType,\n\ttype RouterFeedbackLenBucket,\n\ttype RouterFeedbackRecord,\n\ttype RouterFeedbackTaskClass,\n\ttype RouterFeedbackVersion,\n} from \"./router-feedback-collector.ts\";\n\n/** Minimum non-neutral (\"strong\") record count per cell before any bias applies. */\nexport const BIAS_STRONG_THRESHOLD = 5;\n\n/** Bias magnitude bound, matching the resolver's bias clamp. */\nexport const BIAS_MAX_STEPS = 2;\n\n/** Bounded per-cell bias, in ladder steps. Never outside [-2, 2]. */\nexport type RouterBiasSteps = -2 | -1 | 0 | 1 | 2;\n\n/** The feature tuple a bias cell is keyed on — identical dimensions the router already computes locally. */\nexport interface RouterBiasCellKey {\n\treadonly predictedClass: RouterFeedbackTaskClass;\n\treadonly laneType: RouterFeedbackLaneType;\n\treadonly lenBucket: RouterFeedbackLenBucket;\n\treadonly hadFence: boolean;\n\treadonly hadDiff: boolean;\n}\n\nexport interface RouterBiasCell extends RouterBiasCellKey {\n\treadonly biasSteps: RouterBiasSteps;\n\t/** Non-neutral (up/down-signaling) record count for this cell. */\n\treadonly nStrong: number;\n\t/** Total record count for this cell, including neutral (accepted/same/pass) records. */\n\treadonly nTotal: number;\n}\n\nexport interface RouterBiasSnapshot {\n\treadonly schemaVersion: \"router-bias-snapshot/v1\";\n\t/** sha256 hex digest of the canonicalized valid-record set actually compiled; not a prompt hash. */\n\treadonly sourceRecordDigest: string;\n\t/** Count of records that were validated and included in this snapshot. */\n\treadonly consideredCount: number;\n\t/** Count of records excluded from this snapshot (schema-invalid, or router-version mismatch). */\n\treadonly droppedCount: number;\n\t/** Bias cells, sorted deterministically. Empty when there is no ledger data or no cell reaches the threshold. */\n\treadonly biasCells: readonly RouterBiasCell[];\n}\n\nexport interface CompileBiasSnapshotOptions {\n\t/**\n\t * Restrict compilation to one router version. The only supported runtime\n\t * router version is v4; records tagged with any other value are invalid and\n\t * counted in `droppedCount`.\n\t */\n\treadonly routerVersion?: RouterFeedbackVersion;\n}\n\nfunction cellKeyString(key: RouterBiasCellKey): string {\n\treturn [key.predictedClass, key.laneType, String(key.lenBucket), String(key.hadFence), String(key.hadDiff)].join(\n\t\t\"|\",\n\t);\n}\n\n/** +1 \"needed more effort\", -1 \"needed less effort\", 0 neutral (no directional bias contribution). */\nfunction outcomeDirection(outcome: RouterFeedbackRecord[\"outcome\"]): -1 | 0 | 1 {\n\tif (outcome === \"up\" || outcome === \"fail\" || outcome === \"debug-follow-up\") return 1;\n\tif (outcome === \"down\") return -1;\n\treturn 0;\n}\n\n/** Fixed-key-order JSON so identical records always canonicalize to identical bytes. */\nfunction canonicalRecordJson(record: RouterFeedbackRecord): string {\n\treturn JSON.stringify({\n\t\trouterVersion: record.routerVersion,\n\t\tlaneType: record.laneType,\n\t\tpredictedClass: record.predictedClass,\n\t\tresolvedLevel: record.resolvedLevel,\n\t\tacceptedLevel: record.acceptedLevel,\n\t\tsignal: record.signal,\n\t\toutcome: record.outcome,\n\t\tlenBucket: record.lenBucket,\n\t\thadFence: record.hadFence,\n\t\thadDiff: record.hadDiff,\n\t});\n}\n\ninterface CellAccumulator extends RouterBiasCellKey {\n\tnTotal: number;\n\tupVotes: number;\n\tdownVotes: number;\n}\n\nfunction computeBiasSteps(upVotes: number, downVotes: number, nStrong: number): RouterBiasSteps {\n\tif (nStrong < BIAS_STRONG_THRESHOLD) return 0;\n\tconst majority = upVotes - downVotes;\n\tif (majority === 0) return 0;\n\n\tconst dominance = Math.abs(majority) / nStrong;\n\tconst magnitude = dominance >= 0.75 ? BIAS_MAX_STEPS : 1;\n\tconst signedMagnitude = majority > 0 ? magnitude : -magnitude;\n\treturn Math.max(-BIAS_MAX_STEPS, Math.min(BIAS_MAX_STEPS, signedMagnitude)) as RouterBiasSteps;\n}\n\n/**\n * Deterministically compile a bounded bias snapshot from raw (possibly\n * malformed) ledger entries. Unknown-shaped entries — including ones with\n * extra keys, missing keys, or out-of-range enum values — are dropped and\n * counted, never thrown. Records tagged with a `routerVersion` other than the\n * target version are also dropped and counted (never merged across router\n * versions into one snapshot). An empty or all-dropped input compiles to an\n * empty-bias snapshot (`biasCells: []`), which is exactly the shipped\n * no-learning behavior — this makes \"empty ledger -> zero bias\" true by\n * construction, not by a special case.\n *\n * Pure: same `records` (any order) -> same `RouterBiasSnapshot` (byte-\n * identical `JSON.stringify` output), because cells are grouped by an\n * order-independent key and the emitted cell/record ordering is always\n * sorted before serialization.\n */\nexport function compileBiasSnapshot(\n\trecords: readonly unknown[],\n\toptions: CompileBiasSnapshotOptions = {},\n): RouterBiasSnapshot {\n\tconst routerVersion = options.routerVersion ?? \"v4\";\n\tconst valid: RouterFeedbackRecord[] = [];\n\tlet droppedCount = 0;\n\n\tfor (const entry of records) {\n\t\tif (isRouterFeedbackRecord(entry) && entry.routerVersion === routerVersion) {\n\t\t\tvalid.push(entry);\n\t\t} else {\n\t\t\tdroppedCount += 1;\n\t\t}\n\t}\n\n\tconst cells = new Map<string, CellAccumulator>();\n\tfor (const record of valid) {\n\t\tconst key: RouterBiasCellKey = {\n\t\t\tpredictedClass: record.predictedClass,\n\t\t\tlaneType: record.laneType,\n\t\t\tlenBucket: record.lenBucket,\n\t\t\thadFence: record.hadFence,\n\t\t\thadDiff: record.hadDiff,\n\t\t};\n\t\tconst mapKey = cellKeyString(key);\n\t\tconst existing = cells.get(mapKey) ?? { ...key, nTotal: 0, upVotes: 0, downVotes: 0 };\n\t\texisting.nTotal += 1;\n\t\tconst direction = outcomeDirection(record.outcome);\n\t\tif (direction === 1) existing.upVotes += 1;\n\t\telse if (direction === -1) existing.downVotes += 1;\n\t\tcells.set(mapKey, existing);\n\t}\n\n\tconst biasCells: RouterBiasCell[] = [];\n\tfor (const mapKey of [...cells.keys()].sort()) {\n\t\tconst acc = cells.get(mapKey);\n\t\tif (!acc) continue;\n\t\tconst nStrong = acc.upVotes + acc.downVotes;\n\t\tbiasCells.push({\n\t\t\tpredictedClass: acc.predictedClass,\n\t\t\tlaneType: acc.laneType,\n\t\t\tlenBucket: acc.lenBucket,\n\t\t\thadFence: acc.hadFence,\n\t\t\thadDiff: acc.hadDiff,\n\t\t\tbiasSteps: computeBiasSteps(acc.upVotes, acc.downVotes, nStrong),\n\t\t\tnStrong,\n\t\t\tnTotal: acc.nTotal,\n\t\t});\n\t}\n\n\tconst canonicalValid = valid.map(canonicalRecordJson).sort();\n\tconst sourceRecordDigest = createHash(\"sha256\").update(canonicalValid.join(\"\\n\")).digest(\"hex\");\n\n\treturn {\n\t\tschemaVersion: \"router-bias-snapshot/v1\",\n\t\tsourceRecordDigest,\n\t\tconsideredCount: valid.length,\n\t\tdroppedCount,\n\t\tbiasCells,\n\t};\n}\n\n/** Look up the compiled bias for one feature cell; 0 (no bias) when the cell is absent from the snapshot. */\nexport function getBiasStepsForCell(snapshot: RouterBiasSnapshot, key: RouterBiasCellKey): RouterBiasSteps {\n\tconst wantKey = cellKeyString(key);\n\tfor (const cell of snapshot.biasCells) {\n\t\tif (cellKeyString(cell) === wantKey) return cell.biasSteps;\n\t}\n\treturn 0;\n}\n\n// ============================================================================\n// Snapshot parser / loader (Goal 010 Lane I)\n// ============================================================================\n//\n// Everything below lets a caller safely turn untrusted on-disk JSON text into\n// a validated RouterBiasSnapshot. Same posture as isRouterFeedbackRecord in\n// router-feedback-collector.ts: an exact positive key-set allowlist (not a\n// denylist), bounded per-field checks reusing the ledger's own closed enums,\n// and \"never throw, return null/false instead\" on any malformed input.\n\n/** Bounded bias-step values a compiled cell may carry; mirrors RouterBiasSteps. */\nconst ROUTER_BIAS_STEPS_VALUES: readonly RouterBiasSteps[] = [-2, -1, 0, 1, 2];\n\n/** Exact allowed key set for one compiled `RouterBiasCell`, sorted for canonical set-equality comparison. */\nconst ROUTER_BIAS_CELL_KEYS: readonly string[] = [\n\t\"predictedClass\",\n\t\"laneType\",\n\t\"lenBucket\",\n\t\"hadFence\",\n\t\"hadDiff\",\n\t\"biasSteps\",\n\t\"nStrong\",\n\t\"nTotal\",\n].sort();\n\n/** Exact allowed key set for a `RouterBiasSnapshot`, sorted for canonical set-equality comparison. */\nconst ROUTER_BIAS_SNAPSHOT_KEYS: readonly string[] = [\n\t\"schemaVersion\",\n\t\"sourceRecordDigest\",\n\t\"consideredCount\",\n\t\"droppedCount\",\n\t\"biasCells\",\n].sort();\n\n/** sha256 hex digest shape check for `sourceRecordDigest` (never trusts the value is actually a real digest of anything, only that it has the right shape). */\nconst SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction hasExactKeySet(value: Record<string, unknown>, allowedSortedKeys: readonly string[]): boolean {\n\tconst keys = Object.keys(value).sort();\n\tif (keys.length !== allowedSortedKeys.length) return false;\n\tfor (let i = 0; i < keys.length; i++) {\n\t\tif (keys[i] !== allowedSortedKeys[i]) return false;\n\t}\n\treturn true;\n}\n\nfunction isBoundedNonNegativeInteger(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isInteger(value) && value >= 0;\n}\n\n/**\n * Strict runtime validator for one compiled bias cell. Exact eight-key\n * allowlist plus bounded per-field checks, reusing the same closed enums as\n * `isRouterFeedbackRecord` (`ROUTER_FEEDBACK_TASK_CLASSES`,\n * `ROUTER_FEEDBACK_LANE_TYPES`, `ROUTER_FEEDBACK_LEN_BUCKETS`) so this\n * validator cannot silently drift from the ledger's own schema. `nStrong` is\n * additionally required to never exceed `nTotal` (true by construction for\n * every cell `compileBiasSnapshot` can produce; a cheap sanity bound against\n * a hand-tampered file).\n */\nexport function isRouterBiasCell(value: unknown): value is RouterBiasCell {\n\tif (!isPlainObject(value)) return false;\n\tif (!hasExactKeySet(value, ROUTER_BIAS_CELL_KEYS)) return false;\n\tif (!ROUTER_FEEDBACK_TASK_CLASSES.includes(value.predictedClass as RouterFeedbackTaskClass)) return false;\n\tif (!ROUTER_FEEDBACK_LANE_TYPES.includes(value.laneType as RouterFeedbackLaneType)) return false;\n\tif (!ROUTER_FEEDBACK_LEN_BUCKETS.includes(value.lenBucket as RouterFeedbackLenBucket)) return false;\n\tif (typeof value.hadFence !== \"boolean\") return false;\n\tif (typeof value.hadDiff !== \"boolean\") return false;\n\tif (!ROUTER_BIAS_STEPS_VALUES.includes(value.biasSteps as RouterBiasSteps)) return false;\n\tif (!isBoundedNonNegativeInteger(value.nStrong)) return false;\n\tif (!isBoundedNonNegativeInteger(value.nTotal)) return false;\n\tif ((value.nStrong as number) > (value.nTotal as number)) return false;\n\treturn true;\n}\n\n/**\n * Strict runtime validator for a compiled bias snapshot. Exact five-key\n * allowlist; `biasCells` must be an array whose every element passes\n * `isRouterBiasCell`. Never trusts on-disk content: any unexpected shape,\n * extra key, or out-of-range value returns `false` rather than throwing.\n */\nexport function isRouterBiasSnapshot(value: unknown): value is RouterBiasSnapshot {\n\tif (!isPlainObject(value)) return false;\n\tif (!hasExactKeySet(value, ROUTER_BIAS_SNAPSHOT_KEYS)) return false;\n\tif (value.schemaVersion !== \"router-bias-snapshot/v1\") return false;\n\tif (typeof value.sourceRecordDigest !== \"string\" || !SHA256_HEX_PATTERN.test(value.sourceRecordDigest)) return false;\n\tif (!isBoundedNonNegativeInteger(value.consideredCount)) return false;\n\tif (!isBoundedNonNegativeInteger(value.droppedCount)) return false;\n\tif (!Array.isArray(value.biasCells)) return false;\n\tfor (const cell of value.biasCells) {\n\t\tif (!isRouterBiasCell(cell)) return false;\n\t}\n\treturn true;\n}\n\n/**\n * Safe parse of raw (untrusted, e.g. on-disk) JSON text into a validated\n * `RouterBiasSnapshot`. Never throws: malformed JSON or any schema violation\n * returns `null`. Performs no filesystem access itself — callers own the\n * actual read — so this module's zero-I/O determinism guarantee for\n * `compileBiasSnapshot`/`getBiasStepsForCell` is unaffected.\n */\nexport function parseRouterBiasSnapshot(raw: string): RouterBiasSnapshot | null {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch {\n\t\treturn null;\n\t}\n\treturn isRouterBiasSnapshot(parsed) ? parsed : null;\n}\n\n/**\n * Default compiled-bias-snapshot path:\n * `<agentDir>/router-feedback/weights/router-bias-snapshot.<version>.json` —\n * matches `compile-bias-snapshot.ts`'s own default `--out` naming exactly, so\n * a snapshot compiled offline for `version` is found here with no extra\n * configuration. Owner-only agent dir, never repo-local (mirrors\n * `getDefaultRouterFeedbackLedgerPath`). Defaults to \"v4\", the router version\n * this lane wires bias *consumption* for.\n */\nexport function getDefaultRouterBiasSnapshotPath(version: RouterFeedbackVersion = \"v4\"): string {\n\treturn join(getAgentDir(), \"router-feedback\", \"weights\", `router-bias-snapshot.${version}.json`);\n}\n"]}
|
|
@@ -1,21 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Reasoning-router bias compiler —
|
|
2
|
+
* Reasoning-router bias compiler — privacy-safe learning ledger.
|
|
3
3
|
*
|
|
4
|
-
* Pure, deterministic compiler from
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* agent-session.ts by this lane.
|
|
11
|
-
*
|
|
12
|
-
* Goal 010 Lane I addition: `isRouterBiasCell` / `isRouterBiasSnapshot` /
|
|
13
|
-
* `parseRouterBiasSnapshot` / `getDefaultRouterBiasSnapshotPath` below let a
|
|
14
|
-
* (now opt-in) caller safely load a snapshot this module previously only
|
|
15
|
-
* wrote. `parseRouterBiasSnapshot` performs no filesystem access itself — it
|
|
16
|
-
* only validates already-read text — so this module's zero-I/O determinism
|
|
17
|
-
* guarantee for `compileBiasSnapshot`/`getBiasStepsForCell` is unchanged; the
|
|
18
|
-
* actual disk read lives in agent-session.ts.
|
|
4
|
+
* Pure, deterministic compiler from v4 feedback records (see
|
|
5
|
+
* router-feedback-collector.ts) to a bounded, per-cell reasoning-level bias
|
|
6
|
+
* snapshot. No clock, randomness, network, or model calls: the same input array
|
|
7
|
+
* always produces the same output (`JSON.stringify` byte-identical), regardless
|
|
8
|
+
* of input record order. `parseRouterBiasSnapshot` performs no filesystem
|
|
9
|
+
* access itself; the actual disk read lives in agent-session.ts.
|
|
19
10
|
*/
|
|
20
11
|
import { createHash } from "crypto";
|
|
21
12
|
import { join } from "path";
|
|
@@ -23,7 +14,7 @@ import { getAgentDir } from "../config.js";
|
|
|
23
14
|
import { isRouterFeedbackRecord, ROUTER_FEEDBACK_LANE_TYPES, ROUTER_FEEDBACK_LEN_BUCKETS, ROUTER_FEEDBACK_TASK_CLASSES, } from "./router-feedback-collector.js";
|
|
24
15
|
/** Minimum non-neutral ("strong") record count per cell before any bias applies. */
|
|
25
16
|
export const BIAS_STRONG_THRESHOLD = 5;
|
|
26
|
-
/** Bias magnitude bound, matching the resolver's
|
|
17
|
+
/** Bias magnitude bound, matching the resolver's bias clamp. */
|
|
27
18
|
export const BIAS_MAX_STEPS = 2;
|
|
28
19
|
function cellKeyString(key) {
|
|
29
20
|
return [key.predictedClass, key.laneType, String(key.lenBucket), String(key.hadFence), String(key.hadDiff)].join("|");
|
|
@@ -79,7 +70,7 @@ function computeBiasSteps(upVotes, downVotes, nStrong) {
|
|
|
79
70
|
* sorted before serialization.
|
|
80
71
|
*/
|
|
81
72
|
export function compileBiasSnapshot(records, options = {}) {
|
|
82
|
-
const routerVersion = options.routerVersion ?? "
|
|
73
|
+
const routerVersion = options.routerVersion ?? "v4";
|
|
83
74
|
const valid = [];
|
|
84
75
|
let droppedCount = 0;
|
|
85
76
|
for (const entry of records) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reasoning-router-bias.js","sourceRoot":"","sources":["../../src/core/reasoning-router-bias.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Reasoning-router bias compiler — Goal 009 Wave 1 Lane L (privacy-safe learning ledger).\n *\n * Pure, deterministic compiler from a set of privacy-safe feedback records\n * (see router-feedback-collector.ts) to a bounded, per-cell reasoning-level\n * bias snapshot. No clock, randomness, network, or model calls: the same\n * input array always produces the same output (`JSON.stringify` byte-\n * identical), regardless of input record order. This module is a standalone,\n * pure utility — nothing here is wired into a router resolver or\n * agent-session.ts by this lane.\n *\n * Goal 010 Lane I addition: `isRouterBiasCell` / `isRouterBiasSnapshot` /\n * `parseRouterBiasSnapshot` / `getDefaultRouterBiasSnapshotPath` below let a\n * (now opt-in) caller safely load a snapshot this module previously only\n * wrote. `parseRouterBiasSnapshot` performs no filesystem access itself — it\n * only validates already-read text — so this module's zero-I/O determinism\n * guarantee for `compileBiasSnapshot`/`getBiasStepsForCell` is unchanged; the\n * actual disk read lives in agent-session.ts.\n */\n\nimport { createHash } from \"crypto\";\nimport { join } from \"path\";\nimport { getAgentDir } from \"../config.ts\";\nimport {\n\tisRouterFeedbackRecord,\n\tROUTER_FEEDBACK_LANE_TYPES,\n\tROUTER_FEEDBACK_LEN_BUCKETS,\n\tROUTER_FEEDBACK_TASK_CLASSES,\n\ttype RouterFeedbackLaneType,\n\ttype RouterFeedbackLenBucket,\n\ttype RouterFeedbackRecord,\n\ttype RouterFeedbackTaskClass,\n\ttype RouterFeedbackVersion,\n} from \"./router-feedback-collector.ts\";\n\n/** Minimum non-neutral (\"strong\") record count per cell before any bias applies. */\nexport const BIAS_STRONG_THRESHOLD = 5;\n\n/** Bias magnitude bound, matching the resolver's existing bias clamp (reasoning-router-v2.ts BIAS_MAX). */\nexport const BIAS_MAX_STEPS = 2;\n\n/** Bounded per-cell bias, in ladder steps. Never outside [-2, 2]. */\nexport type RouterBiasSteps = -2 | -1 | 0 | 1 | 2;\n\n/** The feature tuple a bias cell is keyed on — identical dimensions the router already computes locally. */\nexport interface RouterBiasCellKey {\n\treadonly predictedClass: RouterFeedbackTaskClass;\n\treadonly laneType: RouterFeedbackLaneType;\n\treadonly lenBucket: RouterFeedbackLenBucket;\n\treadonly hadFence: boolean;\n\treadonly hadDiff: boolean;\n}\n\nexport interface RouterBiasCell extends RouterBiasCellKey {\n\treadonly biasSteps: RouterBiasSteps;\n\t/** Non-neutral (up/down-signaling) record count for this cell. */\n\treadonly nStrong: number;\n\t/** Total record count for this cell, including neutral (accepted/same/pass) records. */\n\treadonly nTotal: number;\n}\n\nexport interface RouterBiasSnapshot {\n\treadonly schemaVersion: \"router-bias-snapshot/v1\";\n\t/** sha256 hex digest of the canonicalized valid-record set actually compiled; not a prompt hash. */\n\treadonly sourceRecordDigest: string;\n\t/** Count of records that were validated and included in this snapshot. */\n\treadonly consideredCount: number;\n\t/** Count of records excluded from this snapshot (schema-invalid, or router-version mismatch). */\n\treadonly droppedCount: number;\n\t/** Bias cells, sorted deterministically. Empty when there is no ledger data or no cell reaches the threshold. */\n\treadonly biasCells: readonly RouterBiasCell[];\n}\n\nexport interface CompileBiasSnapshotOptions {\n\t/**\n\t * Restrict compilation to one router version — `compileBiasSnapshot` never\n\t * mixes records from different router versions into one snapshot. Records\n\t * tagged with any other `routerVersion` are excluded from this snapshot and\n\t * counted in `droppedCount`. Defaults to \"v3\" for API compatibility; the\n\t * offline CLI now defaults to \"v4\", matching the only auto-router wired to\n\t * consume a learning-bias snapshot at runtime.\n\t */\n\treadonly routerVersion?: RouterFeedbackVersion;\n}\n\nfunction cellKeyString(key: RouterBiasCellKey): string {\n\treturn [key.predictedClass, key.laneType, String(key.lenBucket), String(key.hadFence), String(key.hadDiff)].join(\n\t\t\"|\",\n\t);\n}\n\n/** +1 \"needed more effort\", -1 \"needed less effort\", 0 neutral (no directional bias contribution). */\nfunction outcomeDirection(outcome: RouterFeedbackRecord[\"outcome\"]): -1 | 0 | 1 {\n\tif (outcome === \"up\" || outcome === \"fail\" || outcome === \"debug-follow-up\") return 1;\n\tif (outcome === \"down\") return -1;\n\treturn 0;\n}\n\n/** Fixed-key-order JSON so identical records always canonicalize to identical bytes. */\nfunction canonicalRecordJson(record: RouterFeedbackRecord): string {\n\treturn JSON.stringify({\n\t\trouterVersion: record.routerVersion,\n\t\tlaneType: record.laneType,\n\t\tpredictedClass: record.predictedClass,\n\t\tresolvedLevel: record.resolvedLevel,\n\t\tacceptedLevel: record.acceptedLevel,\n\t\tsignal: record.signal,\n\t\toutcome: record.outcome,\n\t\tlenBucket: record.lenBucket,\n\t\thadFence: record.hadFence,\n\t\thadDiff: record.hadDiff,\n\t});\n}\n\ninterface CellAccumulator extends RouterBiasCellKey {\n\tnTotal: number;\n\tupVotes: number;\n\tdownVotes: number;\n}\n\nfunction computeBiasSteps(upVotes: number, downVotes: number, nStrong: number): RouterBiasSteps {\n\tif (nStrong < BIAS_STRONG_THRESHOLD) return 0;\n\tconst majority = upVotes - downVotes;\n\tif (majority === 0) return 0;\n\n\tconst dominance = Math.abs(majority) / nStrong;\n\tconst magnitude = dominance >= 0.75 ? BIAS_MAX_STEPS : 1;\n\tconst signedMagnitude = majority > 0 ? magnitude : -magnitude;\n\treturn Math.max(-BIAS_MAX_STEPS, Math.min(BIAS_MAX_STEPS, signedMagnitude)) as RouterBiasSteps;\n}\n\n/**\n * Deterministically compile a bounded bias snapshot from raw (possibly\n * malformed) ledger entries. Unknown-shaped entries — including ones with\n * extra keys, missing keys, or out-of-range enum values — are dropped and\n * counted, never thrown. Records tagged with a `routerVersion` other than the\n * target version are also dropped and counted (never merged across router\n * versions into one snapshot). An empty or all-dropped input compiles to an\n * empty-bias snapshot (`biasCells: []`), which is exactly the shipped\n * no-learning behavior — this makes \"empty ledger -> zero bias\" true by\n * construction, not by a special case.\n *\n * Pure: same `records` (any order) -> same `RouterBiasSnapshot` (byte-\n * identical `JSON.stringify` output), because cells are grouped by an\n * order-independent key and the emitted cell/record ordering is always\n * sorted before serialization.\n */\nexport function compileBiasSnapshot(\n\trecords: readonly unknown[],\n\toptions: CompileBiasSnapshotOptions = {},\n): RouterBiasSnapshot {\n\tconst routerVersion = options.routerVersion ?? \"v3\";\n\tconst valid: RouterFeedbackRecord[] = [];\n\tlet droppedCount = 0;\n\n\tfor (const entry of records) {\n\t\tif (isRouterFeedbackRecord(entry) && entry.routerVersion === routerVersion) {\n\t\t\tvalid.push(entry);\n\t\t} else {\n\t\t\tdroppedCount += 1;\n\t\t}\n\t}\n\n\tconst cells = new Map<string, CellAccumulator>();\n\tfor (const record of valid) {\n\t\tconst key: RouterBiasCellKey = {\n\t\t\tpredictedClass: record.predictedClass,\n\t\t\tlaneType: record.laneType,\n\t\t\tlenBucket: record.lenBucket,\n\t\t\thadFence: record.hadFence,\n\t\t\thadDiff: record.hadDiff,\n\t\t};\n\t\tconst mapKey = cellKeyString(key);\n\t\tconst existing = cells.get(mapKey) ?? { ...key, nTotal: 0, upVotes: 0, downVotes: 0 };\n\t\texisting.nTotal += 1;\n\t\tconst direction = outcomeDirection(record.outcome);\n\t\tif (direction === 1) existing.upVotes += 1;\n\t\telse if (direction === -1) existing.downVotes += 1;\n\t\tcells.set(mapKey, existing);\n\t}\n\n\tconst biasCells: RouterBiasCell[] = [];\n\tfor (const mapKey of [...cells.keys()].sort()) {\n\t\tconst acc = cells.get(mapKey);\n\t\tif (!acc) continue;\n\t\tconst nStrong = acc.upVotes + acc.downVotes;\n\t\tbiasCells.push({\n\t\t\tpredictedClass: acc.predictedClass,\n\t\t\tlaneType: acc.laneType,\n\t\t\tlenBucket: acc.lenBucket,\n\t\t\thadFence: acc.hadFence,\n\t\t\thadDiff: acc.hadDiff,\n\t\t\tbiasSteps: computeBiasSteps(acc.upVotes, acc.downVotes, nStrong),\n\t\t\tnStrong,\n\t\t\tnTotal: acc.nTotal,\n\t\t});\n\t}\n\n\tconst canonicalValid = valid.map(canonicalRecordJson).sort();\n\tconst sourceRecordDigest = createHash(\"sha256\").update(canonicalValid.join(\"\\n\")).digest(\"hex\");\n\n\treturn {\n\t\tschemaVersion: \"router-bias-snapshot/v1\",\n\t\tsourceRecordDigest,\n\t\tconsideredCount: valid.length,\n\t\tdroppedCount,\n\t\tbiasCells,\n\t};\n}\n\n/** Look up the compiled bias for one feature cell; 0 (no bias) when the cell is absent from the snapshot. */\nexport function getBiasStepsForCell(snapshot: RouterBiasSnapshot, key: RouterBiasCellKey): RouterBiasSteps {\n\tconst wantKey = cellKeyString(key);\n\tfor (const cell of snapshot.biasCells) {\n\t\tif (cellKeyString(cell) === wantKey) return cell.biasSteps;\n\t}\n\treturn 0;\n}\n\n// ============================================================================\n// Snapshot parser / loader (Goal 010 Lane I)\n// ============================================================================\n//\n// Everything below lets a caller safely turn untrusted on-disk JSON text into\n// a validated RouterBiasSnapshot. Same posture as isRouterFeedbackRecord in\n// router-feedback-collector.ts: an exact positive key-set allowlist (not a\n// denylist), bounded per-field checks reusing the ledger's own closed enums,\n// and \"never throw, return null/false instead\" on any malformed input.\n\n/** Bounded bias-step values a compiled cell may carry; mirrors RouterBiasSteps. */\nconst ROUTER_BIAS_STEPS_VALUES: readonly RouterBiasSteps[] = [-2, -1, 0, 1, 2];\n\n/** Exact allowed key set for one compiled `RouterBiasCell`, sorted for canonical set-equality comparison. */\nconst ROUTER_BIAS_CELL_KEYS: readonly string[] = [\n\t\"predictedClass\",\n\t\"laneType\",\n\t\"lenBucket\",\n\t\"hadFence\",\n\t\"hadDiff\",\n\t\"biasSteps\",\n\t\"nStrong\",\n\t\"nTotal\",\n].sort();\n\n/** Exact allowed key set for a `RouterBiasSnapshot`, sorted for canonical set-equality comparison. */\nconst ROUTER_BIAS_SNAPSHOT_KEYS: readonly string[] = [\n\t\"schemaVersion\",\n\t\"sourceRecordDigest\",\n\t\"consideredCount\",\n\t\"droppedCount\",\n\t\"biasCells\",\n].sort();\n\n/** sha256 hex digest shape check for `sourceRecordDigest` (never trusts the value is actually a real digest of anything, only that it has the right shape). */\nconst SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction hasExactKeySet(value: Record<string, unknown>, allowedSortedKeys: readonly string[]): boolean {\n\tconst keys = Object.keys(value).sort();\n\tif (keys.length !== allowedSortedKeys.length) return false;\n\tfor (let i = 0; i < keys.length; i++) {\n\t\tif (keys[i] !== allowedSortedKeys[i]) return false;\n\t}\n\treturn true;\n}\n\nfunction isBoundedNonNegativeInteger(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isInteger(value) && value >= 0;\n}\n\n/**\n * Strict runtime validator for one compiled bias cell. Exact eight-key\n * allowlist plus bounded per-field checks, reusing the same closed enums as\n * `isRouterFeedbackRecord` (`ROUTER_FEEDBACK_TASK_CLASSES`,\n * `ROUTER_FEEDBACK_LANE_TYPES`, `ROUTER_FEEDBACK_LEN_BUCKETS`) so this\n * validator cannot silently drift from the ledger's own schema. `nStrong` is\n * additionally required to never exceed `nTotal` (true by construction for\n * every cell `compileBiasSnapshot` can produce; a cheap sanity bound against\n * a hand-tampered file).\n */\nexport function isRouterBiasCell(value: unknown): value is RouterBiasCell {\n\tif (!isPlainObject(value)) return false;\n\tif (!hasExactKeySet(value, ROUTER_BIAS_CELL_KEYS)) return false;\n\tif (!ROUTER_FEEDBACK_TASK_CLASSES.includes(value.predictedClass as RouterFeedbackTaskClass)) return false;\n\tif (!ROUTER_FEEDBACK_LANE_TYPES.includes(value.laneType as RouterFeedbackLaneType)) return false;\n\tif (!ROUTER_FEEDBACK_LEN_BUCKETS.includes(value.lenBucket as RouterFeedbackLenBucket)) return false;\n\tif (typeof value.hadFence !== \"boolean\") return false;\n\tif (typeof value.hadDiff !== \"boolean\") return false;\n\tif (!ROUTER_BIAS_STEPS_VALUES.includes(value.biasSteps as RouterBiasSteps)) return false;\n\tif (!isBoundedNonNegativeInteger(value.nStrong)) return false;\n\tif (!isBoundedNonNegativeInteger(value.nTotal)) return false;\n\tif ((value.nStrong as number) > (value.nTotal as number)) return false;\n\treturn true;\n}\n\n/**\n * Strict runtime validator for a compiled bias snapshot. Exact five-key\n * allowlist; `biasCells` must be an array whose every element passes\n * `isRouterBiasCell`. Never trusts on-disk content: any unexpected shape,\n * extra key, or out-of-range value returns `false` rather than throwing.\n */\nexport function isRouterBiasSnapshot(value: unknown): value is RouterBiasSnapshot {\n\tif (!isPlainObject(value)) return false;\n\tif (!hasExactKeySet(value, ROUTER_BIAS_SNAPSHOT_KEYS)) return false;\n\tif (value.schemaVersion !== \"router-bias-snapshot/v1\") return false;\n\tif (typeof value.sourceRecordDigest !== \"string\" || !SHA256_HEX_PATTERN.test(value.sourceRecordDigest)) return false;\n\tif (!isBoundedNonNegativeInteger(value.consideredCount)) return false;\n\tif (!isBoundedNonNegativeInteger(value.droppedCount)) return false;\n\tif (!Array.isArray(value.biasCells)) return false;\n\tfor (const cell of value.biasCells) {\n\t\tif (!isRouterBiasCell(cell)) return false;\n\t}\n\treturn true;\n}\n\n/**\n * Safe parse of raw (untrusted, e.g. on-disk) JSON text into a validated\n * `RouterBiasSnapshot`. Never throws: malformed JSON or any schema violation\n * returns `null`. Performs no filesystem access itself — callers own the\n * actual read — so this module's zero-I/O determinism guarantee for\n * `compileBiasSnapshot`/`getBiasStepsForCell` is unaffected.\n */\nexport function parseRouterBiasSnapshot(raw: string): RouterBiasSnapshot | null {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch {\n\t\treturn null;\n\t}\n\treturn isRouterBiasSnapshot(parsed) ? parsed : null;\n}\n\n/**\n * Default compiled-bias-snapshot path:\n * `<agentDir>/router-feedback/weights/router-bias-snapshot.<version>.json` —\n * matches `compile-bias-snapshot.ts`'s own default `--out` naming exactly, so\n * a snapshot compiled offline for `version` is found here with no extra\n * configuration. Owner-only agent dir, never repo-local (mirrors\n * `getDefaultRouterFeedbackLedgerPath`). Defaults to \"v4\", the router version\n * this lane wires bias *consumption* for.\n */\nexport function getDefaultRouterBiasSnapshotPath(version: RouterFeedbackVersion = \"v4\"): string {\n\treturn join(getAgentDir(), \"router-feedback\", \"weights\", `router-bias-snapshot.${version}.json`);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"reasoning-router-bias.js","sourceRoot":"","sources":["../../src/core/reasoning-router-bias.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EACN,sBAAsB,EACtB,0BAA0B,EAC1B,2BAA2B,EAC3B,4BAA4B,GAM5B,MAAM,gCAAgC,CAAC;AAExC,oFAAoF;AACpF,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC;AAEvC,gEAAgE;AAChE,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AA2ChC,SAAS,aAAa,CAAC,GAAsB,EAAU;IACtD,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAC/G,GAAG,CACH,CAAC;AAAA,CACF;AAED,sGAAsG;AACtG,SAAS,gBAAgB,CAAC,OAAwC,EAAc;IAC/E,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,MAAM,IAAI,OAAO,KAAK,iBAAiB;QAAE,OAAO,CAAC,CAAC;IACtF,IAAI,OAAO,KAAK,MAAM;QAAE,OAAO,CAAC,CAAC,CAAC;IAClC,OAAO,CAAC,CAAC;AAAA,CACT;AAED,wFAAwF;AACxF,SAAS,mBAAmB,CAAC,MAA4B,EAAU;IAClE,OAAO,IAAI,CAAC,SAAS,CAAC;QACrB,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,cAAc,EAAE,MAAM,CAAC,cAAc;QACrC,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,OAAO,EAAE,MAAM,CAAC,OAAO;KACvB,CAAC,CAAC;AAAA,CACH;AAQD,SAAS,gBAAgB,CAAC,OAAe,EAAE,SAAiB,EAAE,OAAe,EAAmB;IAC/F,IAAI,OAAO,GAAG,qBAAqB;QAAE,OAAO,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;IACrC,IAAI,QAAQ,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAE7B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC;IAC/C,MAAM,SAAS,GAAG,SAAS,IAAI,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;IACzD,MAAM,eAAe,GAAG,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC9D,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,cAAc,EAAE,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,eAAe,CAAC,CAAoB,CAAC;AAAA,CAC/F;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,mBAAmB,CAClC,OAA2B,EAC3B,OAAO,GAA+B,EAAE,EACnB;IACrB,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,IAAI,CAAC;IACpD,MAAM,KAAK,GAA2B,EAAE,CAAC;IACzC,IAAI,YAAY,GAAG,CAAC,CAAC;IAErB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,sBAAsB,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,aAAa,KAAK,aAAa,EAAE,CAAC;YAC5E,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACnB,CAAC;aAAM,CAAC;YACP,YAAY,IAAI,CAAC,CAAC;QACnB,CAAC;IACF,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,GAAG,EAA2B,CAAC;IACjD,KAAK,MAAM,MAAM,IAAI,KAAK,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAsB;YAC9B,cAAc,EAAE,MAAM,CAAC,cAAc;YACrC,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,OAAO,EAAE,MAAM,CAAC,OAAO;SACvB,CAAC;QACF,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;QAClC,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;QACtF,QAAQ,CAAC,MAAM,IAAI,CAAC,CAAC;QACrB,MAAM,SAAS,GAAG,gBAAgB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACnD,IAAI,SAAS,KAAK,CAAC;YAAE,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;aACtC,IAAI,SAAS,KAAK,CAAC,CAAC;YAAE,QAAQ,CAAC,SAAS,IAAI,CAAC,CAAC;QACnD,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC7B,CAAC;IAED,MAAM,SAAS,GAAqB,EAAE,CAAC;IACvC,KAAK,MAAM,MAAM,IAAI,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QAC/C,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC9B,IAAI,CAAC,GAAG;YAAE,SAAS;QACnB,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,GAAG,GAAG,CAAC,SAAS,CAAC;QAC5C,SAAS,CAAC,IAAI,CAAC;YACd,cAAc,EAAE,GAAG,CAAC,cAAc;YAClC,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,SAAS,EAAE,GAAG,CAAC,SAAS;YACxB,QAAQ,EAAE,GAAG,CAAC,QAAQ;YACtB,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,SAAS,EAAE,gBAAgB,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC;YAChE,OAAO;YACP,MAAM,EAAE,GAAG,CAAC,MAAM;SAClB,CAAC,CAAC;IACJ,CAAC;IAED,MAAM,cAAc,GAAG,KAAK,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7D,MAAM,kBAAkB,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAEhG,OAAO;QACN,aAAa,EAAE,yBAAyB;QACxC,kBAAkB;QAClB,eAAe,EAAE,KAAK,CAAC,MAAM;QAC7B,YAAY;QACZ,SAAS;KACT,CAAC;AAAA,CACF;AAED,6GAA6G;AAC7G,MAAM,UAAU,mBAAmB,CAAC,QAA4B,EAAE,GAAsB,EAAmB;IAC1G,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IACnC,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;QACvC,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,OAAO;YAAE,OAAO,IAAI,CAAC,SAAS,CAAC;IAC5D,CAAC;IACD,OAAO,CAAC,CAAC;AAAA,CACT;AAED,+EAA+E;AAC/E,6CAA6C;AAC7C,+EAA+E;AAC/E,EAAE;AACF,8EAA8E;AAC9E,4EAA4E;AAC5E,2EAA2E;AAC3E,6EAA6E;AAC7E,uEAAuE;AAEvE,mFAAmF;AACnF,MAAM,wBAAwB,GAA+B,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAE/E,6GAA6G;AAC7G,MAAM,qBAAqB,GAAsB;IAChD,gBAAgB;IAChB,UAAU;IACV,WAAW;IACX,UAAU;IACV,SAAS;IACT,WAAW;IACX,SAAS;IACT,QAAQ;CACR,CAAC,IAAI,EAAE,CAAC;AAET,sGAAsG;AACtG,MAAM,yBAAyB,GAAsB;IACpD,eAAe;IACf,oBAAoB;IACpB,iBAAiB;IACjB,cAAc;IACd,WAAW;CACX,CAAC,IAAI,EAAE,CAAC;AAET,+JAA+J;AAC/J,MAAM,kBAAkB,GAAG,gBAAgB,CAAC;AAE5C,SAAS,aAAa,CAAC,KAAc,EAAoC;IACxE,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAAA,CAC5E;AAED,SAAS,cAAc,CAAC,KAA8B,EAAE,iBAAoC,EAAW;IACtG,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;IACvC,IAAI,IAAI,CAAC,MAAM,KAAK,iBAAiB,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC3D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,iBAAiB,CAAC,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC;IACpD,CAAC;IACD,OAAO,IAAI,CAAC;AAAA,CACZ;AAED,SAAS,2BAA2B,CAAC,KAAc,EAAmB;IACrE,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;AAAA,CAC1E;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAc,EAA2B;IACzE,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACxC,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,qBAAqB,CAAC;QAAE,OAAO,KAAK,CAAC;IAChE,IAAI,CAAC,4BAA4B,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAyC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1G,IAAI,CAAC,0BAA0B,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAkC,CAAC;QAAE,OAAO,KAAK,CAAC;IACjG,IAAI,CAAC,2BAA2B,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAoC,CAAC;QAAE,OAAO,KAAK,CAAC;IACpG,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACtD,IAAI,OAAO,KAAK,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACrD,IAAI,CAAC,wBAAwB,CAAC,QAAQ,CAAC,KAAK,CAAC,SAA4B,CAAC;QAAE,OAAO,KAAK,CAAC;IACzF,IAAI,CAAC,2BAA2B,CAAC,KAAK,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9D,IAAI,CAAC,2BAA2B,CAAC,KAAK,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7D,IAAK,KAAK,CAAC,OAAkB,GAAI,KAAK,CAAC,MAAiB;QAAE,OAAO,KAAK,CAAC;IACvE,OAAO,IAAI,CAAC;AAAA,CACZ;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAc,EAA+B;IACjF,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACxC,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,yBAAyB,CAAC;QAAE,OAAO,KAAK,CAAC;IACpE,IAAI,KAAK,CAAC,aAAa,KAAK,yBAAyB;QAAE,OAAO,KAAK,CAAC;IACpE,IAAI,OAAO,KAAK,CAAC,kBAAkB,KAAK,QAAQ,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC;QAAE,OAAO,KAAK,CAAC;IACrH,IAAI,CAAC,2BAA2B,CAAC,KAAK,CAAC,eAAe,CAAC;QAAE,OAAO,KAAK,CAAC;IACtE,IAAI,CAAC,2BAA2B,CAAC,KAAK,CAAC,YAAY,CAAC;QAAE,OAAO,KAAK,CAAC;IACnE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC;QAAE,OAAO,KAAK,CAAC;IAClD,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QACpC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;IAC3C,CAAC;IACD,OAAO,IAAI,CAAC;AAAA,CACZ;AAED;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,GAAW,EAA6B;IAC/E,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACJ,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAC;IACb,CAAC;IACD,OAAO,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AAAA,CACpD;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,gCAAgC,CAAC,OAAO,GAA0B,IAAI,EAAU;IAC/F,OAAO,IAAI,CAAC,WAAW,EAAE,EAAE,iBAAiB,EAAE,SAAS,EAAE,wBAAwB,OAAO,OAAO,CAAC,CAAC;AAAA,CACjG","sourcesContent":["/**\n * Reasoning-router bias compiler — privacy-safe learning ledger.\n *\n * Pure, deterministic compiler from v4 feedback records (see\n * router-feedback-collector.ts) to a bounded, per-cell reasoning-level bias\n * snapshot. No clock, randomness, network, or model calls: the same input array\n * always produces the same output (`JSON.stringify` byte-identical), regardless\n * of input record order. `parseRouterBiasSnapshot` performs no filesystem\n * access itself; the actual disk read lives in agent-session.ts.\n */\n\nimport { createHash } from \"crypto\";\nimport { join } from \"path\";\nimport { getAgentDir } from \"../config.ts\";\nimport {\n\tisRouterFeedbackRecord,\n\tROUTER_FEEDBACK_LANE_TYPES,\n\tROUTER_FEEDBACK_LEN_BUCKETS,\n\tROUTER_FEEDBACK_TASK_CLASSES,\n\ttype RouterFeedbackLaneType,\n\ttype RouterFeedbackLenBucket,\n\ttype RouterFeedbackRecord,\n\ttype RouterFeedbackTaskClass,\n\ttype RouterFeedbackVersion,\n} from \"./router-feedback-collector.ts\";\n\n/** Minimum non-neutral (\"strong\") record count per cell before any bias applies. */\nexport const BIAS_STRONG_THRESHOLD = 5;\n\n/** Bias magnitude bound, matching the resolver's bias clamp. */\nexport const BIAS_MAX_STEPS = 2;\n\n/** Bounded per-cell bias, in ladder steps. Never outside [-2, 2]. */\nexport type RouterBiasSteps = -2 | -1 | 0 | 1 | 2;\n\n/** The feature tuple a bias cell is keyed on — identical dimensions the router already computes locally. */\nexport interface RouterBiasCellKey {\n\treadonly predictedClass: RouterFeedbackTaskClass;\n\treadonly laneType: RouterFeedbackLaneType;\n\treadonly lenBucket: RouterFeedbackLenBucket;\n\treadonly hadFence: boolean;\n\treadonly hadDiff: boolean;\n}\n\nexport interface RouterBiasCell extends RouterBiasCellKey {\n\treadonly biasSteps: RouterBiasSteps;\n\t/** Non-neutral (up/down-signaling) record count for this cell. */\n\treadonly nStrong: number;\n\t/** Total record count for this cell, including neutral (accepted/same/pass) records. */\n\treadonly nTotal: number;\n}\n\nexport interface RouterBiasSnapshot {\n\treadonly schemaVersion: \"router-bias-snapshot/v1\";\n\t/** sha256 hex digest of the canonicalized valid-record set actually compiled; not a prompt hash. */\n\treadonly sourceRecordDigest: string;\n\t/** Count of records that were validated and included in this snapshot. */\n\treadonly consideredCount: number;\n\t/** Count of records excluded from this snapshot (schema-invalid, or router-version mismatch). */\n\treadonly droppedCount: number;\n\t/** Bias cells, sorted deterministically. Empty when there is no ledger data or no cell reaches the threshold. */\n\treadonly biasCells: readonly RouterBiasCell[];\n}\n\nexport interface CompileBiasSnapshotOptions {\n\t/**\n\t * Restrict compilation to one router version. The only supported runtime\n\t * router version is v4; records tagged with any other value are invalid and\n\t * counted in `droppedCount`.\n\t */\n\treadonly routerVersion?: RouterFeedbackVersion;\n}\n\nfunction cellKeyString(key: RouterBiasCellKey): string {\n\treturn [key.predictedClass, key.laneType, String(key.lenBucket), String(key.hadFence), String(key.hadDiff)].join(\n\t\t\"|\",\n\t);\n}\n\n/** +1 \"needed more effort\", -1 \"needed less effort\", 0 neutral (no directional bias contribution). */\nfunction outcomeDirection(outcome: RouterFeedbackRecord[\"outcome\"]): -1 | 0 | 1 {\n\tif (outcome === \"up\" || outcome === \"fail\" || outcome === \"debug-follow-up\") return 1;\n\tif (outcome === \"down\") return -1;\n\treturn 0;\n}\n\n/** Fixed-key-order JSON so identical records always canonicalize to identical bytes. */\nfunction canonicalRecordJson(record: RouterFeedbackRecord): string {\n\treturn JSON.stringify({\n\t\trouterVersion: record.routerVersion,\n\t\tlaneType: record.laneType,\n\t\tpredictedClass: record.predictedClass,\n\t\tresolvedLevel: record.resolvedLevel,\n\t\tacceptedLevel: record.acceptedLevel,\n\t\tsignal: record.signal,\n\t\toutcome: record.outcome,\n\t\tlenBucket: record.lenBucket,\n\t\thadFence: record.hadFence,\n\t\thadDiff: record.hadDiff,\n\t});\n}\n\ninterface CellAccumulator extends RouterBiasCellKey {\n\tnTotal: number;\n\tupVotes: number;\n\tdownVotes: number;\n}\n\nfunction computeBiasSteps(upVotes: number, downVotes: number, nStrong: number): RouterBiasSteps {\n\tif (nStrong < BIAS_STRONG_THRESHOLD) return 0;\n\tconst majority = upVotes - downVotes;\n\tif (majority === 0) return 0;\n\n\tconst dominance = Math.abs(majority) / nStrong;\n\tconst magnitude = dominance >= 0.75 ? BIAS_MAX_STEPS : 1;\n\tconst signedMagnitude = majority > 0 ? magnitude : -magnitude;\n\treturn Math.max(-BIAS_MAX_STEPS, Math.min(BIAS_MAX_STEPS, signedMagnitude)) as RouterBiasSteps;\n}\n\n/**\n * Deterministically compile a bounded bias snapshot from raw (possibly\n * malformed) ledger entries. Unknown-shaped entries — including ones with\n * extra keys, missing keys, or out-of-range enum values — are dropped and\n * counted, never thrown. Records tagged with a `routerVersion` other than the\n * target version are also dropped and counted (never merged across router\n * versions into one snapshot). An empty or all-dropped input compiles to an\n * empty-bias snapshot (`biasCells: []`), which is exactly the shipped\n * no-learning behavior — this makes \"empty ledger -> zero bias\" true by\n * construction, not by a special case.\n *\n * Pure: same `records` (any order) -> same `RouterBiasSnapshot` (byte-\n * identical `JSON.stringify` output), because cells are grouped by an\n * order-independent key and the emitted cell/record ordering is always\n * sorted before serialization.\n */\nexport function compileBiasSnapshot(\n\trecords: readonly unknown[],\n\toptions: CompileBiasSnapshotOptions = {},\n): RouterBiasSnapshot {\n\tconst routerVersion = options.routerVersion ?? \"v4\";\n\tconst valid: RouterFeedbackRecord[] = [];\n\tlet droppedCount = 0;\n\n\tfor (const entry of records) {\n\t\tif (isRouterFeedbackRecord(entry) && entry.routerVersion === routerVersion) {\n\t\t\tvalid.push(entry);\n\t\t} else {\n\t\t\tdroppedCount += 1;\n\t\t}\n\t}\n\n\tconst cells = new Map<string, CellAccumulator>();\n\tfor (const record of valid) {\n\t\tconst key: RouterBiasCellKey = {\n\t\t\tpredictedClass: record.predictedClass,\n\t\t\tlaneType: record.laneType,\n\t\t\tlenBucket: record.lenBucket,\n\t\t\thadFence: record.hadFence,\n\t\t\thadDiff: record.hadDiff,\n\t\t};\n\t\tconst mapKey = cellKeyString(key);\n\t\tconst existing = cells.get(mapKey) ?? { ...key, nTotal: 0, upVotes: 0, downVotes: 0 };\n\t\texisting.nTotal += 1;\n\t\tconst direction = outcomeDirection(record.outcome);\n\t\tif (direction === 1) existing.upVotes += 1;\n\t\telse if (direction === -1) existing.downVotes += 1;\n\t\tcells.set(mapKey, existing);\n\t}\n\n\tconst biasCells: RouterBiasCell[] = [];\n\tfor (const mapKey of [...cells.keys()].sort()) {\n\t\tconst acc = cells.get(mapKey);\n\t\tif (!acc) continue;\n\t\tconst nStrong = acc.upVotes + acc.downVotes;\n\t\tbiasCells.push({\n\t\t\tpredictedClass: acc.predictedClass,\n\t\t\tlaneType: acc.laneType,\n\t\t\tlenBucket: acc.lenBucket,\n\t\t\thadFence: acc.hadFence,\n\t\t\thadDiff: acc.hadDiff,\n\t\t\tbiasSteps: computeBiasSteps(acc.upVotes, acc.downVotes, nStrong),\n\t\t\tnStrong,\n\t\t\tnTotal: acc.nTotal,\n\t\t});\n\t}\n\n\tconst canonicalValid = valid.map(canonicalRecordJson).sort();\n\tconst sourceRecordDigest = createHash(\"sha256\").update(canonicalValid.join(\"\\n\")).digest(\"hex\");\n\n\treturn {\n\t\tschemaVersion: \"router-bias-snapshot/v1\",\n\t\tsourceRecordDigest,\n\t\tconsideredCount: valid.length,\n\t\tdroppedCount,\n\t\tbiasCells,\n\t};\n}\n\n/** Look up the compiled bias for one feature cell; 0 (no bias) when the cell is absent from the snapshot. */\nexport function getBiasStepsForCell(snapshot: RouterBiasSnapshot, key: RouterBiasCellKey): RouterBiasSteps {\n\tconst wantKey = cellKeyString(key);\n\tfor (const cell of snapshot.biasCells) {\n\t\tif (cellKeyString(cell) === wantKey) return cell.biasSteps;\n\t}\n\treturn 0;\n}\n\n// ============================================================================\n// Snapshot parser / loader (Goal 010 Lane I)\n// ============================================================================\n//\n// Everything below lets a caller safely turn untrusted on-disk JSON text into\n// a validated RouterBiasSnapshot. Same posture as isRouterFeedbackRecord in\n// router-feedback-collector.ts: an exact positive key-set allowlist (not a\n// denylist), bounded per-field checks reusing the ledger's own closed enums,\n// and \"never throw, return null/false instead\" on any malformed input.\n\n/** Bounded bias-step values a compiled cell may carry; mirrors RouterBiasSteps. */\nconst ROUTER_BIAS_STEPS_VALUES: readonly RouterBiasSteps[] = [-2, -1, 0, 1, 2];\n\n/** Exact allowed key set for one compiled `RouterBiasCell`, sorted for canonical set-equality comparison. */\nconst ROUTER_BIAS_CELL_KEYS: readonly string[] = [\n\t\"predictedClass\",\n\t\"laneType\",\n\t\"lenBucket\",\n\t\"hadFence\",\n\t\"hadDiff\",\n\t\"biasSteps\",\n\t\"nStrong\",\n\t\"nTotal\",\n].sort();\n\n/** Exact allowed key set for a `RouterBiasSnapshot`, sorted for canonical set-equality comparison. */\nconst ROUTER_BIAS_SNAPSHOT_KEYS: readonly string[] = [\n\t\"schemaVersion\",\n\t\"sourceRecordDigest\",\n\t\"consideredCount\",\n\t\"droppedCount\",\n\t\"biasCells\",\n].sort();\n\n/** sha256 hex digest shape check for `sourceRecordDigest` (never trusts the value is actually a real digest of anything, only that it has the right shape). */\nconst SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction hasExactKeySet(value: Record<string, unknown>, allowedSortedKeys: readonly string[]): boolean {\n\tconst keys = Object.keys(value).sort();\n\tif (keys.length !== allowedSortedKeys.length) return false;\n\tfor (let i = 0; i < keys.length; i++) {\n\t\tif (keys[i] !== allowedSortedKeys[i]) return false;\n\t}\n\treturn true;\n}\n\nfunction isBoundedNonNegativeInteger(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isInteger(value) && value >= 0;\n}\n\n/**\n * Strict runtime validator for one compiled bias cell. Exact eight-key\n * allowlist plus bounded per-field checks, reusing the same closed enums as\n * `isRouterFeedbackRecord` (`ROUTER_FEEDBACK_TASK_CLASSES`,\n * `ROUTER_FEEDBACK_LANE_TYPES`, `ROUTER_FEEDBACK_LEN_BUCKETS`) so this\n * validator cannot silently drift from the ledger's own schema. `nStrong` is\n * additionally required to never exceed `nTotal` (true by construction for\n * every cell `compileBiasSnapshot` can produce; a cheap sanity bound against\n * a hand-tampered file).\n */\nexport function isRouterBiasCell(value: unknown): value is RouterBiasCell {\n\tif (!isPlainObject(value)) return false;\n\tif (!hasExactKeySet(value, ROUTER_BIAS_CELL_KEYS)) return false;\n\tif (!ROUTER_FEEDBACK_TASK_CLASSES.includes(value.predictedClass as RouterFeedbackTaskClass)) return false;\n\tif (!ROUTER_FEEDBACK_LANE_TYPES.includes(value.laneType as RouterFeedbackLaneType)) return false;\n\tif (!ROUTER_FEEDBACK_LEN_BUCKETS.includes(value.lenBucket as RouterFeedbackLenBucket)) return false;\n\tif (typeof value.hadFence !== \"boolean\") return false;\n\tif (typeof value.hadDiff !== \"boolean\") return false;\n\tif (!ROUTER_BIAS_STEPS_VALUES.includes(value.biasSteps as RouterBiasSteps)) return false;\n\tif (!isBoundedNonNegativeInteger(value.nStrong)) return false;\n\tif (!isBoundedNonNegativeInteger(value.nTotal)) return false;\n\tif ((value.nStrong as number) > (value.nTotal as number)) return false;\n\treturn true;\n}\n\n/**\n * Strict runtime validator for a compiled bias snapshot. Exact five-key\n * allowlist; `biasCells` must be an array whose every element passes\n * `isRouterBiasCell`. Never trusts on-disk content: any unexpected shape,\n * extra key, or out-of-range value returns `false` rather than throwing.\n */\nexport function isRouterBiasSnapshot(value: unknown): value is RouterBiasSnapshot {\n\tif (!isPlainObject(value)) return false;\n\tif (!hasExactKeySet(value, ROUTER_BIAS_SNAPSHOT_KEYS)) return false;\n\tif (value.schemaVersion !== \"router-bias-snapshot/v1\") return false;\n\tif (typeof value.sourceRecordDigest !== \"string\" || !SHA256_HEX_PATTERN.test(value.sourceRecordDigest)) return false;\n\tif (!isBoundedNonNegativeInteger(value.consideredCount)) return false;\n\tif (!isBoundedNonNegativeInteger(value.droppedCount)) return false;\n\tif (!Array.isArray(value.biasCells)) return false;\n\tfor (const cell of value.biasCells) {\n\t\tif (!isRouterBiasCell(cell)) return false;\n\t}\n\treturn true;\n}\n\n/**\n * Safe parse of raw (untrusted, e.g. on-disk) JSON text into a validated\n * `RouterBiasSnapshot`. Never throws: malformed JSON or any schema violation\n * returns `null`. Performs no filesystem access itself — callers own the\n * actual read — so this module's zero-I/O determinism guarantee for\n * `compileBiasSnapshot`/`getBiasStepsForCell` is unaffected.\n */\nexport function parseRouterBiasSnapshot(raw: string): RouterBiasSnapshot | null {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch {\n\t\treturn null;\n\t}\n\treturn isRouterBiasSnapshot(parsed) ? parsed : null;\n}\n\n/**\n * Default compiled-bias-snapshot path:\n * `<agentDir>/router-feedback/weights/router-bias-snapshot.<version>.json` —\n * matches `compile-bias-snapshot.ts`'s own default `--out` naming exactly, so\n * a snapshot compiled offline for `version` is found here with no extra\n * configuration. Owner-only agent dir, never repo-local (mirrors\n * `getDefaultRouterFeedbackLedgerPath`). Defaults to \"v4\", the router version\n * this lane wires bias *consumption* for.\n */\nexport function getDefaultRouterBiasSnapshotPath(version: RouterFeedbackVersion = \"v4\"): string {\n\treturn join(getAgentDir(), \"router-feedback\", \"weights\", `router-bias-snapshot.${version}.json`);\n}\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared resolver core for the v4 reasoning router: canonical task-class ->
|
|
3
|
+
* ThinkingLevel rule table, lane-step adjustment, ladder-index clamping, and
|
|
4
|
+
* bounded bias/hint/escalation resolution logic.
|
|
5
|
+
*
|
|
6
|
+
* Deterministic, side-effect-free: no clock, randomness, or I/O.
|
|
7
|
+
*/
|
|
8
|
+
import type { ThinkingLevel } from "omk-agent-core";
|
|
9
|
+
/** The 7-class union used by the v4 classifier. */
|
|
10
|
+
export type ReasoningTaskClass = "trivial" | "simple-edit" | "code-gen" | "debug" | "refactor" | "review" | "plan";
|
|
11
|
+
/** The 6-lane union used by the v4 classifier. */
|
|
12
|
+
export type ReasoningLane = "planner" | "security" | "explorer" | "coder" | "reviewer" | "tester";
|
|
13
|
+
/** Reasoning ladder used for targets and clamping. Intentionally excludes "off". */
|
|
14
|
+
export declare const REASONING_LADDER: readonly ThinkingLevel[];
|
|
15
|
+
/** Static rule table: task class -> recommended ThinkingLevel (before lane adjustment, bias, hint, escalation, and clamping). */
|
|
16
|
+
export declare const TASK_CLASS_THINKING_LEVELS: Readonly<Record<ReasoningTaskClass, ThinkingLevel>>;
|
|
17
|
+
/** Lane adjustment in ladder steps: planner/security escalate, explorer de-escalates. */
|
|
18
|
+
export declare const LANE_STEP: Readonly<Partial<Record<ReasoningLane, 1 | -1>>>;
|
|
19
|
+
/** Maximum magnitude of the learning/consult bias (ladder steps). */
|
|
20
|
+
export declare const BIAS_MAX = 2;
|
|
21
|
+
/** Confidence floor at which an override hint is fused into the target. */
|
|
22
|
+
export declare const HINT_CONFIDENCE_THRESHOLD = 0.7;
|
|
23
|
+
/**
|
|
24
|
+
* Clamp a target ladder index to capability: the highest level in
|
|
25
|
+
* `availableLevels` that is <= the target; if no available level is at/below
|
|
26
|
+
* the target, return the lowest available reasoning level. Never invents a
|
|
27
|
+
* level outside `availableLevels`. `"off"` is only returned when
|
|
28
|
+
* `availableLevels` is empty (callers are expected to bypass the router
|
|
29
|
+
* entirely for models with `reasoning: false`).
|
|
30
|
+
*/
|
|
31
|
+
export declare function clampToAvailable(targetIndex: number, availableLevels: readonly ThinkingLevel[]): ThinkingLevel;
|
|
32
|
+
/**
|
|
33
|
+
* Canonical resolver core for v4.
|
|
34
|
+
*
|
|
35
|
+
* Pipeline: base rule table -> lane step -> bounded bias [-BIAS_MAX,+BIAS_MAX]
|
|
36
|
+
* -> optional hint fusion (bounded ±BIAS_MAX steps toward hint.level, only
|
|
37
|
+
* when hint.confidence >= HINT_CONFIDENCE_THRESHOLD) -> non-negative
|
|
38
|
+
* escalationSteps bump -> clamp to availableLevels.
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolveThinkingLevelCore(taskClass: ReasoningTaskClass, availableLevels: readonly ThinkingLevel[], laneType: ReasoningLane | undefined, bias: number, hint: {
|
|
41
|
+
level: ThinkingLevel;
|
|
42
|
+
confidence: number;
|
|
43
|
+
} | null, escalationSteps: number): ThinkingLevel;
|
|
44
|
+
//# sourceMappingURL=reasoning-router-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reasoning-router-resolver.d.ts","sourceRoot":"","sources":["../../src/core/reasoning-router-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD,mDAAmD;AACnD,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,aAAa,GAAG,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEnH,kDAAkD;AAClD,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,CAAC;AAElG,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,EAAE,SAAS,aAAa,EAAyD,CAAC;AAE/G,iIAAiI;AACjI,eAAO,MAAM,0BAA0B,EAAE,QAAQ,CAAC,MAAM,CAAC,kBAAkB,EAAE,aAAa,CAAC,CAQ1F,CAAC;AAEF,yFAAyF;AACzF,eAAO,MAAM,SAAS,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAItE,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,QAAQ,IAAI,CAAC;AAC1B,2EAA2E;AAC3E,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAM7C;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,eAAe,EAAE,SAAS,aAAa,EAAE,GAAG,aAAa,CAU9G;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACvC,SAAS,EAAE,kBAAkB,EAC7B,eAAe,EAAE,SAAS,aAAa,EAAE,EACzC,QAAQ,EAAE,aAAa,GAAG,SAAS,EACnC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE;IAAE,KAAK,EAAE,aAAa,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,EACzD,eAAe,EAAE,MAAM,GACrB,aAAa,CAoBf","sourcesContent":["/**\n * Shared resolver core for the v4 reasoning router: canonical task-class ->\n * ThinkingLevel rule table, lane-step adjustment, ladder-index clamping, and\n * bounded bias/hint/escalation resolution logic.\n *\n * Deterministic, side-effect-free: no clock, randomness, or I/O.\n */\n\nimport type { ThinkingLevel } from \"omk-agent-core\";\n\n/** The 7-class union used by the v4 classifier. */\nexport type ReasoningTaskClass = \"trivial\" | \"simple-edit\" | \"code-gen\" | \"debug\" | \"refactor\" | \"review\" | \"plan\";\n\n/** The 6-lane union used by the v4 classifier. */\nexport type ReasoningLane = \"planner\" | \"security\" | \"explorer\" | \"coder\" | \"reviewer\" | \"tester\";\n\n/** Reasoning ladder used for targets and clamping. Intentionally excludes \"off\". */\nexport const REASONING_LADDER: readonly ThinkingLevel[] = [\"minimal\", \"low\", \"medium\", \"high\", \"xhigh\", \"max\"];\n\n/** Static rule table: task class -> recommended ThinkingLevel (before lane adjustment, bias, hint, escalation, and clamping). */\nexport const TASK_CLASS_THINKING_LEVELS: Readonly<Record<ReasoningTaskClass, ThinkingLevel>> = {\n\ttrivial: \"minimal\",\n\t\"simple-edit\": \"low\",\n\t\"code-gen\": \"medium\",\n\tdebug: \"high\",\n\trefactor: \"high\",\n\treview: \"high\",\n\tplan: \"xhigh\",\n};\n\n/** Lane adjustment in ladder steps: planner/security escalate, explorer de-escalates. */\nexport const LANE_STEP: Readonly<Partial<Record<ReasoningLane, 1 | -1>>> = {\n\tplanner: 1,\n\tsecurity: 1,\n\texplorer: -1,\n};\n\n/** Maximum magnitude of the learning/consult bias (ladder steps). */\nexport const BIAS_MAX = 2;\n/** Confidence floor at which an override hint is fused into the target. */\nexport const HINT_CONFIDENCE_THRESHOLD = 0.7;\n\nfunction clampToLadderIndex(index: number): number {\n\treturn Math.max(0, Math.min(index, REASONING_LADDER.length - 1));\n}\n\n/**\n * Clamp a target ladder index to capability: the highest level in\n * `availableLevels` that is <= the target; if no available level is at/below\n * the target, return the lowest available reasoning level. Never invents a\n * level outside `availableLevels`. `\"off\"` is only returned when\n * `availableLevels` is empty (callers are expected to bypass the router\n * entirely for models with `reasoning: false`).\n */\nexport function clampToAvailable(targetIndex: number, availableLevels: readonly ThinkingLevel[]): ThinkingLevel {\n\tconst availableOnLadder = REASONING_LADDER.filter((level) => availableLevels.includes(level));\n\tif (availableOnLadder.length === 0) {\n\t\treturn availableLevels[0] ?? \"off\";\n\t}\n\tfor (let i = targetIndex; i >= 0; i--) {\n\t\tconst candidate = REASONING_LADDER[i];\n\t\tif (availableOnLadder.includes(candidate)) return candidate;\n\t}\n\treturn availableOnLadder[0];\n}\n\n/**\n * Canonical resolver core for v4.\n *\n * Pipeline: base rule table -> lane step -> bounded bias [-BIAS_MAX,+BIAS_MAX]\n * -> optional hint fusion (bounded ±BIAS_MAX steps toward hint.level, only\n * when hint.confidence >= HINT_CONFIDENCE_THRESHOLD) -> non-negative\n * escalationSteps bump -> clamp to availableLevels.\n */\nexport function resolveThinkingLevelCore(\n\ttaskClass: ReasoningTaskClass,\n\tavailableLevels: readonly ThinkingLevel[],\n\tlaneType: ReasoningLane | undefined,\n\tbias: number,\n\thint: { level: ThinkingLevel; confidence: number } | null,\n\tescalationSteps: number,\n): ThinkingLevel {\n\tconst baseIndex = REASONING_LADDER.indexOf(TASK_CLASS_THINKING_LEVELS[taskClass]);\n\tconst laneStep = laneType ? (LANE_STEP[laneType] ?? 0) : 0;\n\tconst biasClamped = Math.max(-BIAS_MAX, Math.min(BIAS_MAX, bias));\n\tlet targetIndex = clampToLadderIndex(baseIndex + laneStep + biasClamped);\n\n\tif (hint !== null && hint.confidence >= HINT_CONFIDENCE_THRESHOLD) {\n\t\tconst hintIndex = REASONING_LADDER.indexOf(hint.level);\n\t\tif (hintIndex >= 0) {\n\t\t\tconst delta = hintIndex - targetIndex;\n\t\t\tconst step = Math.max(-BIAS_MAX, Math.min(BIAS_MAX, delta));\n\t\t\ttargetIndex = clampToLadderIndex(targetIndex + step);\n\t\t}\n\t}\n\n\tif (escalationSteps > 0) {\n\t\ttargetIndex = clampToLadderIndex(targetIndex + escalationSteps);\n\t}\n\n\treturn clampToAvailable(targetIndex, availableLevels);\n}\n"]}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared resolver core for the v4 reasoning router: canonical task-class ->
|
|
3
|
+
* ThinkingLevel rule table, lane-step adjustment, ladder-index clamping, and
|
|
4
|
+
* bounded bias/hint/escalation resolution logic.
|
|
5
|
+
*
|
|
6
|
+
* Deterministic, side-effect-free: no clock, randomness, or I/O.
|
|
7
|
+
*/
|
|
8
|
+
/** Reasoning ladder used for targets and clamping. Intentionally excludes "off". */
|
|
9
|
+
export const REASONING_LADDER = ["minimal", "low", "medium", "high", "xhigh", "max"];
|
|
10
|
+
/** Static rule table: task class -> recommended ThinkingLevel (before lane adjustment, bias, hint, escalation, and clamping). */
|
|
11
|
+
export const TASK_CLASS_THINKING_LEVELS = {
|
|
12
|
+
trivial: "minimal",
|
|
13
|
+
"simple-edit": "low",
|
|
14
|
+
"code-gen": "medium",
|
|
15
|
+
debug: "high",
|
|
16
|
+
refactor: "high",
|
|
17
|
+
review: "high",
|
|
18
|
+
plan: "xhigh",
|
|
19
|
+
};
|
|
20
|
+
/** Lane adjustment in ladder steps: planner/security escalate, explorer de-escalates. */
|
|
21
|
+
export const LANE_STEP = {
|
|
22
|
+
planner: 1,
|
|
23
|
+
security: 1,
|
|
24
|
+
explorer: -1,
|
|
25
|
+
};
|
|
26
|
+
/** Maximum magnitude of the learning/consult bias (ladder steps). */
|
|
27
|
+
export const BIAS_MAX = 2;
|
|
28
|
+
/** Confidence floor at which an override hint is fused into the target. */
|
|
29
|
+
export const HINT_CONFIDENCE_THRESHOLD = 0.7;
|
|
30
|
+
function clampToLadderIndex(index) {
|
|
31
|
+
return Math.max(0, Math.min(index, REASONING_LADDER.length - 1));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Clamp a target ladder index to capability: the highest level in
|
|
35
|
+
* `availableLevels` that is <= the target; if no available level is at/below
|
|
36
|
+
* the target, return the lowest available reasoning level. Never invents a
|
|
37
|
+
* level outside `availableLevels`. `"off"` is only returned when
|
|
38
|
+
* `availableLevels` is empty (callers are expected to bypass the router
|
|
39
|
+
* entirely for models with `reasoning: false`).
|
|
40
|
+
*/
|
|
41
|
+
export function clampToAvailable(targetIndex, availableLevels) {
|
|
42
|
+
const availableOnLadder = REASONING_LADDER.filter((level) => availableLevels.includes(level));
|
|
43
|
+
if (availableOnLadder.length === 0) {
|
|
44
|
+
return availableLevels[0] ?? "off";
|
|
45
|
+
}
|
|
46
|
+
for (let i = targetIndex; i >= 0; i--) {
|
|
47
|
+
const candidate = REASONING_LADDER[i];
|
|
48
|
+
if (availableOnLadder.includes(candidate))
|
|
49
|
+
return candidate;
|
|
50
|
+
}
|
|
51
|
+
return availableOnLadder[0];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Canonical resolver core for v4.
|
|
55
|
+
*
|
|
56
|
+
* Pipeline: base rule table -> lane step -> bounded bias [-BIAS_MAX,+BIAS_MAX]
|
|
57
|
+
* -> optional hint fusion (bounded ±BIAS_MAX steps toward hint.level, only
|
|
58
|
+
* when hint.confidence >= HINT_CONFIDENCE_THRESHOLD) -> non-negative
|
|
59
|
+
* escalationSteps bump -> clamp to availableLevels.
|
|
60
|
+
*/
|
|
61
|
+
export function resolveThinkingLevelCore(taskClass, availableLevels, laneType, bias, hint, escalationSteps) {
|
|
62
|
+
const baseIndex = REASONING_LADDER.indexOf(TASK_CLASS_THINKING_LEVELS[taskClass]);
|
|
63
|
+
const laneStep = laneType ? (LANE_STEP[laneType] ?? 0) : 0;
|
|
64
|
+
const biasClamped = Math.max(-BIAS_MAX, Math.min(BIAS_MAX, bias));
|
|
65
|
+
let targetIndex = clampToLadderIndex(baseIndex + laneStep + biasClamped);
|
|
66
|
+
if (hint !== null && hint.confidence >= HINT_CONFIDENCE_THRESHOLD) {
|
|
67
|
+
const hintIndex = REASONING_LADDER.indexOf(hint.level);
|
|
68
|
+
if (hintIndex >= 0) {
|
|
69
|
+
const delta = hintIndex - targetIndex;
|
|
70
|
+
const step = Math.max(-BIAS_MAX, Math.min(BIAS_MAX, delta));
|
|
71
|
+
targetIndex = clampToLadderIndex(targetIndex + step);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (escalationSteps > 0) {
|
|
75
|
+
targetIndex = clampToLadderIndex(targetIndex + escalationSteps);
|
|
76
|
+
}
|
|
77
|
+
return clampToAvailable(targetIndex, availableLevels);
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=reasoning-router-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reasoning-router-resolver.js","sourceRoot":"","sources":["../../src/core/reasoning-router-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared resolver core for the v4 reasoning router: canonical task-class ->\n * ThinkingLevel rule table, lane-step adjustment, ladder-index clamping, and\n * bounded bias/hint/escalation resolution logic.\n *\n * Deterministic, side-effect-free: no clock, randomness, or I/O.\n */\n\nimport type { ThinkingLevel } from \"omk-agent-core\";\n\n/** The 7-class union used by the v4 classifier. */\nexport type ReasoningTaskClass = \"trivial\" | \"simple-edit\" | \"code-gen\" | \"debug\" | \"refactor\" | \"review\" | \"plan\";\n\n/** The 6-lane union used by the v4 classifier. */\nexport type ReasoningLane = \"planner\" | \"security\" | \"explorer\" | \"coder\" | \"reviewer\" | \"tester\";\n\n/** Reasoning ladder used for targets and clamping. Intentionally excludes \"off\". */\nexport const REASONING_LADDER: readonly ThinkingLevel[] = [\"minimal\", \"low\", \"medium\", \"high\", \"xhigh\", \"max\"];\n\n/** Static rule table: task class -> recommended ThinkingLevel (before lane adjustment, bias, hint, escalation, and clamping). */\nexport const TASK_CLASS_THINKING_LEVELS: Readonly<Record<ReasoningTaskClass, ThinkingLevel>> = {\n\ttrivial: \"minimal\",\n\t\"simple-edit\": \"low\",\n\t\"code-gen\": \"medium\",\n\tdebug: \"high\",\n\trefactor: \"high\",\n\treview: \"high\",\n\tplan: \"xhigh\",\n};\n\n/** Lane adjustment in ladder steps: planner/security escalate, explorer de-escalates. */\nexport const LANE_STEP: Readonly<Partial<Record<ReasoningLane, 1 | -1>>> = {\n\tplanner: 1,\n\tsecurity: 1,\n\texplorer: -1,\n};\n\n/** Maximum magnitude of the learning/consult bias (ladder steps). */\nexport const BIAS_MAX = 2;\n/** Confidence floor at which an override hint is fused into the target. */\nexport const HINT_CONFIDENCE_THRESHOLD = 0.7;\n\nfunction clampToLadderIndex(index: number): number {\n\treturn Math.max(0, Math.min(index, REASONING_LADDER.length - 1));\n}\n\n/**\n * Clamp a target ladder index to capability: the highest level in\n * `availableLevels` that is <= the target; if no available level is at/below\n * the target, return the lowest available reasoning level. Never invents a\n * level outside `availableLevels`. `\"off\"` is only returned when\n * `availableLevels` is empty (callers are expected to bypass the router\n * entirely for models with `reasoning: false`).\n */\nexport function clampToAvailable(targetIndex: number, availableLevels: readonly ThinkingLevel[]): ThinkingLevel {\n\tconst availableOnLadder = REASONING_LADDER.filter((level) => availableLevels.includes(level));\n\tif (availableOnLadder.length === 0) {\n\t\treturn availableLevels[0] ?? \"off\";\n\t}\n\tfor (let i = targetIndex; i >= 0; i--) {\n\t\tconst candidate = REASONING_LADDER[i];\n\t\tif (availableOnLadder.includes(candidate)) return candidate;\n\t}\n\treturn availableOnLadder[0];\n}\n\n/**\n * Canonical resolver core for v4.\n *\n * Pipeline: base rule table -> lane step -> bounded bias [-BIAS_MAX,+BIAS_MAX]\n * -> optional hint fusion (bounded ±BIAS_MAX steps toward hint.level, only\n * when hint.confidence >= HINT_CONFIDENCE_THRESHOLD) -> non-negative\n * escalationSteps bump -> clamp to availableLevels.\n */\nexport function resolveThinkingLevelCore(\n\ttaskClass: ReasoningTaskClass,\n\tavailableLevels: readonly ThinkingLevel[],\n\tlaneType: ReasoningLane | undefined,\n\tbias: number,\n\thint: { level: ThinkingLevel; confidence: number } | null,\n\tescalationSteps: number,\n): ThinkingLevel {\n\tconst baseIndex = REASONING_LADDER.indexOf(TASK_CLASS_THINKING_LEVELS[taskClass]);\n\tconst laneStep = laneType ? (LANE_STEP[laneType] ?? 0) : 0;\n\tconst biasClamped = Math.max(-BIAS_MAX, Math.min(BIAS_MAX, bias));\n\tlet targetIndex = clampToLadderIndex(baseIndex + laneStep + biasClamped);\n\n\tif (hint !== null && hint.confidence >= HINT_CONFIDENCE_THRESHOLD) {\n\t\tconst hintIndex = REASONING_LADDER.indexOf(hint.level);\n\t\tif (hintIndex >= 0) {\n\t\t\tconst delta = hintIndex - targetIndex;\n\t\t\tconst step = Math.max(-BIAS_MAX, Math.min(BIAS_MAX, delta));\n\t\t\ttargetIndex = clampToLadderIndex(targetIndex + step);\n\t\t}\n\t}\n\n\tif (escalationSteps > 0) {\n\t\ttargetIndex = clampToLadderIndex(targetIndex + escalationSteps);\n\t}\n\n\treturn clampToAvailable(targetIndex, availableLevels);\n}\n"]}
|
|
@@ -1,68 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Reasoning-router v4 — frozen types and
|
|
3
|
-
* (Goal 009 Wave 1 Lane A, specs/008-reasoning-router-advanced-accuracy).
|
|
2
|
+
* Reasoning-router v4 — frozen types and calibratable weight schema.
|
|
4
3
|
*
|
|
5
4
|
* Pure, side-effect-free, erasable TypeScript. No runtime imports. This file
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* SIBLING of v1/v2/v3 — nothing here is imported by v1/v2/v3/agent-session.ts,
|
|
9
|
-
* and this lane does not edit any of those files.
|
|
5
|
+
* owns the closed task/lane unions, named scorer weights, and calibrated default
|
|
6
|
+
* preset used by `/think auto`.
|
|
10
7
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* v2's `RouterWeights` cross-multiplies every (feature, class) pair through a
|
|
15
|
-
* shared `RouterFeatures` shape (see reasoning-router-weights.ts). v3 abandoned
|
|
16
|
-
* that in favor of v1-style contextual boolean features (leadingIntent,
|
|
17
|
-
* localEdit, diagnosticEvidence, reviewScope, planBrief, refactorCue,
|
|
18
|
-
* implementationObject) scored with inline integer literals. v4 restores the
|
|
19
|
-
* "verdict is scored from named weights, not literals" discipline WITHOUT
|
|
20
|
-
* discarding v3's richer contextual feature set: every literal v3 used to bump
|
|
21
|
-
* a score now has a named field here, and `DEFAULT_WEIGHTS_V4` reproduces v3's
|
|
22
|
-
* exact literals (see reasoning-router-v3.ts) so classifyTaskV4's default
|
|
23
|
-
* output matches v3 on the full gold set and focused sentinels (spec 008 Req 2
|
|
24
|
-
* "Default weights produce v3-equivalent behavior unless new governance
|
|
25
|
-
* evidence justifies calibrated changes").
|
|
8
|
+
* `keywordFamily` is the class -> weight table for whole-prompt keyword-family
|
|
9
|
+
* patterns (see reasoning-router-v4.ts). `trivial` carries weight 0 because it
|
|
10
|
+
* is reached only via the zero-score fallback cascade.
|
|
26
11
|
*
|
|
27
|
-
* `
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* weight=4 for every family. `trivial` carries weight 0 (no keyword family;
|
|
32
|
-
* trivial is reached only via the zero-score fallback cascade, same as v1/v2/v3).
|
|
33
|
-
*
|
|
34
|
-
* `secondClauseIntent` is new in v4 (spec 008 plan.md "second-clause intent for
|
|
35
|
-
* compound prompts"): a bounded bonus applied when a second, distinct leading
|
|
36
|
-
* verb is found after a short-range conjunction split (see
|
|
37
|
-
* `splitCompoundClauseV4` in reasoning-router-v4.ts). It is INERT for every
|
|
38
|
-
* existing GOLD_SET row (verified: no gold-set prompt produces a non-null
|
|
39
|
-
* second-clause intent that differs from the primary), so enabling it changes
|
|
40
|
-
* no gold-set classification; it only sharpens confidence/margin metadata and
|
|
41
|
-
* gives future calibration lanes a lever without touching the algorithm.
|
|
42
|
-
*
|
|
43
|
-
* `negationWindowChars` bounds v4's negation gating (spec 008 plan.md "bounded
|
|
44
|
-
* negation window"): a keyword-family/contextual-feature match is only
|
|
45
|
-
* suppressed when a closed set of negation cues (don't/doesn't/isn't/never/
|
|
46
|
-
* avoid/skip/without/no need to/...) appears within this many characters
|
|
47
|
-
* immediately BEFORE the match, and only within the same clause (a
|
|
48
|
-
* .,;!? boundary stops the scan). This is intentionally narrow: it fixes
|
|
49
|
-
* concrete false-positive cases (e.g. "don't refactor this, just fix the
|
|
50
|
-
* crash") without touching any pattern that is anchored to the start of the
|
|
51
|
-
* leading clause (those are already negation-immune by construction, since
|
|
52
|
-
* "don't" never matches an anchored "^refactor" test).
|
|
12
|
+
* `secondClauseIntent` applies a bounded bonus when a second, distinct leading
|
|
13
|
+
* verb is found after a short-range conjunction split. `negationWindowChars`
|
|
14
|
+
* bounds negation gating so a nearby "don't/skip/without/no need" cue can
|
|
15
|
+
* suppress a whole-prompt signal without crossing a clause boundary.
|
|
53
16
|
*
|
|
54
17
|
* `lowConfidenceThreshold` / `highConfidenceThreshold` bound the confidence
|
|
55
18
|
* bands (`ClassifierVerdictV4.confidenceBand`) computed from
|
|
56
19
|
* `margin / topScore` in reasoning-router-v4.ts. They are metadata-only: they
|
|
57
20
|
* never change `taskClass`, only how `resolveThinkingLevelV4WithUncertainty`
|
|
58
|
-
* treats an already-decided class
|
|
59
|
-
* confidence may only hold or escalate effort, never reduce it below the
|
|
60
|
-
* text-derived base level).
|
|
61
|
-
* ============================================================================
|
|
21
|
+
* treats an already-decided class.
|
|
62
22
|
*/
|
|
63
|
-
/** Closed set of v4 task classes.
|
|
23
|
+
/** Closed set of v4 task classes. */
|
|
64
24
|
export type TaskClassV4 = "trivial" | "simple-edit" | "code-gen" | "debug" | "refactor" | "review" | "plan";
|
|
65
|
-
/** Subagent lane types recognized by the v4 classifier and resolvers.
|
|
25
|
+
/** Subagent lane types recognized by the v4 classifier and resolvers. */
|
|
66
26
|
export type ReasoningLaneTypeV4 = "planner" | "security" | "explorer" | "coder" | "reviewer" | "tester";
|
|
67
27
|
/** Fixed iteration order over TaskClassV4 (matches the type-declaration order; also v4's argmax tie-break precedence, highest first). */
|
|
68
28
|
export declare const TASK_CLASSES_V4: readonly TaskClassV4[];
|
|
@@ -72,29 +32,29 @@ export declare const TASK_CLASSES_V4: readonly TaskClassV4[];
|
|
|
72
32
|
* I/O anywhere in this file or its consumer.
|
|
73
33
|
*/
|
|
74
34
|
export interface RouterWeightsV4 {
|
|
75
|
-
/** Bump to "code-gen" when a code fence or diff hunk is present
|
|
35
|
+
/** Bump to "code-gen" when a code fence or diff hunk is present. */
|
|
76
36
|
readonly codeFenceOrDiff: number;
|
|
77
|
-
/** Bump to the primary leading-clause intent class, if any
|
|
37
|
+
/** Bump to the primary leading-clause intent class, if any. */
|
|
78
38
|
readonly leadingIntent: number;
|
|
79
39
|
/** Bump to a distinct second-clause intent class in a detected compound prompt (v4-new; default half of `leadingIntent`). */
|
|
80
40
|
readonly secondClauseIntent: number;
|
|
81
|
-
/** Bump to "simple-edit" when the composite local-edit feature fires
|
|
41
|
+
/** Bump to "simple-edit" when the composite local-edit feature fires. */
|
|
82
42
|
readonly localEdit: number;
|
|
83
|
-
/** Bump to "debug" when the composite diagnostic-evidence feature fires
|
|
43
|
+
/** Bump to "debug" when the composite diagnostic-evidence feature fires. */
|
|
84
44
|
readonly diagnosticEvidence: number;
|
|
85
|
-
/** Bump to "review" when the composite review-scope feature fires
|
|
45
|
+
/** Bump to "review" when the composite review-scope feature fires. */
|
|
86
46
|
readonly reviewScope: number;
|
|
87
|
-
/** Bump to "plan" when the composite plan-brief feature fires
|
|
47
|
+
/** Bump to "plan" when the composite plan-brief feature fires. */
|
|
88
48
|
readonly planBrief: number;
|
|
89
49
|
/** Bump to "plan" for release-bound operational runbooks (commit/push/tag/publish/changelog/CI bundles). */
|
|
90
50
|
readonly operationalRunbook: number;
|
|
91
|
-
/** Bump to "refactor" when the refactor-cue pattern matches anywhere in the prompt
|
|
51
|
+
/** Bump to "refactor" when the refactor-cue pattern matches anywhere in the prompt. */
|
|
92
52
|
readonly refactorCue: number;
|
|
93
|
-
/** Bump to "code-gen" when an implementation-object noun is present
|
|
53
|
+
/** Bump to "code-gen" when an implementation-object noun is present. */
|
|
94
54
|
readonly implementationObject: number;
|
|
95
|
-
/** Per-class whole-prompt keyword-family bump
|
|
55
|
+
/** Per-class whole-prompt keyword-family bump; trivial has no family, 0. */
|
|
96
56
|
readonly keywordFamily: Readonly<Record<TaskClassV4, number>>;
|
|
97
|
-
/** Bump to "code-gen" for a bare "add" keyword, gated by !localEdit
|
|
57
|
+
/** Bump to "code-gen" for a bare "add" keyword, gated by !localEdit. */
|
|
98
58
|
readonly addKeyword: number;
|
|
99
59
|
/** Bump to the class matching `history[0]`, if supplied (v4-new; 0 under DEFAULT_WEIGHTS_V4, inert until calibrated). */
|
|
100
60
|
readonly multiTurnPrior: number;
|
|
@@ -110,15 +70,11 @@ export interface RouterWeightsV4 {
|
|
|
110
70
|
readonly highConfidenceThreshold: number;
|
|
111
71
|
}
|
|
112
72
|
/**
|
|
113
|
-
* Calibrated default preset
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
* v4-new fields (`secondClauseIntent`, `negationWindowChars`) and the three
|
|
119
|
-
* inert extension fields (`multiTurnPrior`, `pressureBucket`, `judgeVote`) are
|
|
120
|
-
* the only behavior deltas from v3, and are proven inert on the current
|
|
121
|
-
* GOLD_SET (see the evidence file for this lane).
|
|
73
|
+
* Calibrated default preset. The named weights below are the production v4
|
|
74
|
+
* scorer configuration covered by
|
|
75
|
+
* test/suite/regressions/013-reasoning-router-v4-accuracy.test.ts. Extension
|
|
76
|
+
* fields (`multiTurnPrior`, `pressureBucket`, `judgeVote`) are wired but inert
|
|
77
|
+
* until a future calibration changes them.
|
|
122
78
|
*/
|
|
123
79
|
export declare const DEFAULT_WEIGHTS_V4: RouterWeightsV4;
|
|
124
80
|
//# sourceMappingURL=reasoning-router-v4-weights.d.ts.map
|