0xray 4.0.22 → 4.0.24

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 (103) hide show
  1. package/.grok-plugin/plugin.json +1 -1
  2. package/.opencode/skills/orchestrator/SKILL.md +7 -7
  3. package/.xray/features.json +11 -1
  4. package/.xray/features.schema.json +17 -0
  5. package/AGENTS-consumer.md +1 -1
  6. package/AGENTS.md +1 -1
  7. package/README.md +2 -2
  8. package/SKILLS.md +1 -1
  9. package/dist/AGENTS.md +1 -1
  10. package/dist/CHANGELOG.md +44 -1
  11. package/dist/README.md +2 -2
  12. package/dist/SKILLS.md +1 -1
  13. package/dist/cli/index.js +131 -100
  14. package/dist/core/features-config.d.ts +4 -0
  15. package/dist/core/features-config.js +10 -0
  16. package/dist/inference/inference-accumulator.d.ts +2 -0
  17. package/dist/inference/inference-accumulator.js +110 -6
  18. package/dist/inference/inference-cycle.d.ts +12 -0
  19. package/dist/inference/inference-cycle.js +159 -16
  20. package/dist/inference/inference-proposal-generator.js +206 -5
  21. package/dist/inference/inference-run-gate.d.ts +8 -0
  22. package/dist/inference/inference-run-gate.js +10 -0
  23. package/dist/inference/session-capture.d.ts +31 -0
  24. package/dist/inference/session-capture.js +277 -1
  25. package/dist/integrations/cursor/hooks/pre-compact.js +89 -1
  26. package/dist/integrations/governance/index.d.ts +5 -0
  27. package/dist/integrations/governance/index.js +22 -2
  28. package/dist/integrations/governance/types.d.ts +11 -0
  29. package/dist/integrations/governance/types.js +16 -0
  30. package/dist/integrations/hooks/plates.cjs +165 -0
  31. package/dist/integrations/hooks/station-hook-runtime.cjs +29 -11
  32. package/dist/integrations/hooks/station-memory-ingest.mjs +109 -42
  33. package/dist/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  34. package/dist/mcps/governance.server.d.ts +2 -0
  35. package/dist/mcps/governance.server.js +12 -1
  36. package/dist/mcps/researcher-confidence.d.ts +5 -1
  37. package/dist/mcps/researcher-confidence.js +37 -0
  38. package/dist/mcps/researcher.server.js +2 -2
  39. package/dist/memory-routing/index.d.ts +1 -1
  40. package/dist/memory-routing/index.js +1 -1
  41. package/dist/memory-routing/lesson-pickup.d.ts +2 -0
  42. package/dist/memory-routing/lesson-pickup.js +57 -0
  43. package/dist/memory-routing/plates.d.ts +16 -0
  44. package/dist/memory-routing/plates.js +71 -0
  45. package/dist/memory-routing/provider-loader.d.ts +5 -0
  46. package/dist/memory-routing/provider-loader.js +46 -0
  47. package/dist/memory-routing/provider-registry.d.ts +7 -2
  48. package/dist/memory-routing/provider-registry.js +26 -6
  49. package/dist/memory-routing/record-lesson.d.ts +16 -0
  50. package/dist/memory-routing/record-lesson.js +26 -0
  51. package/dist/memory-routing/types.d.ts +17 -0
  52. package/dist/memory-routing/types.js +2 -1
  53. package/dist/nucleus/thin-dispatch.d.ts +2 -0
  54. package/dist/nucleus/thin-dispatch.js +3 -2
  55. package/dist/processors/implementations/inference-improvement-processor.d.ts +5 -0
  56. package/dist/processors/implementations/inference-improvement-processor.js +101 -2
  57. package/dist/processors/implementations/storytelling-trigger-processor.js +1 -11
  58. package/dist/processors/processor-manager.d.ts +1 -1
  59. package/dist/processors/processor-manager.js +52 -20
  60. package/dist/skills/orchestrator/SKILL.md +7 -7
  61. package/llms.txt +1 -1
  62. package/package.json +1 -1
  63. package/scripts/foundry/plant/skills/mill/SKILL.md +1 -1
  64. package/scripts/foundry/reconcile-version.mjs +23 -1
  65. package/scripts/foundry/release.mjs +1 -1
  66. package/scripts/foundry/version-manager.mjs +53 -0
  67. package/scripts/node/consumer-install-smoke.mjs +3 -3
  68. package/src/integrations/openclaw/plugin/xray-pre-tool/package.json +1 -1
  69. package/src/mcps/governance.server.ts +12 -1
  70. package/src/mcps/researcher-confidence.ts +56 -3
  71. package/src/mcps/researcher.server.ts +4 -5
  72. package/src/skills/orchestrator/SKILL.md +7 -7
  73. package/vendor/@0xray/repertoire/README.md +3 -1
  74. package/vendor/@0xray/repertoire/data/stack-overlay.json +4 -4
  75. package/vendor/@0xray/repertoire/data/subject-overlay.json +3 -3
  76. package/vendor/@0xray/repertoire/dist/RepertoireService.d.ts +4 -0
  77. package/vendor/@0xray/repertoire/dist/RepertoireService.d.ts.map +1 -1
  78. package/vendor/@0xray/repertoire/dist/RepertoireService.js +4 -4
  79. package/vendor/@0xray/repertoire/dist/RepertoireService.js.map +1 -1
  80. package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.d.ts.map +1 -1
  81. package/vendor/@0xray/repertoire/dist/ingestion/xray-session-ingester.js.map +1 -1
  82. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.d.ts.map +1 -1
  83. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.js +5 -0
  84. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/confidence-gate.js.map +1 -1
  85. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.d.ts.map +1 -1
  86. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.js +16 -0
  87. package/vendor/@0xray/repertoire/dist/orchestrator-bridge/signal-injector.js.map +1 -1
  88. package/vendor/@0xray/repertoire/dist/paths.d.ts +3 -3
  89. package/vendor/@0xray/repertoire/dist/paths.d.ts.map +1 -1
  90. package/vendor/@0xray/repertoire/dist/paths.js.map +1 -1
  91. package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.d.ts +11 -0
  92. package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.d.ts.map +1 -1
  93. package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.js +2 -0
  94. package/vendor/@0xray/repertoire/dist/provider/memory-routing-provider.js.map +1 -1
  95. package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.d.ts +74 -4
  96. package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.d.ts.map +1 -1
  97. package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.js +431 -13
  98. package/vendor/@0xray/repertoire/dist/registry/CuratedSignalsManager.js.map +1 -1
  99. package/vendor/@0xray/repertoire/dist/types.d.ts +21 -0
  100. package/vendor/@0xray/repertoire/dist/types.d.ts.map +1 -1
  101. package/vendor/@0xray/repertoire/package.json +1 -1
  102. package/xray/features.json +11 -1
  103. package/xray/features.schema.json +17 -0
