@duckcodeailabs/dql-agent 1.14.0 → 1.14.2
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/dist/agent-run-engine.d.ts +68 -2
- package/dist/agent-run-engine.d.ts.map +1 -1
- package/dist/agent-run-engine.js +520 -26
- package/dist/agent-run-engine.js.map +1 -1
- package/dist/agent-run-gates.d.ts.map +1 -1
- package/dist/agent-run-gates.js +25 -0
- package/dist/agent-run-gates.js.map +1 -1
- package/dist/agentic/analyst-loop.d.ts +91 -0
- package/dist/agentic/analyst-loop.d.ts.map +1 -0
- package/dist/agentic/analyst-loop.js +331 -0
- package/dist/agentic/analyst-loop.js.map +1 -0
- package/dist/agentic/assumptions.d.ts +62 -0
- package/dist/agentic/assumptions.d.ts.map +1 -0
- package/dist/agentic/assumptions.js +73 -0
- package/dist/agentic/assumptions.js.map +1 -0
- package/dist/agentic/business-explanation.d.ts +56 -0
- package/dist/agentic/business-explanation.d.ts.map +1 -0
- package/dist/agentic/business-explanation.js +130 -0
- package/dist/agentic/business-explanation.js.map +1 -0
- package/dist/agentic/identifier-ledger.d.ts +74 -0
- package/dist/agentic/identifier-ledger.d.ts.map +1 -0
- package/dist/agentic/identifier-ledger.js +151 -0
- package/dist/agentic/identifier-ledger.js.map +1 -0
- package/dist/agentic/ledger-tools.d.ts +68 -0
- package/dist/agentic/ledger-tools.d.ts.map +1 -0
- package/dist/agentic/ledger-tools.js +198 -0
- package/dist/agentic/ledger-tools.js.map +1 -0
- package/dist/agentic/orchestrator-policy.d.ts +60 -0
- package/dist/agentic/orchestrator-policy.d.ts.map +1 -0
- package/dist/agentic/orchestrator-policy.js +73 -0
- package/dist/agentic/orchestrator-policy.js.map +1 -0
- package/dist/agentic/orchestrator.d.ts +54 -0
- package/dist/agentic/orchestrator.d.ts.map +1 -0
- package/dist/agentic/orchestrator.js +67 -0
- package/dist/agentic/orchestrator.js.map +1 -0
- package/dist/agentic/preview-tool.d.ts +36 -0
- package/dist/agentic/preview-tool.d.ts.map +1 -0
- package/dist/agentic/preview-tool.js +71 -0
- package/dist/agentic/preview-tool.js.map +1 -0
- package/dist/agentic/rerank.d.ts +50 -0
- package/dist/agentic/rerank.d.ts.map +1 -0
- package/dist/agentic/rerank.js +140 -0
- package/dist/agentic/rerank.js.map +1 -0
- package/dist/agentic/research/hypothesis.d.ts +110 -0
- package/dist/agentic/research/hypothesis.d.ts.map +1 -0
- package/dist/agentic/research/hypothesis.js +125 -0
- package/dist/agentic/research/hypothesis.js.map +1 -0
- package/dist/agentic/research/synthesis.d.ts +36 -0
- package/dist/agentic/research/synthesis.d.ts.map +1 -0
- package/dist/agentic/research/synthesis.js +109 -0
- package/dist/agentic/research/synthesis.js.map +1 -0
- package/dist/agentic/research-agent.d.ts +54 -0
- package/dist/agentic/research-agent.d.ts.map +1 -0
- package/dist/agentic/research-agent.js +177 -0
- package/dist/agentic/research-agent.js.map +1 -0
- package/dist/agentic/sql-authorization.d.ts +131 -0
- package/dist/agentic/sql-authorization.d.ts.map +1 -0
- package/dist/agentic/sql-authorization.js +460 -0
- package/dist/agentic/sql-authorization.js.map +1 -0
- package/dist/agentic/tool-loop.d.ts +29 -0
- package/dist/agentic/tool-loop.d.ts.map +1 -1
- package/dist/agentic/tool-loop.js +129 -52
- package/dist/agentic/tool-loop.js.map +1 -1
- package/dist/agentic/toolset.d.ts.map +1 -1
- package/dist/agentic/toolset.js +155 -0
- package/dist/agentic/toolset.js.map +1 -1
- package/dist/agentic/turn-plan.d.ts +35 -0
- package/dist/agentic/turn-plan.d.ts.map +1 -0
- package/dist/agentic/turn-plan.js +108 -0
- package/dist/agentic/turn-plan.js.map +1 -0
- package/dist/agentic/value-probe.d.ts +89 -0
- package/dist/agentic/value-probe.d.ts.map +1 -0
- package/dist/agentic/value-probe.js +304 -0
- package/dist/agentic/value-probe.js.map +1 -0
- package/dist/analytical-frame.d.ts.map +1 -1
- package/dist/analytical-frame.js +10 -1
- package/dist/analytical-frame.js.map +1 -1
- package/dist/analytical-orchestration.d.ts +382 -0
- package/dist/analytical-orchestration.d.ts.map +1 -1
- package/dist/analytical-orchestration.js +782 -6
- package/dist/analytical-orchestration.js.map +1 -1
- package/dist/analytical-request-policy.d.ts +26 -0
- package/dist/analytical-request-policy.d.ts.map +1 -0
- package/dist/analytical-request-policy.js +82 -0
- package/dist/analytical-request-policy.js.map +1 -0
- package/dist/analytical-result-facts.d.ts +13 -0
- package/dist/analytical-result-facts.d.ts.map +1 -1
- package/dist/analytical-result-facts.js +37 -1
- package/dist/analytical-result-facts.js.map +1 -1
- package/dist/answer-loop.d.ts +105 -16
- package/dist/answer-loop.d.ts.map +1 -1
- package/dist/answer-loop.js +833 -108
- package/dist/answer-loop.js.map +1 -1
- package/dist/answer-shape.d.ts +32 -1
- package/dist/answer-shape.d.ts.map +1 -1
- package/dist/answer-shape.js +49 -3
- package/dist/answer-shape.js.map +1 -1
- package/dist/cascade/budgets.d.ts.map +1 -1
- package/dist/cascade/budgets.js +8 -1
- package/dist/cascade/budgets.js.map +1 -1
- package/dist/cascade/packer.d.ts +88 -0
- package/dist/cascade/packer.d.ts.map +1 -0
- package/dist/cascade/packer.js +106 -0
- package/dist/cascade/packer.js.map +1 -0
- package/dist/conversation/result-ops.d.ts +18 -3
- package/dist/conversation/result-ops.d.ts.map +1 -1
- package/dist/conversation/result-ops.js +77 -5
- package/dist/conversation/result-ops.js.map +1 -1
- package/dist/conversation/rolling-summary.d.ts.map +1 -1
- package/dist/conversation/rolling-summary.js +7 -2
- package/dist/conversation/rolling-summary.js.map +1 -1
- package/dist/conversation/session-store.d.ts +3 -0
- package/dist/conversation/session-store.d.ts.map +1 -1
- package/dist/conversation/session-store.js +8 -4
- package/dist/conversation/session-store.js.map +1 -1
- package/dist/conversation/snapshot.d.ts +20 -0
- package/dist/conversation/snapshot.d.ts.map +1 -1
- package/dist/conversation/snapshot.js +40 -2
- package/dist/conversation/snapshot.js.map +1 -1
- package/dist/fixtures/ask-ai-office-shaped.d.ts +153 -0
- package/dist/fixtures/ask-ai-office-shaped.d.ts.map +1 -0
- package/dist/fixtures/ask-ai-office-shaped.js +94 -0
- package/dist/fixtures/ask-ai-office-shaped.js.map +1 -0
- package/dist/hints/store.d.ts.map +1 -1
- package/dist/hints/store.js +15 -5
- package/dist/hints/store.js.map +1 -1
- package/dist/index.d.ts +29 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -6
- package/dist/index.js.map +1 -1
- package/dist/intent-controller.d.ts +36 -0
- package/dist/intent-controller.d.ts.map +1 -1
- package/dist/intent-controller.js +95 -0
- package/dist/intent-controller.js.map +1 -1
- package/dist/meaning-resolution.d.ts +69 -2
- package/dist/meaning-resolution.d.ts.map +1 -1
- package/dist/meaning-resolution.js +128 -7
- package/dist/meaning-resolution.js.map +1 -1
- package/dist/metadata/analysis-planner.d.ts.map +1 -1
- package/dist/metadata/analysis-planner.js +124 -7
- package/dist/metadata/analysis-planner.js.map +1 -1
- package/dist/metadata/block-fit.d.ts +36 -0
- package/dist/metadata/block-fit.d.ts.map +1 -1
- package/dist/metadata/block-fit.js +290 -38
- package/dist/metadata/block-fit.js.map +1 -1
- package/dist/metadata/catalog.d.ts +20 -23
- package/dist/metadata/catalog.d.ts.map +1 -1
- package/dist/metadata/catalog.js +193 -22
- package/dist/metadata/catalog.js.map +1 -1
- package/dist/metadata/meaning-evidence.d.ts +10 -1
- package/dist/metadata/meaning-evidence.d.ts.map +1 -1
- package/dist/metadata/meaning-evidence.js +275 -53
- package/dist/metadata/meaning-evidence.js.map +1 -1
- package/dist/metadata/metric-match.d.ts +32 -0
- package/dist/metadata/metric-match.d.ts.map +1 -1
- package/dist/metadata/metric-match.js +120 -9
- package/dist/metadata/metric-match.js.map +1 -1
- package/dist/research-loop.d.ts +33 -2
- package/dist/research-loop.d.ts.map +1 -1
- package/dist/research-loop.js +66 -4
- package/dist/research-loop.js.map +1 -1
- package/dist/resolved-analytical-plan.d.ts +7 -0
- package/dist/resolved-analytical-plan.d.ts.map +1 -1
- package/dist/resolved-analytical-plan.js +11 -4
- package/dist/resolved-analytical-plan.js.map +1 -1
- package/dist/router.d.ts +32 -19
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +1977 -73
- package/dist/router.js.map +1 -1
- package/dist/semantic-bridge/member-select.d.ts.map +1 -1
- package/dist/semantic-bridge/member-select.js +39 -21
- package/dist/semantic-bridge/member-select.js.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hypothesis-driven research state.
|
|
3
|
+
*
|
|
4
|
+
* `planResearch` emits a fixed three-step template — baseline, compare over
|
|
5
|
+
* time, break down — always bound to ONE metric, with dimensions scraped by
|
|
6
|
+
* `/\bby\s+([a-z_][a-z0-9_]*)/gi`. Its own comment says it is deterministic and
|
|
7
|
+
* offline, `RESEARCH_BUDGET.plannerCalls` is TYPED as the literal `1`, and the
|
|
8
|
+
* branches it fans out are frozen against the root plan.
|
|
9
|
+
*
|
|
10
|
+
* The consequence is the reported one: research answers a question instead of
|
|
11
|
+
* investigating it. A template cannot be wrong, so it never notices that its
|
|
12
|
+
* first finding ruled the premise out, and it never follows the thread that
|
|
13
|
+
* finding opened.
|
|
14
|
+
*
|
|
15
|
+
* What is missing is not more steps. It is the edge from an OBSERVATION back to
|
|
16
|
+
* the plan: a finding has to be able to refute a hypothesis, sharpen it, or
|
|
17
|
+
* spawn a new one. This module owns that edge as a pure state machine, so the
|
|
18
|
+
* part that decides what to investigate next is testable without a provider,
|
|
19
|
+
* a warehouse, or a clock.
|
|
20
|
+
*/
|
|
21
|
+
export type HypothesisStatus = 'open' | 'supported' | 'refuted' | 'inconclusive';
|
|
22
|
+
export interface Hypothesis {
|
|
23
|
+
id: string;
|
|
24
|
+
/** A falsifiable statement, not a task ("revenue fell because enterprise churned"). */
|
|
25
|
+
statement: string;
|
|
26
|
+
/** Belief before any evidence, 0..1. Drives which hypothesis is tested next. */
|
|
27
|
+
priorConfidence: number;
|
|
28
|
+
status: HypothesisStatus;
|
|
29
|
+
/** Ids of observations that bear on this hypothesis. */
|
|
30
|
+
evidenceIds: string[];
|
|
31
|
+
/** Which hypothesis this was spawned from, when a finding opened a new thread. */
|
|
32
|
+
parentId?: string;
|
|
33
|
+
/** Why it reached its current status — carried into the final narrative. */
|
|
34
|
+
rationale?: string;
|
|
35
|
+
}
|
|
36
|
+
/** What one investigation step established. */
|
|
37
|
+
export interface HypothesisFinding {
|
|
38
|
+
id: string;
|
|
39
|
+
hypothesisId: string;
|
|
40
|
+
/**
|
|
41
|
+
* Whether the finding supports, refutes, or fails to settle the hypothesis.
|
|
42
|
+
* `inconclusive` is a real outcome, not a failure: an executed query that
|
|
43
|
+
* simply does not discriminate must not be recorded as support.
|
|
44
|
+
*/
|
|
45
|
+
verdict: 'supports' | 'refutes' | 'inconclusive';
|
|
46
|
+
summary: string;
|
|
47
|
+
/** Confidence the finding itself carries, 0..1. */
|
|
48
|
+
strength: number;
|
|
49
|
+
/**
|
|
50
|
+
* A hypothesis this finding suggests testing next. The edge that a frozen
|
|
51
|
+
* branch plan cannot express.
|
|
52
|
+
*/
|
|
53
|
+
spawns?: Array<{
|
|
54
|
+
statement: string;
|
|
55
|
+
priorConfidence: number;
|
|
56
|
+
}>;
|
|
57
|
+
}
|
|
58
|
+
export interface ResearchState {
|
|
59
|
+
rootQuestion: string;
|
|
60
|
+
hypotheses: Hypothesis[];
|
|
61
|
+
findings: HypothesisFinding[];
|
|
62
|
+
hopsUsed: number;
|
|
63
|
+
}
|
|
64
|
+
export interface ResearchLimits {
|
|
65
|
+
/** Total investigation steps. Bounded: an unbounded loop is an outage. */
|
|
66
|
+
maxHops: number;
|
|
67
|
+
/** Ceiling on live hypotheses, so spawning cannot fan out without end. */
|
|
68
|
+
maxOpenHypotheses: number;
|
|
69
|
+
}
|
|
70
|
+
export declare const DEFAULT_RESEARCH_LIMITS: ResearchLimits;
|
|
71
|
+
export declare function createResearchState(rootQuestion: string, initial: Array<Omit<Hypothesis, 'status' | 'evidenceIds'>>): ResearchState;
|
|
72
|
+
/**
|
|
73
|
+
* Which hypothesis to test next: the open one with the highest prior.
|
|
74
|
+
*
|
|
75
|
+
* Returns `undefined` when the budget is spent or nothing is open, which is how
|
|
76
|
+
* the caller learns to stop. Deliberately not "cheapest first" — the point of
|
|
77
|
+
* research is to resolve the question, and testing a likely explanation first is
|
|
78
|
+
* what lets a later hop be skipped entirely.
|
|
79
|
+
*/
|
|
80
|
+
export declare function nextHypothesis(state: ResearchState, limits?: ResearchLimits): Hypothesis | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* Fold a finding into the state — the replan edge.
|
|
83
|
+
*
|
|
84
|
+
* A refutation CLOSES its hypothesis rather than leaving it open to be retried:
|
|
85
|
+
* the template's failure was continuing to elaborate a premise its own evidence
|
|
86
|
+
* had already ruled out.
|
|
87
|
+
*
|
|
88
|
+
* Spawned hypotheses are admitted only while there is room, and never duplicate
|
|
89
|
+
* an existing statement — a loop that keeps re-proposing the same idea burns the
|
|
90
|
+
* whole hop budget without learning anything.
|
|
91
|
+
*/
|
|
92
|
+
export declare function applyFinding(state: ResearchState, finding: HypothesisFinding, limits?: ResearchLimits): ResearchState;
|
|
93
|
+
export interface ResearchConclusion {
|
|
94
|
+
/** Hypotheses the evidence actually settled, strongest first. */
|
|
95
|
+
supported: Hypothesis[];
|
|
96
|
+
refuted: Hypothesis[];
|
|
97
|
+
/** Open or inconclusive — what the investigation could NOT establish. */
|
|
98
|
+
unresolved: Hypothesis[];
|
|
99
|
+
/** True when the hop budget ran out with hypotheses still open. */
|
|
100
|
+
exhausted: boolean;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Summarise what the investigation established.
|
|
104
|
+
*
|
|
105
|
+
* `unresolved` is reported as prominently as `supported` on purpose. A research
|
|
106
|
+
* answer that lists only what it confirmed reads as a complete explanation, and
|
|
107
|
+
* the one thing a bounded investigation cannot honestly claim is completeness.
|
|
108
|
+
*/
|
|
109
|
+
export declare function concludeResearch(state: ResearchState, limits?: ResearchLimits): ResearchConclusion;
|
|
110
|
+
//# sourceMappingURL=hypothesis.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hypothesis.d.ts","sourceRoot":"","sources":["../../../src/agentic/research/hypothesis.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,cAAc,CAAC;AAEjF,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,uFAAuF;IACvF,SAAS,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,gBAAgB,CAAC;IACzB,wDAAwD;IACxD,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,+CAA+C;AAC/C,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,OAAO,EAAE,UAAU,GAAG,SAAS,GAAG,cAAc,CAAC;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChE;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,UAAU,EAAE,CAAC;IACzB,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,eAAO,MAAM,uBAAuB,EAAE,cAGrC,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,GAAG,aAAa,CAAC,CAAC,GAAG,aAAa,CAOnI;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,MAAM,GAAE,cAAwC,GAAG,UAAU,GAAG,SAAS,CAK7H;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,iBAAiB,EAC1B,MAAM,GAAE,cAAwC,GAC/C,aAAa,CAuCf;AAED,MAAM,WAAW,kBAAkB;IACjC,iEAAiE;IACjE,SAAS,EAAE,UAAU,EAAE,CAAC;IACxB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,yEAAyE;IACzE,UAAU,EAAE,UAAU,EAAE,CAAC;IACzB,mEAAmE;IACnE,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,aAAa,EAAE,MAAM,GAAE,cAAwC,GAAG,kBAAkB,CAU3H"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hypothesis-driven research state.
|
|
3
|
+
*
|
|
4
|
+
* `planResearch` emits a fixed three-step template — baseline, compare over
|
|
5
|
+
* time, break down — always bound to ONE metric, with dimensions scraped by
|
|
6
|
+
* `/\bby\s+([a-z_][a-z0-9_]*)/gi`. Its own comment says it is deterministic and
|
|
7
|
+
* offline, `RESEARCH_BUDGET.plannerCalls` is TYPED as the literal `1`, and the
|
|
8
|
+
* branches it fans out are frozen against the root plan.
|
|
9
|
+
*
|
|
10
|
+
* The consequence is the reported one: research answers a question instead of
|
|
11
|
+
* investigating it. A template cannot be wrong, so it never notices that its
|
|
12
|
+
* first finding ruled the premise out, and it never follows the thread that
|
|
13
|
+
* finding opened.
|
|
14
|
+
*
|
|
15
|
+
* What is missing is not more steps. It is the edge from an OBSERVATION back to
|
|
16
|
+
* the plan: a finding has to be able to refute a hypothesis, sharpen it, or
|
|
17
|
+
* spawn a new one. This module owns that edge as a pure state machine, so the
|
|
18
|
+
* part that decides what to investigate next is testable without a provider,
|
|
19
|
+
* a warehouse, or a clock.
|
|
20
|
+
*/
|
|
21
|
+
export const DEFAULT_RESEARCH_LIMITS = {
|
|
22
|
+
maxHops: 12,
|
|
23
|
+
maxOpenHypotheses: 6,
|
|
24
|
+
};
|
|
25
|
+
export function createResearchState(rootQuestion, initial) {
|
|
26
|
+
return {
|
|
27
|
+
rootQuestion,
|
|
28
|
+
hypotheses: initial.map((hypothesis) => ({ ...hypothesis, status: 'open', evidenceIds: [] })),
|
|
29
|
+
findings: [],
|
|
30
|
+
hopsUsed: 0,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Which hypothesis to test next: the open one with the highest prior.
|
|
35
|
+
*
|
|
36
|
+
* Returns `undefined` when the budget is spent or nothing is open, which is how
|
|
37
|
+
* the caller learns to stop. Deliberately not "cheapest first" — the point of
|
|
38
|
+
* research is to resolve the question, and testing a likely explanation first is
|
|
39
|
+
* what lets a later hop be skipped entirely.
|
|
40
|
+
*/
|
|
41
|
+
export function nextHypothesis(state, limits = DEFAULT_RESEARCH_LIMITS) {
|
|
42
|
+
if (state.hopsUsed >= limits.maxHops)
|
|
43
|
+
return undefined;
|
|
44
|
+
return [...state.hypotheses]
|
|
45
|
+
.filter((hypothesis) => hypothesis.status === 'open')
|
|
46
|
+
.sort((left, right) => right.priorConfidence - left.priorConfidence || left.id.localeCompare(right.id))[0];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Fold a finding into the state — the replan edge.
|
|
50
|
+
*
|
|
51
|
+
* A refutation CLOSES its hypothesis rather than leaving it open to be retried:
|
|
52
|
+
* the template's failure was continuing to elaborate a premise its own evidence
|
|
53
|
+
* had already ruled out.
|
|
54
|
+
*
|
|
55
|
+
* Spawned hypotheses are admitted only while there is room, and never duplicate
|
|
56
|
+
* an existing statement — a loop that keeps re-proposing the same idea burns the
|
|
57
|
+
* whole hop budget without learning anything.
|
|
58
|
+
*/
|
|
59
|
+
export function applyFinding(state, finding, limits = DEFAULT_RESEARCH_LIMITS) {
|
|
60
|
+
const hypotheses = state.hypotheses.map((hypothesis) => {
|
|
61
|
+
if (hypothesis.id !== finding.hypothesisId)
|
|
62
|
+
return hypothesis;
|
|
63
|
+
const status = finding.verdict === 'supports'
|
|
64
|
+
? 'supported'
|
|
65
|
+
: finding.verdict === 'refutes'
|
|
66
|
+
? 'refuted'
|
|
67
|
+
: 'inconclusive';
|
|
68
|
+
return {
|
|
69
|
+
...hypothesis,
|
|
70
|
+
status,
|
|
71
|
+
evidenceIds: [...hypothesis.evidenceIds, finding.id],
|
|
72
|
+
rationale: finding.summary,
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
const known = new Set(hypotheses.map((hypothesis) => normalizeStatement(hypothesis.statement)));
|
|
76
|
+
const openCount = () => hypotheses.filter((hypothesis) => hypothesis.status === 'open').length;
|
|
77
|
+
for (const spawn of finding.spawns ?? []) {
|
|
78
|
+
const key = normalizeStatement(spawn.statement);
|
|
79
|
+
if (!spawn.statement.trim() || known.has(key))
|
|
80
|
+
continue;
|
|
81
|
+
if (openCount() >= limits.maxOpenHypotheses)
|
|
82
|
+
break;
|
|
83
|
+
known.add(key);
|
|
84
|
+
hypotheses.push({
|
|
85
|
+
id: `${finding.hypothesisId}.${hypotheses.length + 1}`,
|
|
86
|
+
statement: spawn.statement,
|
|
87
|
+
priorConfidence: clamp01(spawn.priorConfidence),
|
|
88
|
+
status: 'open',
|
|
89
|
+
evidenceIds: [],
|
|
90
|
+
parentId: finding.hypothesisId,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
...state,
|
|
95
|
+
hypotheses,
|
|
96
|
+
findings: [...state.findings, finding],
|
|
97
|
+
hopsUsed: state.hopsUsed + 1,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Summarise what the investigation established.
|
|
102
|
+
*
|
|
103
|
+
* `unresolved` is reported as prominently as `supported` on purpose. A research
|
|
104
|
+
* answer that lists only what it confirmed reads as a complete explanation, and
|
|
105
|
+
* the one thing a bounded investigation cannot honestly claim is completeness.
|
|
106
|
+
*/
|
|
107
|
+
export function concludeResearch(state, limits = DEFAULT_RESEARCH_LIMITS) {
|
|
108
|
+
const byConfidence = (left, right) => right.priorConfidence - left.priorConfidence || left.id.localeCompare(right.id);
|
|
109
|
+
const open = state.hypotheses.filter((hypothesis) => hypothesis.status === 'open');
|
|
110
|
+
return {
|
|
111
|
+
supported: state.hypotheses.filter((h) => h.status === 'supported').sort(byConfidence),
|
|
112
|
+
refuted: state.hypotheses.filter((h) => h.status === 'refuted').sort(byConfidence),
|
|
113
|
+
unresolved: [...open, ...state.hypotheses.filter((h) => h.status === 'inconclusive')].sort(byConfidence),
|
|
114
|
+
exhausted: state.hopsUsed >= limits.maxHops && open.length > 0,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function normalizeStatement(value) {
|
|
118
|
+
return value.trim().toLowerCase().replace(/\s+/g, ' ');
|
|
119
|
+
}
|
|
120
|
+
function clamp01(value) {
|
|
121
|
+
if (!Number.isFinite(value))
|
|
122
|
+
return 0;
|
|
123
|
+
return Math.max(0, Math.min(1, value));
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=hypothesis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hypothesis.js","sourceRoot":"","sources":["../../../src/agentic/research/hypothesis.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn the branch outcomes into the story the dossier was missing.
|
|
3
|
+
*
|
|
4
|
+
* Measured on a live run: five grounded hypotheses were planned, all five
|
|
5
|
+
* branches executed, and the answer the reader got was a single fact from one
|
|
6
|
+
* of them — "2025-05-01T00:00:00.000Z is the largest order_month at 31.1K". The
|
|
7
|
+
* other four findings existed and were discarded. The real story was sitting in
|
|
8
|
+
* the data: one explanation was answerable, four were blocked by uncertified
|
|
9
|
+
* joins. That is a useful answer about the project, and it never surfaced.
|
|
10
|
+
*
|
|
11
|
+
* This layer is DELIBERATELY DETERMINISTIC. It reports what was tested and what
|
|
12
|
+
* each branch established or could not establish, and it never says which
|
|
13
|
+
* hypothesis is TRUE — that is a judgment the branch summaries do not license,
|
|
14
|
+
* and inventing it here would put an unverified causal claim into a governed
|
|
15
|
+
* dossier. Narration of the numbers stays with the verified-fact narrator.
|
|
16
|
+
*/
|
|
17
|
+
import { type ResearchLimits } from './hypothesis.js';
|
|
18
|
+
export interface ResearchBranchOutcome {
|
|
19
|
+
/** The hypothesis this branch was testing. */
|
|
20
|
+
statement: string;
|
|
21
|
+
/** Did the branch actually produce data? */
|
|
22
|
+
produced: boolean;
|
|
23
|
+
/** The branch's own summary, used to explain a blocked branch. */
|
|
24
|
+
summary?: string;
|
|
25
|
+
status?: string;
|
|
26
|
+
/** Only receipt-bound deterministic validation may set supported/contradicted. */
|
|
27
|
+
verdict?: 'supported' | 'contradicted' | 'inconclusive' | 'failed' | 'skipped';
|
|
28
|
+
/** Fact IDs already retained by the branch receipt, never free-form prose. */
|
|
29
|
+
counterEvidenceFactIds?: string[];
|
|
30
|
+
}
|
|
31
|
+
export declare function synthesizeResearchNarrative(input: {
|
|
32
|
+
question: string;
|
|
33
|
+
branches: ResearchBranchOutcome[];
|
|
34
|
+
limits?: ResearchLimits;
|
|
35
|
+
}): string | undefined;
|
|
36
|
+
//# sourceMappingURL=synthesis.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"synthesis.d.ts","sourceRoot":"","sources":["../../../src/agentic/research/synthesis.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,qBAAqB;IACpC,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,QAAQ,EAAE,OAAO,CAAC;IAClB,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kFAAkF;IAClF,OAAO,CAAC,EAAE,WAAW,GAAG,cAAc,GAAG,cAAc,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC/E,8EAA8E;IAC9E,sBAAsB,CAAC,EAAE,MAAM,EAAE,CAAC;CACnC;AAuBD,wBAAgB,2BAA2B,CAAC,KAAK,EAAE;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,qBAAqB,EAAE,CAAC;IAClC,MAAM,CAAC,EAAE,cAAc,CAAC;CACzB,GAAG,MAAM,GAAG,SAAS,CAkGrB"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn the branch outcomes into the story the dossier was missing.
|
|
3
|
+
*
|
|
4
|
+
* Measured on a live run: five grounded hypotheses were planned, all five
|
|
5
|
+
* branches executed, and the answer the reader got was a single fact from one
|
|
6
|
+
* of them — "2025-05-01T00:00:00.000Z is the largest order_month at 31.1K". The
|
|
7
|
+
* other four findings existed and were discarded. The real story was sitting in
|
|
8
|
+
* the data: one explanation was answerable, four were blocked by uncertified
|
|
9
|
+
* joins. That is a useful answer about the project, and it never surfaced.
|
|
10
|
+
*
|
|
11
|
+
* This layer is DELIBERATELY DETERMINISTIC. It reports what was tested and what
|
|
12
|
+
* each branch established or could not establish, and it never says which
|
|
13
|
+
* hypothesis is TRUE — that is a judgment the branch summaries do not license,
|
|
14
|
+
* and inventing it here would put an unverified causal claim into a governed
|
|
15
|
+
* dossier. Narration of the numbers stays with the verified-fact narrator.
|
|
16
|
+
*/
|
|
17
|
+
import { applyFinding, concludeResearch, createResearchState, } from './hypothesis.js';
|
|
18
|
+
/** Why a branch produced nothing, in the reader's language. */
|
|
19
|
+
function blockedReason(summary) {
|
|
20
|
+
if (!summary)
|
|
21
|
+
return undefined;
|
|
22
|
+
if (/isn't certified yet|not certified|uncertified/i.test(summary)) {
|
|
23
|
+
return 'the join it needs is not certified yet';
|
|
24
|
+
}
|
|
25
|
+
if (/not bound to any modeled entity|unmodelled|unmodeled/i.test(summary)) {
|
|
26
|
+
return 'it references data that is not modeled';
|
|
27
|
+
}
|
|
28
|
+
if (/missing|no result|0 rows|empty/i.test(summary)) {
|
|
29
|
+
return 'the query returned nothing to compare';
|
|
30
|
+
}
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
function sentenceList(items) {
|
|
34
|
+
if (items.length === 0)
|
|
35
|
+
return '';
|
|
36
|
+
if (items.length === 1)
|
|
37
|
+
return items[0];
|
|
38
|
+
return `${items.slice(0, -1).join('; ')}; and ${items[items.length - 1]}`;
|
|
39
|
+
}
|
|
40
|
+
export function synthesizeResearchNarrative(input) {
|
|
41
|
+
const branches = input.branches.filter((branch) => branch.statement.trim().length > 0);
|
|
42
|
+
if (branches.length === 0)
|
|
43
|
+
return undefined;
|
|
44
|
+
let state = createResearchState(input.question, branches.map((branch, index) => ({
|
|
45
|
+
id: `h${index + 1}`,
|
|
46
|
+
statement: branch.statement.trim(),
|
|
47
|
+
priorConfidence: 1 - index / (branches.length + 1),
|
|
48
|
+
})));
|
|
49
|
+
branches.forEach((branch, index) => {
|
|
50
|
+
state = applyFinding(state, {
|
|
51
|
+
id: `f${index + 1}`,
|
|
52
|
+
hypothesisId: `h${index + 1}`,
|
|
53
|
+
// Callers may provide a verdict only after deterministic receipt-bound
|
|
54
|
+
// validation. Plain returned rows remain inconclusive and can never
|
|
55
|
+
// become a causal conclusion in this synthesis layer.
|
|
56
|
+
verdict: branch.verdict === 'supported' || branch.verdict === 'contradicted'
|
|
57
|
+
? branch.verdict === 'supported' ? 'supports' : 'refutes'
|
|
58
|
+
: 'inconclusive',
|
|
59
|
+
summary: branch.summary ?? '',
|
|
60
|
+
strength: branch.produced ? 0.6 : 0.1,
|
|
61
|
+
}, input.limits);
|
|
62
|
+
});
|
|
63
|
+
const conclusion = concludeResearch(state, input.limits);
|
|
64
|
+
const supported = conclusion.supported;
|
|
65
|
+
const contradicted = conclusion.refuted;
|
|
66
|
+
// Ordinary rows land in `unresolved`; split that set by whether the branch
|
|
67
|
+
// produced an observable result. This remains a surrounding-context report,
|
|
68
|
+
// not a causal claim.
|
|
69
|
+
const producedBy = new Map(branches.map((branch) => [branch.statement.trim(), branch.produced]));
|
|
70
|
+
const investigated = conclusion.unresolved.filter((h) => producedBy.get(h.statement) === true);
|
|
71
|
+
const blocked = conclusion.unresolved.filter((h) => producedBy.get(h.statement) !== true);
|
|
72
|
+
const lines = [
|
|
73
|
+
`I tested ${branches.length} competing explanation${branches.length === 1 ? '' : 's'} for this question.`,
|
|
74
|
+
];
|
|
75
|
+
if (investigated.length > 0) {
|
|
76
|
+
lines.push('', `**Investigated (${investigated.length}):**`, ...investigated.map((hypothesis) => `- ${hypothesis.statement}`));
|
|
77
|
+
}
|
|
78
|
+
if (supported.length > 0) {
|
|
79
|
+
lines.push('', `**Supported observations (${supported.length}):**`, ...supported.map((hypothesis) => `- ${hypothesis.statement}`));
|
|
80
|
+
}
|
|
81
|
+
if (contradicted.length > 0) {
|
|
82
|
+
lines.push('', `**Counter-evidence (${contradicted.length}):**`, ...contradicted.map((hypothesis) => `- ${hypothesis.statement}`));
|
|
83
|
+
}
|
|
84
|
+
if (blocked.length > 0) {
|
|
85
|
+
const reasons = new Set(blocked
|
|
86
|
+
.map((hypothesis) => {
|
|
87
|
+
const branch = branches.find((candidate) => candidate.statement.trim() === hypothesis.statement);
|
|
88
|
+
return blockedReason(branch?.summary);
|
|
89
|
+
})
|
|
90
|
+
.filter((reason) => Boolean(reason)));
|
|
91
|
+
lines.push('', `**Could not be settled (${blocked.length}):**`, ...blocked.map((hypothesis) => `- ${hypothesis.statement}`));
|
|
92
|
+
if (reasons.size > 0) {
|
|
93
|
+
lines.push('', `These were blocked because ${sentenceList([...reasons])}.`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (investigated.length === 0) {
|
|
97
|
+
// The honest headline when nothing could be established: say so first,
|
|
98
|
+
// rather than leading with a number from the one branch that ran.
|
|
99
|
+
lines.splice(1, 0, '', 'None of them could be settled with the governed model as it stands.');
|
|
100
|
+
}
|
|
101
|
+
const counterEvidenceFacts = branches
|
|
102
|
+
.flatMap((branch) => branch.counterEvidenceFactIds ?? [])
|
|
103
|
+
.filter(Boolean);
|
|
104
|
+
if (counterEvidenceFacts.length > 0) {
|
|
105
|
+
lines.push('', `Counter-evidence is retained in ${new Set(counterEvidenceFacts).size} receipt-bound fact${new Set(counterEvidenceFacts).size === 1 ? '' : 's'}.`);
|
|
106
|
+
}
|
|
107
|
+
return lines.join('\n');
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=synthesis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"synthesis.js","sourceRoot":"","sources":["../../../src/agentic/research/synthesis.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hypothesis-driven research planning.
|
|
3
|
+
*
|
|
4
|
+
* `planResearch` emits a fixed template — baseline, compare over time, break
|
|
5
|
+
* down by a dimension scraped out of the question with a regex — bound to one
|
|
6
|
+
* metric and frozen before any observation. That is why deep research answers
|
|
7
|
+
* one question instead of telling a story: the shape of the investigation is
|
|
8
|
+
* decided before the investigation starts, so nothing it learns can change what
|
|
9
|
+
* it does next.
|
|
10
|
+
*
|
|
11
|
+
* This produces HYPOTHESES instead: falsifiable statements about why the
|
|
12
|
+
* observed thing happened, each mapped to a step that would support or refute
|
|
13
|
+
* it. The existing executor runs them unchanged, because the output is still a
|
|
14
|
+
* `ResearchStep[]` — the strangler seam is the plan, not the runner.
|
|
15
|
+
*
|
|
16
|
+
* Grounding is unchanged and non-negotiable: every hypothesis must name a real
|
|
17
|
+
* metric, block, or dimension from the catalog. A hypothesis about an asset
|
|
18
|
+
* that does not exist cannot be investigated, and inventing one is how a
|
|
19
|
+
* research dossier ends up confidently discussing a table nobody has.
|
|
20
|
+
*/
|
|
21
|
+
import type { ResearchStep } from '../research-loop.js';
|
|
22
|
+
/** The assets a hypothesis may reference. Nothing outside this may appear. */
|
|
23
|
+
export interface ResearchAssets {
|
|
24
|
+
metrics: string[];
|
|
25
|
+
blocks: string[];
|
|
26
|
+
dimensions: string[];
|
|
27
|
+
}
|
|
28
|
+
export interface ResearchHypothesis {
|
|
29
|
+
/** A falsifiable statement, not a task. */
|
|
30
|
+
statement: string;
|
|
31
|
+
/** Belief before evidence, 0..1 — decides what is tested first. */
|
|
32
|
+
priorConfidence: number;
|
|
33
|
+
/** The asset whose inspection would settle it. Must exist in ResearchAssets. */
|
|
34
|
+
target: string;
|
|
35
|
+
action: ResearchStep['action']['kind'];
|
|
36
|
+
/** What an observation must show for the statement to hold. */
|
|
37
|
+
expectation: string;
|
|
38
|
+
}
|
|
39
|
+
interface PlanProvider {
|
|
40
|
+
generate(messages: {
|
|
41
|
+
role: 'system' | 'user' | 'assistant';
|
|
42
|
+
content: string;
|
|
43
|
+
}[], options?: Record<string, unknown>): Promise<string>;
|
|
44
|
+
}
|
|
45
|
+
export declare function parseResearchHypotheses(raw: string, assets: ResearchAssets): ResearchHypothesis[];
|
|
46
|
+
/** One structured call. Returns [] on any failure, so the caller keeps its template. */
|
|
47
|
+
export declare function planResearchHypotheses(provider: PlanProvider, question: string, assets: ResearchAssets, options?: {
|
|
48
|
+
signal?: AbortSignal;
|
|
49
|
+
timeoutMs?: number;
|
|
50
|
+
}): Promise<ResearchHypothesis[]>;
|
|
51
|
+
/** Render hypotheses as the steps the existing executor already knows how to run. */
|
|
52
|
+
export declare function hypothesesToSteps(hypotheses: ResearchHypothesis[]): ResearchStep[];
|
|
53
|
+
export {};
|
|
54
|
+
//# sourceMappingURL=research-agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"research-agent.d.ts","sourceRoot":"","sources":["../../src/agentic/research-agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,8EAA8E;AAC9E,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,eAAe,EAAE,MAAM,CAAC;IACxB,gFAAgF;IAChF,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;IACvC,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,YAAY;IACpB,QAAQ,CACN,QAAQ,EAAE;QAAE,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,EACtE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,OAAO,CAAC,MAAM,CAAC,CAAC;CACpB;AAkED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,kBAAkB,EAAE,CA+BjG;AAED,wFAAwF;AACxF,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,WAAW,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACzD,OAAO,CAAC,kBAAkB,EAAE,CAAC,CA0B/B;AAED,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,kBAAkB,EAAE,GAAG,YAAY,EAAE,CAQlF"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hypothesis-driven research planning.
|
|
3
|
+
*
|
|
4
|
+
* `planResearch` emits a fixed template — baseline, compare over time, break
|
|
5
|
+
* down by a dimension scraped out of the question with a regex — bound to one
|
|
6
|
+
* metric and frozen before any observation. That is why deep research answers
|
|
7
|
+
* one question instead of telling a story: the shape of the investigation is
|
|
8
|
+
* decided before the investigation starts, so nothing it learns can change what
|
|
9
|
+
* it does next.
|
|
10
|
+
*
|
|
11
|
+
* This produces HYPOTHESES instead: falsifiable statements about why the
|
|
12
|
+
* observed thing happened, each mapped to a step that would support or refute
|
|
13
|
+
* it. The existing executor runs them unchanged, because the output is still a
|
|
14
|
+
* `ResearchStep[]` — the strangler seam is the plan, not the runner.
|
|
15
|
+
*
|
|
16
|
+
* Grounding is unchanged and non-negotiable: every hypothesis must name a real
|
|
17
|
+
* metric, block, or dimension from the catalog. A hypothesis about an asset
|
|
18
|
+
* that does not exist cannot be investigated, and inventing one is how a
|
|
19
|
+
* research dossier ends up confidently discussing a table nobody has.
|
|
20
|
+
*/
|
|
21
|
+
const ACTION_KINDS = new Set([
|
|
22
|
+
'lookup_metric', 'lookup_block', 'breakdown', 'compare_time', 'check_lineage', 'compose_app',
|
|
23
|
+
]);
|
|
24
|
+
const PROMPT = `You plan a data investigation. Do not answer the question and do not write SQL.
|
|
25
|
+
|
|
26
|
+
Propose competing HYPOTHESES for what the data will show — falsifiable statements
|
|
27
|
+
about WHY, not a list of tasks. "Revenue fell because enterprise customers churned"
|
|
28
|
+
is a hypothesis. "Break revenue down by segment" is a task; do not write those.
|
|
29
|
+
|
|
30
|
+
Rules:
|
|
31
|
+
- Every hypothesis must name one asset from the catalog list you are given, exactly
|
|
32
|
+
as spelled there. A hypothesis about an asset that does not exist cannot be tested.
|
|
33
|
+
- Propose competing explanations, not one explanation in several phrasings. If two
|
|
34
|
+
hypotheses would be settled by the same observation, keep only the stronger.
|
|
35
|
+
- priorConfidence is your belief BEFORE evidence, 0..1. Do not make them all equal.
|
|
36
|
+
- expectation must state what an observation has to show for the statement to HOLD,
|
|
37
|
+
so that seeing the opposite refutes it.
|
|
38
|
+
|
|
39
|
+
Reply with ONLY a JSON object:
|
|
40
|
+
{"hypotheses":[{"statement":"...","priorConfidence":0.6,"target":"<asset>",
|
|
41
|
+
"action":"lookup_metric|lookup_block|breakdown|compare_time|check_lineage",
|
|
42
|
+
"expectation":"..."}]}
|
|
43
|
+
Between three and six hypotheses when the catalog has enough distinct grounded
|
|
44
|
+
assets. If it does not, return only the grounded hypotheses; do not invent one.`;
|
|
45
|
+
function extractJsonObject(raw) {
|
|
46
|
+
const trimmed = raw.trim();
|
|
47
|
+
const start = trimmed.indexOf('{');
|
|
48
|
+
if (start < 0)
|
|
49
|
+
return undefined;
|
|
50
|
+
let depth = 0;
|
|
51
|
+
let inString = false;
|
|
52
|
+
let escaped = false;
|
|
53
|
+
for (let i = start; i < trimmed.length; i += 1) {
|
|
54
|
+
const char = trimmed[i];
|
|
55
|
+
if (escaped) {
|
|
56
|
+
escaped = false;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (char === '\\') {
|
|
60
|
+
escaped = true;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (char === '"') {
|
|
64
|
+
inString = !inString;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (inString)
|
|
68
|
+
continue;
|
|
69
|
+
if (char === '{')
|
|
70
|
+
depth += 1;
|
|
71
|
+
else if (char === '}') {
|
|
72
|
+
depth -= 1;
|
|
73
|
+
if (depth === 0) {
|
|
74
|
+
try {
|
|
75
|
+
return JSON.parse(trimmed.slice(start, i + 1));
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
/** Match a model-named asset to a real one, tolerating case and qualification. */
|
|
86
|
+
function resolveAsset(target, assets) {
|
|
87
|
+
const pool = [...assets.metrics, ...assets.blocks, ...assets.dimensions];
|
|
88
|
+
const wanted = target.trim().toLowerCase();
|
|
89
|
+
if (!wanted)
|
|
90
|
+
return undefined;
|
|
91
|
+
const exact = pool.find((asset) => asset.toLowerCase() === wanted);
|
|
92
|
+
if (exact)
|
|
93
|
+
return exact;
|
|
94
|
+
// `orders.revenue` offered as `revenue`, and the reverse.
|
|
95
|
+
return pool.find((asset) => {
|
|
96
|
+
const leaf = asset.toLowerCase().split('.').at(-1) ?? '';
|
|
97
|
+
return leaf === wanted || asset.toLowerCase() === wanted.split('.').at(-1);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
export function parseResearchHypotheses(raw, assets) {
|
|
101
|
+
const parsed = extractJsonObject(raw);
|
|
102
|
+
if (!parsed || typeof parsed !== 'object')
|
|
103
|
+
return [];
|
|
104
|
+
const list = parsed.hypotheses;
|
|
105
|
+
if (!Array.isArray(list))
|
|
106
|
+
return [];
|
|
107
|
+
const seen = new Set();
|
|
108
|
+
const out = [];
|
|
109
|
+
for (const entry of list) {
|
|
110
|
+
if (!entry || typeof entry !== 'object')
|
|
111
|
+
continue;
|
|
112
|
+
const record = entry;
|
|
113
|
+
const statement = typeof record.statement === 'string' ? record.statement.trim() : '';
|
|
114
|
+
const expectation = typeof record.expectation === 'string' ? record.expectation.trim() : '';
|
|
115
|
+
if (!statement || !expectation)
|
|
116
|
+
continue;
|
|
117
|
+
// UNGROUNDED HYPOTHESES ARE DROPPED, not repaired. A statement about an
|
|
118
|
+
// asset nobody has cannot be investigated, and keeping it would put an
|
|
119
|
+
// invented table into a dossier that reads as governed.
|
|
120
|
+
const target = resolveAsset(typeof record.target === 'string' ? record.target : '', assets);
|
|
121
|
+
if (!target)
|
|
122
|
+
continue;
|
|
123
|
+
const action = typeof record.action === 'string' && ACTION_KINDS.has(record.action)
|
|
124
|
+
? record.action
|
|
125
|
+
: 'lookup_metric';
|
|
126
|
+
const key = statement.toLowerCase().replace(/\s+/g, ' ');
|
|
127
|
+
if (seen.has(key))
|
|
128
|
+
continue;
|
|
129
|
+
seen.add(key);
|
|
130
|
+
const prior = typeof record.priorConfidence === 'number' && Number.isFinite(record.priorConfidence)
|
|
131
|
+
? Math.max(0, Math.min(1, record.priorConfidence))
|
|
132
|
+
: 0.5;
|
|
133
|
+
out.push({ statement, priorConfidence: prior, target, action, expectation });
|
|
134
|
+
if (out.length >= 6)
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
return out.sort((left, right) => right.priorConfidence - left.priorConfidence);
|
|
138
|
+
}
|
|
139
|
+
/** One structured call. Returns [] on any failure, so the caller keeps its template. */
|
|
140
|
+
export async function planResearchHypotheses(provider, question, assets, options = {}) {
|
|
141
|
+
if (assets.metrics.length === 0 && assets.blocks.length === 0)
|
|
142
|
+
return [];
|
|
143
|
+
const controller = new AbortController();
|
|
144
|
+
const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? 20_000);
|
|
145
|
+
const onAbort = () => controller.abort();
|
|
146
|
+
options.signal?.addEventListener('abort', onAbort, { once: true });
|
|
147
|
+
try {
|
|
148
|
+
const catalog = [
|
|
149
|
+
assets.metrics.length ? `metrics: ${assets.metrics.join(', ')}` : '',
|
|
150
|
+
assets.blocks.length ? `certified blocks: ${assets.blocks.join(', ')}` : '',
|
|
151
|
+
assets.dimensions.length ? `dimensions: ${assets.dimensions.join(', ')}` : '',
|
|
152
|
+
].filter(Boolean).join('\n');
|
|
153
|
+
const raw = await provider.generate([
|
|
154
|
+
{ role: 'system', content: `${PROMPT}\n\nCatalog:\n${catalog}` },
|
|
155
|
+
{ role: 'user', content: question },
|
|
156
|
+
], { signal: controller.signal });
|
|
157
|
+
return parseResearchHypotheses(raw, assets);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
clearTimeout(timer);
|
|
164
|
+
options.signal?.removeEventListener('abort', onAbort);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/** Render hypotheses as the steps the existing executor already knows how to run. */
|
|
168
|
+
export function hypothesesToSteps(hypotheses) {
|
|
169
|
+
return hypotheses.map((hypothesis) => ({
|
|
170
|
+
// The thought IS the hypothesis, so the trace and the dossier show what is
|
|
171
|
+
// being tested rather than which tool is being called.
|
|
172
|
+
thought: hypothesis.statement,
|
|
173
|
+
action: { kind: hypothesis.action, target: hypothesis.target },
|
|
174
|
+
expectation: hypothesis.expectation,
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=research-agent.js.map
|