@joshuaswarren/openclaw-engram 9.0.88 → 9.0.91

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -134,7 +134,24 @@ Run the stdio MCP server:
134
134
  openclaw engram access mcp-serve
135
135
  ```
136
136
 
137
- Point your MCP client's command at `openclaw engram access mcp-serve`. Works with Claude Code, and any other MCP-compatible client. The server exposes the same 8 tools as the HTTP endpoint.
137
+ Point your MCP client's command at `openclaw engram access mcp-serve`. Works with Claude Code, and any other MCP-compatible client. The server exposes the same tools as the HTTP endpoint.
138
+
139
+ **Claude Code (MCP over HTTP):** Start the Engram HTTP server, then add to `~/.claude.json`:
140
+
141
+ ```json
142
+ {
143
+ "mcpServers": {
144
+ "engram": {
145
+ "url": "http://localhost:4318/mcp",
146
+ "headers": {
147
+ "Authorization": "Bearer ${ENGRAM_TOKEN}"
148
+ }
149
+ }
150
+ }
151
+ }
152
+ ```
153
+
154
+ See the [Standalone Server Guide](docs/guides/standalone-server.md) for multi-tenant setups and connecting multiple agent harnesses.
138
155
 
139
156
  ## How It Works
140
157
 
@@ -202,6 +219,10 @@ Use a preset to jump to a recommended level: `conservative`, `balanced`, `resear
202
219
  - **Scripts & automation** — Authenticated REST API for custom integrations
203
220
  - **Local LLMs** — Run extraction and reranking with local models (Ollama, LM Studio, etc.)
204
221
 
222
+ ### Standalone Multi-Tenant Server
223
+
224
+ Run Engram as a standalone HTTP server that multiple agent harnesses share. Isolate tenants with namespace policies, feed conversations from any client via the observe endpoint, and search archived history with LCM full-text search. Works with OpenClaw, Codex CLI, Claude Code, and custom HTTP agents. See the [Standalone Server Guide](docs/guides/standalone-server.md).
225
+
205
226
  ### Built for production
206
227
 
207
228
  - **672 tests** with CI enforcement
@@ -215,6 +236,7 @@ Use a preset to jump to a recommended level: `conservative`, `balanced`, `resear
215
236
  ### Core
216
237
 
217
238
  - **Automatic memory extraction** — Facts, decisions, preferences, corrections extracted from conversations
239
+ - **Observe endpoint** — Feed conversation messages from any agent into the extraction pipeline via HTTP or MCP
218
240
  - **Recall injection** — Relevant memories injected before each agent turn
219
241
  - **Entity tracking** — People, projects, tools, companies tracked as structured entities
220
242
  - **Lifecycle management** — Memories age through active, validated, stale, archived states
@@ -320,6 +342,8 @@ Available via both stdio and HTTP transports:
320
342
  | `engram.suggestion_submit` | Queue a memory for review |
321
343
  | `engram.entity_get` | Look up a known entity |
322
344
  | `engram.review_queue_list` | View the governance review queue |
345
+ | `engram.observe` | Feed conversation messages into memory pipeline (LCM + extraction) |
346
+ | `engram.lcm_search` | Full-text search over LCM-archived conversations |
323
347
  | `engram_context_search` | Full-text search across all archived conversation history (LCM) |
324
348
  | `engram_context_describe` | Get a compressed summary of a turn range (LCM) |
325
349
  | `engram_context_expand` | Retrieve raw lossless messages for a turn range (LCM) |
@@ -395,6 +419,7 @@ All settings live in `openclaw.json` under `plugins.entries.openclaw-engram.conf
395
419
  - [Writing a Search Backend](docs/writing-a-search-backend.md) — Build your own adapter
396
420
  - [API Reference](docs/api.md) — HTTP, MCP, and CLI documentation
397
421
  - [Codex CLI Integration](docs/guides/codex-cli.md) — Setup Engram with OpenAI's Codex
422
+ - [Standalone Server Guide](docs/guides/standalone-server.md) — Multi-tenant HTTP server for multiple agent harnesses
398
423
  - [Local LLM Guide](docs/guides/local-llm.md) — Local-first extraction and reranking
