@remnic/core 9.16.1 → 9.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -100,7 +100,7 @@ import {
100
100
  validateTaxonomy,
101
101
  watchForChanges,
102
102
  writeManifest
103
- } from "./chunk-GRJ5UOFO.js";
103
+ } from "./chunk-YHURQ3M5.js";
104
104
  import {
105
105
  WEARABLE_SOURCE_PREFIX,
106
106
  buildExtractionTurns,
@@ -41,7 +41,7 @@ import {
41
41
  throwIfRecallAborted,
42
42
  tokenizeRecallQuery,
43
43
  utcDateKeysForLocalDay
44
- } from "./chunk-GRJ5UOFO.js";
44
+ } from "./chunk-YHURQ3M5.js";
45
45
  import "./chunk-5SB7SC2G.js";
46
46
  import {
47
47
  formatCompressionGuidelinesForRecall
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/core",
3
- "version": "9.16.1",
3
+ "version": "9.16.2",
4
4
  "description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -3045,7 +3045,7 @@
3045
3045
  "core"
3046
3046
  ],
3047
3047
  "peerDependencies": {
3048
- "@remnic/coding-graph": "^9.16.1"
3048
+ "@remnic/coding-graph": "^9.16.2"
3049
3049
  },
3050
3050
  "peerDependenciesMeta": {
3051
3051
  "@remnic/coding-graph": {
@@ -0,0 +1,137 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import http from "node:http";
4
+ import { mkdtemp, rm } from "node:fs/promises";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+
8
+ import { parseConfig } from "./config.js";
9
+ import { Orchestrator } from "./orchestrator.js";
10
+
11
+ type SummarizeFn = (text: string, targetTokens: number, aggressive: boolean) => Promise<string | null>;
12
+
13
+ interface LcmEngineTestSurface {
14
+ summarizeFn: SummarizeFn;
15
+ }
16
+
17
+ interface ChatStub {
18
+ url: string;
19
+ hits: () => number;
20
+ close: () => Promise<void>;
21
+ }
22
+
23
+ /**
24
+ * Minimal OpenAI-compatible chat stub that records how often it served a
25
+ * completion. Both lanes get one so the test can observe which client the
26
+ * LCM summarize closure actually uses.
27
+ */
28
+ async function startChatStub(label: string): Promise<ChatStub> {
29
+ let hits = 0;
30
+ const server = http.createServer((req, res) => {
31
+ let body = "";
32
+ req.on("data", (chunk) => {
33
+ body += chunk;
34
+ });
35
+ req.on("end", () => {
36
+ if (req.url?.endsWith("/chat/completions")) {
37
+ hits += 1;
38
+ res.setHeader("content-type", "application/json");
39
+ res.end(
40
+ JSON.stringify({
41
+ choices: [{ message: { role: "assistant", content: `summary from ${label}` }, finish_reason: "stop" }],
42
+ usage: { prompt_tokens: 10, completion_tokens: 4, total_tokens: 14 },
43
+ }),
44
+ );
45
+ return;
46
+ }
47
+ // Model detection / health probes: report a single model per lane.
48
+ res.setHeader("content-type", "application/json");
49
+ res.end(JSON.stringify({ data: [{ id: `${label}-model` }], object: "list" }));
50
+ });
51
+ });
52
+ const listening = Promise.withResolvers<void>();
53
+ server.once("error", listening.reject);
54
+ server.listen(0, "127.0.0.1", listening.resolve);
55
+ try {
56
+ await listening.promise;
57
+ } catch (err) {
58
+ server.close();
59
+ throw err;
60
+ }
61
+ const address = server.address();
62
+ if (address === null || typeof address !== "object") {
63
+ server.close();
64
+ throw new Error("stub did not bind");
65
+ }
66
+ return {
67
+ url: `http://127.0.0.1:${address.port}/v1`,
68
+ hits: () => hits,
69
+ close: () => {
70
+ const closed = Promise.withResolvers<void>();
71
+ server.close(() => closed.resolve());
72
+ return closed.promise;
73
+ },
74
+ };
75
+ }
76
+
77
+ test("LCM summarize uses the fast local-LLM lane when configured (latency contract)", async () => {
78
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-lcm-lane-"));
79
+ let mainStub: ChatStub | null = null;
80
+ let fastStub: ChatStub | null = null;
81
+ try {
82
+ mainStub = await startChatStub("main");
83
+ fastStub = await startChatStub("fast");
84
+ const config = parseConfig({
85
+ memoryDir,
86
+ qmdEnabled: false,
87
+ lcmEnabled: true,
88
+ localLlmEnabled: true,
89
+ localLlmUrl: mainStub.url,
90
+ localLlmModel: "main-model",
91
+ localLlmFastEnabled: true,
92
+ localLlmFastUrl: fastStub.url,
93
+ localLlmFastModel: "fast-model",
94
+ });
95
+ const orchestrator = new Orchestrator(config);
96
+ assert.ok(orchestrator.lcmEngine, "LCM engine must exist with lcmEnabled");
97
+ const summarizeFn = (orchestrator.lcmEngine as unknown as LcmEngineTestSurface).summarizeFn;
98
+
99
+ const result = await summarizeFn("A conversation segment worth compressing.", 64, false);
100
+
101
+ assert.equal(result, "summary from fast", "the summary must come from the fast lane");
102
+ assert.equal(fastStub.hits(), 1, "fast lane serves the LCM summarize call");
103
+ assert.equal(mainStub.hits(), 0, "the heavy main extraction lane must not serve lcm-summarize");
104
+ } finally {
105
+ await mainStub?.close();
106
+ await fastStub?.close();
107
+ await rm(memoryDir, { recursive: true, force: true });
108
+ }
109
+ });
110
+
111
+ test("LCM summarize falls back to the main client when the fast lane is disabled", async () => {
112
+ const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-lcm-lane-fallback-"));
113
+ let mainStub: ChatStub | null = null;
114
+ try {
115
+ mainStub = await startChatStub("main");
116
+ const config = parseConfig({
117
+ memoryDir,
118
+ qmdEnabled: false,
119
+ lcmEnabled: true,
120
+ localLlmEnabled: true,
121
+ localLlmUrl: mainStub.url,
122
+ localLlmModel: "main-model",
123
+ localLlmFastEnabled: false,
124
+ });
125
+ const orchestrator = new Orchestrator(config);
126
+ assert.ok(orchestrator.lcmEngine, "LCM engine must exist with lcmEnabled");
127
+ const summarizeFn = (orchestrator.lcmEngine as unknown as LcmEngineTestSurface).summarizeFn;
128
+
129
+ const result = await summarizeFn("A conversation segment worth compressing.", 64, false);
130
+
131
+ assert.equal(result, "summary from main", "with no fast lane the main client serves the call");
132
+ assert.equal(mainStub.hits(), 1);
133
+ } finally {
134
+ await mainStub?.close();
135
+ await rm(memoryDir, { recursive: true, force: true });
136
+ }
137
+ });
@@ -1724,7 +1724,7 @@ export class Orchestrator {
1724
1724
  ? { agentId: this.config.fastGatewayAgentId }
1725
1725
  : gatewayTaskChainOptions(this.config)),
1726
1726
  })
1727
- : await this.localLlm.chatCompletion(messages, {
1727
+ : await this.fastLlm.chatCompletion(messages, {
1728
1728
  maxTokens: targetTokens * 2,
1729
1729
  operation: "lcm-summarize",
1730
1730
  priority: "background",