@joshuaswarren/openclaw-engram 9.1.13 → 9.1.15

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.
Files changed (28) hide show
  1. package/dist/access-cli.js +4 -4
  2. package/dist/{calibration-QBVYTFTQ.js → calibration-JLSDBR4C.js} +6 -6
  3. package/dist/calibration-JLSDBR4C.js.map +1 -0
  4. package/dist/{causal-consolidation-T7EEYKPA.js → causal-consolidation-HCZHJO2J.js} +8 -8
  5. package/dist/causal-consolidation-HCZHJO2J.js.map +1 -0
  6. package/dist/{chunk-XCAYYSI7.js → chunk-5YCBFXXX.js} +31 -10
  7. package/dist/chunk-5YCBFXXX.js.map +1 -0
  8. package/dist/{chunk-4PXEFPHR.js → chunk-MQ5EZ6VE.js} +7 -5
  9. package/dist/chunk-MQ5EZ6VE.js.map +1 -0
  10. package/dist/{chunk-4XBRUH6C.js → chunk-TS3YC3MY.js} +1 -1
  11. package/dist/chunk-TS3YC3MY.js.map +1 -0
  12. package/dist/{chunk-7L2Q2HQB.js → chunk-W7435EXA.js} +141 -48
  13. package/dist/chunk-W7435EXA.js.map +1 -0
  14. package/dist/{engine-KBL7KFAQ.js → engine-SBLMS64G.js} +3 -3
  15. package/dist/{fallback-llm-KV6HAJ2N.js → fallback-llm-HZFQ7ADY.js} +2 -2
  16. package/dist/index.js +7 -7
  17. package/dist/{storage-LPSULUCO.js → storage-BIOBCHNN.js} +2 -2
  18. package/openclaw.plugin.json +32 -0
  19. package/package.json +1 -1
  20. package/dist/calibration-QBVYTFTQ.js.map +0 -1
  21. package/dist/causal-consolidation-T7EEYKPA.js.map +0 -1
  22. package/dist/chunk-4PXEFPHR.js.map +0 -1
  23. package/dist/chunk-4XBRUH6C.js.map +0 -1
  24. package/dist/chunk-7L2Q2HQB.js.map +0 -1
  25. package/dist/chunk-XCAYYSI7.js.map +0 -1
  26. /package/dist/{engine-KBL7KFAQ.js.map → engine-SBLMS64G.js.map} +0 -0
  27. /package/dist/{fallback-llm-KV6HAJ2N.js.map → fallback-llm-HZFQ7ADY.js.map} +0 -0
  28. /package/dist/{storage-LPSULUCO.js.map → storage-BIOBCHNN.js.map} +0 -0
@@ -3,12 +3,12 @@ import {
3
3
  EngramAccessService,
4
4
  Orchestrator,
5
5
  parseConfig
6
- } from "./chunk-7L2Q2HQB.js";
7
- import "./chunk-4PXEFPHR.js";
6
+ } from "./chunk-W7435EXA.js";
7
+ import "./chunk-MQ5EZ6VE.js";
8
8
  import "./chunk-IMMYYNXG.js";
9
- import "./chunk-4XBRUH6C.js";
9
+ import "./chunk-TS3YC3MY.js";
10
10
  import "./chunk-6KX4XLQJ.js";
11
- import "./chunk-XCAYYSI7.js";
11
+ import "./chunk-5YCBFXXX.js";
12
12
  import "./chunk-MKM2BCQH.js";
13
13
  import "./chunk-DEIBZP3O.js";
14
14
  import "./chunk-SSIIJJKA.js";
@@ -1,7 +1,7 @@
1
1
  // openclaw-engram: Local-first memory plugin
2
2
  import {
3
3
  FallbackLlmClient
4
- } from "./chunk-XCAYYSI7.js";
4
+ } from "./chunk-5YCBFXXX.js";
5
5
  import {
6
6
  listJsonFiles
7
7
  } from "./chunk-DEIBZP3O.js";
@@ -109,7 +109,7 @@ Output valid JSON only:
109
109
  }
110
110
  ]
111
111
  }`;
112
- async function synthesizeCalibrationRules(corrections, llm, existingRules) {
112
+ async function synthesizeCalibrationRules(corrections, llm, existingRules, agentId) {
113
113
  if (corrections.length < 2) return [];
114
114
  const correctionText = corrections.slice(0, 50).map((c, i) => `[${c.id}] ${c.content}`).join("\n\n");
115
115
  const existingRulesText = existingRules.length > 0 ? `
@@ -123,7 +123,7 @@ ${existingRules.map((r) => `- ${r.condition}: ${r.calibration}`).join("\n")}` :
123
123
 
124
124
  ${correctionText}${existingRulesText}` }
125
125
  ],
126
- { temperature: 0.3, maxTokens: 3e3 }
126
+ { temperature: 0.3, maxTokens: 3e3, agentId }
127
127
  );
128
128
  if (!response?.content) return [];
