@knights-eye-chess/ai-analysis 0.0.0-stage → 0.1.0-dev.17
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/LICENSE +235 -0
- package/LICENSING.md +26 -0
- package/NOTICE +5 -0
- package/NPM-PUBLISHING.md +28 -0
- package/README.md +139 -2
- package/SOURCE-ORIGIN.json +16 -0
- package/THIRD_PARTY_NOTICES.md +18 -0
- package/build.mjs +22 -0
- package/dist/answer-pipeline.test.mjs +375 -0
- package/dist/chunk-PLKNJRPF.js +10662 -0
- package/dist/chunk-XQDZJOYD.js +2515 -0
- package/dist/index.js +146 -0
- package/dist/owner-tests/chess-reasoning-compact-search-preparation.test.mjs +963 -0
- package/dist/owner-tests/mechanism-encoding.test.mjs +529 -0
- package/dist/owner-tests/move-mechanism-facts.test.mjs +396 -0
- package/dist/owner-tests/pipeline-host-preparation.test.mjs +10556 -0
- package/dist/owner-tests/position-preparation.test.mjs +625 -0
- package/dist/owner-tests/preparation-consolidation.test.mjs +1052 -0
- package/dist/owner-tests/prose-reference-admission.test.mjs +1770 -0
- package/dist/owner-tests/score-comparison-notices.test.mjs +240 -0
- package/dist/owner-tests/simple-engine-presentation.test.mjs +233 -0
- package/dist/owner-tests/tool-input-loop-guard.test.mjs +78 -0
- package/dist/research.js +2274 -0
- package/dist/runtime-closure.json +103 -0
- package/dist/shared-runtime-closure.json +39 -0
- package/dist/shared.js +152 -0
- package/dist/types/ai-request-failure.d.ts +14 -0
- package/dist/types/ai-request-limits.d.ts +4 -0
- package/dist/types/ai-routing-diagnostics.d.ts +14 -0
- package/dist/types/chess-reasoning/ablation-adaptive-evidence-loop.d.ts +127 -0
- package/dist/types/chess-reasoning/ablation-adaptive-runtime.d.ts +178 -0
- package/dist/types/chess-reasoning/ablation-direct-synthesis.d.ts +1030 -0
- package/dist/types/chess-reasoning/ablation-evidence-bundle.d.ts +185 -0
- package/dist/types/chess-reasoning/ablation-evidence-capture.d.ts +37 -0
- package/dist/types/chess-reasoning/ablation-lean-direct-runtime.d.ts +193 -0
- package/dist/types/chess-reasoning/ablation-model-facing-evidence.d.ts +38 -0
- package/dist/types/chess-reasoning/ablation-transport-correction.d.ts +13 -0
- package/dist/types/chess-reasoning/ai-sdk-runtime-support.d.ts +93 -0
- package/dist/types/chess-reasoning/answer-host-contracts.d.ts +59 -0
- package/dist/types/chess-reasoning/answer-model-connector.d.ts +68 -0
- package/dist/types/chess-reasoning/answer-pipeline-exports.d.ts +98 -0
- package/dist/types/chess-reasoning/answer-pipeline.d.ts +41 -0
- package/dist/types/chess-reasoning/answer-reference-validation.d.ts +40 -0
- package/dist/types/chess-reasoning/answer-tool-host.d.ts +8 -0
- package/dist/types/chess-reasoning/branch-expansion-policy.d.ts +13 -0
- package/dist/types/chess-reasoning/branch-expansion.d.ts +39 -0
- package/dist/types/chess-reasoning/causal-claim-checks.d.ts +21 -0
- package/dist/types/chess-reasoning/chess-reference-dictionary.d.ts +13 -0
- package/dist/types/chess-reasoning/classification-question-target.d.ts +22 -0
- package/dist/types/chess-reasoning/classification-reassessment.d.ts +31 -0
- package/dist/types/chess-reasoning/compact-search-preparation.d.ts +7 -0
- package/dist/types/chess-reasoning/concept-registry.d.ts +99 -0
- package/dist/types/chess-reasoning/conversation-context.d.ts +12 -0
- package/dist/types/chess-reasoning/engine-search-failure.d.ts +7 -0
- package/dist/types/chess-reasoning/evidence-citation-normalization.d.ts +9 -0
- package/dist/types/chess-reasoning/evidence-licenses.d.ts +57 -0
- package/dist/types/chess-reasoning/evidence-selector-contract.d.ts +5 -0
- package/dist/types/chess-reasoning/existing-analysis-context.d.ts +104 -0
- package/dist/types/chess-reasoning/fact-observation-provider.d.ts +15 -0
- package/dist/types/chess-reasoning/fact-provider.d.ts +111 -0
- package/dist/types/chess-reasoning/fresh-engine-pv-witnesses.d.ts +67 -0
- package/dist/types/chess-reasoning/historical-prose.d.ts +23 -0
- package/dist/types/chess-reasoning/idea-graph.d.ts +34 -0
- package/dist/types/chess-reasoning/initial-search-plan.d.ts +37 -0
- package/dist/types/chess-reasoning/lossless-evidence-sharing.d.ts +18 -0
- package/dist/types/chess-reasoning/matched-comparison.d.ts +10 -0
- package/dist/types/chess-reasoning/material-accounting.d.ts +80 -0
- package/dist/types/chess-reasoning/material-format.d.ts +4 -0
- package/dist/types/chess-reasoning/mechanical-fact-provider.d.ts +33 -0
- package/dist/types/chess-reasoning/mechanical-ray-facts.d.ts +19 -0
- package/dist/types/chess-reasoning/mechanism-presentation.d.ts +9 -0
- package/dist/types/chess-reasoning/move-mechanism-facts.d.ts +45 -0
- package/dist/types/chess-reasoning/native-answer-connector.d.ts +4 -0
- package/dist/types/chess-reasoning/opening-source-contracts.d.ts +54 -0
- package/dist/types/chess-reasoning/opening-source-credits.d.ts +16 -0
- package/dist/types/chess-reasoning/opponent-threat-preparation.d.ts +7 -0
- package/dist/types/chess-reasoning/piece-identities.d.ts +11 -0
- package/dist/types/chess-reasoning/poc-contracts.d.ts +1026 -0
- package/dist/types/chess-reasoning/poc-hypotheses.d.ts +74 -0
- package/dist/types/chess-reasoning/poc-move-witnesses.d.ts +13 -0
- package/dist/types/chess-reasoning/poc-observations.d.ts +23 -0
- package/dist/types/chess-reasoning/poc-piece-refs.d.ts +12 -0
- package/dist/types/chess-reasoning/poc-plan-model.d.ts +45 -0
- package/dist/types/chess-reasoning/poc-semantic-text.d.ts +8 -0
- package/dist/types/chess-reasoning/poc-tools.d.ts +81 -0
- package/dist/types/chess-reasoning/portable-answer-runtime.d.ts +291 -0
- package/dist/types/chess-reasoning/position-analysis-contracts.d.ts +31 -0
- package/dist/types/chess-reasoning/position-analysis-scope.d.ts +3 -0
- package/dist/types/chess-reasoning/preparation-consolidation.d.ts +21 -0
- package/dist/types/chess-reasoning/production-observation-provider.d.ts +16 -0
- package/dist/types/chess-reasoning/question-routing.d.ts +326 -0
- package/dist/types/chess-reasoning/recorded-main-line.d.ts +37 -0
- package/dist/types/chess-reasoning/recorded-position-tools.d.ts +11 -0
- package/dist/types/chess-reasoning/recorded-reference-context.d.ts +13 -0
- package/dist/types/chess-reasoning/recorded-selected-classification.d.ts +36 -0
- package/dist/types/chess-reasoning/reference-limits.d.ts +2 -0
- package/dist/types/chess-reasoning/research-exports.d.ts +197 -0
- package/dist/types/chess-reasoning/saved-score-presentation.d.ts +14 -0
- package/dist/types/chess-reasoning/score-comparison-notices.d.ts +33 -0
- package/dist/types/chess-reasoning/server-simple-policy.d.ts +31 -0
- package/dist/types/chess-reasoning/simple-agent-config.d.ts +73 -0
- package/dist/types/chess-reasoning/simple-board-analysis.d.ts +85 -0
- package/dist/types/chess-reasoning/simple-compact-evidence.d.ts +7 -0
- package/dist/types/chess-reasoning/simple-completion.d.ts +23 -0
- package/dist/types/chess-reasoning/simple-engine-presentation.d.ts +3 -0
- package/dist/types/chess-reasoning/simple-evidence-preparation.d.ts +17 -0
- package/dist/types/chess-reasoning/simple-evidence-view.d.ts +17 -0
- package/dist/types/chess-reasoning/simple-explanation-coverage.d.ts +90 -0
- package/dist/types/chess-reasoning/simple-fact-delivery.d.ts +43 -0
- package/dist/types/chess-reasoning/simple-fallback-premise.d.ts +5 -0
- package/dist/types/chess-reasoning/simple-prose-references.d.ts +108 -0
- package/dist/types/chess-reasoning/simple-runtime-version.d.ts +1 -0
- package/dist/types/chess-reasoning/stable.d.ts +2 -0
- package/dist/types/chess-reasoning/tool-input-loop-guard.d.ts +5 -0
- package/dist/types/chess-reasoning/types.d.ts +180 -0
- package/dist/types/chess-reasoning/visible-support-lines.d.ts +12 -0
- package/dist/types/chess-reasoning/wikibooks-index.d.ts +30 -0
- package/dist/types/chess-reasoning/wikibooks-tool-context.d.ts +10 -0
- package/dist/types/player-perspective.d.ts +16 -0
- package/dist/types/shared-exports.d.ts +22 -0
- package/docs/API.md +82 -0
- package/docs/OWNERSHIP-ADOPTION.md +35 -0
- package/docs/OWNERSHIP-DRIFT.json +55 -0
- package/docs/RESEARCH-API.md +28 -0
- package/licenses/third-party/INVENTORY.json +202 -0
- package/licenses/third-party/ai-7.0.85/LICENSE +13 -0
- package/licenses/third-party/ai-sdk--gateway-4.0.69/LICENSE +13 -0
- package/licenses/third-party/ai-sdk--provider-4.0.9/LICENSE +13 -0
- package/licenses/third-party/ai-sdk--provider-utils-5.0.34/APACHE-2.0.txt +73 -0
- package/licenses/third-party/ai-sdk--provider-utils-5.0.34/src/to-json-schema/zod3-to-json-schema/LICENSE +16 -0
- package/licenses/third-party/chess.js-1.4.0/LICENSE +23 -0
- package/licenses/third-party/eventsource-parser-3.1.1/LICENSE +21 -0
- package/licenses/third-party/json-schema-0.4.0/LICENSE +195 -0
- package/licenses/third-party/standard-schema--spec-1.1.0/LICENSE +21 -0
- package/licenses/third-party/types--json-schema-7.0.15/LICENSE +21 -0
- package/licenses/third-party/types--node-22.19.19/LICENSE +21 -0
- package/licenses/third-party/undici-7.30.0/LICENSE +21 -0
- package/licenses/third-party/undici-7.30.0/lib/web/fetch/LICENSE +21 -0
- package/licenses/third-party/undici-types-6.21.0/LICENSE +21 -0
- package/licenses/third-party/vercel--oidc-3.2.0/LICENSE +202 -0
- package/licenses/third-party/workflow--serde-4.1.0/APACHE-2.0.txt +73 -0
- package/licenses/third-party/workflow--serde-4.1.0/LICENSE.md +3 -0
- package/licenses/third-party/zod-4.6.5/LICENSE +21 -0
- package/npm-release.json +5 -0
- package/package.json +72 -4
- package/scripts/check-release-tooling.mjs +22 -0
- package/scripts/pack.mjs +68 -0
- package/scripts/publish-reviewed.mjs +85 -0
- package/scripts/release-tooling.json +13 -0
- package/scripts/update-dependency.mjs +44 -0
- package/scripts/verify-release.mjs +52 -0
- package/src/ai-request-failure.ts +34 -0
- package/src/ai-request-limits.ts +4 -0
- package/src/ai-routing-diagnostics.ts +33 -0
- package/src/chess-reasoning/ablation-adaptive-evidence-loop.ts +515 -0
- package/src/chess-reasoning/ablation-adaptive-runtime.ts +924 -0
- package/src/chess-reasoning/ablation-direct-synthesis.ts +1128 -0
- package/src/chess-reasoning/ablation-evidence-bundle.ts +726 -0
- package/src/chess-reasoning/ablation-evidence-capture.ts +513 -0
- package/src/chess-reasoning/ablation-lean-direct-runtime.ts +670 -0
- package/src/chess-reasoning/ablation-model-facing-evidence.ts +212 -0
- package/src/chess-reasoning/ablation-transport-correction.ts +106 -0
- package/src/chess-reasoning/ai-sdk-runtime-support.ts +381 -0
- package/src/chess-reasoning/answer-host-contracts.ts +10 -0
- package/src/chess-reasoning/answer-model-connector.ts +83 -0
- package/src/chess-reasoning/answer-pipeline-exports.ts +98 -0
- package/src/chess-reasoning/answer-pipeline.ts +195 -0
- package/src/chess-reasoning/answer-reference-validation.ts +124 -0
- package/src/chess-reasoning/answer-tool-host.ts +10 -0
- package/src/chess-reasoning/branch-expansion-policy.ts +32 -0
- package/src/chess-reasoning/branch-expansion.ts +80 -0
- package/src/chess-reasoning/causal-claim-checks.ts +256 -0
- package/src/chess-reasoning/chess-reference-dictionary.ts +21 -0
- package/src/chess-reasoning/classification-question-target.ts +30 -0
- package/src/chess-reasoning/classification-reassessment.ts +34 -0
- package/src/chess-reasoning/compact-search-preparation.ts +125 -0
- package/src/chess-reasoning/concept-registry.ts +275 -0
- package/src/chess-reasoning/conversation-context.ts +29 -0
- package/src/chess-reasoning/engine-search-failure.ts +11 -0
- package/src/chess-reasoning/evidence-citation-normalization.ts +15 -0
- package/src/chess-reasoning/evidence-licenses.ts +166 -0
- package/src/chess-reasoning/evidence-selector-contract.ts +5 -0
- package/src/chess-reasoning/existing-analysis-context.ts +544 -0
- package/src/chess-reasoning/fact-observation-provider.ts +134 -0
- package/src/chess-reasoning/fact-provider.ts +250 -0
- package/src/chess-reasoning/fresh-engine-pv-witnesses.ts +128 -0
- package/src/chess-reasoning/historical-prose.ts +116 -0
- package/src/chess-reasoning/idea-graph.ts +224 -0
- package/src/chess-reasoning/initial-search-plan.ts +115 -0
- package/src/chess-reasoning/lossless-evidence-sharing.ts +135 -0
- package/src/chess-reasoning/matched-comparison.ts +40 -0
- package/src/chess-reasoning/material-accounting.ts +55 -0
- package/src/chess-reasoning/material-format.ts +47 -0
- package/src/chess-reasoning/mechanical-fact-provider.ts +1602 -0
- package/src/chess-reasoning/mechanical-ray-facts.ts +123 -0
- package/src/chess-reasoning/mechanism-presentation.ts +94 -0
- package/src/chess-reasoning/move-mechanism-facts.ts +185 -0
- package/src/chess-reasoning/native-answer-connector.ts +13 -0
- package/src/chess-reasoning/opening-source-contracts.ts +47 -0
- package/src/chess-reasoning/opening-source-credits.ts +53 -0
- package/src/chess-reasoning/opponent-threat-preparation.ts +98 -0
- package/src/chess-reasoning/piece-identities.ts +159 -0
- package/src/chess-reasoning/poc-contracts.ts +1097 -0
- package/src/chess-reasoning/poc-hypotheses.ts +759 -0
- package/src/chess-reasoning/poc-move-witnesses.ts +170 -0
- package/src/chess-reasoning/poc-observations.ts +611 -0
- package/src/chess-reasoning/poc-piece-refs.ts +62 -0
- package/src/chess-reasoning/poc-plan-model.ts +345 -0
- package/src/chess-reasoning/poc-semantic-text.ts +10 -0
- package/src/chess-reasoning/poc-tools.ts +978 -0
- package/src/chess-reasoning/portable-answer-runtime.ts +684 -0
- package/src/chess-reasoning/position-analysis-contracts.ts +88 -0
- package/src/chess-reasoning/position-analysis-scope.ts +20 -0
- package/src/chess-reasoning/preparation-consolidation.ts +91 -0
- package/src/chess-reasoning/production-observation-provider.ts +41 -0
- package/src/chess-reasoning/question-routing.ts +332 -0
- package/src/chess-reasoning/recorded-main-line.ts +55 -0
- package/src/chess-reasoning/recorded-position-tools.ts +78 -0
- package/src/chess-reasoning/recorded-reference-context.ts +44 -0
- package/src/chess-reasoning/recorded-selected-classification.ts +54 -0
- package/src/chess-reasoning/reference-limits.ts +2 -0
- package/src/chess-reasoning/research-exports.ts +197 -0
- package/src/chess-reasoning/saved-score-presentation.ts +6 -0
- package/src/chess-reasoning/score-comparison-notices.ts +115 -0
- package/src/chess-reasoning/server-simple-policy.ts +75 -0
- package/src/chess-reasoning/simple-agent-config.ts +58 -0
- package/src/chess-reasoning/simple-board-analysis.ts +57 -0
- package/src/chess-reasoning/simple-compact-evidence.ts +79 -0
- package/src/chess-reasoning/simple-completion.ts +37 -0
- package/src/chess-reasoning/simple-engine-presentation.ts +95 -0
- package/src/chess-reasoning/simple-evidence-preparation.ts +32 -0
- package/src/chess-reasoning/simple-evidence-view.ts +69 -0
- package/src/chess-reasoning/simple-explanation-coverage.ts +118 -0
- package/src/chess-reasoning/simple-fact-delivery.ts +52 -0
- package/src/chess-reasoning/simple-fallback-premise.ts +65 -0
- package/src/chess-reasoning/simple-prose-references.ts +528 -0
- package/src/chess-reasoning/simple-runtime-version.ts +2 -0
- package/src/chess-reasoning/stable.ts +18 -0
- package/src/chess-reasoning/tool-input-loop-guard.ts +38 -0
- package/src/chess-reasoning/types.ts +195 -0
- package/src/chess-reasoning/visible-support-lines.ts +44 -0
- package/src/chess-reasoning/wikibooks-index.ts +56 -0
- package/src/chess-reasoning/wikibooks-tool-context.ts +91 -0
- package/src/player-perspective.ts +41 -0
- package/src/shared-exports.ts +23 -0
- package/test/answer-pipeline.test.mjs +375 -0
- package/test/owner/chess-reasoning-compact-search-preparation.test.mjs +111 -0
- package/test/owner/mechanism-encoding.test.mjs +78 -0
- package/test/owner/move-mechanism-facts.test.mjs +105 -0
- package/test/owner/pipeline-host-preparation.test.mjs +115 -0
- package/test/owner/position-preparation.test.mjs +22 -0
- package/test/owner/preparation-consolidation.test.mjs +74 -0
- package/test/owner/prose-reference-admission.test.mjs +26 -0
- package/test/owner/score-comparison-notices.test.mjs +116 -0
- package/test/owner/simple-engine-presentation.test.mjs +50 -0
- package/test/owner/tool-input-loop-guard.test.mjs +25 -0
- package/test-types.mts +23 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
import { Output, generateText, jsonSchema, type LanguageModel, type LanguageModelCallOptions } from "ai";
|
|
2
|
+
import type {
|
|
3
|
+
AdaptiveCallUsage,
|
|
4
|
+
AdaptiveEvidenceLoop,
|
|
5
|
+
AdaptiveEvidenceLoopInput,
|
|
6
|
+
AdaptiveEvidenceLoopResult,
|
|
7
|
+
AdaptiveEvidenceToolFailure,
|
|
8
|
+
AdaptiveEvidenceToolRound,
|
|
9
|
+
} from "./ablation-adaptive-runtime";
|
|
10
|
+
import {
|
|
11
|
+
validateAblationEvidenceClosureBundle,
|
|
12
|
+
type AblationEvidenceClosureCoverage,
|
|
13
|
+
type AblationEvidenceClosureBundle,
|
|
14
|
+
} from "./ablation-evidence-bundle";
|
|
15
|
+
import { compactLeanEvidencePacket, projectAblationEvidenceForModel } from "./ablation-model-facing-evidence";
|
|
16
|
+
import { emitObservableTrace, type ObservableLlmTraceSink, numericUsage } from "./ai-sdk-runtime-support";
|
|
17
|
+
import {
|
|
18
|
+
REASONING_POC_VERSION,
|
|
19
|
+
type ReasoningPositionContext,
|
|
20
|
+
type ReasoningToolDescriptor,
|
|
21
|
+
type ReasoningToolName,
|
|
22
|
+
type ReasoningToolResult,
|
|
23
|
+
} from "./poc-contracts";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A deliberately transparent evidence adapter for B/B′. Unlike ToolLoopAgent,
|
|
27
|
+
* it makes one provider request for each decision, so an ablation runner can
|
|
28
|
+
* put a fresh cache-isolation nonce at the start of every provider request.
|
|
29
|
+
*/
|
|
30
|
+
export const ABLATION_ADAPTIVE_EVIDENCE_LOOP_VERSION = "architecture-ablation-adaptive-evidence-loop/2" as const;
|
|
31
|
+
|
|
32
|
+
const PRESEMANTIC_TOOLS = new Set<ReasoningToolName>([
|
|
33
|
+
"inspect_position", "analyze_move", "explore_continuations", "compare_moves", "enumerate_defenses", "test_line",
|
|
34
|
+
]);
|
|
35
|
+
const FACT_TOOLS = new Set<ReasoningToolName>(["inspect_position", "analyze_move"]);
|
|
36
|
+
const FORBIDDEN_PAYLOAD_KEYS = new Set([
|
|
37
|
+
"laterMoves", "evaluatorAnchor", "evaluatorAnchors", "idealProse", "answerKey",
|
|
38
|
+
"hypotheses", "selectedEntities", "selectedRelations", "graph", "critic", "checkpoint",
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
export const ADAPTIVE_EVIDENCE_DECISION_SCHEMA = Object.freeze({
|
|
42
|
+
type: "object",
|
|
43
|
+
additionalProperties: false,
|
|
44
|
+
required: ["schemaVersion", "decision"],
|
|
45
|
+
properties: {
|
|
46
|
+
schemaVersion: { const: ABLATION_ADAPTIVE_EVIDENCE_LOOP_VERSION },
|
|
47
|
+
decision: {
|
|
48
|
+
oneOf: [
|
|
49
|
+
{
|
|
50
|
+
type: "object", additionalProperties: false,
|
|
51
|
+
required: ["type", "tool", "input", "selectionRationale", "modelStatedRationale"],
|
|
52
|
+
properties: {
|
|
53
|
+
type: { const: "tool" }, tool: { type: "string", minLength: 1, maxLength: 120 }, input: { type: "object" },
|
|
54
|
+
selectionRationale: { type: "string", minLength: 1, maxLength: 1_000 },
|
|
55
|
+
modelStatedRationale: { type: "string", minLength: 1, maxLength: 1_000 },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
type: "object", additionalProperties: false,
|
|
60
|
+
required: ["type", "rationale", "modelStatedRationale"],
|
|
61
|
+
properties: {
|
|
62
|
+
type: { const: "finish" }, rationale: { type: "string", minLength: 1, maxLength: 1_000 },
|
|
63
|
+
modelStatedRationale: { type: "string", minLength: 1, maxLength: 1_000 },
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
type ToolDecision = Readonly<{
|
|
72
|
+
type: "tool";
|
|
73
|
+
tool: ReasoningToolName;
|
|
74
|
+
input: Record<string, unknown>;
|
|
75
|
+
selectionRationale: string;
|
|
76
|
+
modelStatedRationale: string;
|
|
77
|
+
}>;
|
|
78
|
+
type FinishDecision = Readonly<{ type: "finish"; rationale: string; modelStatedRationale: string }>;
|
|
79
|
+
type AdaptiveEvidenceDecision = ToolDecision | FinishDecision;
|
|
80
|
+
|
|
81
|
+
export type AdaptiveEvidenceToolExecutorInput = Readonly<{
|
|
82
|
+
callId: string;
|
|
83
|
+
requestId: string;
|
|
84
|
+
tool: ReasoningToolName;
|
|
85
|
+
input: Record<string, unknown>;
|
|
86
|
+
context: Readonly<ReasoningPositionContext>;
|
|
87
|
+
signal?: AbortSignal;
|
|
88
|
+
}>;
|
|
89
|
+
|
|
90
|
+
/** Browser and Node hosts adapt their existing exact fact/probe toolset here. */
|
|
91
|
+
export interface AdaptiveEvidenceToolExecutor {
|
|
92
|
+
execute(input: AdaptiveEvidenceToolExecutorInput): Promise<ReasoningToolResult>;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type AdaptiveEvidenceProviderMetadata = Readonly<{
|
|
96
|
+
cost?: number | null;
|
|
97
|
+
cacheTokens?: number | null;
|
|
98
|
+
}>;
|
|
99
|
+
|
|
100
|
+
export type AdaptiveEvidenceLoopOptions = Readonly<{
|
|
101
|
+
model: LanguageModel;
|
|
102
|
+
toolExecutor: AdaptiveEvidenceToolExecutor;
|
|
103
|
+
maxOutputTokens: number;
|
|
104
|
+
/** Optional compact, immutable subset; the full source stays local for retrieval. */
|
|
105
|
+
initialEvidenceView?: Readonly<AblationEvidenceClosureBundle>;
|
|
106
|
+
discoverRetainedEvidence?: boolean;
|
|
107
|
+
observableTraceSink?: ObservableLlmTraceSink;
|
|
108
|
+
reasoning?: NonNullable<LanguageModelCallOptions["reasoning"]>;
|
|
109
|
+
temperature?: number;
|
|
110
|
+
/** Optional sanitized relay metadata; never provider bodies or credentials. */
|
|
111
|
+
consumeProviderMetadata?: () => readonly AdaptiveEvidenceProviderMetadata[];
|
|
112
|
+
}>;
|
|
113
|
+
|
|
114
|
+
function record(value: unknown): value is Record<string, unknown> {
|
|
115
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function text(value: unknown, label: string, max = 1_000): string {
|
|
119
|
+
if (typeof value !== "string" || !value.trim() || value.length > max) throw new Error(`${label} must be a bounded non-empty string`);
|
|
120
|
+
return value;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function exactKeys(value: Record<string, unknown>, keys: readonly string[], label: string): void {
|
|
124
|
+
const actual = Object.keys(value);
|
|
125
|
+
if (actual.length !== keys.length || actual.some((key) => !keys.includes(key))) throw new Error(`${label} has unsupported or missing fields`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function clone<T>(value: T): T {
|
|
129
|
+
return structuredClone(value);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function now(): number {
|
|
133
|
+
return typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function assertNoForbiddenPayload(value: unknown, path = "adaptive evidence"): void {
|
|
137
|
+
if (Array.isArray(value)) {
|
|
138
|
+
value.forEach((item, index) => assertNoForbiddenPayload(item, `${path}[${index}]`));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (!record(value)) return;
|
|
142
|
+
for (const [key, child] of Object.entries(value)) {
|
|
143
|
+
if (FORBIDDEN_PAYLOAD_KEYS.has(key)) throw new Error(`Adaptive evidence payload contains forbidden state at ${path}.${key}`);
|
|
144
|
+
assertNoForbiddenPayload(child, `${path}.${key}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function normalizedDescriptors(tools: readonly ReasoningToolDescriptor[]): readonly ReasoningToolDescriptor[] {
|
|
149
|
+
const names = new Set<string>();
|
|
150
|
+
const normalized = tools.map((descriptor) => {
|
|
151
|
+
if (!record(descriptor) || typeof descriptor.name !== "string" || !PRESEMANTIC_TOOLS.has(descriptor.name as ReasoningToolName)
|
|
152
|
+
|| (descriptor.category !== "fact" && descriptor.category !== "probe") || !record(descriptor.inputSchema)
|
|
153
|
+
|| names.has(descriptor.name)) {
|
|
154
|
+
throw new Error("Adaptive evidence loop accepts only unique supplied fact/probe descriptors");
|
|
155
|
+
}
|
|
156
|
+
names.add(descriptor.name);
|
|
157
|
+
return clone(descriptor);
|
|
158
|
+
});
|
|
159
|
+
return Object.freeze(normalized.sort((left, right) => left.name.localeCompare(right.name)));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function validateLimits(input: AdaptiveEvidenceLoopInput, options: AdaptiveEvidenceLoopOptions): void {
|
|
163
|
+
if (!Number.isInteger(options.maxOutputTokens) || options.maxOutputTokens < 1) throw new Error("Adaptive evidence loop needs a positive output-token budget");
|
|
164
|
+
for (const [label, value, minimum] of [
|
|
165
|
+
["maxToolCalls", input.maxToolCalls, 0], ["maxProbeCalls", input.maxProbeCalls, 0], ["maxModelSteps", input.maxModelSteps, 1],
|
|
166
|
+
] as const) {
|
|
167
|
+
if (!Number.isInteger(value) || value < minimum) throw new Error(`Adaptive evidence loop ${label} is invalid`);
|
|
168
|
+
}
|
|
169
|
+
if (!input.firstProviderVisiblePrefix.trim() || typeof input.nextProviderVisiblePrefix !== "function") throw new Error("Adaptive evidence loop requires nonce prefixes");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function baseSystem(input: AdaptiveEvidenceLoopInput): string {
|
|
173
|
+
if (!input.system.startsWith(input.firstProviderVisiblePrefix)) throw new Error("First adaptive provider system must begin with the supplied nonce prefix");
|
|
174
|
+
const body = input.system.slice(input.firstProviderVisiblePrefix.length).trimStart();
|
|
175
|
+
if (!body) throw new Error("Adaptive evidence loop system body is missing");
|
|
176
|
+
return body;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function systemForDecision(input: AdaptiveEvidenceLoopInput, body: string, decisionIndex: number, usedPrefixes: Set<string>): string {
|
|
180
|
+
const prefix = decisionIndex === 0 ? input.firstProviderVisiblePrefix : input.nextProviderVisiblePrefix();
|
|
181
|
+
if (typeof prefix !== "string" || !prefix.trim()) throw new Error("Adaptive evidence loop received an invalid fresh provider prefix");
|
|
182
|
+
if (usedPrefixes.has(prefix)) throw new Error("Every adaptive evidence provider request requires a fresh cache-isolation prefix");
|
|
183
|
+
usedPrefixes.add(prefix);
|
|
184
|
+
return `${prefix}\n\n${body}`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function parseDecision(value: unknown, descriptors: readonly ReasoningToolDescriptor[]): AdaptiveEvidenceDecision {
|
|
188
|
+
if (!record(value)) throw new Error("Adaptive evidence decision must be an object");
|
|
189
|
+
exactKeys(value, ["schemaVersion", "decision"], "Adaptive evidence decision envelope");
|
|
190
|
+
if (value.schemaVersion !== ABLATION_ADAPTIVE_EVIDENCE_LOOP_VERSION || !record(value.decision)) throw new Error("Adaptive evidence decision version or payload is invalid");
|
|
191
|
+
const decision = value.decision;
|
|
192
|
+
if (decision.type === "tool") {
|
|
193
|
+
exactKeys(decision, ["type", "tool", "input", "selectionRationale", "modelStatedRationale"], "Adaptive tool decision");
|
|
194
|
+
const tool = text(decision.tool, "Adaptive tool name", 120) as ReasoningToolName;
|
|
195
|
+
if (!descriptors.some((descriptor) => descriptor.name === tool)) throw new Error("Adaptive evidence decision selected an unadvertised tool");
|
|
196
|
+
if (!record(decision.input)) throw new Error("Adaptive evidence tool input must be an object");
|
|
197
|
+
assertNoForbiddenPayload(decision.input, "adaptive tool input");
|
|
198
|
+
return Object.freeze({
|
|
199
|
+
type: "tool", tool, input: clone(decision.input), selectionRationale: text(decision.selectionRationale, "Adaptive selection rationale"),
|
|
200
|
+
modelStatedRationale: text(decision.modelStatedRationale, "Adaptive model rationale"),
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
if (decision.type === "finish") {
|
|
204
|
+
exactKeys(decision, ["type", "rationale", "modelStatedRationale"], "Adaptive finish decision");
|
|
205
|
+
return Object.freeze({ type: "finish", rationale: text(decision.rationale, "Adaptive finish rationale"), modelStatedRationale: text(decision.modelStatedRationale, "Adaptive finish model rationale") });
|
|
206
|
+
}
|
|
207
|
+
throw new Error("Adaptive evidence decision must select exactly one tool or finish_evidence_collection");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function cacheUsage(usage: unknown, metadata: AdaptiveEvidenceProviderMetadata | undefined): { hits: number; misses: number } {
|
|
211
|
+
const fromMetadata = metadata?.cacheTokens;
|
|
212
|
+
const recordUsage = record(usage) ? usage : null;
|
|
213
|
+
const direct = recordUsage ? ["cachedInputTokens", "cacheReadInputTokens", "cacheTokens"].map((key) => numericUsage(recordUsage[key])).find((value) => value !== null) : null;
|
|
214
|
+
const tokens = typeof fromMetadata === "number" ? fromMetadata : direct;
|
|
215
|
+
// Every provider decision is either evidenced as a prefix-cache hit or is a
|
|
216
|
+
// measured miss. Do not leave cold calls invisible in ablation telemetry.
|
|
217
|
+
return typeof tokens === "number" && tokens > 0 ? { hits: 1, misses: 0 } : { hits: 0, misses: 1 };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function usageFromResult(result: Awaited<ReturnType<typeof generateText>>, startedAt: number, metadata: AdaptiveEvidenceProviderMetadata | undefined): AdaptiveCallUsage {
|
|
221
|
+
const last = result.steps.at(-1);
|
|
222
|
+
return {
|
|
223
|
+
inputTokens: numericUsage(result.usage.inputTokens),
|
|
224
|
+
outputTokens: numericUsage(result.usage.outputTokens),
|
|
225
|
+
latencyMs: last?.performance.responseTimeMs ?? Math.round(now() - startedAt),
|
|
226
|
+
cache: cacheUsage(result.usage, metadata),
|
|
227
|
+
cost: typeof metadata?.cost === "number" && Number.isFinite(metadata.cost) ? metadata.cost : null,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function sum(values: readonly (number | null)[]): number | null {
|
|
232
|
+
return values.every((value) => value === null) ? null : values.reduce<number>((total, value) => total + (value || 0), 0);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function aggregateUsage(rounds: readonly AdaptiveEvidenceToolRound[], failures: readonly AdaptiveEvidenceToolFailure[], decisionUsage: readonly AdaptiveCallUsage[]): AdaptiveEvidenceLoopResult["usage"] {
|
|
236
|
+
const all = [...rounds.map((round) => round.usage), ...failures.map((failure) => failure.usage), ...decisionUsage];
|
|
237
|
+
return {
|
|
238
|
+
inputTokens: sum(all.map((item) => item.inputTokens)),
|
|
239
|
+
outputTokens: sum(all.map((item) => item.outputTokens)),
|
|
240
|
+
latencyMs: sum(all.map((item) => item.latencyMs)),
|
|
241
|
+
cache: { hits: all.reduce((total, item) => total + item.cache.hits, 0), misses: all.reduce((total, item) => total + item.cache.misses, 0) },
|
|
242
|
+
cost: sum(all.map((item) => item.cost)),
|
|
243
|
+
corrections: 0,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function resultEvidence(result: ReasoningToolResult, contextHash: string, callId: string, tool: ReasoningToolName, knownEvidenceIds: Set<string>, retrieved: boolean = false): void {
|
|
248
|
+
if (!record(result) || result.schemaVersion !== REASONING_POC_VERSION || result.contextHash !== contextHash || result.callId !== callId || result.tool !== tool
|
|
249
|
+
|| !record(result.data) || !Array.isArray(result.evidence) || !Array.isArray(result.limitations) || typeof result.durationMs !== "number") {
|
|
250
|
+
throw new Error("Adaptive evidence executor returned malformed or cross-context result");
|
|
251
|
+
}
|
|
252
|
+
assertNoForbiddenPayload(result.data, "adaptive tool result data");
|
|
253
|
+
for (const item of result.evidence) {
|
|
254
|
+
if (!record(item) || item.contextHash !== contextHash || typeof item.id !== "string" || !item.id.trim() || (!retrieved && knownEvidenceIds.has(item.id))) {
|
|
255
|
+
throw new Error("Adaptive evidence executor returned duplicate or cross-context evidence");
|
|
256
|
+
}
|
|
257
|
+
assertNoForbiddenPayload(item.data, `adaptive tool evidence ${item.id}`);
|
|
258
|
+
knownEvidenceIds.add(item.id);
|
|
259
|
+
}
|
|
260
|
+
if (result.limitations.some((item) => typeof item !== "string" || !item.trim())) throw new Error("Adaptive evidence tool result has malformed limitations");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function probeCount(rounds: readonly AdaptiveEvidenceToolRound[], failures: readonly AdaptiveEvidenceToolFailure[] = []): number {
|
|
264
|
+
return [...rounds, ...failures].filter((round) => !FACT_TOOLS.has(round.tool)).length;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function unusedTools(descriptors: readonly ReasoningToolDescriptor[], rounds: readonly AdaptiveEvidenceToolRound[], failures: readonly AdaptiveEvidenceToolFailure[] = []): ReasoningToolName[] {
|
|
268
|
+
const used = new Set([...rounds, ...failures].map((round) => round.tool));
|
|
269
|
+
return descriptors.filter((descriptor) => !used.has(descriptor.name)).map((descriptor) => descriptor.name).sort();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function coverageFromRun(
|
|
273
|
+
original: AblationEvidenceClosureCoverage,
|
|
274
|
+
rounds: readonly AdaptiveEvidenceToolRound[],
|
|
275
|
+
failures: readonly AdaptiveEvidenceToolFailure[],
|
|
276
|
+
stoppingReason: "finish" | "tool-budget" | "probe-budget" | "model-budget",
|
|
277
|
+
): AblationEvidenceClosureCoverage {
|
|
278
|
+
const covered = new Set(original.covered);
|
|
279
|
+
const limitations = new Set(original.limitations);
|
|
280
|
+
for (const round of rounds) {
|
|
281
|
+
covered.add(round.tool);
|
|
282
|
+
for (const item of round.result.limitations) limitations.add(item);
|
|
283
|
+
}
|
|
284
|
+
const unavailable = [...original.unavailable];
|
|
285
|
+
for (const failure of failures) {
|
|
286
|
+
if (!unavailable.some((item) => item.id === failure.tool)) unavailable.push({ id: failure.tool, reason: failure.failureCode });
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
// This adapter cannot convert bounded probing into exhaustive coverage.
|
|
290
|
+
complete: false,
|
|
291
|
+
covered: [...covered].sort(),
|
|
292
|
+
omitted: [...original.omitted],
|
|
293
|
+
unavailable: unavailable.sort((left, right) => left.id.localeCompare(right.id)),
|
|
294
|
+
limitations: [...limitations, `Adaptive evidence collection stopped: ${stoppingReason}.`].sort(),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Discovery metadata is not evidence; retrieval preserves the source records verbatim. */
|
|
299
|
+
export function retainedEvidenceCatalogue(bundle: Readonly<AblationEvidenceClosureBundle>) {
|
|
300
|
+
return bundle.evidence.map((item) => ({
|
|
301
|
+
id: item.id, kind: item.kind, source: item.source,
|
|
302
|
+
preview: item.summary.slice(0, 240),
|
|
303
|
+
previewTruncated: item.summary.length > 240,
|
|
304
|
+
scope: record(item.data) ? item.data.phase || item.data.scope || "see-record-replay" : "see-record-replay",
|
|
305
|
+
licenses: item.licenses,
|
|
306
|
+
observations: bundle.observations.filter((observation) => observation.factEvidenceIds.includes(item.id)).map((observation) => ({
|
|
307
|
+
type: observation.observationType, factKind: observation.attributes.factKind || null,
|
|
308
|
+
})),
|
|
309
|
+
}));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function promptForDecision(options: AdaptiveEvidenceLoopOptions, input: AdaptiveEvidenceLoopInput, descriptors: readonly ReasoningToolDescriptor[], rounds: readonly AdaptiveEvidenceToolRound[], failures: readonly AdaptiveEvidenceToolFailure[], modelCalls: number) {
|
|
313
|
+
const view = projectAblationEvidenceForModel(input.bundle);
|
|
314
|
+
const original = options.initialEvidenceView || view.bundle;
|
|
315
|
+
return JSON.stringify({
|
|
316
|
+
schemaVersion: ABLATION_ADAPTIVE_EVIDENCE_LOOP_VERSION,
|
|
317
|
+
...(!options.discoverRetainedEvidence ? { arm: input.arm } : {}),
|
|
318
|
+
question: original.question,
|
|
319
|
+
sourceBundleHash: view.sourceBundleHash,
|
|
320
|
+
modelFacingEvidenceViewVersion: options.initialEvidenceView ? "lean-compact-initial/1" : view.schemaVersion,
|
|
321
|
+
modelFacingEvidenceViewHash: options.initialEvidenceView?.bundleHash || view.viewHash,
|
|
322
|
+
// The canonical context is frozen before this loop starts; no future game history is present.
|
|
323
|
+
context: original.context,
|
|
324
|
+
frozenInitialEvidence: options.discoverRetainedEvidence
|
|
325
|
+
? { observations: compactLeanEvidencePacket(original).observations, evidence: original.evidence, coverage: original.coverage }
|
|
326
|
+
: { observations: original.observations, evidence: original.evidence, coverage: original.coverage },
|
|
327
|
+
...(options.discoverRetainedEvidence ? { retainedEvidenceCatalogue: retainedEvidenceCatalogue(input.bundle) } : {}),
|
|
328
|
+
accumulatedToolResults: rounds.map((round) => ({ callId: round.callId, tool: round.tool, input: round.input,
|
|
329
|
+
result: options.discoverRetainedEvidence
|
|
330
|
+
? { contextHash: round.result.contextHash, support: round.result.support, evidence: round.result.evidence, limitations: round.result.limitations }
|
|
331
|
+
: round.result })),
|
|
332
|
+
// Execution failures are not evidence, but they are part of the frozen
|
|
333
|
+
// episode state. Showing their generic status prevents a later decision
|
|
334
|
+
// from treating a requested tool as if it had never been attempted.
|
|
335
|
+
failedToolExecutions: failures.map((failure) => ({ callId: failure.callId, tool: failure.tool, input: failure.input, status: failure.failureCode })),
|
|
336
|
+
tools: descriptors,
|
|
337
|
+
control: { name: "finish_evidence_collection", description: "Stop acquisition when the currently admitted evidence is sufficient. This is not a chess tool and has no input." },
|
|
338
|
+
remainingBudget: {
|
|
339
|
+
modelDecisions: Math.max(0, input.maxModelSteps - modelCalls),
|
|
340
|
+
toolCalls: Math.max(0, input.maxToolCalls - rounds.length - failures.length),
|
|
341
|
+
probes: Math.max(0, input.maxProbeCalls - probeCount(rounds, failures)),
|
|
342
|
+
},
|
|
343
|
+
instruction: "Return exactly one decision: either one advertised fact/probe tool with object input and short rationales, or finish_evidence_collection. Never return multiple tools.",
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function appendBudgetOmissions(
|
|
348
|
+
coverage: AblationEvidenceClosureCoverage,
|
|
349
|
+
descriptors: readonly ReasoningToolDescriptor[],
|
|
350
|
+
rounds: readonly AdaptiveEvidenceToolRound[],
|
|
351
|
+
failures: readonly AdaptiveEvidenceToolFailure[],
|
|
352
|
+
reason: "not-requested" | "budget",
|
|
353
|
+
): AblationEvidenceClosureCoverage {
|
|
354
|
+
const called = new Set([...rounds, ...failures].map((round) => round.tool));
|
|
355
|
+
const omitted = [...coverage.omitted];
|
|
356
|
+
for (const descriptor of descriptors) {
|
|
357
|
+
if (!called.has(descriptor.name) && !omitted.some((item) => item.domain === "tool" && item.id === descriptor.name)) {
|
|
358
|
+
omitted.push({ domain: "tool", id: descriptor.name, reason });
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return { ...coverage, omitted: omitted.sort((left, right) => `${left.domain}:${left.id}`.localeCompare(`${right.domain}:${right.id}`)) };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** A browser/Node-neutral implementation of B/B′'s model-owned evidence loop. */
|
|
365
|
+
export class ModelOwnedAdaptiveEvidenceLoop implements AdaptiveEvidenceLoop {
|
|
366
|
+
constructor(private readonly options: AdaptiveEvidenceLoopOptions) {}
|
|
367
|
+
|
|
368
|
+
async run(input: AdaptiveEvidenceLoopInput): Promise<AdaptiveEvidenceLoopResult> {
|
|
369
|
+
validateLimits(input, this.options);
|
|
370
|
+
validateAblationEvidenceClosureBundle(input.bundle);
|
|
371
|
+
if (input.bundle.lineage.kind !== "original") throw new Error("Adaptive evidence loop requires the frozen original evidence bundle");
|
|
372
|
+
assertNoForbiddenPayload(input.bundle);
|
|
373
|
+
const modelView = projectAblationEvidenceForModel(input.bundle);
|
|
374
|
+
if (this.options.initialEvidenceView) {
|
|
375
|
+
validateAblationEvidenceClosureBundle(this.options.initialEvidenceView);
|
|
376
|
+
const view = this.options.initialEvidenceView;
|
|
377
|
+
if (view.contextHash !== input.bundle.contextHash || view.question !== input.bundle.question
|
|
378
|
+
|| view.observations.some((item) => !input.bundle.observations.some((source) => source.id === item.id && JSON.stringify(source) === JSON.stringify(item)))
|
|
379
|
+
|| view.evidence.some((item) => !input.bundle.evidence.some((source) => source.id === item.id && JSON.stringify(source) === JSON.stringify(item)))) {
|
|
380
|
+
throw new Error("Adaptive compact view must preserve source scope and exact evidence");
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const descriptors = normalizedDescriptors(input.tools).map((descriptor) => this.options.discoverRetainedEvidence && descriptor.name === "inspect_position" ? {
|
|
384
|
+
...descriptor,
|
|
385
|
+
description: descriptor.description + " Alternatively retrieve 1–8 exact retained records from the catalogue by supplying only evidenceIds; this consumes one tool invocation and performs no new search.",
|
|
386
|
+
inputSchema: { oneOf: [descriptor.inputSchema, { type: "object", required: ["evidenceIds"], additionalProperties: false,
|
|
387
|
+
properties: { evidenceIds: { type: "array", minItems: 1, maxItems: 8, uniqueItems: true, items: { type: "string" } } } }] },
|
|
388
|
+
} : descriptor);
|
|
389
|
+
const systemBody = baseSystem(input);
|
|
390
|
+
const rounds: AdaptiveEvidenceToolRound[] = [];
|
|
391
|
+
const failures: AdaptiveEvidenceToolFailure[] = [];
|
|
392
|
+
const decisionUsage: AdaptiveCallUsage[] = [];
|
|
393
|
+
const knownEvidenceIds = new Set(input.bundle.evidence.map((item) => item.id));
|
|
394
|
+
const usedPrefixes = new Set<string>();
|
|
395
|
+
let modelCalls = 0;
|
|
396
|
+
let stopping: AdaptiveEvidenceLoopResult["stopping"] | null = null;
|
|
397
|
+
let stoppingReason: "finish" | "tool-budget" | "probe-budget" | "model-budget" = "model-budget";
|
|
398
|
+
|
|
399
|
+
while (!stopping) {
|
|
400
|
+
if (input.signal?.aborted) throw input.signal.reason || new Error("Adaptive evidence collection cancelled");
|
|
401
|
+
if (rounds.length + failures.length >= input.maxToolCalls) {
|
|
402
|
+
stoppingReason = "tool-budget";
|
|
403
|
+
stopping = { reason: "tool-budget-exhausted", rationale: "Stopped because the adaptive evidence tool-call budget was exhausted.", unusedRelevantTools: unusedTools(descriptors, rounds, failures) };
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
if (modelCalls >= input.maxModelSteps) {
|
|
407
|
+
stoppingReason = "model-budget";
|
|
408
|
+
stopping = { reason: "model-decision-budget-exhausted", rationale: "Stopped because the adaptive evidence model-decision budget was exhausted.", unusedRelevantTools: unusedTools(descriptors, rounds, failures) };
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
const allowed = descriptors.filter((descriptor) => FACT_TOOLS.has(descriptor.name) || probeCount(rounds, failures) < input.maxProbeCalls);
|
|
412
|
+
if (!allowed.length) {
|
|
413
|
+
stoppingReason = "probe-budget";
|
|
414
|
+
stopping = { reason: "probe-budget-exhausted", rationale: "Stopped because the adaptive evidence probe budget was exhausted.", unusedRelevantTools: unusedTools(descriptors, rounds, failures) };
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
const prompt = promptForDecision(this.options, input, allowed, rounds, failures, modelCalls);
|
|
418
|
+
const system = systemForDecision(input, systemBody, modelCalls, usedPrefixes);
|
|
419
|
+
const started = now();
|
|
420
|
+
emitObservableTrace(this.options.observableTraceSink, { model: typeof this.options.model === "string" ? this.options.model : this.options.model.modelId, providerRoute: "adaptive-provider", reasoningEffort: String(this.options.reasoning || "provider-default"), stage: "ablation-lean-acquisition", direction: "input", payload: {
|
|
421
|
+
system, prompt, responseSchema: ADAPTIVE_EVIDENCE_DECISION_SCHEMA, maxOutputTokens: this.options.maxOutputTokens,
|
|
422
|
+
} });
|
|
423
|
+
const result = await generateText({
|
|
424
|
+
model: this.options.model,
|
|
425
|
+
system,
|
|
426
|
+
prompt,
|
|
427
|
+
output: Output.object({
|
|
428
|
+
schema: jsonSchema<Record<string, unknown>>(ADAPTIVE_EVIDENCE_DECISION_SCHEMA as never),
|
|
429
|
+
name: "knights_eye_adaptive_evidence_decision",
|
|
430
|
+
description: "Exactly one bounded fact/probe decision or the finish_evidence_collection control.",
|
|
431
|
+
}),
|
|
432
|
+
onStepFinish: (step) => emitObservableTrace(this.options.observableTraceSink, { model: typeof this.options.model === "string" ? this.options.model : this.options.model.modelId, providerRoute: "adaptive-provider", reasoningEffort: String(this.options.reasoning || "provider-default"), stage: "ablation-lean-acquisition", direction: "output", payload: {
|
|
433
|
+
text: step.text, finishReason: step.finishReason, usage: step.usage, model: step.model,
|
|
434
|
+
} }),
|
|
435
|
+
abortSignal: input.signal,
|
|
436
|
+
maxOutputTokens: this.options.maxOutputTokens,
|
|
437
|
+
maxRetries: 0,
|
|
438
|
+
reasoning: this.options.reasoning || "provider-default",
|
|
439
|
+
...(this.options.temperature === undefined ? {} : { temperature: this.options.temperature }),
|
|
440
|
+
});
|
|
441
|
+
if (input.signal?.aborted) throw input.signal.reason || new Error("Adaptive evidence collection cancelled");
|
|
442
|
+
modelCalls += 1;
|
|
443
|
+
const metadata = this.options.consumeProviderMetadata?.().at(-1);
|
|
444
|
+
decisionUsage.push(usageFromResult(result, started, metadata));
|
|
445
|
+
const decision = parseDecision(result.output, allowed);
|
|
446
|
+
if (decision.type === "finish") {
|
|
447
|
+
stoppingReason = "finish";
|
|
448
|
+
stopping = { reason: "model-finished", rationale: decision.rationale, unusedRelevantTools: unusedTools(descriptors, rounds, failures) };
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
const attempt = rounds.length + failures.length + 1;
|
|
452
|
+
const callId = `ablation-adaptive-evidence:${attempt}`;
|
|
453
|
+
const requestId = `${input.bundle.context.nodeId}:adaptive-evidence:${attempt}`;
|
|
454
|
+
const toolStarted = now();
|
|
455
|
+
let toolResult: ReasoningToolResult;
|
|
456
|
+
const retrieved = Boolean(this.options.discoverRetainedEvidence && decision.tool === "inspect_position" && "evidenceIds" in decision.input);
|
|
457
|
+
emitObservableTrace(this.options.observableTraceSink, { model: typeof this.options.model === "string" ? this.options.model : this.options.model.modelId, providerRoute: "adaptive-provider", reasoningEffort: String(this.options.reasoning || "provider-default"), stage: "ablation-lean-tool", direction: "input", payload: {
|
|
458
|
+
callId, tool: decision.tool, input: decision.input, selectionRationale: decision.selectionRationale,
|
|
459
|
+
} });
|
|
460
|
+
try {
|
|
461
|
+
if (retrieved) {
|
|
462
|
+
exactKeys(decision.input, ["evidenceIds"], "Retained evidence retrieval");
|
|
463
|
+
const ids = decision.input.evidenceIds;
|
|
464
|
+
if (!Array.isArray(ids) || !ids.length || ids.length > 8 || new Set(ids).size !== ids.length) throw new Error("Invalid retained evidence IDs");
|
|
465
|
+
const evidence = ids.map((id) => input.bundle.evidence.find((item) => item.id === id));
|
|
466
|
+
if (evidence.some((item) => !item)) throw new Error("Unknown retained evidence");
|
|
467
|
+
toolResult = { schemaVersion: REASONING_POC_VERSION, callId, tool: decision.tool, contextHash: input.bundle.contextHash,
|
|
468
|
+
support: "supported", data: { retrieval: true }, evidence: clone(evidence as ReasoningToolResult["evidence"]),
|
|
469
|
+
limitations: ["Retrieved unchanged source records; no new chess search or authority."], durationMs: Math.round(now() - toolStarted) };
|
|
470
|
+
} else toolResult = await this.options.toolExecutor.execute({
|
|
471
|
+
callId, requestId, tool: decision.tool, input: clone(decision.input), context: clone(input.bundle.context), signal: input.signal,
|
|
472
|
+
});
|
|
473
|
+
} catch (error) {
|
|
474
|
+
emitObservableTrace(this.options.observableTraceSink, { model: typeof this.options.model === "string" ? this.options.model : this.options.model.modelId, providerRoute: "adaptive-provider", reasoningEffort: String(this.options.reasoning || "provider-default"), stage: "ablation-lean-tool", direction: "error", payload: { callId, failureCode: "tool-execution-failed" } });
|
|
475
|
+
if (input.signal?.aborted) throw input.signal.reason || error;
|
|
476
|
+
failures.push(Object.freeze({
|
|
477
|
+
attempt, callId, requestId, tool: decision.tool, input: clone(decision.input), selectionRationale: decision.selectionRationale,
|
|
478
|
+
modelStatedRationale: decision.modelStatedRationale, failureCode: "tool-execution-failed",
|
|
479
|
+
usage: { inputTokens: null, outputTokens: null, latencyMs: Math.max(0, Math.round(now() - toolStarted)), cache: { hits: 0, misses: 0 }, cost: null },
|
|
480
|
+
}));
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
// A host response that violates the frozen result contract is not an
|
|
484
|
+
// ordinary execution failure. Fail closed rather than recording it as a
|
|
485
|
+
// truthful failed call and allowing malformed evidence to influence a
|
|
486
|
+
// later decision.
|
|
487
|
+
if (input.signal?.aborted) throw input.signal.reason || new Error("Adaptive evidence collection cancelled");
|
|
488
|
+
resultEvidence(toolResult, input.bundle.contextHash, callId, decision.tool, knownEvidenceIds, retrieved);
|
|
489
|
+
emitObservableTrace(this.options.observableTraceSink, { model: typeof this.options.model === "string" ? this.options.model : this.options.model.modelId, providerRoute: "adaptive-provider", reasoningEffort: String(this.options.reasoning || "provider-default"), stage: "ablation-lean-tool", direction: "output", payload: { callId, result: toolResult, retrieved } });
|
|
490
|
+
const toolUsage: AdaptiveCallUsage = { inputTokens: null, outputTokens: null, latencyMs: toolResult.durationMs, cache: { hits: 0, misses: 0 }, cost: null };
|
|
491
|
+
rounds.push(Object.freeze({
|
|
492
|
+
attempt, callId, requestId, tool: decision.tool, input: clone(decision.input), selectionRationale: decision.selectionRationale,
|
|
493
|
+
modelStatedRationale: decision.modelStatedRationale, result: clone(toolResult), usage: toolUsage,
|
|
494
|
+
}));
|
|
495
|
+
}
|
|
496
|
+
const hardBudget = stoppingReason !== "finish";
|
|
497
|
+
const rawCoverage = coverageFromRun(input.bundle.coverage, rounds, failures, stoppingReason);
|
|
498
|
+
const coverage = appendBudgetOmissions(rawCoverage, descriptors, rounds, failures, hardBudget ? "budget" : "not-requested");
|
|
499
|
+
return Object.freeze({
|
|
500
|
+
rounds: Object.freeze(rounds),
|
|
501
|
+
failedToolCalls: Object.freeze(failures),
|
|
502
|
+
availableTools: descriptors,
|
|
503
|
+
// This is a model-facing set: it starts with the projected closure and
|
|
504
|
+
// grows only with the evidence this episode itself acquired.
|
|
505
|
+
saliencePresentedFactIds: Object.freeze([...new Set([
|
|
506
|
+
...(this.options.initialEvidenceView?.evidence.map((item) => item.id) || modelView.visibleEvidenceIds),
|
|
507
|
+
...rounds.flatMap((round) => round.result.evidence.map((record) => record.id)),
|
|
508
|
+
])].sort()),
|
|
509
|
+
coverage: Object.freeze(coverage),
|
|
510
|
+
stopping: Object.freeze(stopping!),
|
|
511
|
+
modelCalls,
|
|
512
|
+
usage: Object.freeze(aggregateUsage(rounds, failures, decisionUsage)),
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
}
|