@@ -196,8 +196,19 @@ class GovernanceServer extends XrayKnowledgeSkillBase {
196
196
  return false;
197
197
  }
198
198
  }
199
+ /** Boot stays flag-gated. A required external call starts the client on demand. */
200
+ async armRequiredDynamo(requireExternal) {
201
+ if (!requireExternal)
202
+ return;
203
+ const integration = await initializeGovernanceIntegration();
204
+ if (!integration.isAvailable()) {
205
+ await integration.ensureDynamoClient();
206
+ }
207
+ }
199
208
  async handleGovernProposals(args) {
200
209
  const service = getGovernanceService();
210
+ const requireExternal = this.resolveRequireExternal(args.options?.require_external);
211
+ await this.armRequiredDynamo(requireExternal);
201
212
  const request = {
202
213
  proposals: args.proposals.map((p, i) => ({
203
214
  id: p.id || `prop-${Date.now()}-${i}`,
@@ -210,7 +221,7 @@ class GovernanceServer extends XrayKnowledgeSkillBase {
210
221
  })),
211
222
  context: args.context || {},
212
223
  options: {
213
- requireExternalDynamo: this.resolveRequireExternal(args.options?.require_external),
224
+ requireExternalDynamo: requireExternal,
214
225
  },
215
226
  };
216
227
  frameworkLogger.log("governance-mcp", "delegating-to-governance-service", "info", {
@@ -1,10 +1,14 @@
1
- import type { MemoryRoutingContext, MemoryTaskConfidence } from '../memory-routing/types.js';
1
+ import { type MemoryRoutingContext, type MemorySignalLessons, type MemoryTaskConfidence } from '../memory-routing/types.js';
2
2
  export interface ResearcherMemoryContext {
3
3
  providerId: string;
4
4
  confidence: MemoryTaskConfidence;
5
5
  matchedSignals: string[];
6
6
  recommendedAgent: string | null;
7
7
  triggeredBy: 'trap-language' | 'high-confidence-primitives';
8
+ /** Graded lines for signals this task already matched. */
9
+ lessons?: MemorySignalLessons[];
10
+ /** Proposal text used to recall at most one pipeline plate. */
11
+ proposalText?: string;
8
12
  }
9
13
  export declare function shouldQueryRepertoireConfidence(description: string, proposalType?: string): boolean;
10
14
  export declare function hasHighConfidencePrimitiveMatch(routingContext: MemoryRoutingContext, minConfidence?: number): boolean;
@@ -1,5 +1,7 @@
1
1
  import { frameworkLogger } from '../core/framework-logger.js';
2
2
  import { getMemoryRoutingProvider } from '../memory-routing/index.js';
3
+ import { recallPlate } from '../memory-routing/plates.js';
4
+ import { LESSON_TEXT_CAP, } from '../memory-routing/types.js';
3
5
  const TRAP_LANGUAGE = /TYPE:\s*ontological-trap|ontological[- ]trap/i;
4
6
  const DEFAULT_MIN_CONFIDENCE_GATE = 0.55;
5
7
  export function shouldQueryRepertoireConfidence(description, proposalType = '') {
@@ -40,12 +42,15 @@ export async function resolveResearcherMemoryContext(input) {
40
42
  const matchedSignals = confidence.matchedSignals.length > 0
41
43
  ? confidence.matchedSignals
42
44
  : routingContext.matchedSignals;
45
+ const lessons = lessonsForMatchedSignals(routingContext.lessons, matchedSignals);
43
46
  const context = {
44
47
  providerId: provider.id,
45
48
  confidence,
46
49
  matchedSignals,
47
50
  recommendedAgent: confidence.recommendedAgent,
48
51
  triggeredBy: trapLanguageDetected ? 'trap-language' : 'high-confidence-primitives',
52
+ proposalText: taskText,
53
+ ...(lessons.length > 0 ? { lessons } : {}),
49
54
  };
50
55
  if (confidence.highConfidenceTrapPresent) {
51
56
  frameworkLogger.log('mcps/researcher', 'repertoire-trap-detected', 'info', {
@@ -73,6 +78,10 @@ export function buildMemoryRoutingEvidence(context) {
73
78
  if (recommendedAgent) {
74
79
  lines.push(`Recommended agent for trap handling: ${recommendedAgent}`);
75
80
  }
81
+ for (const line of lessonSpeechLines(context.lessons, matchedSignals)) {
82
+ lines.push(`Lesson: ${line}`);
83
+ }
84
+ appendRecalledPlate(lines, context.proposalText);
76
85
  return lines;
77
86
  }
78
87
  export function formatMemoryRoutingBlock(context) {
@@ -88,5 +97,33 @@ export function formatMemoryRoutingBlock(context) {
88
97
  if (recommendedAgent) {
89
98
  lines.push(` recommendedAgent: ${recommendedAgent}`);
90
99
  }
100
+ for (const line of lessonSpeechLines(context.lessons, matchedSignals)) {
101
+ lines.push(` lesson: ${line}`);
102
+ }
103
+ appendRecalledPlate(lines, context.proposalText);
91
104
  return lines.join('\n');
92
105
  }
106
+ function appendRecalledPlate(lines, proposalText) {
107
+ const plate = recallPlate(proposalText);
108
+ if (!plate)
109
+ return;
110
+ lines.push(`Plate: ${plate.id}`);
111
+ lines.push(plate.body);
112
+ }
113
+ function lessonsForMatchedSignals(lessons, matchedSignals) {
114
+ const matched = new Set(matchedSignals);
115
+ return (lessons ?? []).filter((lesson) => matched.has(lesson.name));
116
+ }
117
+ /** Stored lesson text for signals this task matched. Empty text is omitted. */
118
+ function lessonSpeechLines(lessons, matchedSignals) {
119
+ const speech = [];
120
+ for (const lesson of lessonsForMatchedSignals(lessons, matchedSignals)) {
121
+ for (const line of lesson.lines) {
122
+ const text = line.text.trim().slice(0, LESSON_TEXT_CAP);
123
+ if (text.length === 0)
124
+ continue;
125
+ speech.push(`${lesson.name}: ${text}`);
126
+ }
127
+ }
128
+ return speech;
129
+ }
@@ -388,7 +388,7 @@ class XrayLibrarianServer extends XrayKnowledgeSkillBase {
388
388
  proposalType,
389
389
  });
390
390
  const enrichedEvidence = [...(evidence || [])];
391
- if (memoryContext?.confidence.highConfidenceTrapPresent) {
391
+ if (memoryContext) {
392
392
  enrichedEvidence.push(...buildMemoryRoutingEvidence(memoryContext));
393
393
  }
394
394
  const vote = (await tryLLMGovernance("researcher", proposalTitle || "", proposalDescription || "", enrichedEvidence, proposalType || "")) ??
@@ -398,7 +398,7 @@ class XrayLibrarianServer extends XrayKnowledgeSkillBase {
398
398
  highConfidenceTrapPresent: memoryContext?.confidence.highConfidenceTrapPresent,
399
399
  recommendedAgent: memoryContext?.recommendedAgent,
400
400
  });
401
- const memoryRoutingBlock = memoryContext?.confidence.highConfidenceTrapPresent
401
+ const memoryRoutingBlock = memoryContext
402
402
  ? `\n${formatMemoryRoutingBlock(memoryContext)}`
403
403
  : "";
404
404
  return {
@@ -1,5 +1,5 @@
1
1
  export type { MemoryRoutingProvider, MemoryRoutingContext, MemoryInheritedContext, MemoryAgentCapability, MemoryOrchestrationTask, MemoryThinDispatchResult, MemoryRoutingConfig, OrchestratorFeedbackEntry, } from './types.js';
2
- export { getMemoryRoutingProvider, getMemoryRoutingProviderSync, getMemoryRoutingConfig, resetMemoryRoutingProvider, initializeMemoryRouting, } from './provider-registry.js';
2
+ export { getMemoryRoutingProvider, getMemoryRoutingProviderSync, ensureMemoryRoutingProviderSync, getMemoryRoutingConfig, resetMemoryRoutingProvider, initializeMemoryRouting, } from './provider-registry.js';
3
3
  export { NullMemoryRoutingProvider, createMemoryRoutingProvider } from './null-provider.js';
4
4
  export { loadMemoryRoutingProvider, isLeftoverMemoryRoutingOff, isExplicitMemoryRoutingOptOut, resolveLeftoverEnabledConfig, } from './provider-loader.js';
5
5
  export { validateMemoryRoutingConfig } from './validate-config.js';
@@ -1,4 +1,4 @@
1
- export { getMemoryRoutingProvider, getMemoryRoutingProviderSync, getMemoryRoutingConfig, resetMemoryRoutingProvider, initializeMemoryRouting, } from './provider-registry.js';
1
+ export { getMemoryRoutingProvider, getMemoryRoutingProviderSync, ensureMemoryRoutingProviderSync, getMemoryRoutingConfig, resetMemoryRoutingProvider, initializeMemoryRouting, } from './provider-registry.js';
2
2
  export { NullMemoryRoutingProvider, createMemoryRoutingProvider } from './null-provider.js';
3
3
  export { loadMemoryRoutingProvider, isLeftoverMemoryRoutingOff, isExplicitMemoryRoutingOptOut, resolveLeftoverEnabledConfig, } from './provider-loader.js';
4
4
  export { validateMemoryRoutingConfig } from './validate-config.js';
@@ -0,0 +1,2 @@
1
+ /** Refresh the Station lessons section from dest. Leaves the rest of the card alone. */
2
+ export declare function writeLessonPickup(projectRoot: string): void;
@@ -0,0 +1,57 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ const START = "<!-- lessons -->";
4
+ const END = "<!-- /lessons -->";
5
+ const DEST = path.join(".xray", "state", "repertoire", "curated_signals.json");
6
+ const FLOOR = 0.55;
7
+ function aboveFloor(value) {
8
+ return typeof value === "number" && value > FLOOR && Math.abs(value - FLOOR) > 1e-4;
9
+ }
10
+ function lessonBlock(signals) {
11
+ const lines = ["## Lessons"];
12
+ const graded = signals.filter((signal) => typeof signal.name === "string"
13
+ && aboveFloor(signal.observation_stats?.avg_confidence)
14
+ && (signal.lessons?.length ?? 0) > 0);
15
+ if (graded.length === 0) {
16
+ lines.push("No graded lines above the floor.");
17
+ return lines.join("\n");
18
+ }
19
+ for (const signal of graded) {
20
+ lines.push(`### ${signal.name}`);
21
+ if (typeof signal.definition === "string" && signal.definition.length > 0) {
22
+ lines.push(signal.definition);
23
+ }
24
+ for (const lesson of signal.lessons ?? []) {
25
+ const text = lesson.text.length > 0 ? lesson.text : "graded";
26
+ lines.push(`- ${lesson.decision} \`${lesson.taskId}\` — ${text}`);
27
+ }
28
+ }
29
+ return lines.join("\n");
30
+ }
31
+ /** Refresh the Station lessons section from dest. Leaves the rest of the card alone. */
32
+ export function writeLessonPickup(projectRoot) {
33
+ const stationPath = path.join(projectRoot, ".xray", "state", "STATION.md");
34
+ if (!fs.existsSync(stationPath))
35
+ return;
36
+ const destPath = path.join(projectRoot, DEST);
37
+ let signals = [];
38
+ if (fs.existsSync(destPath)) {
39
+ try {
40
+ const parsed = JSON.parse(fs.readFileSync(destPath, "utf-8"));
41
+ if (parsed && typeof parsed === "object" && "signals" in parsed && Array.isArray(parsed.signals)) {
42
+ signals = parsed.signals;
43
+ }
44
+ }
45
+ catch {
46
+ signals = [];
47
+ }
48
+ }
49
+ const section = `${START}\n${lessonBlock(signals)}\n${END}`;
50
+ const current = fs.readFileSync(stationPath, "utf-8");
51
+ const pattern = /<!-- lessons -->[\s\S]*?<!-- \/lessons -->/;
52
+ const next = pattern.test(current)
53
+ ? current.replace(pattern, section)
54
+ : `${current.trimEnd()}\n\n${section}\n`;
55
+ if (next !== current)
56
+ fs.writeFileSync(stationPath, next);
57
+ }
@@ -0,0 +1,16 @@
1
+ export declare const PLATE_IDS: readonly ["routing", "governance", "boot", "orchestration", "processor", "reporting", "memory-recall"];
2
+ export type PlateId = (typeof PLATE_IDS)[number];
3
+ export interface PlateStamp {
4
+ id: PlateId;
5
+ body: string;
6
+ sourcePath: string;
7
+ }
8
+ export interface PlateStampResult {
9
+ id: PlateId;
10
+ path: string;
11
+ written: boolean;
12
+ }
13
+ export declare function loadPlate(id: PlateId): PlateStamp;
14
+ export declare function recallPlate(intent: string | null | undefined): PlateStamp | null;
15
+ export declare function stampPlateIfMissing(projectRoot: string, id: PlateId): PlateStampResult;
16
+ export declare function plateStockLine(intent: string | null | undefined): string | null;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Typed plate API. The runtime reader is plates.cjs so Station and the
3
+ * Cursor preCompact hook share one implementation.
4
+ */
5
+ import { createRequire } from 'node:module';
6
+ import { dirname, join } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ export const PLATE_IDS = [
9
+ 'routing',
10
+ 'governance',
11
+ 'boot',
12
+ 'orchestration',
13
+ 'processor',
14
+ 'reporting',
15
+ 'memory-recall',
16
+ ];
17
+ function isPlateId(value) {
18
+ return PLATE_IDS.includes(value);
19
+ }
20
+ function isRuntime(value) {
21
+ if (!value || typeof value !== 'object')
22
+ return false;
23
+ const row = value;
24
+ return (typeof row.loadPlate === 'function'
25
+ && typeof row.recallPlate === 'function'
26
+ && typeof row.stampPlateIfMissing === 'function'
27
+ && typeof row.plateStockLine === 'function'
28
+ && Array.isArray(row.PLATE_IDS));
29
+ }
30
+ function loadRuntime() {
31
+ const require = createRequire(import.meta.url);
32
+ const runtimePath = join(dirname(fileURLToPath(import.meta.url)), '../integrations/hooks/plates.cjs');
33
+ const loaded = require(runtimePath);
34
+ if (!isRuntime(loaded)) {
35
+ throw new Error('plate runtime missing');
36
+ }
37
+ return loaded;
38
+ }
39
+ const runtime = loadRuntime();
40
+ function asStamp(value) {
41
+ if (!value)
42
+ return null;
43
+ if (!isPlateId(value.id) || typeof value.body !== 'string') {
44
+ throw new Error('plate runtime returned an unknown stamp');
45
+ }
46
+ return value;
47
+ }
48
+ export function loadPlate(id) {
49
+ const stamp = asStamp(runtime.loadPlate(id));
50
+ if (!stamp)
51
+ throw new Error(`plate missing: ${id}`);
52
+ return stamp;
53
+ }
54
+ export function recallPlate(intent) {
55
+ return asStamp(runtime.recallPlate(intent));
56
+ }
57
+ export function stampPlateIfMissing(projectRoot, id) {
58
+ const result = runtime.stampPlateIfMissing(projectRoot, id);
59
+ if (!isPlateId(result.id) || typeof result.path !== 'string' || typeof result.written !== 'boolean') {
60
+ throw new Error('plate runtime returned an unknown stamp result');
61
+ }
62
+ return result;
63
+ }
64
+ export function plateStockLine(intent) {
65
+ const line = runtime.plateStockLine(intent);
66
+ if (line === null || line === undefined)
67
+ return null;
68
+ if (typeof line !== 'string')
69
+ throw new Error('plate stock line was not text');
70
+ return line;
71
+ }
@@ -5,4 +5,9 @@ export declare function isExplicitMemoryRoutingOptOut(config: MemoryRoutingConfi
5
5
  * Explicit opt-out is enabled: false + provider: repertoire. */
6
6
  export declare function isLeftoverMemoryRoutingOff(config: MemoryRoutingConfig): boolean;
7
7
  export declare function resolveLeftoverEnabledConfig(config: MemoryRoutingConfig, cwd: string): MemoryRoutingConfig | null;
8
+ /**
9
+ * First route cannot wait on the async import. Node loads this ESM with require.
10
+ * Returns null when routing is off or the module cannot be constructed.
11
+ */
12
+ export declare function loadMemoryRoutingProviderSync(config: MemoryRoutingConfig, cwd?: string): MemoryRoutingProvider | null;
8
13
  export declare function loadMemoryRoutingProvider(config: MemoryRoutingConfig, cwd?: string): Promise<MemoryRoutingProvider>;
@@ -1,4 +1,5 @@
1
1
  import { existsSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
2
3
  import { resolve, dirname, join } from 'node:path';
3
4
  import { pathToFileURL } from 'node:url';
4
5
  import { frameworkLogger } from '../core/framework-logger.js';
@@ -19,6 +20,7 @@ function resolveUnavailableReason(provider) {
19
20
  return null;
20
21
  }
21
22
  }
23
+ const require = createRequire(import.meta.url);
22
24
  const REPERTOIRE_CANDIDATE_PATHS = [
23
25
  'node_modules/@0xray/repertoire/dist/provider/memory-routing-provider.js',
24
26
  'vendor/@0xray/repertoire/dist/provider/memory-routing-provider.js',
@@ -99,6 +101,50 @@ export function resolveLeftoverEnabledConfig(config, cwd) {
99
101
  enabled.config = config.config;
100
102
  return enabled;
101
103
  }
104
+ function providerFromModule(mod, modulePath, effective) {
105
+ const factory = mod.createMemoryRoutingProvider ?? mod.default?.createMemoryRoutingProvider;
106
+ if (typeof factory !== 'function') {
107
+ throw new Error(`Module ${modulePath} must export createMemoryRoutingProvider()`);
108
+ }
109
+ const provider = factory((effective.config ?? {}));
110
+ if (!provider?.id || typeof provider.isAvailable !== 'function') {
111
+ throw new Error(`Invalid memory routing provider from ${modulePath}`);
112
+ }
113
+ return provider;
114
+ }
115
+ function resolveEffectiveProvider(config, cwd) {
116
+ const validation = validateMemoryRoutingConfig(config);
117
+ if (!validation.valid)
118
+ return null;
119
+ const leftover = resolveLeftoverEnabledConfig(validation.normalized, cwd);
120
+ const effective = leftover ?? validation.normalized;
121
+ if (!effective.enabled || effective.provider === 'null')
122
+ return null;
123
+ const modulePath = resolveModulePath(effective.module_path, cwd) ??
124
+ defaultPathForProvider(effective.provider, cwd);
125
+ if (!modulePath)
126
+ return null;
127
+ return { effective, modulePath };
128
+ }
129
+ /**
130
+ * First route cannot wait on the async import. Node loads this ESM with require.
131
+ * Returns null when routing is off or the module cannot be constructed.
132
+ */
133
+ export function loadMemoryRoutingProviderSync(config, cwd = process.cwd()) {
134
+ const resolved = resolveEffectiveProvider(config, cwd);
135
+ if (!resolved)
136
+ return null;
137
+ try {
138
+ const mod = require(resolved.modulePath);
139
+ const provider = providerFromModule(mod, resolved.modulePath, resolved.effective);
140
+ if (!provider.isAvailable())
141
+ return null;
142
+ return provider;
143
+ }
144
+ catch {
145
+ return null;
146
+ }
147
+ }
102
148
  export async function loadMemoryRoutingProvider(config, cwd = process.cwd()) {
103
149
  const validation = validateMemoryRoutingConfig(config);
104
150
  if (!validation.valid) {
@@ -1,8 +1,13 @@
1
1
  import type { MemoryRoutingConfig, MemoryRoutingProvider } from './types.js';
2
2
  export declare function getMemoryRoutingConfig(): MemoryRoutingConfig;
3
3
  export declare function getMemoryRoutingProvider(forceReload?: boolean): Promise<MemoryRoutingProvider>;
4
- /** Synchronous accessor — returns shared null singleton until async load completes. */
4
+ /** Synchronous accessor — returns shared null singleton until a load completes. */
5
5
  export declare function getMemoryRoutingProviderSync(): MemoryRoutingProvider;
6
+ /**
7
+ * The first scoreAndRoute call loads the organ before it routes.
8
+ * A later sync read sees the same provider. Off or a missing module stays null.
9
+ */
10
+ export declare function ensureMemoryRoutingProviderSync(): MemoryRoutingProvider;
6
11
  export declare function resetMemoryRoutingProvider(): void;
7
- /** Kick off background load at module init. */
12
+ /** Kick off load at module init. Sync require first so a later route does not require() an ESM module while its dynamic import is still in flight. */
8
13
  export declare function initializeMemoryRouting(): void;
@@ -1,6 +1,6 @@
1
1
  import { featuresConfigLoader } from '../core/features-config.js';
2
2
  import { NullMemoryRoutingProvider } from './null-provider.js';
3
- import { loadMemoryRoutingProvider, resolveLeftoverEnabledConfig } from './provider-loader.js';
3
+ import { loadMemoryRoutingProvider, loadMemoryRoutingProviderSync, resolveLeftoverEnabledConfig, } from './provider-loader.js';
4
4
  import { validateMemoryRoutingConfig } from './validate-config.js';
5
5
  let cachedProvider = null;
6
6
  let loadPromise = null;
@@ -12,30 +12,50 @@ export function getMemoryRoutingConfig() {
12
12
  validation.normalized);
13
13
  }
14
14
  export async function getMemoryRoutingProvider(forceReload = false) {
15
- if (!forceReload && cachedProvider)
15
+ if (!forceReload && cachedProvider && cachedProvider.id !== 'null')
16
16
  return cachedProvider;
17
17
  if (!forceReload && loadPromise)
18
18
  return loadPromise;
19
19
  const config = getMemoryRoutingConfig();
20
20
  loadPromise = loadMemoryRoutingProvider(config).then((provider) => {
21
+ if (provider.id === 'null' && cachedProvider && cachedProvider.id !== 'null') {
22
+ loadPromise = null;
23
+ return cachedProvider;
24
+ }
21
25
  cachedProvider = provider;
22
26
  loadPromise = null;
23
27
  return provider;
24
28
  });
25
29
  return loadPromise;
26
30
  }
27
- /** Synchronous accessor — returns shared null singleton until async load completes. */
31
+ /** Synchronous accessor — returns shared null singleton until a load completes. */
28
32
  export function getMemoryRoutingProviderSync() {
29
33
  return cachedProvider ?? nullProviderSingleton;
30
34
  }
35
+ /**
36
+ * The first scoreAndRoute call loads the organ before it routes.
37
+ * A later sync read sees the same provider. Off or a missing module stays null.
38
+ */
39
+ export function ensureMemoryRoutingProviderSync() {
40
+ if (cachedProvider && cachedProvider.id !== 'null')
41
+ return cachedProvider;
42
+ const config = getMemoryRoutingConfig();
43
+ if (!config.enabled)
44
+ return cachedProvider ?? nullProviderSingleton;
45
+ const loaded = loadMemoryRoutingProviderSync(config);
46
+ if (!loaded || loaded.id === 'null')
47
+ return cachedProvider ?? nullProviderSingleton;
48
+ cachedProvider = loaded;
49
+ return loaded;
50
+ }
31
51
  export function resetMemoryRoutingProvider() {
32
52
  cachedProvider = null;
33
53
  loadPromise = null;
34
54
  }
35
- /** Kick off background load at module init. */
55
+ /** Kick off load at module init. Sync require first so a later route does not require() an ESM module while its dynamic import is still in flight. */
36
56
  export function initializeMemoryRouting() {
37
- const config = getMemoryRoutingConfig();
38
- if (config.enabled) {
57
+ const loaded = ensureMemoryRoutingProviderSync();
58
+ if (loaded.id === 'null' && getMemoryRoutingConfig().enabled) {
39
59
  void getMemoryRoutingProvider();
40
60
  }
41
61
  }
@@ -0,0 +1,16 @@
1
+ export interface LessonInput {
2
+ operation: string;
3
+ success: boolean;
4
+ taskId: string;
5
+ assignedAgent: string;
6
+ sessionId: string;
7
+ /** Signals the finished task already named. When set, text matching is not used. */
8
+ signals?: string[];
9
+ /** Approaches, solutions, or the wrong turn from the sessions that named the law. */
10
+ lesson?: string;
11
+ }
12
+ /**
13
+ * A finished task names the signals its text actually matched and says
14
+ * whether the outcome was good. That is the one-tenth step. No match, no write.
15
+ */
16
+ export declare function recordLesson(input: LessonInput): string[];
@@ -0,0 +1,26 @@
1
+ import { ensureMemoryRoutingProviderSync } from "./provider-registry.js";
2
+ /**
3
+ * A finished task names the signals its text actually matched and says
4
+ * whether the outcome was good. That is the one-tenth step. No match, no write.
5
+ */
6
+ export function recordLesson(input) {
7
+ const provider = ensureMemoryRoutingProviderSync();
8
+ if (provider.id === "null" || !provider.ingestFeedback)
9
+ return [];
10
+ const named = input.signals ?? provider.buildRoutingContext(input.operation).matchedSignals;
11
+ const names = [...new Set(named.filter((name) => name.length > 0))];
12
+ if (names.length === 0)
13
+ return [];
14
+ provider.ingestFeedback({
15
+ timestamp: new Date().toISOString(),
16
+ sessionId: input.sessionId,
17
+ taskId: input.taskId,
18
+ assignedAgent: input.assignedAgent,
19
+ memorySignals: names,
20
+ complexity: 0,
21
+ success: input.success,
22
+ durationMs: 0,
23
+ lesson: input.lesson ?? "",
24
+ });
25
+ return names;
26
+ }
@@ -33,6 +33,19 @@ export interface MemoryTaskConfidence {
33
33
  complexityBoost: number;
34
34
  recommendedAgent: string | null;
35
35
  }
36
+ /** Characters kept on one graded lesson line. lessonTrace stores with this cap. */
37
+ export declare const LESSON_TEXT_CAP = 400;
38
+ export interface MemoryLessonLine {
39
+ taskId: string;
40
+ decision: 'success' | 'failure';
41
+ text: string;
42
+ at: string;
43
+ }
44
+ export interface MemorySignalLessons {
45
+ name: string;
46
+ definition: string;
47
+ lines: MemoryLessonLine[];
48
+ }
36
49
  export interface MemoryRoutingContext {
37
50
  providerId: string;
38
51
  matchedSignals: string[];
@@ -41,6 +54,8 @@ export interface MemoryRoutingContext {
41
54
  synthesisAvailable: boolean;
42
55
  signalConfidences?: Record<string, number>;
43
56
  avgMatchConfidence?: number;
57
+ /** Laws above the floor, with the graded lines a wake reads. */
58
+ lessons?: MemorySignalLessons[];
44
59
  }
45
60
  export interface MemoryInheritedContext {
46
61
  providerId: string;
@@ -67,6 +82,8 @@ export interface OrchestratorFeedbackEntry {
67
82
  success: boolean;
68
83
  durationMs: number;
69
84
  dynamoResult?: Record<string, unknown>;
85
+ /** Approaches, solutions, or the wrong turn from the sessions that named the law. */
86
+ lesson?: string;
70
87
  }
71
88
  export interface MemoryRoutingProviderConfig {
72
89
  [key: string]: unknown;
@@ -4,4 +4,5 @@
4
4
  * Repertoire is one implementation. Any provider exposing createMemoryRoutingProvider()
5
5
  * can be loaded via features.json memory_routing.module_path.
6
6
  */
7
- export {};
7
+ /** Characters kept on one graded lesson line. lessonTrace stores with this cap. */
8
+ export const LESSON_TEXT_CAP = 400;
@@ -7,6 +7,7 @@
7
7
  * Import from here or from delegation/index.ts (which re-exports these).
8
8
  */
9
9
  import { ComplexityMetrics, ComplexityScore, ComplexityLevel, ComplexityThresholds } from "../delegation/complexity-core.js";
10
+ import type { MemorySignalLessons } from "../memory-routing/types.js";
10
11
  export type { ComplexityMetrics, ComplexityScore, ComplexityLevel, ComplexityThresholds };
11
12
  export declare const NUCLEUS_THIN_DISPATCH_VERSION = "0.1.0";
12
13
  /**
@@ -29,5 +30,6 @@ export declare function scoreAndRoute(operation: string, context: unknown, thres
29
30
  providerId: string;
30
31
  adjustedScore: number;
31
32
  signals: string[];
33
+ lessons?: MemorySignalLessons[];
32
34
  };
33
35
  };
@@ -9,7 +9,7 @@
9
9
  import { frameworkLogger } from "../core/framework-logger.js";
10
10
  import { getLevelFromScore, getAgentForTier, getAgentCountForLevel, getStrategyForLevel, levelToTier, } from "../delegation/complexity-core.js";
11
11
  import { ComplexityAnalyzer } from "../delegation/complexity-analyzer.js";
12
- import { getMemoryRoutingProviderSync, initializeMemoryRouting, } from "../memory-routing/index.js";
12
+ import { ensureMemoryRoutingProviderSync, initializeMemoryRouting, } from "../memory-routing/index.js";
13
13
  initializeMemoryRouting();
14
14
  export const NUCLEUS_THIN_DISPATCH_VERSION = "0.1.0";
15
15
  /**
@@ -58,7 +58,7 @@ export function routeToAgent(scoreOrLevel, level) {
58
58
  export function scoreAndRoute(operation, context, thresholds) {
59
59
  const score = scoreComplexity(operation, context, thresholds);
60
60
  let agent = routeToAgent(score);
61
- const provider = getMemoryRoutingProviderSync();
61
+ const provider = ensureMemoryRoutingProviderSync();
62
62
  let memoryRouting;
63
63
  if (provider.id !== "null") {
64
64
  const resolved = provider.resolveThinDispatch(agent, operation, score.score);
@@ -67,6 +67,7 @@ export function scoreAndRoute(operation, context, thresholds) {
67
67
  providerId: resolved.context.providerId,
68
68
  adjustedScore: resolved.adjustedScore,
69
69
  signals: resolved.context.matchedSignals,
70
+ ...(resolved.context.lessons ? { lessons: resolved.context.lessons } : {}),
70
71
  };
71
72
  const adjustedLevel = getLevelFromScore(resolved.adjustedScore, thresholds);
72
73
  frameworkLogger.log("nucleus-thin-dispatch", "memory-routing", "info", {
@@ -33,4 +33,9 @@ export declare class InferenceImprovementProcessor extends PostProcessor {
33
33
  private generateCodeReviewerPrompt;
34
34
  private generateEnforcerPrompt;
35
35
  private triggerAgentWorkflow;
36
+ /**
37
+ * The operation is finished once its prompt result text is on disk.
38
+ * One session per operation id. Empty speech writes nothing.
39
+ */
40
+ private captureFinishedOperationSpeech;
36
41
  }