129
129
  try {
@@ -173,7 +173,7 @@ function buildCalibrationRecallSection(rules, query, maxChars = 1200) {
173
173
  async function runCalibrationConsolidation(options) {
174
174
  try {
175
175
  const llm = new FallbackLlmClient(options.gatewayConfig);
176
- if (!llm.isAvailable()) {
176
+ if (!llm.isAvailable(options.gatewayAgentId)) {
177
177
  log.debug("[calibration] no LLM available \u2014 skipping consolidation");
178
178
  return [];
179
179
  }
@@ -183,7 +183,7 @@ async function runCalibrationConsolidation(options) {
183
183
  return [];
184
184
  }
185
185
  const existingIndex = await readCalibrationIndex(options.memoryDir);
186
- const newRules = await synthesizeCalibrationRules(corrections, llm, existingIndex.rules);
186
+ const newRules = await synthesizeCalibrationRules(corrections, llm, existingIndex.rules, options.gatewayAgentId);
187
187
  if (newRules.length === 0) {
188
188
  log.debug("[calibration] no new calibration rules synthesized");
189
189
  return existingIndex.rules;
@@ -233,4 +233,4 @@ export {
233
233
  runCalibrationIfEnabled,
234
234
  synthesizeCalibrationRules
235
235
  };
236
- //# sourceMappingURL=calibration-QBVYTFTQ.js.map
236
+ //# sourceMappingURL=calibration-JLSDBR4C.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/calibration.ts"],"sourcesContent":["/**\n * calibration.ts — Prediction-Error-Driven Model-User Calibration\n *\n * Analyzes patterns in user corrections to identify systematic miscalibration\n * between the model's predictions and the user's actual expectations.\n * During consolidation, replays chains of similar corrections through an LLM\n * to synthesize CalibrationRules that adjust model behavior for this specific user.\n *\n * Inspired by:\n * - Cerebellar motor calibration (prediction errors drive lasting adjustments)\n * - Temporal difference learning (dopamine signals prediction error)\n * - Tesla FSD shadow mode (divergence between prediction and reality = training signal)\n */\n\nimport { createHash } from \"node:crypto\";\nimport path from \"node:path\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { FallbackLlmClient } from \"./fallback-llm.js\";\nimport type { GatewayConfig, MemoryFile } from \"./types.js\";\nimport { listJsonFiles, readJsonFile } from \"./json-store.js\";\nimport { isRecord } from \"./store-contract.js\";\nimport { log } from \"./logger.js\";\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface CalibrationRule {\n id: string;\n ruleType: \"model_tendency\" | \"user_expectation\" | \"scope_boundary\" | \"verification_required\";\n condition: string;\n modelTendency: string;\n userExpectation: string;\n calibration: string;\n confidence: number;\n evidenceCount: number;\n evidenceCorrectionIds: string[];\n createdAt: string;\n lastReinforcedAt: string;\n}\n\nexport interface CalibrationIndex {\n rules: CalibrationRule[];\n updatedAt: string;\n totalCorrectionsAnalyzed: number;\n}\n\n// ─── Storage ─────────────────────────────────────────────────────────────────\n\nfunction calibrationDir(memoryDir: string): string {\n return path.join(memoryDir, \"state\", \"calibration\");\n}\n\nfunction calibrationIndexPath(memoryDir: string): string {\n return path.join(calibrationDir(memoryDir), \"calibration-index.json\");\n}\n\nexport async function readCalibrationIndex(memoryDir: string): Promise<CalibrationIndex> {\n try {\n const raw = JSON.parse(await readFile(calibrationIndexPath(memoryDir), \"utf8\"));\n return {\n rules: Array.isArray(raw.rules) ? raw.rules : [],\n updatedAt: typeof raw.updatedAt === \"string\" ? raw.updatedAt : new Date().toISOString(),\n totalCorrectionsAnalyzed: typeof raw.totalCorrectionsAnalyzed === \"number\" ? raw.totalCorrectionsAnalyzed : 0,\n };\n } catch {\n return { rules: [], updatedAt: new Date().toISOString(), totalCorrectionsAnalyzed: 0 };\n }\n}\n\nasync function writeCalibrationIndex(memoryDir: string, index: CalibrationIndex): Promise<void> {\n const dir = calibrationDir(memoryDir);\n await mkdir(dir, { recursive: true });\n index.updatedAt = new Date().toISOString();\n await writeFile(calibrationIndexPath(memoryDir), JSON.stringify(index, null, 2), \"utf8\");\n}\n\n// ─── Correction Reading ──────────────────────────────────────────────────────\n\ninterface CorrectionMemory {\n id: string;\n content: string;\n created: string;\n confidence: number;\n entityRefs: string[];\n tags: string[];\n}\n\nasync function readCorrections(memoryDir: string): Promise<CorrectionMemory[]> {\n const correctionsDir = path.join(memoryDir, \"corrections\");\n const files = await listJsonFiles(correctionsDir).catch(() => {\n // Corrections might be in facts/ directories too\n return [] as string[];\n });\n\n // Also scan facts directories for correction-category files\n const factsDir = path.join(memoryDir, \"facts\");\n try {\n const { readdir } = await import(\"node:fs/promises\");\n const dayDirs = (await readdir(factsDir)).filter((d: string) => /^\\d{4}-\\d{2}-\\d{2}$/.test(d));\n for (const day of dayDirs) {\n const dayPath = path.join(factsDir, day);\n const dayFiles = (await readdir(dayPath))\n .filter((f: string) => f.startsWith(\"correction-\") && f.endsWith(\".md\"))\n .map((f: string) => path.join(dayPath, f));\n files.push(...dayFiles);\n }\n } catch {\n // facts dir might not exist\n }\n\n // Also check the dedicated corrections directory\n try {\n const { readdir } = await import(\"node:fs/promises\");\n const corrFiles = (await readdir(correctionsDir))\n .filter((f: string) => f.endsWith(\".md\"))\n .map((f: string) => path.join(correctionsDir, f));\n files.push(...corrFiles);\n } catch {\n // corrections dir might not exist\n }\n\n const corrections: CorrectionMemory[] = [];\n const seen = new Set<string>();\n\n for (const filePath of files) {\n try {\n const raw = await readFile(filePath, \"utf8\");\n\n // Parse frontmatter\n const fmMatch = raw.match(/^---\\n([\\s\\S]*?)\\n---\\n([\\s\\S]*)$/);\n if (!fmMatch) continue;\n\n const content = fmMatch[2].trim();\n if (!content || content.length < 10) continue;\n\n // Extract id from frontmatter\n const idMatch = fmMatch[1].match(/^id:\\s*(.+)$/m);\n const id = idMatch?.[1]?.trim() ?? path.basename(filePath, \".md\");\n\n if (seen.has(id)) continue;\n seen.add(id);\n\n const confMatch = fmMatch[1].match(/^confidence:\\s*(.+)$/m);\n const confidence = confMatch ? parseFloat(confMatch[1]) : 0.9;\n\n const entityMatch = fmMatch[1].match(/^entityRef:\\s*(.+)$/m);\n const entityRefs = entityMatch ? [entityMatch[1].trim()] : [];\n\n corrections.push({ id, content, created: \"\", confidence, entityRefs, tags: [] });\n } catch {\n // skip unparseable files\n }\n }\n\n return corrections;\n}\n\n// ─── LLM-Assisted Clustering and Replay ──────────────────────────────────────\n\nconst CLUSTER_PROMPT = `You are analyzing user corrections to an AI assistant. Each correction represents a moment where the assistant's prediction of what the user wanted was WRONG.\n\nYour job: Group these corrections into clusters where the SAME TYPE of misunderstanding is happening. Then for each cluster, synthesize a CalibrationRule.\n\nA CalibrationRule describes:\n- condition: When does this type of mistake happen?\n- modelTendency: What does the model tend to assume or do wrong?\n- userExpectation: What does the user actually want instead?\n- calibration: How should the model adjust its behavior?\n- ruleType: One of \"model_tendency\", \"user_expectation\", \"scope_boundary\", \"verification_required\"\n\nFocus on PATTERNS, not individual corrections. A cluster needs at least 2 corrections to be worth a rule.\n\nOutput valid JSON only:\n{\n \"rules\": [\n {\n \"ruleType\": \"model_tendency\",\n \"condition\": \"When discussing project scope or task boundaries\",\n \"modelTendency\": \"The model tends to assume broader scope than the user intends\",\n \"userExpectation\": \"The user prefers narrow, specific task definitions and wants to be asked before scope expansion\",\n \"calibration\": \"When uncertain about scope, ask for clarification rather than assuming. Default to the narrower interpretation.\",\n \"confidence\": 0.85,\n \"evidenceIds\": [\"correction-id-1\", \"correction-id-2\"]\n }\n ]\n}`;\n\nexport async function synthesizeCalibrationRules(\n corrections: CorrectionMemory[],\n llm: FallbackLlmClient,\n existingRules: CalibrationRule[],\n agentId?: string,\n): Promise<CalibrationRule[]> {\n if (corrections.length < 2) return [];\n\n // Format corrections for the LLM\n const correctionText = corrections\n .slice(0, 50) // limit to avoid huge prompts\n .map((c, i) => `[${c.id}] ${c.content}`)\n .join(\"\\n\\n\");\n\n const existingRulesText = existingRules.length > 0\n ? `\\n\\nExisting calibration rules (avoid duplicating these):\\n${existingRules.map((r) => `- ${r.condition}: ${r.calibration}`).join(\"\\n\")}`\n : \"\";\n\n const response = await llm.chatCompletion(\n [\n { role: \"system\", content: CLUSTER_PROMPT },\n { role: \"user\", content: `Here are ${corrections.length} corrections from this user:\\n\\n${correctionText}${existingRulesText}` },\n ],\n { temperature: 0.3, maxTokens: 3000, agentId },\n );\n\n if (!response?.content) return [];\n\n try {\n let jsonStr = response.content.trim();\n const fenceMatch = jsonStr.match(/```(?:json)?\\s*\\n?([\\s\\S]*?)\\n?\\s*```/);\n if (fenceMatch) jsonStr = fenceMatch[1];\n\n const parsed = JSON.parse(jsonStr);\n if (!Array.isArray(parsed.rules)) return [];\n\n const now = new Date().toISOString();\n return parsed.rules\n .filter((r: any) => r.condition && r.calibration && r.modelTendency)\n .map((r: any) => ({\n id: `cal-${createHash(\"sha256\").update(r.condition + r.calibration).digest(\"hex\").slice(0, 12)}`,\n ruleType: r.ruleType ?? \"model_tendency\",\n condition: String(r.condition),\n modelTendency: String(r.modelTendency),\n userExpectation: String(r.userExpectation ?? \"\"),\n calibration: String(r.calibration),\n confidence: typeof r.confidence === \"number\" ? r.confidence : 0.7,\n evidenceCount: Array.isArray(r.evidenceIds) ? r.evidenceIds.length : 1,\n evidenceCorrectionIds: Array.isArray(r.evidenceIds) ? r.evidenceIds : [],\n createdAt: now,\n lastReinforcedAt: now,\n }));\n } catch {\n log.warn(\"[calibration] failed to parse LLM response\");\n return [];\n }\n}\n\n// ─── Recall Section ──────────────────────────────────────────────────────────\n\n/**\n * Build a recall section from calibration rules relevant to the current query.\n * Uses the LLM to select which rules apply to the current context.\n */\nexport function buildCalibrationRecallSection(\n rules: CalibrationRule[],\n query: string,\n maxChars: number = 1200,\n): string | null {\n if (rules.length === 0) return null;\n\n // Simple relevance: include all rules (they're already filtered to this user)\n // In production, could use embedding similarity to filter\n const lines: string[] = [\n \"## Model Calibration (learned from past corrections)\",\n \"\",\n \"Adjustments for this specific user, learned from patterns in their corrections:\",\n \"\",\n ];\n\n let totalChars = lines.join(\"\\n\").length;\n\n for (const rule of rules) {\n const line = `- **${rule.condition}**: ${rule.modelTendency} → Instead: ${rule.calibration}`;\n if (totalChars + line.length + 1 > maxChars) break;\n lines.push(line);\n totalChars += line.length + 1;\n }\n\n if (lines.length <= 4) return null;\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────────\n\n/**\n * Run the full calibration pipeline:\n * 1. Read all corrections\n * 2. Send to LLM for clustering and rule synthesis\n * 3. Merge with existing rules\n * 4. Write updated index\n */\nexport async function runCalibrationConsolidation(options: {\n memoryDir: string;\n gatewayConfig?: GatewayConfig;\n gatewayAgentId?: string;\n}): Promise<CalibrationRule[]> {\n try {\n const llm = new FallbackLlmClient(options.gatewayConfig);\n if (!llm.isAvailable(options.gatewayAgentId)) {\n log.debug(\"[calibration] no LLM available — skipping consolidation\");\n return [];\n }\n\n const corrections = await readCorrections(options.memoryDir);\n if (corrections.length < 3) {\n log.debug(`[calibration] only ${corrections.length} corrections — need at least 3`);\n return [];\n }\n\n const existingIndex = await readCalibrationIndex(options.memoryDir);\n\n const newRules = await synthesizeCalibrationRules(corrections, llm, existingIndex.rules, options.gatewayAgentId);\n if (newRules.length === 0) {\n log.debug(\"[calibration] no new calibration rules synthesized\");\n return existingIndex.rules;\n }\n\n // Merge: keep existing rules, add new ones (deduplicate by id)\n const ruleMap = new Map(existingIndex.rules.map((r) => [r.id, r]));\n for (const rule of newRules) {\n if (ruleMap.has(rule.id)) {\n // Reinforce existing rule\n const existing = ruleMap.get(rule.id)!;\n existing.lastReinforcedAt = new Date().toISOString();\n existing.evidenceCount += rule.evidenceCount;\n existing.confidence = Math.min(1, existing.confidence + 0.05);\n } else {\n ruleMap.set(rule.id, rule);\n }\n }\n\n const allRules = [...ruleMap.values()];\n await writeCalibrationIndex(options.memoryDir, {\n rules: allRules,\n updatedAt: new Date().toISOString(),\n totalCorrectionsAnalyzed: corrections.length,\n });\n\n log.debug(`[calibration] synthesized ${newRules.length} new rule(s), ${allRules.length} total`);\n return allRules;\n } catch (error) {\n log.warn(`[calibration] consolidation failed (non-fatal): ${error instanceof Error ? error.message : String(error)}`);\n return [];\n }\n}\n\n/**\n * Standalone entry point for calibration consolidation that can be called\n * independently of weekly compounding. The compounding engine's\n * `synthesizeWeekly()` is one trigger, but orchestrators or periodic\n * maintenance jobs should call this directly so calibration is not gated\n * on weekly compounding being enabled.\n */\nexport async function runCalibrationIfEnabled(options: {\n memoryDir: string;\n calibrationEnabled: boolean;\n gatewayConfig?: GatewayConfig;\n}): Promise<CalibrationRule[]> {\n if (!options.calibrationEnabled) {\n return [];\n }\n return runCalibrationConsolidation({\n memoryDir: options.memoryDir,\n gatewayConfig: options.gatewayConfig,\n });\n}\n\n/**\n * Get calibration rules for recall injection.\n * Reads the pre-computed calibration index.\n */\nexport async function getCalibrationRulesForRecall(\n memoryDir: string,\n): Promise<CalibrationRule[]> {\n const index = await readCalibrationIndex(memoryDir);\n return index.rules;\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  // openclaw-engram: Local-first memory plugin
2
2
  import {
3
3
  FallbackLlmClient
4
- } from "./chunk-XCAYYSI7.js";
4
+ } from "./chunk-5YCBFXXX.js";
5
5
  import {
6
6
  readChainIndex,
7
7
  resolveChainsDir
@@ -100,13 +100,13 @@ Output valid JSON only:
100
100
  }
101
101
 
102
102
  If no clear patterns exist, return {"rules": [], "preferences": []}.`;
103
- async function consolidateWithLlm(context, llm) {
103
+ async function consolidateWithLlm(context, llm, agentId) {
104
104
  const response = await llm.chatCompletion(
105
105
  [
106
106
  { role: "system", content: CONSOLIDATION_PROMPT },
107
107
  { role: "user", content: context }
108
108
  ],
109
- { temperature: 0.2, maxTokens: 2e3 }
109
+ { temperature: 0.2, maxTokens: 2e3, agentId }
110
110
  );
111
111
  if (!response?.content) {
112
112
  return { rules: [], preferences: [] };
@@ -161,11 +161,11 @@ async function deriveCausalPromotionCandidates(options) {
161
161
  const chainIndex = await readChainIndex(chainsDir);
162
162
  const context = formatCausalContext(trajectories, chainIndex);
163
163
  const llm = new FallbackLlmClient(options.gatewayConfig);
164
- if (!llm.isAvailable()) {
164
+ if (!llm.isAvailable(options.gatewayAgentId)) {
165
165
  log.debug("[cmc] no LLM available for consolidation \u2014 skipping");
166
166
  return [];
167
167
  }
168
- const result = await consolidateWithLlm(context, llm);
168
+ const result = await consolidateWithLlm(context, llm, options.gatewayAgentId);
169
169
  const candidates = llmResultToCandidates(result);
170
170
  log.debug(`[cmc] LLM consolidation produced ${candidates.length} rule(s) and ${result.preferences.length} preference(s)`);
171
171
  return candidates;
@@ -182,8 +182,8 @@ async function synthesizeCausalPreferencesViaLlm(options) {
182
182
  const chainIndex = await readChainIndex(chainsDir);
183
183
  const context = formatCausalContext(trajectories, chainIndex);
184
184
  const llm = new FallbackLlmClient(options.gatewayConfig);
185
- if (!llm.isAvailable()) return null;
186
- const result = await consolidateWithLlm(context, llm);
185
+ if (!llm.isAvailable(options.gatewayAgentId)) return null;
186
+ const result = await consolidateWithLlm(context, llm, options.gatewayAgentId);
187
187
  if (result.preferences.length === 0 && result.rules.length === 0) return null;
188
188
  const lines = ["## Behavioral Insights (from Causal Chain Analysis)", ""];
189
189
  for (const pref of result.preferences) {
@@ -203,4 +203,4 @@ export {
203
203
  deriveCausalPromotionCandidates,
204
204
  synthesizeCausalPreferencesViaLlm
205
205
  };
206
- //# sourceMappingURL=causal-consolidation-T7EEYKPA.js.map
206
+ //# sourceMappingURL=causal-consolidation-HCZHJO2J.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/causal-consolidation.ts"],"sourcesContent":["/**\n * causal-consolidation.ts — CMC Phase 2: LLM-Assisted Causal Consolidation\n *\n * Uses an LLM to analyze causal trajectory patterns across sessions.\n * The LLM receives the causal chain graph as context — connected trajectories\n * from different sessions — and identifies recurring behavioral patterns,\n * preference signals, and actionable rules.\n *\n * This is the core CMC innovation: the LLM gets cross-session causal context\n * that no other memory system provides. It can see that a user investigated\n * a bug in session 1, attempted a fix in session 2, and succeeded in session 3 —\n * and synthesize a rule or preference from that chain.\n */\n\nimport { createHash } from \"node:crypto\";\nimport type { CausalTrajectoryRecord } from \"./causal-trajectory.js\";\nimport { readChainIndex, resolveChainsDir, type CausalChainIndex, type CausalEdge } from \"./causal-chain.js\";\nimport { listJsonFiles, readJsonFile } from \"./json-store.js\";\nimport { isRecord } from \"./store-contract.js\";\nimport { FallbackLlmClient } from \"./fallback-llm.js\";\nimport type { GatewayConfig } from \"./types.js\";\nimport path from \"node:path\";\nimport { log } from \"./logger.js\";\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface CausalPatternCandidate {\n id: string;\n sourceType: \"causal-pattern\";\n subject: string;\n category: \"principle\" | \"rule\";\n content: string;\n score: number;\n rationale: string;\n outcome: null;\n provenance: string[];\n agent: string | null;\n workflow: string | null;\n}\n\nexport interface ConsolidationConfig {\n minRecurrence: number;\n minSessions: number;\n successThreshold: number;\n}\n\nexport interface LlmConsolidationResult {\n rules: Array<{\n content: string;\n category: \"rule\" | \"principle\" | \"preference\";\n confidence: number;\n evidence: string[];\n }>;\n preferences: Array<{\n statement: string;\n confidence: number;\n evidence: string[];\n }>;\n}\n\n// ─── Trajectory Reading ──────────────────────────────────────────────────────\n\nasync function readAllTrajectories(\n memoryDir: string,\n causalTrajectoryStoreDir?: string,\n): Promise<CausalTrajectoryRecord[]> {\n const root = causalTrajectoryStoreDir\n ? path.join(memoryDir, causalTrajectoryStoreDir)\n : path.join(memoryDir, \"state\", \"causal-trajectories\");\n const trajectoriesDir = path.join(root, \"trajectories\");\n\n const files = await listJsonFiles(trajectoriesDir).catch(() => [] as string[]);\n const results: CausalTrajectoryRecord[] = [];\n\n for (const filePath of files) {\n try {\n const raw = await readJsonFile(filePath);\n if (isRecord(raw) && typeof raw.trajectoryId === \"string\") {\n results.push(raw as unknown as CausalTrajectoryRecord);\n }\n } catch {\n // skip invalid\n }\n }\n\n return results;\n}\n\n// ─── Context Formatting ──────────────────────────────────────────────────────\n\n/**\n * Format trajectories and their causal connections as a readable context\n * for the LLM. Groups by session and shows chain connections.\n */\nfunction formatCausalContext(\n trajectories: CausalTrajectoryRecord[],\n chainIndex: CausalChainIndex,\n maxChars: number = 8000,\n): string {\n // Group trajectories by session\n const bySession = new Map<string, CausalTrajectoryRecord[]>();\n for (const t of trajectories) {\n const list = bySession.get(t.sessionKey) ?? [];\n list.push(t);\n bySession.set(t.sessionKey, list);\n }\n\n const lines: string[] = [];\n lines.push(`## Causal Trajectories (${trajectories.length} across ${bySession.size} sessions)`);\n lines.push(\"\");\n\n // Format each session's trajectories\n for (const [sessionKey, sessionTrajs] of bySession) {\n lines.push(`### Session: ${sessionKey}`);\n for (const t of sessionTrajs.slice(0, 5)) {\n const outcome = t.outcomeKind === \"success\" ? \"+\" : t.outcomeKind === \"failure\" ? \"-\" : \"~\";\n lines.push(`[${outcome}] Goal: ${t.goal}`);\n lines.push(` Action: ${t.actionSummary}`);\n lines.push(` Outcome: ${t.outcomeSummary}`);\n if (t.followUpSummary) lines.push(` Follow-up: ${t.followUpSummary}`);\n if (t.entityRefs?.length) lines.push(` Entities: ${t.entityRefs.join(\", \")}`);\n }\n lines.push(\"\");\n }\n\n // Format causal chain connections\n const edgeCount = Object.keys(chainIndex.edges).length;\n if (edgeCount > 0) {\n lines.push(`## Cross-Session Causal Chains (${edgeCount} connections)`);\n lines.push(\"\");\n\n const trajectoryMap = new Map(trajectories.map((t) => [t.trajectoryId, t]));\n const shown = new Set<string>();\n\n for (const [edgeId, edge] of Object.entries(chainIndex.edges)) {\n if (shown.size >= 10) break; // limit output size\n const from = trajectoryMap.get(edge.fromTrajectoryId);\n const to = trajectoryMap.get(edge.toTrajectoryId);\n if (!from || !to) continue;\n\n lines.push(`${edge.edgeType}: \"${from.goal}\" (${from.sessionKey}) → \"${to.goal}\" (${to.sessionKey})`);\n shown.add(edgeId);\n }\n lines.push(\"\");\n }\n\n const result = lines.join(\"\\n\");\n return result.length > maxChars ? result.slice(0, maxChars) + \"\\n[truncated]\" : result;\n}\n\n// ─── LLM Consolidation ──────────────────────────────────────────────────────\n\nconst CONSOLIDATION_PROMPT = `You are analyzing a user's causal trajectory history across multiple sessions. Trajectories record what the user tried to do (goal), what they did (action), and what happened (outcome).\n\nYour job is to identify:\n1. BEHAVIORAL RULES: Recurring patterns where the same approach consistently succeeds or fails. These should be actionable guidance for future sessions.\n2. PREFERENCES: What the user cares about, prefers, or consistently chooses — even if never explicitly stated. Infer preferences from what they repeatedly do, retry until successful, or always include in their workflow.\n\nIMPORTANT:\n- Look for CROSS-SESSION patterns — things that repeat across different sessions are more significant than within-session patterns.\n- A user who retries the same goal across sessions has a strong implicit preference for that outcome.\n- Consistent action choices reveal preferences even when the user never says \"I prefer X.\"\n- Frame preferences as \"The user would prefer responses that...\" when applicable.\n\nOutput valid JSON only:\n{\n \"rules\": [\n {\"content\": \"actionable rule text\", \"category\": \"rule|principle\", \"confidence\": 0.0-1.0, \"evidence\": [\"trajectory IDs\"]}\n ],\n \"preferences\": [\n {\"statement\": \"The user would prefer...\", \"confidence\": 0.0-1.0, \"evidence\": [\"trajectory IDs\"]}\n ]\n}\n\nIf no clear patterns exist, return {\"rules\": [], \"preferences\": []}.`;\n\nasync function consolidateWithLlm(\n context: string,\n llm: FallbackLlmClient,\n agentId?: string,\n): Promise<LlmConsolidationResult> {\n const response = await llm.chatCompletion(\n [\n { role: \"system\", content: CONSOLIDATION_PROMPT },\n { role: \"user\", content: context },\n ],\n { temperature: 0.2, maxTokens: 2000, agentId },\n );\n\n if (!response?.content) {\n return { rules: [], preferences: [] };\n }\n\n try {\n // Extract JSON from response (may have markdown code fences)\n let jsonStr = response.content.trim();\n const fenceMatch = jsonStr.match(/```(?:json)?\\s*\\n?([\\s\\S]*?)\\n?\\s*```/);\n if (fenceMatch) jsonStr = fenceMatch[1];\n\n const parsed = JSON.parse(jsonStr);\n return {\n rules: Array.isArray(parsed.rules) ? parsed.rules.filter(\n (r: any) => typeof r.content === \"string\" && r.content.length > 5,\n ) : [],\n preferences: Array.isArray(parsed.preferences) ? parsed.preferences.filter(\n (p: any) => typeof p.statement === \"string\" && p.statement.length > 5,\n ) : [],\n };\n } catch {\n log.warn(\"[cmc] failed to parse LLM consolidation response\");\n return { rules: [], preferences: [] };\n }\n}\n\n// ─── Candidate Generation ────────────────────────────────────────────────────\n\nfunction stablePatternId(content: string): string {\n const digest = createHash(\"sha256\")\n .update(`causal-pattern\\0${content}`)\n .digest(\"hex\")\n .slice(0, 16);\n return `causal-pattern:${digest}`;\n}\n\nfunction llmResultToCandidates(result: LlmConsolidationResult): CausalPatternCandidate[] {\n const candidates: CausalPatternCandidate[] = [];\n\n for (const rule of result.rules) {\n const category = rule.category === \"principle\" ? \"principle\" : \"rule\";\n candidates.push({\n id: stablePatternId(rule.content),\n sourceType: \"causal-pattern\",\n subject: rule.content.slice(0, 80),\n category,\n content: rule.content,\n score: Math.min(1, rule.confidence ?? 0.7),\n rationale: \"LLM-identified causal pattern from cross-session trajectory analysis\",\n outcome: null,\n provenance: (rule.evidence ?? []).slice(0, 5),\n agent: null,\n workflow: null,\n });\n }\n\n return candidates;\n}\n\n// ─── Public API ──────────────────────────────────────────────────────────────\n\n/**\n * Run LLM-assisted consolidation: read trajectories, format causal context,\n * ask LLM to identify patterns and preferences.\n */\nexport async function deriveCausalPromotionCandidates(options: {\n memoryDir: string;\n causalTrajectoryStoreDir?: string;\n config: ConsolidationConfig;\n gatewayConfig?: GatewayConfig;\n gatewayAgentId?: string;\n}): Promise<CausalPatternCandidate[]> {\n try {\n const trajectories = await readAllTrajectories(options.memoryDir, options.causalTrajectoryStoreDir);\n if (trajectories.length < options.config.minRecurrence) return [];\n\n const chainsDir = resolveChainsDir(options.memoryDir, options.causalTrajectoryStoreDir);\n const chainIndex = await readChainIndex(chainsDir);\n\n // Format the causal context for the LLM\n const context = formatCausalContext(trajectories, chainIndex);\n\n // If no LLM available, fall back to empty (no deterministic fallback)\n const llm = new FallbackLlmClient(options.gatewayConfig);\n if (!llm.isAvailable(options.gatewayAgentId)) {\n log.debug(\"[cmc] no LLM available for consolidation — skipping\");\n return [];\n }\n\n // Call LLM for pattern analysis\n const result = await consolidateWithLlm(context, llm, options.gatewayAgentId);\n const candidates = llmResultToCandidates(result);\n\n log.debug(`[cmc] LLM consolidation produced ${candidates.length} rule(s) and ${result.preferences.length} preference(s)`);\n return candidates;\n } catch (error) {\n log.warn(`[cmc] consolidation failed (non-fatal): ${error instanceof Error ? error.message : String(error)}`);\n return [];\n }\n}\n\n/**\n * Get LLM-synthesized preferences from causal trajectory analysis.\n * Returns formatted preference statements for recall injection.\n */\nexport async function synthesizeCausalPreferencesViaLlm(options: {\n memoryDir: string;\n causalTrajectoryStoreDir?: string;\n gatewayConfig?: GatewayConfig;\n gatewayAgentId?: string;\n minTrajectories?: number;\n}): Promise<string | null> {\n try {\n const trajectories = await readAllTrajectories(options.memoryDir, options.causalTrajectoryStoreDir);\n if (trajectories.length < (options.minTrajectories ?? 2)) return null;\n\n const chainsDir = resolveChainsDir(options.memoryDir, options.causalTrajectoryStoreDir);\n const chainIndex = await readChainIndex(chainsDir);\n const context = formatCausalContext(trajectories, chainIndex);\n\n const llm = new FallbackLlmClient(options.gatewayConfig);\n if (!llm.isAvailable(options.gatewayAgentId)) return null;\n\n const result = await consolidateWithLlm(context, llm, options.gatewayAgentId);\n if (result.preferences.length === 0 && result.rules.length === 0) return null;\n\n const lines: string[] = [\"## Behavioral Insights (from Causal Chain Analysis)\", \"\"];\n\n for (const pref of result.preferences) {\n lines.push(`- ${pref.statement}`);\n }\n\n for (const rule of result.rules) {\n lines.push(`- ${rule.content}`);\n }\n\n lines.push(\"\");\n return lines.join(\"\\n\");\n } catch (error) {\n log.warn(`[cmc] preference synthesis failed (non-fatal): ${error instanceof Error ? error.message : String(error)}`);\n return null;\n }\n}\n"],"mappings":"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","names":[]}
@@ -103,16 +103,17 @@ var FallbackLlmClient = class {
103
103
  /**
104
104
  * Check if fallback is available (gateway config has at least one model).
105
105
  */
106
- isAvailable() {
107
- const models = this.getModelChain();
106
+ isAvailable(agentId) {
107
+ const models = this.getModelChain(agentId);
108
108
  return models.length > 0;
109
109
  }
110
110
  /**
111
111
  * Make a chat completion request using the gateway's default AI chain.
112
112
  * Tries primary first, then each fallback in order.
113
+ * When agentId is provided, uses that agent persona's model chain instead of defaults.
113
114
  */
114
115
  async chatCompletion(messages, options = {}) {
115
- const models = this.getModelChain();
116
+ const models = this.getModelChain(options.agentId);
116
117
  if (models.length === 0) {
117
118
  log.warn("fallback LLM: no models configured in gateway");
118
119
  return null;
@@ -196,18 +197,38 @@ var FallbackLlmClient = class {
196
197
  /**
197
198
  * Get the full model chain from gateway config.
198
199
  * Returns array of models in order: [primary, fallback1, fallback2, ...]
200
+ *
201
+ * When agentId is provided, looks up the matching entry in agents.list[]
202
+ * and uses that persona's model chain. Falls back to agents.defaults.model
203
+ * if agentId is not found or not provided.
199
204
  */
200
- getModelChain() {
205
+ getModelChain(agentId) {
201
206
  const chain = [];
202
207
  const providers = this.gatewayConfig?.models?.providers;
203
- const defaultModelConfig = this.gatewayConfig?.agents?.defaults?.model;
204
208
  if (!providers) return chain;
209
+ let modelConfig;
210
+ if (agentId) {
211
+ const persona = this.gatewayConfig?.agents?.list?.find(
212
+ (a) => a.id === agentId
213
+ );
214
+ if (persona?.model) {
215
+ modelConfig = persona.model;
216
+ log.debug(`fallback LLM: using agent persona "${agentId}" model chain`);
217
+ } else {
218
+ log.warn(
219
+ `fallback LLM: agent persona "${agentId}" not found or has no model config, falling back to defaults`
220
+ );
221
+ }
222
+ }
223
+ if (!modelConfig) {
224
+ modelConfig = this.gatewayConfig?.agents?.defaults?.model;
225
+ }
205
226
  const modelStrings = [];
206
- if (defaultModelConfig?.primary) {
207
- modelStrings.push(defaultModelConfig.primary);
227
+ if (modelConfig?.primary) {
228
+ modelStrings.push(modelConfig.primary);
208
229
  }
209
- if (Array.isArray(defaultModelConfig?.fallbacks)) {
210
- for (const fb of defaultModelConfig.fallbacks) {
230
+ if (Array.isArray(modelConfig?.fallbacks)) {
231
+ for (const fb of modelConfig.fallbacks) {
211
232
  if (typeof fb === "string" && !modelStrings.includes(fb)) {
212
233
  modelStrings.push(fb);
213
234
  }
@@ -362,4 +383,4 @@ export {
362
383
  buildChatCompletionTokenLimit,
363
384
  FallbackLlmClient
364
385
  };
365
- //# sourceMappingURL=chunk-XCAYYSI7.js.map
386
+ //# sourceMappingURL=chunk-5YCBFXXX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/json-extract.ts","../src/openai-chat-compat.ts","../src/fallback-llm.ts"],"sourcesContent":["/**\n * Utilities for extracting JSON payloads from LLM outputs.\n *\n * We see common failure modes:\n * - \"Here's an example: {..}\\nHere's the real answer: {..}\" (multiple JSON blocks)\n * - fenced ```json blocks\n * - leading/trailing prose around JSON\n *\n * These helpers attempt multiple candidates and let callers validate with schemas.\n */\n\nexport function stripCodeFences(text: string): string {\n return text.replace(/```(?:json)?\\s*([\\s\\S]*?)```/gi, (_m, inner) => String(inner).trim());\n}\n\nexport function extractJsonCandidates(text: string): string[] {\n const trimmed = text.trim();\n const cleaned = stripCodeFences(trimmed);\n const candidates: string[] = [];\n\n if (cleaned.length > 0) candidates.push(cleaned);\n candidates.push(...scanBalancedJsonBlocks(cleaned));\n\n // Legacy regex fallback (single object)\n const objMatch = cleaned.match(/\\{[\\s\\S]*\\}/);\n if (objMatch) candidates.push(objMatch[0]);\n\n const seen = new Set<string>();\n return candidates\n .map((c) => c.trim())\n .filter((c) => c.length > 0)\n .filter((c) => {\n if (seen.has(c)) return false;\n seen.add(c);\n return true;\n });\n}\n\nfunction scanBalancedJsonBlocks(text: string): string[] {\n const out: string[] = [];\n const opens = new Set([\"{\", \"[\"]);\n const closes: Record<string, string> = { \"{\": \"}\", \"[\": \"]\" };\n\n for (let i = 0; i < text.length; i++) {\n const start = text[i];\n if (!opens.has(start)) continue;\n\n const expectedClose = closes[start];\n let depth = 0;\n let inString = false;\n let escape = false;\n\n for (let j = i; j < text.length; j++) {\n const ch = text[j];\n\n if (inString) {\n if (escape) {\n escape = false;\n } else if (ch === \"\\\\\") {\n escape = true;\n } else if (ch === \"\\\"\") {\n inString = false;\n }\n continue;\n }\n\n if (ch === \"\\\"\") {\n inString = true;\n continue;\n }\n\n if (ch === start) depth++;\n if (ch === expectedClose) depth--;\n\n if (depth === 0) {\n out.push(text.slice(i, j + 1).trim());\n i = j;\n break;\n }\n }\n }\n\n return out;\n}\n\n","function normalizedModel(model: string): string {\n return model.trim().toLowerCase();\n}\n\nfunction matchesModelFamily(normalized: string, familyPattern: RegExp): boolean {\n return familyPattern.test(normalized);\n}\n\nexport function shouldAssumeOpenAiChatCompletions(baseUrl?: string): boolean {\n if (!baseUrl) return true;\n try {\n return new URL(baseUrl).hostname.toLowerCase() === \"api.openai.com\";\n } catch {\n return false;\n }\n}\n\nexport function usesMaxCompletionTokens(model: string, options?: { assumeOpenAI?: boolean }): boolean {\n const normalized = normalizedModel(model);\n if (options?.assumeOpenAI !== true) return false;\n if (matchesModelFamily(normalized, /^gpt-5(?:$|[-.])/)) return true;\n if (matchesModelFamily(normalized, /^gpt-4o(?:$|[-.])/)) return true;\n if (matchesModelFamily(normalized, /^gpt-4\\.1(?:$|[-.])/)) return true;\n if (matchesModelFamily(normalized, /^o1(?:$|[-.])/)) return true;\n if (matchesModelFamily(normalized, /^o3(?:$|[-.])/)) return true;\n return matchesModelFamily(normalized, /^o4-mini(?:$|[-.])/);\n}\n\nexport function buildChatCompletionTokenLimit(\n model: string,\n maxTokens: number,\n options?: { assumeOpenAI?: boolean },\n): { max_tokens: number } | { max_completion_tokens: number } {\n const safeMaxTokens = Math.max(0, Math.floor(maxTokens));\n if (usesMaxCompletionTokens(model, options)) {\n return { max_completion_tokens: safeMaxTokens };\n }\n return { max_tokens: safeMaxTokens };\n}\n","import { log } from \"./logger.js\";\nimport type { GatewayConfig, ModelProviderConfig, AgentPersona } from \"./types.js\";\nimport { extractJsonCandidates } from \"./json-extract.js\";\nimport {\n buildChatCompletionTokenLimit,\n shouldAssumeOpenAiChatCompletions,\n} from \"./openai-chat-compat.js\";\n\nexport interface FallbackLlmOptions {\n temperature?: number;\n maxTokens?: number;\n timeoutMs?: number;\n /** Override which agent persona's model chain to use (by ID from agents.list[]). */\n agentId?: string;\n}\n\nexport interface FallbackLlmResponse {\n content: string;\n modelUsed: string;\n usage?: {\n inputTokens?: number;\n outputTokens?: number;\n totalTokens?: number;\n };\n}\n\ninterface ModelRef {\n providerId: string;\n modelId: string;\n providerConfig: ModelProviderConfig;\n modelString: string;\n}\n\n/**\n * Generic fallback LLM client that uses the gateway's default AI configuration\n * and walks through the full fallback chain (primary + fallbacks).\n * Supports OpenAI and Anthropic API formats.\n */\nexport class FallbackLlmClient {\n private gatewayConfig: GatewayConfig | undefined;\n\n constructor(gatewayConfig?: GatewayConfig) {\n this.gatewayConfig = gatewayConfig;\n }\n\n /**\n * Check if fallback is available (gateway config has at least one model).\n */\n isAvailable(agentId?: string): boolean {\n const models = this.getModelChain(agentId);\n return models.length > 0;\n }\n\n /**\n * Make a chat completion request using the gateway's default AI chain.\n * Tries primary first, then each fallback in order.\n * When agentId is provided, uses that agent persona's model chain instead of defaults.\n */\n async chatCompletion(\n messages: Array<{ role: \"system\" | \"user\" | \"assistant\"; content: string }>,\n options: FallbackLlmOptions = {},\n ): Promise<FallbackLlmResponse | null> {\n const models = this.getModelChain(options.agentId);\n if (models.length === 0) {\n log.warn(\"fallback LLM: no models configured in gateway\");\n return null;\n }\n\n const runChain = async (): Promise<FallbackLlmResponse | null> => {\n // Try each model in the chain\n for (let i = 0; i < models.length; i++) {\n const model = models[i];\n const isFallback = i > 0;\n\n try {\n const result = await this.tryModel(model, messages, options);\n if (result) {\n if (isFallback) {\n log.info(`fallback LLM: succeeded using ${model.modelString} (fallback ${i})`);\n }\n return {\n content: result.content,\n modelUsed: model.modelString,\n usage: result.usage,\n };\n }\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : String(err);\n log.debug(`fallback LLM: ${model.modelString} failed (${errorMsg}), trying next...`);\n // Continue to next model in chain\n }\n }\n\n log.warn(`fallback LLM: all ${models.length} models in chain failed`);\n return null;\n };\n\n if (typeof options.timeoutMs === \"number\") {\n if (options.timeoutMs <= 0) {\n log.warn(\"fallback LLM: timed out before request started\");\n return null;\n }\n let timeoutHandle: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n runChain(),\n new Promise<null>((resolve) => {\n timeoutHandle = setTimeout(() => {\n log.warn(`fallback LLM: timed out after ${options.timeoutMs}ms`);\n resolve(null);\n }, options.timeoutMs);\n }),\n ]);\n } finally {\n if (timeoutHandle) clearTimeout(timeoutHandle);\n }\n }\n\n return await runChain();\n }\n\n /**\n * Make a request with structured output (Zod schema).\n * Returns parsed JSON or null on failure.\n */\n async parseWithSchema<T>(\n messages: Array<{ role: \"system\" | \"user\" | \"assistant\"; content: string }>,\n schema: { parse: (data: unknown) => T },\n options: FallbackLlmOptions = {},\n ): Promise<T | null> {\n const detailed = await this.parseWithSchemaDetailed(messages, schema, options);\n return detailed?.result ?? null;\n }\n\n /**\n * Like parseWithSchema but also returns the model that was used,\n * so callers can emit accurate trace events.\n */\n async parseWithSchemaDetailed<T>(\n messages: Array<{ role: \"system\" | \"user\" | \"assistant\"; content: string }>,\n schema: { parse: (data: unknown) => T },\n options: FallbackLlmOptions = {},\n ): Promise<{ result: T; modelUsed: string } | null> {\n const response = await this.chatCompletion(messages, options);\n if (!response?.content) return null;\n\n try {\n const candidates = extractJsonCandidates(response.content);\n for (const c of candidates) {\n try {\n const parsed = JSON.parse(c);\n return { result: schema.parse(parsed), modelUsed: response.modelUsed };\n } catch {\n // keep trying other candidates\n }\n }\n return null;\n } catch (err) {\n log.warn(\"fallback LLM: failed to parse structured output:\", err);\n return null;\n }\n }\n\n /**\n * Get the full model chain from gateway config.\n * Returns array of models in order: [primary, fallback1, fallback2, ...]\n *\n * When agentId is provided, looks up the matching entry in agents.list[]\n * and uses that persona's model chain. Falls back to agents.defaults.model\n * if agentId is not found or not provided.\n */\n private getModelChain(agentId?: string): ModelRef[] {\n const chain: ModelRef[] = [];\n const providers = this.gatewayConfig?.models?.providers;\n\n if (!providers) return chain;\n\n // Resolve the model config: agent persona chain or global defaults\n let modelConfig: { primary?: string; fallbacks?: string[] } | undefined;\n\n if (agentId) {\n const persona = this.gatewayConfig?.agents?.list?.find(\n (a) => a.id === agentId,\n );\n if (persona?.model) {\n modelConfig = persona.model;\n log.debug(`fallback LLM: using agent persona \"${agentId}\" model chain`);\n } else {\n log.warn(\n `fallback LLM: agent persona \"${agentId}\" not found or has no model config, falling back to defaults`,\n );\n }\n }\n\n if (!modelConfig) {\n modelConfig = this.gatewayConfig?.agents?.defaults?.model;\n }\n\n // Build list of model strings: primary + fallbacks\n const modelStrings: string[] = [];\n\n if (modelConfig?.primary) {\n modelStrings.push(modelConfig.primary);\n }\n\n if (Array.isArray(modelConfig?.fallbacks)) {\n for (const fb of modelConfig.fallbacks) {\n if (typeof fb === \"string\" && !modelStrings.includes(fb)) {\n modelStrings.push(fb);\n }\n }\n }\n\n // Parse each model string and look up provider config\n for (const modelString of modelStrings) {\n const modelRef = this.parseModelString(modelString, providers);\n if (modelRef) {\n chain.push(modelRef);\n }\n }\n\n return chain;\n }\n\n /**\n * Parse a \"provider/model\" string and look up its config.\n */\n private parseModelString(\n modelString: string,\n providers: Record<string, ModelProviderConfig>,\n ): ModelRef | null {\n // Parse \"provider/model\" format (e.g., \"openai/gpt-5.2\", \"anthropic/claude-opus-4-6\")\n const parts = modelString.split(\"/\");\n if (parts.length < 2) {\n log.warn(`fallback LLM: invalid model format: ${modelString}`);\n return null;\n }\n\n const providerId = parts[0];\n const modelId = parts.slice(1).join(\"/\"); // Handle cases like \"openai/gpt-5.2-turbo\"\n\n const providerConfig = providers[providerId];\n if (!providerConfig) {\n log.warn(`fallback LLM: provider not found: ${providerId}`);\n return null;\n }\n\n return { providerId, modelId, providerConfig, modelString };\n }\n\n /**\n * Try to call a single model.\n */\n private async tryModel(\n model: ModelRef,\n messages: Array<{ role: \"system\" | \"user\" | \"assistant\"; content: string }>,\n options: FallbackLlmOptions,\n ): Promise<{ content: string; usage?: FallbackLlmResponse[\"usage\"] } | null> {\n switch (model.providerConfig.api) {\n case \"anthropic-messages\":\n return await this.callAnthropic(model.providerConfig, model.modelId, messages, options);\n case \"openai-completions\":\n default:\n return await this.callOpenAI(\n model.providerConfig,\n model.modelId,\n messages,\n options,\n shouldAssumeOpenAiChatCompletions(model.providerConfig.baseUrl),\n );\n }\n }\n\n /**\n * Call OpenAI-compatible API.\n */\n private async callOpenAI(\n config: ModelProviderConfig,\n modelId: string,\n messages: Array<{ role: \"system\" | \"user\" | \"assistant\"; content: string }>,\n options: FallbackLlmOptions,\n assumeOpenAI: boolean,\n ): Promise<{ content: string; usage?: FallbackLlmResponse[\"usage\"] } | null> {\n const base = config.baseUrl.replace(/\\/$/, \"\");\n const url = base.endsWith(\"/v1\")\n ? `${base}/chat/completions`\n : `${base}/v1/chat/completions`;\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n ...config.headers,\n };\n\n // Handle auth\n if (config.apiKey) {\n if (config.authHeader !== false) {\n headers[\"Authorization\"] = `Bearer ${config.apiKey}`;\n }\n }\n\n const body = {\n model: modelId,\n messages,\n temperature: options.temperature ?? 0.3,\n ...buildChatCompletionTokenLimit(modelId, options.maxTokens ?? 4096, {\n assumeOpenAI,\n }),\n };\n\n const response = await fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`OpenAI API error: ${response.status} ${error}`);\n }\n\n const data = (await response.json()) as {\n choices: Array<{\n message: {\n content: string;\n };\n }>;\n usage?: {\n prompt_tokens?: number;\n completion_tokens?: number;\n total_tokens?: number;\n };\n };\n\n const content = data.choices?.[0]?.message?.content;\n if (!content) {\n throw new Error(\"Empty response from OpenAI API\");\n }\n\n return {\n content,\n usage: data.usage\n ? {\n inputTokens: data.usage.prompt_tokens,\n outputTokens: data.usage.completion_tokens,\n totalTokens: data.usage.total_tokens,\n }\n : undefined,\n };\n }\n\n /**\n * Call Anthropic Messages API.\n */\n private async callAnthropic(\n config: ModelProviderConfig,\n modelId: string,\n messages: Array<{ role: \"system\" | \"user\" | \"assistant\"; content: string }>,\n options: FallbackLlmOptions,\n ): Promise<{ content: string; usage?: FallbackLlmResponse[\"usage\"] } | null> {\n const url = `${config.baseUrl.replace(/\\/$/, \"\")}/messages`;\n\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"anthropic-version\": \"2023-06-01\",\n ...config.headers,\n };\n\n // Handle auth - Anthropic uses x-api-key header\n if (config.apiKey) {\n headers[\"x-api-key\"] = config.apiKey;\n }\n\n // Extract system message (Anthropic handles it separately)\n const systemMessage = messages.find((m) => m.role === \"system\")?.content;\n const nonSystemMessages = messages.filter((m) => m.role !== \"system\");\n\n // Convert messages to Anthropic format\n const anthropicMessages = nonSystemMessages.map((m) => ({\n role: m.role,\n content: m.content,\n }));\n\n const body: Record<string, unknown> = {\n model: modelId,\n messages: anthropicMessages,\n max_tokens: options.maxTokens ?? 4096,\n temperature: options.temperature ?? 0.3,\n };\n\n if (systemMessage) {\n body.system = systemMessage;\n }\n\n const response = await fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Anthropic API error: ${response.status} ${error}`);\n }\n\n const data = (await response.json()) as {\n content: Array<{\n type: string;\n text: string;\n }>;\n usage?: {\n input_tokens?: number;\n output_tokens?: number;\n };\n };\n\n const content = data.content?.[0]?.text;\n if (!content) {\n throw new Error(\"Empty response from Anthropic API\");\n }\n\n return {\n content,\n usage: data.usage\n ? {\n inputTokens: data.usage.input_tokens,\n outputTokens: data.usage.output_tokens,\n totalTokens: (data.usage.input_tokens ?? 0) + (data.usage.output_tokens ?? 0),\n }\n : undefined,\n };\n }\n}\n"],"mappings":"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","names":[]}
@@ -4,7 +4,7 @@ import {
4
4
  parseContinuityImprovementLoops,
5
5
  parseContinuityIncident,
6
6
  sanitizeMemoryContent
7
- } from "./chunk-4XBRUH6C.js";
7
+ } from "./chunk-TS3YC3MY.js";
8
8
  import {
9
9
  log
10
10
  } from "./chunk-SSIIJJKA.js";
@@ -830,11 +830,12 @@ var CompoundingEngine = class {
830
830
  let promotionCandidates = this.config.compoundingSemanticEnabled ? this.derivePromotionCandidates(outcomeSummary, mistakes.registry, rubrics) : [];
831
831
  if (this.config.cmcConsolidationEnabled) {
832
832
  try {
833
- const { deriveCausalPromotionCandidates } = await import("./causal-consolidation-T7EEYKPA.js");
833
+ const { deriveCausalPromotionCandidates } = await import("./causal-consolidation-HCZHJO2J.js");
834
834
  const causalCandidates = await deriveCausalPromotionCandidates({
835
835
  memoryDir: this.config.memoryDir,
836
836
  causalTrajectoryStoreDir: this.config.causalTrajectoryStoreDir,
837
837
  gatewayConfig: this.config.gatewayConfig,
838
+ gatewayAgentId: this.config.modelSource === "gateway" ? this.config.gatewayAgentId || void 0 : void 0,
838
839
  config: {
839
840
  minRecurrence: this.config.cmcConsolidationMinRecurrence,
840
841
  minSessions: this.config.cmcConsolidationMinSessions,
@@ -850,10 +851,11 @@ var CompoundingEngine = class {
850
851
  }
851
852
  if (this.config.calibrationEnabled) {
852
853
  try {
853
- const { runCalibrationConsolidation } = await import("./calibration-QBVYTFTQ.js");
854
+ const { runCalibrationConsolidation } = await import("./calibration-JLSDBR4C.js");
854
855
  const calRules = await runCalibrationConsolidation({
855
856
  memoryDir: this.config.memoryDir,
856
- gatewayConfig: this.config.gatewayConfig
857
+ gatewayConfig: this.config.gatewayConfig,
858
+ gatewayAgentId: this.config.modelSource === "gateway" ? this.config.gatewayAgentId || void 0 : void 0
857
859
  });
858
860
  log.debug(`[calibration] weekly synthesis produced ${calRules.length} calibration rule(s)`);
859
861
  } catch (error) {
@@ -1837,4 +1839,4 @@ export {
1837
1839
  defaultTierMigrationCycleBudget,
1838
1840
  CompoundingEngine
1839
1841
  };
1840
- //# sourceMappingURL=chunk-4PXEFPHR.js.map
1842
+ //# sourceMappingURL=chunk-MQ5EZ6VE.js.map