399
424
  - [Cost Control Guide](docs/guides/cost-control.md) — Budget mappings and presets
400
425
  - [Namespaces](docs/namespaces.md) — Multi-agent memory isolation
@@ -109,6 +109,7 @@ var SPECULATIVE_TTL_DAYS = 30;
109
109
 
110
110
  // src/memory-projection-store.ts
111
111
  import path2 from "path";
112
+ import { readFileSync } from "fs";
112
113
  import Database from "better-sqlite3";
113
114
  var MEMORY_PROJECTION_SCHEMA_VERSION = 2;
114
115
  function getMemoryProjectionPath(memoryDir) {
@@ -453,9 +454,6 @@ function readProjectedMemoryTimeline(memoryDir, memoryId, limit) {
453
454
  function readProjectedMemoryBrowse(memoryDir, options) {
454
455
  return withProjectionReadonly(memoryDir, (db) => {
455
456
  const normalizedQuery = options.query?.trim().toLowerCase() ?? "";
456
- if (normalizedQuery) {
457
- return null;
458
- }
459
457
  const currentSelect = memoryCurrentSelectExpressions(db);
460
458
  const whereClauses = [];
461
459
  const params = [];
@@ -482,6 +480,57 @@ function readProjectedMemoryBrowse(memoryDir, options) {
482
480
  }
483
481
  })();
484
482
  const whereSql = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
483
+ if (normalizedQuery) {
484
+ const allRows = db.prepare(`
485
+ SELECT
486
+ memory_id,
487
+ path_rel,
488
+ category,
489
+ status,
490
+ created_at,
491
+ updated_at,
492
+ entity_ref,
493
+ ${currentSelect.tagsJson},
494
+ ${currentSelect.previewText}
495
+ FROM memory_current
496
+ ${whereSql}
497
+ ORDER BY ${orderBySql}
498
+ `).all(...params);
499
+ const filtered = allRows.filter((row) => {
500
+ if (typeof row.memory_id !== "string" || typeof row.path_rel !== "string") return false;
501
+ const preview = typeof row.preview_text === "string" ? row.preview_text.toLowerCase() : "";
502
+ const category = typeof row.category === "string" ? row.category.toLowerCase() : "";
503
+ const entityRef = typeof row.entity_ref === "string" ? row.entity_ref.toLowerCase() : "";
504
+ const tags = typeof row.tags_json === "string" ? row.tags_json.toLowerCase() : "";
505
+ if (preview.includes(normalizedQuery) || category.includes(normalizedQuery) || entityRef.includes(normalizedQuery) || tags.includes(normalizedQuery)) {
506
+ return true;
507
+ }
508
+ try {
509
+ const filePath = path2.join(memoryDir, row.path_rel);
510
+ const content = readFileSync(filePath, "utf-8").toLowerCase();
511
+ return content.includes(normalizedQuery);
512
+ } catch {
513
+ return false;
514
+ }
515
+ });
516
+ const pageRows = filtered.slice(options.offset, options.offset + options.limit);
517
+ return {
518
+ total: filtered.length,
519
+ memories: pageRows.filter(
520
+ (row) => typeof row.memory_id === "string" && typeof row.path_rel === "string" && typeof row.category === "string" && typeof row.status === "string"
521
+ ).map((row) => ({
522
+ id: row.memory_id,
523
+ path: path2.join(memoryDir, row.path_rel),
524
+ category: row.category,
525
+ status: row.status,
526
+ created: typeof row.created_at === "string" ? row.created_at : void 0,
527
+ updated: typeof row.updated_at === "string" ? row.updated_at : void 0,
528
+ tags: parseStringArray(row.tags_json),
529
+ entityRef: typeof row.entity_ref === "string" ? row.entity_ref : void 0,
530
+ preview: typeof row.preview_text === "string" ? row.preview_text : ""
531
+ }))
532
+ };
533
+ }
485
534
  const totalRow = db.prepare(`SELECT COUNT(*) AS total FROM memory_current ${whereSql}`).get(...params);
486
535
  const rows = db.prepare(`
487
536
  SELECT
@@ -1167,9 +1216,29 @@ function serializeFrontmatter(fm) {
1167
1216
  if (fm.sourceMemoryId) lines.push(`sourceMemoryId: ${fm.sourceMemoryId}`);
1168
1217
  if (fm.sourceTurnId) lines.push(`sourceTurnId: ${fm.sourceTurnId}`);
1169
1218
  if (fm.memoryKind) lines.push(`memoryKind: ${fm.memoryKind}`);
1219
+ if (fm.structuredAttributes && Object.keys(fm.structuredAttributes).length > 0) {
1220
+ lines.push(`structuredAttributes: ${JSON.stringify(fm.structuredAttributes)}`);
1221
+ }
1170
1222
  lines.push("---");
1171
1223
  return lines.join("\n");
1172
1224
  }
1225
+ function parseStructuredAttributes(raw) {
1226
+ if (!raw || !raw.trim()) return void 0;
1227
+ try {
1228
+ const parsed = JSON.parse(raw);
1229
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
1230
+ const result = {};
1231
+ for (const [k, v] of Object.entries(parsed)) {
1232
+ if (typeof k === "string" && typeof v === "string") {
1233
+ result[k] = v;
1234
+ }
1235
+ }
1236
+ return Object.keys(result).length > 0 ? result : void 0;
1237
+ }
1238
+ } catch {
1239
+ }
1240
+ return void 0;
1241
+ }
1173
1242
  function parseLinkReasonValue(rawValue) {
1174
1243
  const legacyValue = rawValue.replace(/\\"/g, '"');
1175
1244
  const looksLikeLegacyPath = !rawValue.includes("\\\\") && (/[A-Za-z]:\\[A-Za-z0-9._ -]+(?:\\[A-Za-z0-9._ -]+)*/.test(rawValue) || /\\[A-Za-z0-9._ -]+\\[A-Za-z0-9._ -]+/.test(rawValue));
@@ -1282,7 +1351,9 @@ function parseFrontmatter2(raw) {
1282
1351
  sourceMemoryId: fm.sourceMemoryId || void 0,
1283
1352
  sourceTurnId: fm.sourceTurnId || void 0,
1284
1353
  // v8.0 Phase 2B: HiMem episode/note classification
1285
- memoryKind: fm.memoryKind || void 0
1354
+ memoryKind: fm.memoryKind || void 0,
1355
+ // Structured attributes (JSON on a single line)
1356
+ structuredAttributes: parseStructuredAttributes(fm.structuredAttributes)
1286
1357
  },
1287
1358
  content
1288
1359
  };
@@ -1763,9 +1834,16 @@ var StorageManager = class _StorageManager {
1763
1834
  artifactType: options.artifactType,
1764
1835
  sourceMemoryId: options.sourceMemoryId,
1765
1836
  sourceTurnId: options.sourceTurnId,
1766
- memoryKind: options.memoryKind
1837
+ memoryKind: options.memoryKind,
1838
+ structuredAttributes: options.structuredAttributes
1767
1839
  };
1768
- const sanitized = sanitizeMemoryContent(content);
1840
+ let enrichedContent = content;
1841
+ if (options.structuredAttributes && Object.keys(options.structuredAttributes).length > 0) {
1842
+ const attrLines = Object.entries(options.structuredAttributes).map(([k, v]) => `${k}: ${v}`).join("; ");
1843
+ enrichedContent = `${content}
1844
+ [Attributes: ${attrLines}]`;
1845
+ }
1846
+ const sanitized = sanitizeMemoryContent(enrichedContent);
1769
1847
  if (!sanitized.clean) {
1770
1848
  log.warn(`memory content sanitized for ${id}; violations=${sanitized.violations.join(", ")}`);
1771
1849
  }
@@ -3810,4 +3888,4 @@ export {
3810
3888
  serializeEntityFile,
3811
3889
  StorageManager
3812
3890
  };
3813
- //# sourceMappingURL=chunk-C26MLXQM.js.map
3891
+ //# sourceMappingURL=chunk-DDJJSZIV.js.map