claude-flow 3.10.4 → 3.10.5
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/package.json +1 -1
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-agent-planning.smoke.d.ts +18 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-agent-planning.smoke.js +253 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-agent.d.ts +198 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-agent.js +651 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-causal-memory.d.ts +133 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-causal-memory.js +281 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-causal-memory.smoke.d.ts +22 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-causal-memory.smoke.js +300 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-convergence.d.ts +138 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-convergence.js +260 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-convergence.smoke.d.ts +19 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-convergence.smoke.js +246 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-critic.d.ts +123 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-critic.js +312 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-critic.smoke.d.ts +21 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-critic.smoke.js +327 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-decomposer.d.ts +125 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-decomposer.js +350 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-decomposer.smoke.d.ts +21 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-decomposer.smoke.js +228 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-e2e-smoke.d.ts +27 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-e2e-smoke.js +136 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-extract.smoke.d.ts +45 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-extract.smoke.js +242 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/features.d.ts +46 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/features.js +170 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/predictor.d.ts +105 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/predictor.js +260 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/predictor.smoke.d.ts +20 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/predictor.smoke.js +235 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/train-data-loader.d.ts +51 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-hardness/train-data-loader.js +179 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-judge.d.ts +88 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-judge.js +437 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-loader.d.ts +87 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-loader.js +326 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/file_read.d.ts +35 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/file_read.js +403 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/grounded_query.d.ts +126 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/grounded_query.js +225 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/index.d.ts +32 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/index.js +36 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/types.d.ts +62 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/types.js +12 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/web_search.d.ts +30 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-tools/web_search.js +210 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-voting.d.ts +88 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-voting.js +297 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-voting.smoke.d.ts +20 -0
- package/v3/@claude-flow/cli/dist/src/benchmarks/gaia-voting.smoke.js +332 -0
- package/v3/@claude-flow/cli/dist/src/commands/gaia-bench.d.ts +40 -0
- package/v3/@claude-flow/cli/dist/src/commands/gaia-bench.js +597 -0
- package/v3/@claude-flow/cli/dist/src/commands/index.js +2 -0
- package/v3/@claude-flow/cli/dist/src/commands/init.js +41 -4
- package/v3/@claude-flow/cli/dist/src/init/executor.js +25 -6
- package/v3/@claude-flow/cli/dist/src/init/mcp-generator.js +9 -4
- package/v3/@claude-flow/cli/package.json +1 -1
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GAIA Causal Failure-Avoidance Memory — ADR-135 Track I
|
|
3
|
+
*
|
|
4
|
+
* Records causal edges after each failed GAIA trajectory:
|
|
5
|
+
* "trying tool X on question type Y → caused failure Z"
|
|
6
|
+
*
|
|
7
|
+
* Before each new question, retrieves matching causal edges and injects
|
|
8
|
+
* an "avoid these approaches" hint into the agent's system prompt.
|
|
9
|
+
*
|
|
10
|
+
* This is one of ruflo's 6 architectural primitives distinguishing it
|
|
11
|
+
* from HAL: HAL is stateless across runs; ruflo accumulates causal memory.
|
|
12
|
+
*
|
|
13
|
+
* Storage: JSONL file at ~/.cache/ruflo/gaia/causal-edges.jsonl
|
|
14
|
+
* - Simple, portable, no runtime dependency on AgentDB
|
|
15
|
+
* - Production upgrade path: switch to AgentDB causal-edge MCP controller
|
|
16
|
+
* (`mcp__claude-flow__agentdb_causal-edge`) for cross-session persistence
|
|
17
|
+
* and embedding-based similarity matching.
|
|
18
|
+
*
|
|
19
|
+
* Expected lift:
|
|
20
|
+
* - First run (no edges yet): +0pp (empty hint → no overhead)
|
|
21
|
+
* - After 5+ runs (warm-up): +2-5pp compound
|
|
22
|
+
*
|
|
23
|
+
* NOT wired into gaia-bench.ts here — wiring is a follow-up PR once all
|
|
24
|
+
* in-flight iterators (29/31/34/35/37) have landed to avoid conflicts.
|
|
25
|
+
*
|
|
26
|
+
* Refs: ADR-135, ADR-133, #2156
|
|
27
|
+
*/
|
|
28
|
+
import type { GaiaQuestion } from './gaia-loader.js';
|
|
29
|
+
import type { GaiaAgentResult } from './gaia-agent.js';
|
|
30
|
+
/** Observation failure categories derived from trajectory analysis. */
|
|
31
|
+
export type FailureType = 'empty_result' | 'timeout' | 'wrong_answer' | 'tool_error';
|
|
32
|
+
/**
|
|
33
|
+
* A causal edge: "in a question of signature S, using tool T in way W
|
|
34
|
+
* caused failure F." occurrenceCount increments each time the same
|
|
35
|
+
* (signature, tool, step) triple is observed again instead of duplicating.
|
|
36
|
+
*/
|
|
37
|
+
export interface CausalEdge {
|
|
38
|
+
/** Deterministic hash of the normalised question text. */
|
|
39
|
+
questionSignature: string;
|
|
40
|
+
/** Tool name that failed (e.g. 'web_search', 'python_exec'). */
|
|
41
|
+
failedTool: string;
|
|
42
|
+
/** Brief description of what the failing step attempted. */
|
|
43
|
+
failedTrajectoryStep: string;
|
|
44
|
+
/** Categorised failure type. */
|
|
45
|
+
observedFailureType: FailureType;
|
|
46
|
+
/** ISO-8601 timestamp of first observation. */
|
|
47
|
+
createdAt: string;
|
|
48
|
+
/** Increments when the same edge is observed in a subsequent run. */
|
|
49
|
+
occurrenceCount: number;
|
|
50
|
+
}
|
|
51
|
+
/** Options for causal-memory operations. */
|
|
52
|
+
export interface CausalMemoryOptions {
|
|
53
|
+
/**
|
|
54
|
+
* Override the JSONL store path.
|
|
55
|
+
* Default: ~/.cache/ruflo/gaia/causal-edges.jsonl
|
|
56
|
+
*/
|
|
57
|
+
storePath?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Maximum edges to return per question signature when retrieving hints.
|
|
60
|
+
* Default: 5
|
|
61
|
+
*/
|
|
62
|
+
maxEdgesPerSignature?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Signature similarity threshold (0–1). Currently unused for the simple
|
|
65
|
+
* hash-based implementation; reserved for future RuVector upgrade.
|
|
66
|
+
* Default: 0.7
|
|
67
|
+
*/
|
|
68
|
+
similarityThreshold?: number;
|
|
69
|
+
}
|
|
70
|
+
/** Result of a recordCausalFailures call. */
|
|
71
|
+
export interface RecordResult {
|
|
72
|
+
edgesRecorded: number;
|
|
73
|
+
storePath: string;
|
|
74
|
+
}
|
|
75
|
+
/** Result of a retrieveCausalHints call. */
|
|
76
|
+
export interface RetrieveResult {
|
|
77
|
+
/**
|
|
78
|
+
* System-prompt-ready hint string.
|
|
79
|
+
* Empty string when no edges match (caller must not inject empty hints).
|
|
80
|
+
*/
|
|
81
|
+
hint: string;
|
|
82
|
+
edgesMatched: number;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Compute a deterministic question signature.
|
|
86
|
+
*
|
|
87
|
+
* Algorithm (v1, hash-based):
|
|
88
|
+
* 1. Lower-case the question text.
|
|
89
|
+
* 2. Collapse runs of whitespace to a single space and trim.
|
|
90
|
+
* 3. SHA-256 → first 16 hex characters (64-bit prefix, collision-unlikely
|
|
91
|
+
* for the ~450-question GAIA validation set).
|
|
92
|
+
*
|
|
93
|
+
* Future (v2, embedding-based): replace with RuVector cosine similarity so
|
|
94
|
+
* semantically similar questions (paraphrases, translated variants) share
|
|
95
|
+
* causal edges across runs.
|
|
96
|
+
*/
|
|
97
|
+
export declare function computeQuestionSignature(questionText: string): string;
|
|
98
|
+
/** Derive a failure type from a completed agent result + known correctness. */
|
|
99
|
+
export declare function inferFailureType(result: GaiaAgentResult, wasCorrect: boolean): FailureType | null;
|
|
100
|
+
/**
|
|
101
|
+
* After a GAIA trajectory completes, analyse it for causal failure patterns
|
|
102
|
+
* and persist each observed edge in the JSONL store.
|
|
103
|
+
*
|
|
104
|
+
* Behaviour:
|
|
105
|
+
* - If `wasCorrect === true`, no edges are written (zero overhead).
|
|
106
|
+
* - Each (signature, tool, step) triple is deduplicated: if the same triple
|
|
107
|
+
* already exists in the store, its `occurrenceCount` is incremented in
|
|
108
|
+
* place rather than appending a new line.
|
|
109
|
+
* - Edges beyond `maxEdgesPerSignature` (default 5) per signature are
|
|
110
|
+
* discarded to keep the store bounded.
|
|
111
|
+
*
|
|
112
|
+
* @param question - The GaiaQuestion that was attempted.
|
|
113
|
+
* @param result - The agent result from runGaiaAgent().
|
|
114
|
+
* @param wasCorrect - Whether the final answer was judged correct.
|
|
115
|
+
* @param options - Optional store path and limits.
|
|
116
|
+
* @returns Number of edges written/updated and the resolved store path.
|
|
117
|
+
*/
|
|
118
|
+
export declare function recordCausalFailures(question: GaiaQuestion, result: GaiaAgentResult, wasCorrect: boolean, options?: CausalMemoryOptions): Promise<RecordResult>;
|
|
119
|
+
/**
|
|
120
|
+
* Before running a new question, retrieve causal edges from prior failures
|
|
121
|
+
* that match the question's signature and format them as a system-prompt hint.
|
|
122
|
+
*
|
|
123
|
+
* Return contract:
|
|
124
|
+
* - No edges matched → `{ hint: '', edgesMatched: 0 }` — caller MUST NOT
|
|
125
|
+
* inject an empty hint (wastes tokens; may confuse the model).
|
|
126
|
+
* - 1+ edges matched → `{ hint: '[PRIOR FAILURES] …', edgesMatched: N }`.
|
|
127
|
+
*
|
|
128
|
+
* @param question - The GaiaQuestion about to be attempted.
|
|
129
|
+
* @param options - Optional store path and limits.
|
|
130
|
+
* @returns Formatted hint string and match count.
|
|
131
|
+
*/
|
|
132
|
+
export declare function retrieveCausalHints(question: GaiaQuestion, options?: CausalMemoryOptions): Promise<RetrieveResult>;
|
|
133
|
+
//# sourceMappingURL=gaia-causal-memory.d.ts.map
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GAIA Causal Failure-Avoidance Memory — ADR-135 Track I
|
|
3
|
+
*
|
|
4
|
+
* Records causal edges after each failed GAIA trajectory:
|
|
5
|
+
* "trying tool X on question type Y → caused failure Z"
|
|
6
|
+
*
|
|
7
|
+
* Before each new question, retrieves matching causal edges and injects
|
|
8
|
+
* an "avoid these approaches" hint into the agent's system prompt.
|
|
9
|
+
*
|
|
10
|
+
* This is one of ruflo's 6 architectural primitives distinguishing it
|
|
11
|
+
* from HAL: HAL is stateless across runs; ruflo accumulates causal memory.
|
|
12
|
+
*
|
|
13
|
+
* Storage: JSONL file at ~/.cache/ruflo/gaia/causal-edges.jsonl
|
|
14
|
+
* - Simple, portable, no runtime dependency on AgentDB
|
|
15
|
+
* - Production upgrade path: switch to AgentDB causal-edge MCP controller
|
|
16
|
+
* (`mcp__claude-flow__agentdb_causal-edge`) for cross-session persistence
|
|
17
|
+
* and embedding-based similarity matching.
|
|
18
|
+
*
|
|
19
|
+
* Expected lift:
|
|
20
|
+
* - First run (no edges yet): +0pp (empty hint → no overhead)
|
|
21
|
+
* - After 5+ runs (warm-up): +2-5pp compound
|
|
22
|
+
*
|
|
23
|
+
* NOT wired into gaia-bench.ts here — wiring is a follow-up PR once all
|
|
24
|
+
* in-flight iterators (29/31/34/35/37) have landed to avoid conflicts.
|
|
25
|
+
*
|
|
26
|
+
* Refs: ADR-135, ADR-133, #2156
|
|
27
|
+
*/
|
|
28
|
+
import * as crypto from 'node:crypto';
|
|
29
|
+
import * as fs from 'node:fs';
|
|
30
|
+
import * as os from 'node:os';
|
|
31
|
+
import * as path from 'node:path';
|
|
32
|
+
import * as readline from 'node:readline';
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Internal helpers
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
const DEFAULT_STORE_SUFFIX = path.join('.cache', 'ruflo', 'gaia', 'causal-edges.jsonl');
|
|
37
|
+
const DEFAULT_MAX_EDGES = 5;
|
|
38
|
+
/** Resolve the JSONL store path from options, defaulting to ~/.cache/ruflo/… */
|
|
39
|
+
function resolveStorePath(options) {
|
|
40
|
+
if (options?.storePath) {
|
|
41
|
+
return options.storePath;
|
|
42
|
+
}
|
|
43
|
+
return path.join(os.homedir(), DEFAULT_STORE_SUFFIX);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Compute a deterministic question signature.
|
|
47
|
+
*
|
|
48
|
+
* Algorithm (v1, hash-based):
|
|
49
|
+
* 1. Lower-case the question text.
|
|
50
|
+
* 2. Collapse runs of whitespace to a single space and trim.
|
|
51
|
+
* 3. SHA-256 → first 16 hex characters (64-bit prefix, collision-unlikely
|
|
52
|
+
* for the ~450-question GAIA validation set).
|
|
53
|
+
*
|
|
54
|
+
* Future (v2, embedding-based): replace with RuVector cosine similarity so
|
|
55
|
+
* semantically similar questions (paraphrases, translated variants) share
|
|
56
|
+
* causal edges across runs.
|
|
57
|
+
*/
|
|
58
|
+
export function computeQuestionSignature(questionText) {
|
|
59
|
+
const normalised = questionText.toLowerCase().replace(/\s+/g, ' ').trim();
|
|
60
|
+
return crypto.createHash('sha256').update(normalised, 'utf8').digest('hex').slice(0, 16);
|
|
61
|
+
}
|
|
62
|
+
/** Derive a failure type from a completed agent result + known correctness. */
|
|
63
|
+
export function inferFailureType(result, wasCorrect) {
|
|
64
|
+
if (wasCorrect) {
|
|
65
|
+
return null; // Not a failure — callers should skip this question.
|
|
66
|
+
}
|
|
67
|
+
if (result.timedOut === true) {
|
|
68
|
+
return 'timeout';
|
|
69
|
+
}
|
|
70
|
+
if (result.error) {
|
|
71
|
+
return 'tool_error';
|
|
72
|
+
}
|
|
73
|
+
if (result.finalAnswer === null || result.finalAnswer.trim() === '') {
|
|
74
|
+
return 'empty_result';
|
|
75
|
+
}
|
|
76
|
+
return 'wrong_answer';
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Parse one JSONL line into a CausalEdge.
|
|
80
|
+
* Returns null if the line is empty, a comment, or malformed.
|
|
81
|
+
*/
|
|
82
|
+
function parseLine(line) {
|
|
83
|
+
const trimmed = line.trim();
|
|
84
|
+
if (trimmed === '' || trimmed.startsWith('//') || trimmed.startsWith('#')) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const parsed = JSON.parse(trimmed);
|
|
89
|
+
// Minimal shape guard — avoids crashing on partially-written lines.
|
|
90
|
+
if (typeof parsed.questionSignature !== 'string' ||
|
|
91
|
+
typeof parsed.failedTool !== 'string' ||
|
|
92
|
+
typeof parsed.failedTrajectoryStep !== 'string' ||
|
|
93
|
+
typeof parsed.observedFailureType !== 'string' ||
|
|
94
|
+
typeof parsed.createdAt !== 'string' ||
|
|
95
|
+
typeof parsed.occurrenceCount !== 'number') {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
return parsed;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Corrupted line — skip gracefully.
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/** Read all valid CausalEdge entries from the JSONL store. */
|
|
106
|
+
async function readAllEdges(storePath) {
|
|
107
|
+
if (!fs.existsSync(storePath)) {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
const edges = [];
|
|
111
|
+
const fileStream = fs.createReadStream(storePath, { encoding: 'utf8' });
|
|
112
|
+
const rl = readline.createInterface({ input: fileStream, crlfDelay: Infinity });
|
|
113
|
+
for await (const line of rl) {
|
|
114
|
+
const edge = parseLine(line);
|
|
115
|
+
if (edge !== null) {
|
|
116
|
+
edges.push(edge);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return edges;
|
|
120
|
+
}
|
|
121
|
+
/** Write all edges back to the JSONL store (full rewrite for upsert support). */
|
|
122
|
+
function writeAllEdges(storePath, edges) {
|
|
123
|
+
const dir = path.dirname(storePath);
|
|
124
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
125
|
+
const content = edges.map((e) => JSON.stringify(e)).join('\n') + (edges.length > 0 ? '\n' : '');
|
|
126
|
+
fs.writeFileSync(storePath, content, { encoding: 'utf8' });
|
|
127
|
+
}
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Trajectory analysis
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
/**
|
|
132
|
+
* Inspect a failed trajectory and extract tool-level failure events.
|
|
133
|
+
*
|
|
134
|
+
* The agent result carries `toolCallsByName` (tool → call count). For a
|
|
135
|
+
* failed run we attribute the failure to the most-used tool, since that is
|
|
136
|
+
* the tool whose repeated use did not converge to a correct answer.
|
|
137
|
+
*
|
|
138
|
+
* Returns an array of (tool, stepDescription, failureType) triples.
|
|
139
|
+
* Empty array when there is nothing attributable.
|
|
140
|
+
*/
|
|
141
|
+
function extractFailureEvents(result, failureType) {
|
|
142
|
+
const events = [];
|
|
143
|
+
const toolCalls = result.toolCallsByName;
|
|
144
|
+
if (!toolCalls || Object.keys(toolCalls).length === 0) {
|
|
145
|
+
// No tool calls at all — record a synthetic "no_tool" event.
|
|
146
|
+
events.push({
|
|
147
|
+
tool: 'no_tool_called',
|
|
148
|
+
step: `Agent failed with type=${failureType} without making any tool calls`,
|
|
149
|
+
type: failureType,
|
|
150
|
+
});
|
|
151
|
+
return events;
|
|
152
|
+
}
|
|
153
|
+
// Attribute one event per tool that was called at least once.
|
|
154
|
+
for (const [toolName, callCount] of Object.entries(toolCalls)) {
|
|
155
|
+
const step = buildStepDescription(toolName, callCount, failureType);
|
|
156
|
+
events.push({ tool: toolName, step, type: failureType });
|
|
157
|
+
}
|
|
158
|
+
return events;
|
|
159
|
+
}
|
|
160
|
+
function buildStepDescription(toolName, callCount, failureType) {
|
|
161
|
+
const timesStr = callCount === 1 ? 'once' : `${callCount} times`;
|
|
162
|
+
switch (failureType) {
|
|
163
|
+
case 'empty_result':
|
|
164
|
+
return `${toolName} called ${timesStr} but returned empty/no-result`;
|
|
165
|
+
case 'timeout':
|
|
166
|
+
return `${toolName} called ${timesStr} but agent timed out before converging`;
|
|
167
|
+
case 'tool_error':
|
|
168
|
+
return `${toolName} called ${timesStr} but raised an execution error`;
|
|
169
|
+
case 'wrong_answer':
|
|
170
|
+
return `${toolName} called ${timesStr} but final answer was incorrect`;
|
|
171
|
+
default:
|
|
172
|
+
return `${toolName} called ${timesStr}; failure type=${failureType}`;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// Public API
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
/**
|
|
179
|
+
* After a GAIA trajectory completes, analyse it for causal failure patterns
|
|
180
|
+
* and persist each observed edge in the JSONL store.
|
|
181
|
+
*
|
|
182
|
+
* Behaviour:
|
|
183
|
+
* - If `wasCorrect === true`, no edges are written (zero overhead).
|
|
184
|
+
* - Each (signature, tool, step) triple is deduplicated: if the same triple
|
|
185
|
+
* already exists in the store, its `occurrenceCount` is incremented in
|
|
186
|
+
* place rather than appending a new line.
|
|
187
|
+
* - Edges beyond `maxEdgesPerSignature` (default 5) per signature are
|
|
188
|
+
* discarded to keep the store bounded.
|
|
189
|
+
*
|
|
190
|
+
* @param question - The GaiaQuestion that was attempted.
|
|
191
|
+
* @param result - The agent result from runGaiaAgent().
|
|
192
|
+
* @param wasCorrect - Whether the final answer was judged correct.
|
|
193
|
+
* @param options - Optional store path and limits.
|
|
194
|
+
* @returns Number of edges written/updated and the resolved store path.
|
|
195
|
+
*/
|
|
196
|
+
export async function recordCausalFailures(question, result, wasCorrect, options) {
|
|
197
|
+
const storePath = resolveStorePath(options);
|
|
198
|
+
const maxEdges = options?.maxEdgesPerSignature ?? DEFAULT_MAX_EDGES;
|
|
199
|
+
const failureType = inferFailureType(result, wasCorrect);
|
|
200
|
+
if (failureType === null) {
|
|
201
|
+
// Correct answer — nothing to record.
|
|
202
|
+
return { edgesRecorded: 0, storePath };
|
|
203
|
+
}
|
|
204
|
+
const signature = computeQuestionSignature(question.question);
|
|
205
|
+
const failureEvents = extractFailureEvents(result, failureType);
|
|
206
|
+
if (failureEvents.length === 0) {
|
|
207
|
+
return { edgesRecorded: 0, storePath };
|
|
208
|
+
}
|
|
209
|
+
// Load existing edges, upsert, cap, rewrite.
|
|
210
|
+
const existing = await readAllEdges(storePath);
|
|
211
|
+
let edgesRecorded = 0;
|
|
212
|
+
for (const event of failureEvents) {
|
|
213
|
+
// Count how many edges already exist for this signature.
|
|
214
|
+
const sigEdges = existing.filter((e) => e.questionSignature === signature);
|
|
215
|
+
// Check for exact duplicate (signature + tool + step).
|
|
216
|
+
const dupIdx = existing.findIndex((e) => e.questionSignature === signature &&
|
|
217
|
+
e.failedTool === event.tool &&
|
|
218
|
+
e.failedTrajectoryStep === event.step);
|
|
219
|
+
if (dupIdx >= 0) {
|
|
220
|
+
// Increment occurrence count in place.
|
|
221
|
+
existing[dupIdx].occurrenceCount += 1;
|
|
222
|
+
edgesRecorded++;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
// New edge — only add if we haven't hit the per-signature cap.
|
|
226
|
+
if (sigEdges.length >= maxEdges) {
|
|
227
|
+
continue; // Cap reached; discard.
|
|
228
|
+
}
|
|
229
|
+
const newEdge = {
|
|
230
|
+
questionSignature: signature,
|
|
231
|
+
failedTool: event.tool,
|
|
232
|
+
failedTrajectoryStep: event.step,
|
|
233
|
+
observedFailureType: event.type,
|
|
234
|
+
createdAt: new Date().toISOString(),
|
|
235
|
+
occurrenceCount: 1,
|
|
236
|
+
};
|
|
237
|
+
existing.push(newEdge);
|
|
238
|
+
edgesRecorded++;
|
|
239
|
+
}
|
|
240
|
+
if (edgesRecorded > 0) {
|
|
241
|
+
writeAllEdges(storePath, existing);
|
|
242
|
+
}
|
|
243
|
+
return { edgesRecorded, storePath };
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Before running a new question, retrieve causal edges from prior failures
|
|
247
|
+
* that match the question's signature and format them as a system-prompt hint.
|
|
248
|
+
*
|
|
249
|
+
* Return contract:
|
|
250
|
+
* - No edges matched → `{ hint: '', edgesMatched: 0 }` — caller MUST NOT
|
|
251
|
+
* inject an empty hint (wastes tokens; may confuse the model).
|
|
252
|
+
* - 1+ edges matched → `{ hint: '[PRIOR FAILURES] …', edgesMatched: N }`.
|
|
253
|
+
*
|
|
254
|
+
* @param question - The GaiaQuestion about to be attempted.
|
|
255
|
+
* @param options - Optional store path and limits.
|
|
256
|
+
* @returns Formatted hint string and match count.
|
|
257
|
+
*/
|
|
258
|
+
export async function retrieveCausalHints(question, options) {
|
|
259
|
+
const storePath = resolveStorePath(options);
|
|
260
|
+
const maxEdges = options?.maxEdgesPerSignature ?? DEFAULT_MAX_EDGES;
|
|
261
|
+
const signature = computeQuestionSignature(question.question);
|
|
262
|
+
const allEdges = await readAllEdges(storePath);
|
|
263
|
+
// Filter to this question's signature, sort by occurrence count descending
|
|
264
|
+
// so the most-reinforced warnings appear first.
|
|
265
|
+
const matched = allEdges
|
|
266
|
+
.filter((e) => e.questionSignature === signature)
|
|
267
|
+
.sort((a, b) => b.occurrenceCount - a.occurrenceCount)
|
|
268
|
+
.slice(0, maxEdges);
|
|
269
|
+
if (matched.length === 0) {
|
|
270
|
+
return { hint: '', edgesMatched: 0 };
|
|
271
|
+
}
|
|
272
|
+
const lines = matched.map((e) => {
|
|
273
|
+
const times = e.occurrenceCount === 1 ? '1 time' : `${e.occurrenceCount} times`;
|
|
274
|
+
return ` - ${e.failedTool} failed ${times} (${e.observedFailureType}): ${e.failedTrajectoryStep}`;
|
|
275
|
+
});
|
|
276
|
+
const hint = '[PRIOR FAILURES] On similar questions, these approaches failed:\n' +
|
|
277
|
+
lines.join('\n') +
|
|
278
|
+
'\nAvoid repeating these patterns. Try alternative tools or approaches.';
|
|
279
|
+
return { hint, edgesMatched: matched.length };
|
|
280
|
+
}
|
|
281
|
+
//# sourceMappingURL=gaia-causal-memory.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smoke tests — ADR-135 Track I: Causal failure-avoidance edges
|
|
3
|
+
*
|
|
4
|
+
* All I/O is mocked (no real filesystem or network calls).
|
|
5
|
+
*
|
|
6
|
+
* Tests:
|
|
7
|
+
* 1. Record one failure → retrieve same-signature question → returns hint
|
|
8
|
+
* 2. Record 3 failures → retrieve unrelated question → returns empty hint
|
|
9
|
+
* 3. Same edge recorded twice → occurrenceCount=2, not duplicated
|
|
10
|
+
* 4. File doesn't exist → graceful empty result on retrieve
|
|
11
|
+
* 5. Corrupted JSONL line → skipped, doesn't crash
|
|
12
|
+
* 6. maxEdgesPerSignature cap respected (no new edge after cap)
|
|
13
|
+
* 7. Signature computation is deterministic (same input → same output)
|
|
14
|
+
* 8. Correct answer trajectory → no edges recorded
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* npx tsx src/benchmarks/gaia-causal-memory.smoke.ts
|
|
18
|
+
*
|
|
19
|
+
* Refs: ADR-135, #2156
|
|
20
|
+
*/
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=gaia-causal-memory.smoke.d.ts.map
|