@kognai/orchestrator-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +175 -0
- package/dist/lib/aar-middleware.d.ts +6 -0
- package/dist/lib/aar-middleware.js +70 -0
- package/dist/lib/aar-types.d.ts +34 -0
- package/dist/lib/aar-types.js +4 -0
- package/dist/lib/acp-engine.d.ts +68 -0
- package/dist/lib/acp-engine.js +123 -0
- package/dist/lib/acp.d.ts +61 -0
- package/dist/lib/acp.js +425 -0
- package/dist/lib/agent-registry.d.ts +50 -0
- package/dist/lib/agent-registry.js +137 -0
- package/dist/lib/anthropic-direct.d.ts +27 -0
- package/dist/lib/anthropic-direct.js +109 -0
- package/dist/lib/asmr-extractor.d.ts +40 -0
- package/dist/lib/asmr-extractor.js +151 -0
- package/dist/lib/asmr-retrieval.d.ts +76 -0
- package/dist/lib/asmr-retrieval.js +311 -0
- package/dist/lib/asmr.d.ts +8 -0
- package/dist/lib/asmr.js +24 -0
- package/dist/lib/brainx-client.d.ts +72 -0
- package/dist/lib/brainx-client.js +200 -0
- package/dist/lib/brainx-embed.d.ts +14 -0
- package/dist/lib/brainx-embed.js +139 -0
- package/dist/lib/brainx-swarm-bridge.d.ts +93 -0
- package/dist/lib/brainx-swarm-bridge.js +242 -0
- package/dist/lib/byterover-client.d.ts +19 -0
- package/dist/lib/byterover-client.js +59 -0
- package/dist/lib/ceo-wallet.d.ts +37 -0
- package/dist/lib/ceo-wallet.js +176 -0
- package/dist/lib/chomsky-gate.d.ts +24 -0
- package/dist/lib/chomsky-gate.js +178 -0
- package/dist/lib/chomsky-runner.d.ts +29 -0
- package/dist/lib/chomsky-runner.js +157 -0
- package/dist/lib/citizen-score-contract.d.ts +72 -0
- package/dist/lib/citizen-score-contract.js +16 -0
- package/dist/lib/citizen-score-registry.d.ts +25 -0
- package/dist/lib/citizen-score-registry.js +65 -0
- package/dist/lib/citizenship.d.ts +103 -0
- package/dist/lib/citizenship.js +272 -0
- package/dist/lib/clawrouter-client.d.ts +37 -0
- package/dist/lib/clawrouter-client.js +148 -0
- package/dist/lib/code-asset-crystalliser.d.ts +41 -0
- package/dist/lib/code-asset-crystalliser.js +181 -0
- package/dist/lib/code-failure-logger.d.ts +27 -0
- package/dist/lib/code-failure-logger.js +42 -0
- package/dist/lib/cto-approval-gate.d.ts +45 -0
- package/dist/lib/cto-approval-gate.js +478 -0
- package/dist/lib/cto-gate-types.d.ts +28 -0
- package/dist/lib/cto-gate-types.js +8 -0
- package/dist/lib/decomposer-feedback.d.ts +54 -0
- package/dist/lib/decomposer-feedback.js +115 -0
- package/dist/lib/emotional-safety-gate.d.ts +48 -0
- package/dist/lib/emotional-safety-gate.js +97 -0
- package/dist/lib/engine-paths.d.ts +13 -0
- package/dist/lib/engine-paths.js +32 -0
- package/dist/lib/event-bus-listener.d.ts +8 -0
- package/dist/lib/event-bus-listener.js +144 -0
- package/dist/lib/event-bus-publisher.d.ts +25 -0
- package/dist/lib/event-bus-publisher.js +188 -0
- package/dist/lib/event-bus-types.d.ts +73 -0
- package/dist/lib/event-bus-types.js +23 -0
- package/dist/lib/failure-library.d.ts +178 -0
- package/dist/lib/failure-library.js +349 -0
- package/dist/lib/ksl/error-log.d.ts +28 -0
- package/dist/lib/ksl/error-log.js +43 -0
- package/dist/lib/ksl/index.d.ts +9 -0
- package/dist/lib/ksl/index.js +25 -0
- package/dist/lib/ksl/orchestrator-tap.d.ts +16 -0
- package/dist/lib/ksl/orchestrator-tap.js +85 -0
- package/dist/lib/ksl/record-writer.d.ts +46 -0
- package/dist/lib/ksl/record-writer.js +45 -0
- package/dist/lib/llm-cost-table.d.ts +36 -0
- package/dist/lib/llm-cost-table.js +90 -0
- package/dist/lib/local-model-router.d.ts +27 -0
- package/dist/lib/local-model-router.js +61 -0
- package/dist/lib/mc-client.d.ts +51 -0
- package/dist/lib/mc-client.js +249 -0
- package/dist/lib/model-router-contract.d.ts +91 -0
- package/dist/lib/model-router-contract.js +19 -0
- package/dist/lib/model-router-registry.d.ts +24 -0
- package/dist/lib/model-router-registry.js +52 -0
- package/dist/lib/model-router.d.ts +20 -0
- package/dist/lib/model-router.js +79 -0
- package/dist/lib/monotask-state-machine.d.ts +19 -0
- package/dist/lib/monotask-state-machine.js +131 -0
- package/dist/lib/neutral-prompt-checker.d.ts +22 -0
- package/dist/lib/neutral-prompt-checker.js +130 -0
- package/dist/lib/notion-direct.d.ts +92 -0
- package/dist/lib/notion-direct.js +381 -0
- package/dist/lib/ollama-client.d.ts +37 -0
- package/dist/lib/ollama-client.js +158 -0
- package/dist/lib/omel/credential-vault.d.ts +57 -0
- package/dist/lib/omel/credential-vault.js +324 -0
- package/dist/lib/omel/human-brake.d.ts +32 -0
- package/dist/lib/omel/human-brake.js +289 -0
- package/dist/lib/omel/index.d.ts +10 -0
- package/dist/lib/omel/index.js +26 -0
- package/dist/lib/omel/phantom-workspace.d.ts +31 -0
- package/dist/lib/omel/phantom-workspace.js +256 -0
- package/dist/lib/omel/wipe-witness.d.ts +75 -0
- package/dist/lib/omel/wipe-witness.js +398 -0
- package/dist/lib/orchestrate-engine.d.ts +25 -0
- package/dist/lib/orchestrate-engine.js +4436 -0
- package/dist/lib/perm-judge.d.ts +46 -0
- package/dist/lib/perm-judge.js +173 -0
- package/dist/lib/plumber/conformance.d.ts +54 -0
- package/dist/lib/plumber/conformance.js +121 -0
- package/dist/lib/plumber/index.d.ts +9 -0
- package/dist/lib/plumber/index.js +25 -0
- package/dist/lib/plumber/observer.d.ts +52 -0
- package/dist/lib/plumber/observer.js +180 -0
- package/dist/lib/plumber/types.d.ts +78 -0
- package/dist/lib/plumber/types.js +29 -0
- package/dist/lib/research-impl-gate.d.ts +16 -0
- package/dist/lib/research-impl-gate.js +105 -0
- package/dist/lib/sherlock-memory.d.ts +29 -0
- package/dist/lib/sherlock-memory.js +105 -0
- package/dist/lib/skill-crystalliser.d.ts +44 -0
- package/dist/lib/skill-crystalliser.js +60 -0
- package/dist/lib/sprint-runner-engine.d.ts +27 -0
- package/dist/lib/sprint-runner-engine.js +1042 -0
- package/dist/lib/sprint-state.d.ts +71 -0
- package/dist/lib/sprint-state.js +202 -0
- package/dist/lib/stuck-handler.d.ts +17 -0
- package/dist/lib/stuck-handler.js +249 -0
- package/dist/lib/task-contract-checker.d.ts +17 -0
- package/dist/lib/task-contract-checker.js +29 -0
- package/dist/lib/task-router/index.d.ts +17 -0
- package/dist/lib/task-router/index.js +52 -0
- package/dist/lib/task-router/router/generate-execution-id.d.ts +10 -0
- package/dist/lib/task-router/router/generate-execution-id.js +24 -0
- package/dist/lib/task-router/router/resolve-route.d.ts +2 -0
- package/dist/lib/task-router/router/resolve-route.js +49 -0
- package/dist/lib/task-router/types.d.ts +79 -0
- package/dist/lib/task-router/types.js +39 -0
- package/dist/lib/token-budget-validator.d.ts +44 -0
- package/dist/lib/token-budget-validator.js +84 -0
- package/dist/lib/trust-score-updater.d.ts +30 -0
- package/dist/lib/trust-score-updater.js +107 -0
- package/dist/lib/wallet-state.d.ts +26 -0
- package/dist/lib/wallet-state.js +85 -0
- package/package.json +27 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* anthropic-direct.ts — Minimal Anthropic Messages API client.
|
|
4
|
+
*
|
|
5
|
+
* Bypasses OpenClaw gateway entirely. OpenClaw is an agentic gateway that
|
|
6
|
+
* wraps requests with its workspace agent's IDENTITY.md (e.g. "Nova"),
|
|
7
|
+
* overriding caller-supplied system prompts. For senior-coder we need a
|
|
8
|
+
* clean, identity-less Sonnet call.
|
|
9
|
+
*
|
|
10
|
+
* Zero npm deps (uses node `https`). Reads ANTHROPIC_API_KEY from env.
|
|
11
|
+
*
|
|
12
|
+
* Created: 2026-04-28 after senior-coder responses came back as "Nova".
|
|
13
|
+
*/
|
|
14
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
17
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
18
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
19
|
+
}
|
|
20
|
+
Object.defineProperty(o, k2, desc);
|
|
21
|
+
}) : (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
o[k2] = m[k];
|
|
24
|
+
}));
|
|
25
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
26
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
27
|
+
}) : function(o, v) {
|
|
28
|
+
o["default"] = v;
|
|
29
|
+
});
|
|
30
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
31
|
+
var ownKeys = function(o) {
|
|
32
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
33
|
+
var ar = [];
|
|
34
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
35
|
+
return ar;
|
|
36
|
+
};
|
|
37
|
+
return ownKeys(o);
|
|
38
|
+
};
|
|
39
|
+
return function (mod) {
|
|
40
|
+
if (mod && mod.__esModule) return mod;
|
|
41
|
+
var result = {};
|
|
42
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
43
|
+
__setModuleDefault(result, mod);
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
})();
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
exports.callAnthropic = callAnthropic;
|
|
49
|
+
const https = __importStar(require("https"));
|
|
50
|
+
const ANTHROPIC_KEY = process.env.ANTHROPIC_API_KEY || '';
|
|
51
|
+
const ANTHROPIC_VERSION = '2023-06-01';
|
|
52
|
+
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
53
|
+
async function callAnthropic(opts) {
|
|
54
|
+
if (!ANTHROPIC_KEY)
|
|
55
|
+
throw new Error('ANTHROPIC_API_KEY not set');
|
|
56
|
+
const body = JSON.stringify({
|
|
57
|
+
model: opts.model,
|
|
58
|
+
max_tokens: opts.max_tokens ?? 800,
|
|
59
|
+
temperature: opts.temperature ?? 0.4,
|
|
60
|
+
system: opts.system,
|
|
61
|
+
messages: [{ role: 'user', content: opts.user }],
|
|
62
|
+
});
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const req = https.request({
|
|
65
|
+
hostname: 'api.anthropic.com',
|
|
66
|
+
path: '/v1/messages',
|
|
67
|
+
method: 'POST',
|
|
68
|
+
headers: {
|
|
69
|
+
'Content-Type': 'application/json',
|
|
70
|
+
'x-api-key': ANTHROPIC_KEY,
|
|
71
|
+
'anthropic-version': ANTHROPIC_VERSION,
|
|
72
|
+
'Content-Length': Buffer.byteLength(body),
|
|
73
|
+
},
|
|
74
|
+
timeout: opts.timeout_ms ?? DEFAULT_TIMEOUT_MS,
|
|
75
|
+
}, (res) => {
|
|
76
|
+
let chunks = '';
|
|
77
|
+
res.on('data', (c) => (chunks += c));
|
|
78
|
+
res.on('end', () => {
|
|
79
|
+
try {
|
|
80
|
+
const parsed = JSON.parse(chunks);
|
|
81
|
+
if (parsed.error) {
|
|
82
|
+
reject(new Error(`Anthropic ${parsed.error.type}: ${parsed.error.message}`));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const text = parsed.content
|
|
86
|
+
.filter((c) => c.type === 'text')
|
|
87
|
+
.map((c) => c.text || '')
|
|
88
|
+
.join('');
|
|
89
|
+
resolve({
|
|
90
|
+
content: text,
|
|
91
|
+
input_tokens: parsed.usage?.input_tokens ?? 0,
|
|
92
|
+
output_tokens: parsed.usage?.output_tokens ?? 0,
|
|
93
|
+
stop_reason: parsed.stop_reason ?? 'unknown',
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
reject(new Error(`Anthropic parse failed (HTTP ${res.statusCode}): ${chunks.slice(0, 300)}`));
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
req.on('error', reject);
|
|
102
|
+
req.on('timeout', () => {
|
|
103
|
+
req.destroy();
|
|
104
|
+
reject(new Error(`Anthropic timeout after ${opts.timeout_ms ?? DEFAULT_TIMEOUT_MS}ms`));
|
|
105
|
+
});
|
|
106
|
+
req.write(body);
|
|
107
|
+
req.end();
|
|
108
|
+
});
|
|
109
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* asmr-extractor.ts — AMD-21 ASMR Six-Vector Extraction Engine (Sprint 1511)
|
|
3
|
+
*
|
|
4
|
+
* Extracts six semantic vectors from AARReceipt entries for episodic memory.
|
|
5
|
+
* Vectors: topic, emotion, stance, entities, temporality, intent
|
|
6
|
+
*
|
|
7
|
+
* Routing: T1 LOCAL (qwen3:4b) — cost-efficient, zero data egress
|
|
8
|
+
* Fail-open: LLM error → minimal fallback vectors derived from actionSummary
|
|
9
|
+
* Storage: logs/asmr/YYYY-MM-DD.jsonl
|
|
10
|
+
*
|
|
11
|
+
* FP-007: < 200 lines
|
|
12
|
+
*/
|
|
13
|
+
import { AARReceipt } from './aar-types';
|
|
14
|
+
export interface ASMRVectors {
|
|
15
|
+
topic: string;
|
|
16
|
+
emotion: string;
|
|
17
|
+
stance: string;
|
|
18
|
+
entities: string[];
|
|
19
|
+
temporality: string;
|
|
20
|
+
intent: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ASMREntry {
|
|
23
|
+
id: string;
|
|
24
|
+
agentId: string;
|
|
25
|
+
taskId: string;
|
|
26
|
+
sprintId: string;
|
|
27
|
+
timestamp: string;
|
|
28
|
+
actionSummary: string;
|
|
29
|
+
vectors: ASMRVectors;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Extract six semantic vectors from an AAR receipt entry.
|
|
33
|
+
* Fail-open: on LLM error returns minimal fallback vectors.
|
|
34
|
+
*/
|
|
35
|
+
export declare function extractSixVectors(entry: AARReceipt): Promise<ASMRVectors>;
|
|
36
|
+
/**
|
|
37
|
+
* Extract six vectors and persist the ASMR entry to daily JSONL log.
|
|
38
|
+
* Log write failure is non-fatal — entry is always returned.
|
|
39
|
+
*/
|
|
40
|
+
export declare function extractAndStore(entry: AARReceipt): Promise<ASMREntry>;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* asmr-extractor.ts — AMD-21 ASMR Six-Vector Extraction Engine (Sprint 1511)
|
|
4
|
+
*
|
|
5
|
+
* Extracts six semantic vectors from AARReceipt entries for episodic memory.
|
|
6
|
+
* Vectors: topic, emotion, stance, entities, temporality, intent
|
|
7
|
+
*
|
|
8
|
+
* Routing: T1 LOCAL (qwen3:4b) — cost-efficient, zero data egress
|
|
9
|
+
* Fail-open: LLM error → minimal fallback vectors derived from actionSummary
|
|
10
|
+
* Storage: logs/asmr/YYYY-MM-DD.jsonl
|
|
11
|
+
*
|
|
12
|
+
* FP-007: < 200 lines
|
|
13
|
+
*/
|
|
14
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
17
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
18
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
19
|
+
}
|
|
20
|
+
Object.defineProperty(o, k2, desc);
|
|
21
|
+
}) : (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
o[k2] = m[k];
|
|
24
|
+
}));
|
|
25
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
26
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
27
|
+
}) : function(o, v) {
|
|
28
|
+
o["default"] = v;
|
|
29
|
+
});
|
|
30
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
31
|
+
var ownKeys = function(o) {
|
|
32
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
33
|
+
var ar = [];
|
|
34
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
35
|
+
return ar;
|
|
36
|
+
};
|
|
37
|
+
return ownKeys(o);
|
|
38
|
+
};
|
|
39
|
+
return function (mod) {
|
|
40
|
+
if (mod && mod.__esModule) return mod;
|
|
41
|
+
var result = {};
|
|
42
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
43
|
+
__setModuleDefault(result, mod);
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
})();
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
exports.extractSixVectors = extractSixVectors;
|
|
49
|
+
exports.extractAndStore = extractAndStore;
|
|
50
|
+
const fs = __importStar(require("fs"));
|
|
51
|
+
const path = __importStar(require("path"));
|
|
52
|
+
const engine_paths_1 = require("./engine-paths");
|
|
53
|
+
const model_router_registry_1 = require("./model-router-registry");
|
|
54
|
+
// ── Config ────────────────────────────────────────────────────────────────────
|
|
55
|
+
const LOG_DIR = path.join((0, engine_paths_1.resolveEnginePaths)().root, 'logs', 'asmr');
|
|
56
|
+
const EXTRACT_SYSTEM = `You are a structured episodic memory extraction engine.
|
|
57
|
+
Extract exactly six semantic vectors from the agent action entry provided.
|
|
58
|
+
Respond ONLY with valid JSON — no markdown fences, no commentary, no prose.
|
|
59
|
+
Required JSON shape:
|
|
60
|
+
{
|
|
61
|
+
"topic": "one sentence: primary task domain",
|
|
62
|
+
"emotion": "one word from: success|failure|blocked|uncertain|neutral",
|
|
63
|
+
"stance": "one sentence: agent posture (e.g. cautious refactoring, aggressive build)",
|
|
64
|
+
"entities": ["array", "of", "key", "nouns", "files", "agents", "concepts"],
|
|
65
|
+
"temporality": "one phrase: timing relative to sprint (e.g. mid-sprint, post-gate)",
|
|
66
|
+
"intent": "one sentence: the goal the agent was trying to achieve"
|
|
67
|
+
}`;
|
|
68
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
69
|
+
function stripThink(text) {
|
|
70
|
+
return text.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
|
|
71
|
+
}
|
|
72
|
+
function fallbackVectors(entry) {
|
|
73
|
+
return {
|
|
74
|
+
topic: entry.actionSummary.slice(0, 120),
|
|
75
|
+
emotion: entry.status === 'success' ? 'success'
|
|
76
|
+
: entry.status === 'failed' ? 'failure'
|
|
77
|
+
: 'neutral',
|
|
78
|
+
stance: 'autonomous execution',
|
|
79
|
+
entities: [entry.agentId, entry.taskId, entry.sprintId].filter(Boolean),
|
|
80
|
+
temporality: 'sprint-time',
|
|
81
|
+
intent: `${entry.agentId} executed task ${entry.taskId}`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function getLogPath(timestamp) {
|
|
85
|
+
const date = new Date(timestamp).toISOString().slice(0, 10);
|
|
86
|
+
return path.join(LOG_DIR, `${date}.jsonl`);
|
|
87
|
+
}
|
|
88
|
+
// ── Core extractor ────────────────────────────────────────────────────────────
|
|
89
|
+
/**
|
|
90
|
+
* Extract six semantic vectors from an AAR receipt entry.
|
|
91
|
+
* Fail-open: on LLM error returns minimal fallback vectors.
|
|
92
|
+
*/
|
|
93
|
+
async function extractSixVectors(entry) {
|
|
94
|
+
try {
|
|
95
|
+
const prompt = [
|
|
96
|
+
`Agent: ${entry.agentId} | Sprint: ${entry.sprintId} | Task: ${entry.taskId}`,
|
|
97
|
+
`Status: ${entry.status} | Score: ${entry.outcomeScore}`,
|
|
98
|
+
`Summary: ${entry.actionSummary}`,
|
|
99
|
+
`\nExtract six semantic vectors as JSON.`,
|
|
100
|
+
].join('\n');
|
|
101
|
+
const response = await (0, model_router_registry_1.getModelRouter)().routeCall({
|
|
102
|
+
task_type: 'asmr_extraction',
|
|
103
|
+
tier_class: 'text',
|
|
104
|
+
complexity: 'local', // T1 qwen3:4b
|
|
105
|
+
context_tokens: Math.ceil((prompt.length + EXTRACT_SYSTEM.length) / 4),
|
|
106
|
+
constitutional_flag: false,
|
|
107
|
+
agent_id: 'asmr-extractor',
|
|
108
|
+
payload: { system: EXTRACT_SYSTEM, prompt },
|
|
109
|
+
});
|
|
110
|
+
const cleaned = stripThink(response.content);
|
|
111
|
+
const jsonMatch = cleaned.match(/\{[\s\S]*\}/);
|
|
112
|
+
if (!jsonMatch)
|
|
113
|
+
throw new Error('No JSON found in LLM response');
|
|
114
|
+
const p = JSON.parse(jsonMatch[0]);
|
|
115
|
+
return {
|
|
116
|
+
topic: typeof p.topic === 'string' ? p.topic : entry.actionSummary.slice(0, 120),
|
|
117
|
+
emotion: typeof p.emotion === 'string' ? p.emotion : 'neutral',
|
|
118
|
+
stance: typeof p.stance === 'string' ? p.stance : 'autonomous execution',
|
|
119
|
+
entities: Array.isArray(p.entities) ? p.entities : [entry.agentId],
|
|
120
|
+
temporality: typeof p.temporality === 'string' ? p.temporality : 'sprint-time',
|
|
121
|
+
intent: typeof p.intent === 'string' ? p.intent : entry.actionSummary.slice(0, 120),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return fallbackVectors(entry);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Extract six vectors and persist the ASMR entry to daily JSONL log.
|
|
130
|
+
* Log write failure is non-fatal — entry is always returned.
|
|
131
|
+
*/
|
|
132
|
+
async function extractAndStore(entry) {
|
|
133
|
+
const vectors = await extractSixVectors(entry);
|
|
134
|
+
const asmrEntry = {
|
|
135
|
+
id: entry.receiptId,
|
|
136
|
+
agentId: entry.agentId,
|
|
137
|
+
taskId: entry.taskId,
|
|
138
|
+
sprintId: entry.sprintId,
|
|
139
|
+
timestamp: entry.timestamp,
|
|
140
|
+
actionSummary: entry.actionSummary,
|
|
141
|
+
vectors,
|
|
142
|
+
};
|
|
143
|
+
try {
|
|
144
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
145
|
+
fs.appendFileSync(getLogPath(entry.timestamp), JSON.stringify(asmrEntry) + '\n', 'utf8');
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// Log write failure is non-fatal — do not block caller
|
|
149
|
+
}
|
|
150
|
+
return asmrEntry;
|
|
151
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* asmr-retrieval.ts — AMD-21 ASMR Active Memory Retrieval (Sprint 1511)
|
|
3
|
+
*
|
|
4
|
+
* Query-driven episodic memory retrieval over the ASMR six-vector store.
|
|
5
|
+
* Replaces the pattern of loading full MEMORY.md on every agent invocation.
|
|
6
|
+
*
|
|
7
|
+
* Strategy: embed query → embed each ASMR entry (sequential) → cosine rank → top-N
|
|
8
|
+
* Embeddings: nomic-embed-text local (768-dim, no OpenAI dependency)
|
|
9
|
+
* Cap: MAX_ENTRIES = 500 (newest first across all daily JSONL files)
|
|
10
|
+
*
|
|
11
|
+
* FP-007: < 200 lines
|
|
12
|
+
*/
|
|
13
|
+
import { ASMREntry } from './asmr-extractor';
|
|
14
|
+
export interface RankedASMREntry extends ASMREntry {
|
|
15
|
+
similarity: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Retrieve the most relevant episodic memory entries for a given query.
|
|
19
|
+
*
|
|
20
|
+
* Uses cosine similarity over nomic-embed-text embeddings of the six-vector text.
|
|
21
|
+
* Sequential embedding (not parallel) to avoid Ollama overload.
|
|
22
|
+
*
|
|
23
|
+
* @param query Natural-language retrieval query
|
|
24
|
+
* @param limit Maximum number of results to return (default: 5)
|
|
25
|
+
* @returns Array of ASMREntry sorted by descending similarity
|
|
26
|
+
*/
|
|
27
|
+
export declare function retrieveRelevantMemories(query: string, limit?: number): Promise<ASMREntry[]>;
|
|
28
|
+
import { BrainXMemory } from './brainx-client';
|
|
29
|
+
export interface MemoryHit {
|
|
30
|
+
source: 'hot' | 'warm' | 'cold';
|
|
31
|
+
content: string;
|
|
32
|
+
score: number;
|
|
33
|
+
ts: string;
|
|
34
|
+
provenance: string;
|
|
35
|
+
}
|
|
36
|
+
export interface RetrievalResult {
|
|
37
|
+
hot: MemoryHit[];
|
|
38
|
+
warm: MemoryHit[];
|
|
39
|
+
cold: MemoryHit[];
|
|
40
|
+
merged: MemoryHit[];
|
|
41
|
+
latency_ms: {
|
|
42
|
+
hot: number;
|
|
43
|
+
warm: number;
|
|
44
|
+
cold: number;
|
|
45
|
+
total: number;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export interface ThreeTierOpts {
|
|
49
|
+
hotTurns?: number;
|
|
50
|
+
warmDays?: number;
|
|
51
|
+
maxResults?: number;
|
|
52
|
+
coldThresholdDays?: number;
|
|
53
|
+
/** Inject a BrainXClient — primarily for testing. Default: real client. */
|
|
54
|
+
brainx?: {
|
|
55
|
+
retrieve(query: string, opts?: any): Promise<BrainXMemory[]>;
|
|
56
|
+
};
|
|
57
|
+
/** Override cold-scan root directory (testing). Default: workspace/asmr. */
|
|
58
|
+
coldRoot?: string;
|
|
59
|
+
/** Disable cold scan entirely (testing or perf). Default: false. */
|
|
60
|
+
skipCold?: boolean;
|
|
61
|
+
}
|
|
62
|
+
/** Append a turn to the hot buffer. Caller-driven population. */
|
|
63
|
+
export declare function appendHotTurn(agentId: string, content: string, score?: number, capacity?: number): void;
|
|
64
|
+
/** Test helper — clear the buffer (single agent or all). */
|
|
65
|
+
export declare function _clearHotBufferForTesting(agentId?: string): void;
|
|
66
|
+
/**
|
|
67
|
+
* Three-tier retrieval over hot in-memory turns + warm BrainX vectors +
|
|
68
|
+
* cold archive scan. Returns ranked, deduped results with provenance tags
|
|
69
|
+
* and a per-tier latency breakdown.
|
|
70
|
+
*
|
|
71
|
+
* Cold tier only fires when hot+warm don't hit `maxResults` — keeps the
|
|
72
|
+
* common path fast.
|
|
73
|
+
*
|
|
74
|
+
* Never throws. All three tiers fail-open to empty hits on error.
|
|
75
|
+
*/
|
|
76
|
+
export declare function retrieveThreeTier(query: string, agentId: string, opts?: ThreeTierOpts): Promise<RetrievalResult>;
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* asmr-retrieval.ts — AMD-21 ASMR Active Memory Retrieval (Sprint 1511)
|
|
4
|
+
*
|
|
5
|
+
* Query-driven episodic memory retrieval over the ASMR six-vector store.
|
|
6
|
+
* Replaces the pattern of loading full MEMORY.md on every agent invocation.
|
|
7
|
+
*
|
|
8
|
+
* Strategy: embed query → embed each ASMR entry (sequential) → cosine rank → top-N
|
|
9
|
+
* Embeddings: nomic-embed-text local (768-dim, no OpenAI dependency)
|
|
10
|
+
* Cap: MAX_ENTRIES = 500 (newest first across all daily JSONL files)
|
|
11
|
+
*
|
|
12
|
+
* FP-007: < 200 lines
|
|
13
|
+
*/
|
|
14
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
17
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
18
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
19
|
+
}
|
|
20
|
+
Object.defineProperty(o, k2, desc);
|
|
21
|
+
}) : (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
o[k2] = m[k];
|
|
24
|
+
}));
|
|
25
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
26
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
27
|
+
}) : function(o, v) {
|
|
28
|
+
o["default"] = v;
|
|
29
|
+
});
|
|
30
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
31
|
+
var ownKeys = function(o) {
|
|
32
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
33
|
+
var ar = [];
|
|
34
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
35
|
+
return ar;
|
|
36
|
+
};
|
|
37
|
+
return ownKeys(o);
|
|
38
|
+
};
|
|
39
|
+
return function (mod) {
|
|
40
|
+
if (mod && mod.__esModule) return mod;
|
|
41
|
+
var result = {};
|
|
42
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
43
|
+
__setModuleDefault(result, mod);
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
})();
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
exports.retrieveRelevantMemories = retrieveRelevantMemories;
|
|
49
|
+
exports.appendHotTurn = appendHotTurn;
|
|
50
|
+
exports._clearHotBufferForTesting = _clearHotBufferForTesting;
|
|
51
|
+
exports.retrieveThreeTier = retrieveThreeTier;
|
|
52
|
+
const fs = __importStar(require("fs"));
|
|
53
|
+
const path = __importStar(require("path"));
|
|
54
|
+
const engine_paths_1 = require("./engine-paths");
|
|
55
|
+
const brainx_embed_1 = require("./brainx-embed");
|
|
56
|
+
// ── Config ────────────────────────────────────────────────────────────────────
|
|
57
|
+
const ASMR_LOG_DIR = path.join((0, engine_paths_1.resolveEnginePaths)().root, 'logs', 'asmr');
|
|
58
|
+
const MAX_ENTRIES = 500;
|
|
59
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
60
|
+
/**
|
|
61
|
+
* Combine all six ASMR vectors + supporting fields into a single text string
|
|
62
|
+
* for embedding. This text represents the semantic fingerprint of the entry.
|
|
63
|
+
*/
|
|
64
|
+
function vectorText(e) {
|
|
65
|
+
const { topic, emotion, stance, entities, temporality, intent } = e.vectors;
|
|
66
|
+
return [
|
|
67
|
+
topic,
|
|
68
|
+
emotion,
|
|
69
|
+
stance,
|
|
70
|
+
entities.join(' '),
|
|
71
|
+
temporality,
|
|
72
|
+
intent,
|
|
73
|
+
e.actionSummary,
|
|
74
|
+
e.agentId,
|
|
75
|
+
e.sprintId,
|
|
76
|
+
]
|
|
77
|
+
.filter(Boolean)
|
|
78
|
+
.join(' ');
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Load ASMR entries from all daily JSONL files.
|
|
82
|
+
* Returns newest entries first, capped at MAX_ENTRIES.
|
|
83
|
+
*/
|
|
84
|
+
async function loadAllEntries() {
|
|
85
|
+
const entries = [];
|
|
86
|
+
if (!fs.existsSync(ASMR_LOG_DIR))
|
|
87
|
+
return entries;
|
|
88
|
+
const files = fs
|
|
89
|
+
.readdirSync(ASMR_LOG_DIR)
|
|
90
|
+
.filter(f => f.endsWith('.jsonl'))
|
|
91
|
+
.sort()
|
|
92
|
+
.reverse(); // newest date file first
|
|
93
|
+
for (const file of files) {
|
|
94
|
+
if (entries.length >= MAX_ENTRIES)
|
|
95
|
+
break;
|
|
96
|
+
const filePath = path.join(ASMR_LOG_DIR, file);
|
|
97
|
+
let lines;
|
|
98
|
+
try {
|
|
99
|
+
lines = fs.readFileSync(filePath, 'utf8').split('\n').filter(Boolean);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
// Read lines in reverse order so newest entries within the day come first
|
|
105
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
106
|
+
if (entries.length >= MAX_ENTRIES)
|
|
107
|
+
break;
|
|
108
|
+
try {
|
|
109
|
+
entries.push(JSON.parse(lines[i]));
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// Skip malformed JSONL line
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return entries;
|
|
117
|
+
}
|
|
118
|
+
// ── Public API ────────────────────────────────────────────────────────────────
|
|
119
|
+
/**
|
|
120
|
+
* Retrieve the most relevant episodic memory entries for a given query.
|
|
121
|
+
*
|
|
122
|
+
* Uses cosine similarity over nomic-embed-text embeddings of the six-vector text.
|
|
123
|
+
* Sequential embedding (not parallel) to avoid Ollama overload.
|
|
124
|
+
*
|
|
125
|
+
* @param query Natural-language retrieval query
|
|
126
|
+
* @param limit Maximum number of results to return (default: 5)
|
|
127
|
+
* @returns Array of ASMREntry sorted by descending similarity
|
|
128
|
+
*/
|
|
129
|
+
async function retrieveRelevantMemories(query, limit = 5) {
|
|
130
|
+
const entries = await loadAllEntries();
|
|
131
|
+
if (entries.length === 0)
|
|
132
|
+
return [];
|
|
133
|
+
const queryVec = await (0, brainx_embed_1.embed)(query);
|
|
134
|
+
const ranked = [];
|
|
135
|
+
// Sequential embedding — avoids saturating Ollama with parallel requests
|
|
136
|
+
for (const entry of entries) {
|
|
137
|
+
try {
|
|
138
|
+
const entryVec = await (0, brainx_embed_1.embed)(vectorText(entry));
|
|
139
|
+
const similarity = (0, brainx_embed_1.cosineSimilarity)(queryVec, entryVec);
|
|
140
|
+
ranked.push({ ...entry, similarity });
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// Skip entries that fail to embed
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
ranked.sort((a, b) => b.similarity - a.similarity);
|
|
147
|
+
return ranked.slice(0, limit).map(({ similarity: _s, ...rest }) => rest);
|
|
148
|
+
}
|
|
149
|
+
// ── AMD-21 P3: Three-Tier Retrieval (Sprint 1537) ────────────────────────────
|
|
150
|
+
//
|
|
151
|
+
// Hot: in-memory ring buffer, recent N turns per agent (O(1) read)
|
|
152
|
+
// Warm: BrainXClient.retrieve() — pgvector cosine search over recent embeds
|
|
153
|
+
// Cold: lazy scan of workspace/asmr/<agentId>/*.jsonl when hot+warm <maxResults
|
|
154
|
+
//
|
|
155
|
+
// Pipeline: hot → warm → (cold lazy) → dedupe → rank → cap at maxResults.
|
|
156
|
+
// All three tiers fail-open (errors return empty hits, never throw).
|
|
157
|
+
const crypto_1 = require("crypto");
|
|
158
|
+
const brainx_client_1 = require("./brainx-client");
|
|
159
|
+
// ── Hot-tier ring buffer ─────────────────────────────────────────────────────
|
|
160
|
+
const HOT_BUFFER = new Map();
|
|
161
|
+
const HOT_DEFAULT_CAPACITY = 20;
|
|
162
|
+
/** Append a turn to the hot buffer. Caller-driven population. */
|
|
163
|
+
function appendHotTurn(agentId, content, score = 1.0, capacity = HOT_DEFAULT_CAPACITY) {
|
|
164
|
+
const hit = {
|
|
165
|
+
source: 'hot',
|
|
166
|
+
content,
|
|
167
|
+
score,
|
|
168
|
+
ts: new Date().toISOString(),
|
|
169
|
+
provenance: `hot:turn:${agentId}`,
|
|
170
|
+
};
|
|
171
|
+
const buf = HOT_BUFFER.get(agentId) ?? [];
|
|
172
|
+
buf.push(hit);
|
|
173
|
+
while (buf.length > capacity)
|
|
174
|
+
buf.shift();
|
|
175
|
+
HOT_BUFFER.set(agentId, buf);
|
|
176
|
+
}
|
|
177
|
+
/** Test helper — clear the buffer (single agent or all). */
|
|
178
|
+
function _clearHotBufferForTesting(agentId) {
|
|
179
|
+
if (agentId)
|
|
180
|
+
HOT_BUFFER.delete(agentId);
|
|
181
|
+
else
|
|
182
|
+
HOT_BUFFER.clear();
|
|
183
|
+
}
|
|
184
|
+
function readHotTier(agentId, hotTurns) {
|
|
185
|
+
const buf = HOT_BUFFER.get(agentId) ?? [];
|
|
186
|
+
// Newest last — return last N reversed so newest first.
|
|
187
|
+
return buf.slice(-hotTurns).reverse();
|
|
188
|
+
}
|
|
189
|
+
// ── Warm-tier ────────────────────────────────────────────────────────────────
|
|
190
|
+
async function readWarmTier(query, agentId, warmDays, topK, client) {
|
|
191
|
+
try {
|
|
192
|
+
const sinceMs = Date.now() - warmDays * 24 * 60 * 60 * 1000;
|
|
193
|
+
const memories = await client.retrieve(query, { topK, tiers: ['HOT', 'WARM'] });
|
|
194
|
+
return memories
|
|
195
|
+
.filter((m) => {
|
|
196
|
+
// BrainXMemory rows expose a created_at-style field; tolerate either name.
|
|
197
|
+
const ts = m.createdAt ?? m.created_at ?? null;
|
|
198
|
+
if (!ts)
|
|
199
|
+
return true; // can't filter — keep
|
|
200
|
+
return new Date(ts).getTime() >= sinceMs;
|
|
201
|
+
})
|
|
202
|
+
.map((m) => ({
|
|
203
|
+
source: 'warm',
|
|
204
|
+
content: m.content ?? m.text ?? JSON.stringify(m),
|
|
205
|
+
score: typeof m.similarity === 'number' ? m.similarity : 0.5,
|
|
206
|
+
ts: m.createdAt ?? m.created_at ?? new Date().toISOString(),
|
|
207
|
+
provenance: `warm:brainx:${m.id ?? agentId}`,
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// ── Cold-tier (lazy archive scan) ────────────────────────────────────────────
|
|
215
|
+
async function readColdTier(agentId, coldRoot, thresholdDays, budget) {
|
|
216
|
+
if (budget <= 0)
|
|
217
|
+
return [];
|
|
218
|
+
const dir = path.join(coldRoot, agentId);
|
|
219
|
+
if (!fs.existsSync(dir))
|
|
220
|
+
return [];
|
|
221
|
+
const cutoff = Date.now() - thresholdDays * 24 * 60 * 60 * 1000;
|
|
222
|
+
const out = [];
|
|
223
|
+
try {
|
|
224
|
+
const files = fs.readdirSync(dir).filter(f => f.endsWith('.jsonl')).sort().reverse();
|
|
225
|
+
for (const file of files) {
|
|
226
|
+
if (out.length >= budget)
|
|
227
|
+
break;
|
|
228
|
+
const fp = path.join(dir, file);
|
|
229
|
+
const stat = fs.statSync(fp);
|
|
230
|
+
if (stat.mtimeMs > cutoff)
|
|
231
|
+
continue; // file too recent → not cold
|
|
232
|
+
const lines = fs.readFileSync(fp, 'utf8').split('\n').filter(Boolean);
|
|
233
|
+
for (let i = lines.length - 1; i >= 0 && out.length < budget; i--) {
|
|
234
|
+
try {
|
|
235
|
+
const obj = JSON.parse(lines[i]);
|
|
236
|
+
out.push({
|
|
237
|
+
source: 'cold',
|
|
238
|
+
content: obj.content ?? obj.text ?? obj.actionSummary ?? JSON.stringify(obj),
|
|
239
|
+
score: typeof obj.score === 'number' ? obj.score : 0.3,
|
|
240
|
+
ts: obj.ts ?? obj.timestamp ?? stat.mtime.toISOString(),
|
|
241
|
+
provenance: `cold:archive:${file}`,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
catch { /* skip malformed line */ }
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return out;
|
|
250
|
+
}
|
|
251
|
+
return out;
|
|
252
|
+
}
|
|
253
|
+
// ── Merge + dedupe ───────────────────────────────────────────────────────────
|
|
254
|
+
function contentHash(s) {
|
|
255
|
+
return (0, crypto_1.createHash)('sha256').update(s).digest('hex').slice(0, 16);
|
|
256
|
+
}
|
|
257
|
+
function mergeAndDedupe(hot, warm, cold, cap) {
|
|
258
|
+
// Tier priority: hot > warm > cold. Same content from a higher tier wins.
|
|
259
|
+
const seen = new Map();
|
|
260
|
+
for (const h of [...hot, ...warm, ...cold]) {
|
|
261
|
+
const key = contentHash(h.content.trim());
|
|
262
|
+
if (!seen.has(key))
|
|
263
|
+
seen.set(key, h);
|
|
264
|
+
}
|
|
265
|
+
return Array.from(seen.values())
|
|
266
|
+
.sort((a, b) => b.score - a.score)
|
|
267
|
+
.slice(0, cap);
|
|
268
|
+
}
|
|
269
|
+
// ── Public API ───────────────────────────────────────────────────────────────
|
|
270
|
+
/**
|
|
271
|
+
* Three-tier retrieval over hot in-memory turns + warm BrainX vectors +
|
|
272
|
+
* cold archive scan. Returns ranked, deduped results with provenance tags
|
|
273
|
+
* and a per-tier latency breakdown.
|
|
274
|
+
*
|
|
275
|
+
* Cold tier only fires when hot+warm don't hit `maxResults` — keeps the
|
|
276
|
+
* common path fast.
|
|
277
|
+
*
|
|
278
|
+
* Never throws. All three tiers fail-open to empty hits on error.
|
|
279
|
+
*/
|
|
280
|
+
async function retrieveThreeTier(query, agentId, opts = {}) {
|
|
281
|
+
const hotTurns = opts.hotTurns ?? HOT_DEFAULT_CAPACITY;
|
|
282
|
+
const warmDays = opts.warmDays ?? 7;
|
|
283
|
+
const maxResults = opts.maxResults ?? 12;
|
|
284
|
+
const coldThresholdDays = opts.coldThresholdDays ?? 7;
|
|
285
|
+
const coldRoot = opts.coldRoot ?? path.join((0, engine_paths_1.resolveEnginePaths)().root, 'workspace', 'asmr');
|
|
286
|
+
const skipCold = opts.skipCold ?? false;
|
|
287
|
+
const brainx = opts.brainx ?? new brainx_client_1.BrainXClient(agentId);
|
|
288
|
+
const t0 = Date.now();
|
|
289
|
+
const tHotStart = Date.now();
|
|
290
|
+
const hot = readHotTier(agentId, hotTurns);
|
|
291
|
+
const hotMs = Date.now() - tHotStart;
|
|
292
|
+
const tWarmStart = Date.now();
|
|
293
|
+
const warm = await readWarmTier(query, agentId, warmDays, maxResults, brainx);
|
|
294
|
+
const warmMs = Date.now() - tWarmStart;
|
|
295
|
+
let cold = [];
|
|
296
|
+
let coldMs = 0;
|
|
297
|
+
const haveSoFar = hot.length + warm.length;
|
|
298
|
+
if (!skipCold && haveSoFar < maxResults) {
|
|
299
|
+
const tColdStart = Date.now();
|
|
300
|
+
cold = await readColdTier(agentId, coldRoot, coldThresholdDays, maxResults - haveSoFar);
|
|
301
|
+
coldMs = Date.now() - tColdStart;
|
|
302
|
+
}
|
|
303
|
+
const merged = mergeAndDedupe(hot, warm, cold, maxResults);
|
|
304
|
+
return {
|
|
305
|
+
hot,
|
|
306
|
+
warm,
|
|
307
|
+
cold,
|
|
308
|
+
merged,
|
|
309
|
+
latency_ms: { hot: hotMs, warm: warmMs, cold: coldMs, total: Date.now() - t0 },
|
|
310
|
+
};
|
|
311
|
+
}
|