@pyxmate/memory 0.24.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -0
- package/dist/{chunk-4DRKAXJE.mjs → chunk-47O2JYAP.mjs} +1 -1
- package/dist/{chunk-D5PMQYZM.mjs → chunk-N6IRAUXH.mjs} +44 -2
- package/dist/cli/pyx-mem.mjs +164 -14
- package/dist/dashboard.mjs +2 -2
- package/dist/index.d.ts +138 -9
- package/dist/index.mjs +28 -2
- package/dist/react.mjs +2 -2
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -36,6 +36,15 @@ Restart Claude Code. The 7 memory tools (`search_memories`, `store_memory`,
|
|
|
36
36
|
`get_memory`, `list_memories`, `delete_memory`, `ingest_memory_file`,
|
|
37
37
|
`summarize_memory_entity`) are auto-discovered via MCP Tool Search.
|
|
38
38
|
|
|
39
|
+
**That's the whole setup** — no extra LLM API keys. When the agent calls
|
|
40
|
+
`store_memory` without supplying `entities`/`relationships`, the MCP tool
|
|
41
|
+
auto-extracts a graph topology by asking the agent's own LLM via MCP sampling
|
|
42
|
+
(same pattern as image-description / `extractEntitiesV2` for file ingest).
|
|
43
|
+
Your LLM, your credentials. Pass `extractEntities: false` on a per-call basis
|
|
44
|
+
to opt out of auto-extraction. See
|
|
45
|
+
[`graph-auto-entity-extraction-v2`](https://github.com/pyx-corp/pyx-memory-v1/blob/main/docs/specs/graph-auto-entity-extraction-v2/spec.md)
|
|
46
|
+
for the full contract.
|
|
47
|
+
|
|
39
48
|
Drop the [agent-template snippet](https://github.com/pyx-corp/pyx-memory-v1/blob/main/docs/agent-template.md)
|
|
40
49
|
into your project's `CLAUDE.md` / `AGENTS.md` to tell the agent WHEN to search
|
|
41
50
|
vs store.
|
|
@@ -59,6 +68,36 @@ await memory.store({
|
|
|
59
68
|
const results = await memory.search({ query: 'deadline', limit: 5 });
|
|
60
69
|
```
|
|
61
70
|
|
|
71
|
+
### Optional: client-side auto-extraction
|
|
72
|
+
|
|
73
|
+
`MemoryClient.store(entry, options)` accepts an `enrichment.extractEntities`
|
|
74
|
+
callback. The SDK invokes your callback (running against **your own** LLM
|
|
75
|
+
credentials), merges the result with any caller-supplied entities using
|
|
76
|
+
case-insensitive caller-wins, and sends the populated graph data to the server.
|
|
77
|
+
Mirrors the existing file-ingest `extractEntitiesV2` pattern.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
await memory.store(
|
|
81
|
+
{ content: 'Andrej Karpathy joined OpenAI as a co-founder in late 2015.',
|
|
82
|
+
metadata: { topic: 'history', project: 'orca' } },
|
|
83
|
+
{
|
|
84
|
+
enrichment: {
|
|
85
|
+
extractEntities: async ({ content, signal }) => {
|
|
86
|
+
// Call YOUR LLM here. Return { entities, relations } matching the
|
|
87
|
+
// EntityType/RelationType vocabularies. Throw on parse failures —
|
|
88
|
+
// errors propagate to the caller, no silent swallow.
|
|
89
|
+
return { entities: [...], relations: [...] };
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
signal, // AbortSignal forwarded into both the callback and the fetch
|
|
93
|
+
},
|
|
94
|
+
);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Per-call `entry.extractEntities: false` skips the callback entirely;
|
|
98
|
+
`entry.extractEntities: true` with no callback throws a loud error (no silent
|
|
99
|
+
no-op). The server never sees an LLM API key — it just persists what you send.
|
|
100
|
+
|
|
62
101
|
## Entry Points
|
|
63
102
|
|
|
64
103
|
| Import | What you get |
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
// ../client/src/memory-client.ts
|
|
2
|
+
import {
|
|
3
|
+
mergeExtractedEntities
|
|
4
|
+
} from "@pyx-memory/shared";
|
|
2
5
|
var MemoryServerError = class extends Error {
|
|
3
6
|
status;
|
|
4
7
|
constructor(message, status) {
|
|
@@ -50,10 +53,34 @@ var MemoryClient = class {
|
|
|
50
53
|
throw new Error(`Memory server not reachable at ${this.baseUrl}: ${response.status}`);
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
|
-
async store(entry) {
|
|
56
|
+
async store(entry, options) {
|
|
57
|
+
const callback = options?.enrichment?.extractEntities;
|
|
58
|
+
const optedOut = entry.extractEntities === false;
|
|
59
|
+
const wantsForced = entry.extractEntities === true;
|
|
60
|
+
if (wantsForced && !callback) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
"extractEntities=true requested but no enrichment.extractEntities callback was provided. Pass options.enrichment.extractEntities, or set entry.extractEntities=false to skip."
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
let payload = entry;
|
|
66
|
+
if (callback && !optedOut) {
|
|
67
|
+
const extracted = await callback({
|
|
68
|
+
content: entry.content,
|
|
69
|
+
...entry.metadata ? { metadata: entry.metadata } : {},
|
|
70
|
+
...options?.signal ? { signal: options.signal } : {}
|
|
71
|
+
});
|
|
72
|
+
const merged = mergeExtractedEntities(entry.entities, entry.relationships, extracted);
|
|
73
|
+
const { extractEntities: _drop, ...rest } = entry;
|
|
74
|
+
payload = {
|
|
75
|
+
...rest,
|
|
76
|
+
entities: merged.entities,
|
|
77
|
+
relationships: merged.relationships
|
|
78
|
+
};
|
|
79
|
+
}
|
|
54
80
|
return this.fetchApi("/api/memory/ingest", {
|
|
55
81
|
method: "POST",
|
|
56
|
-
body: JSON.stringify(
|
|
82
|
+
body: JSON.stringify(payload),
|
|
83
|
+
...options?.signal ? { signal: options.signal } : {}
|
|
57
84
|
});
|
|
58
85
|
}
|
|
59
86
|
async search(params) {
|
|
@@ -101,6 +128,18 @@ var MemoryClient = class {
|
|
|
101
128
|
const stats = await this.fetchApi("/api/memory/stats");
|
|
102
129
|
return { ...stats, connected: true };
|
|
103
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Fetch the running server's topology snapshot (build variant, declared
|
|
133
|
+
* role, embedding location, active model profile). Round-trips the
|
|
134
|
+
* server's `GET /status` envelope through {@link fetchApi}, surfacing any
|
|
135
|
+
* non-success response as {@link MemoryServerError}. Auth-header
|
|
136
|
+
* forwarding is unchanged from other client methods even though `/status`
|
|
137
|
+
* is server-side public — the server simply ignores credentials on that
|
|
138
|
+
* route.
|
|
139
|
+
*/
|
|
140
|
+
async status() {
|
|
141
|
+
return this.fetchApi("/status");
|
|
142
|
+
}
|
|
104
143
|
async shutdown() {
|
|
105
144
|
}
|
|
106
145
|
async list(params = {}) {
|
|
@@ -109,6 +148,9 @@ var MemoryClient = class {
|
|
|
109
148
|
if (params.limit != null) searchParams.set("limit", String(params.limit));
|
|
110
149
|
if (params.type) searchParams.set("type", params.type);
|
|
111
150
|
if (params.agentId) searchParams.set("agentId", params.agentId);
|
|
151
|
+
if (params.includeKinds && params.includeKinds.length > 0) {
|
|
152
|
+
searchParams.set("includeKinds", params.includeKinds.join(","));
|
|
153
|
+
}
|
|
112
154
|
const qs = searchParams.toString();
|
|
113
155
|
return this.fetchApi(`/api/memory/entries${qs ? `?${qs}` : ""}`);
|
|
114
156
|
}
|
package/dist/cli/pyx-mem.mjs
CHANGED
|
@@ -517,6 +517,44 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
517
517
|
// src/mcp/instructions.ts
|
|
518
518
|
var PYX_MEMORY_INSTRUCTIONS = `Use pyx-memory to search durable project/user memory before assuming prior decisions, and to store concise facts after corrections, bug fixes, design decisions, integration discoveries, gotchas, explicit preferences, or "remember this" requests. Store decisions, not deliberation. Include topic and project. Add entities/relationships for named people, tools, organizations, locations, or events. Use file ingest for documents/images worth persisting; images require a description.`;
|
|
519
519
|
|
|
520
|
+
// src/mcp/sampling.ts
|
|
521
|
+
function createSamplingClient(server) {
|
|
522
|
+
function isAvailable() {
|
|
523
|
+
const caps = server.server.getClientCapabilities();
|
|
524
|
+
return Boolean(caps?.sampling);
|
|
525
|
+
}
|
|
526
|
+
return {
|
|
527
|
+
isAvailable,
|
|
528
|
+
async complete(prompt, opts) {
|
|
529
|
+
if (!isAvailable()) {
|
|
530
|
+
throw new Error(
|
|
531
|
+
"MCP sampling unavailable: the connected client did not advertise the `sampling` capability. Either supply entities/relationships explicitly or set extractEntities:false on the request."
|
|
532
|
+
);
|
|
533
|
+
}
|
|
534
|
+
const result = await server.server.createMessage(
|
|
535
|
+
{
|
|
536
|
+
messages: [{ role: "user", content: { type: "text", text: prompt } }],
|
|
537
|
+
maxTokens: opts?.maxTokens ?? 2048
|
|
538
|
+
},
|
|
539
|
+
opts?.signal ? { signal: opts.signal } : void 0
|
|
540
|
+
);
|
|
541
|
+
const content = result.content;
|
|
542
|
+
if (content && typeof content === "object" && !Array.isArray(content)) {
|
|
543
|
+
const block = content;
|
|
544
|
+
if (block.type === "text" && typeof block.text === "string") return block.text;
|
|
545
|
+
}
|
|
546
|
+
if (Array.isArray(content)) {
|
|
547
|
+
for (const block of content) {
|
|
548
|
+
if (block && typeof block === "object" && block.type === "text" && typeof block.text === "string") {
|
|
549
|
+
return block.text;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
throw new Error("MCP sampling returned no text content");
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
|
|
520
558
|
// ../../node_modules/zod/v4/classic/external.js
|
|
521
559
|
var external_exports = {};
|
|
522
560
|
__export(external_exports, {
|
|
@@ -15343,6 +15381,83 @@ var searchMemoriesTool = {
|
|
|
15343
15381
|
}
|
|
15344
15382
|
};
|
|
15345
15383
|
|
|
15384
|
+
// src/mcp/tools/status.ts
|
|
15385
|
+
var inputShape6 = {};
|
|
15386
|
+
var statusTool = {
|
|
15387
|
+
name: "status",
|
|
15388
|
+
config: {
|
|
15389
|
+
title: "Get pyx-memory topology",
|
|
15390
|
+
description: "Fetch the running pyx-memory server topology: build variant (cloud|full), declared deployment role, server version, embedding location (inline|remote), and active model profile. HTTP proxy to GET /status \u2014 no in-process branch.",
|
|
15391
|
+
inputSchema: inputShape6,
|
|
15392
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
15393
|
+
},
|
|
15394
|
+
handler: (deps) => async () => {
|
|
15395
|
+
const creds = await deps.readCredentials();
|
|
15396
|
+
if (!creds.ok) return creds.result;
|
|
15397
|
+
const http = createHttpClient(creds.credentials, deps.fetchImpl);
|
|
15398
|
+
const res = await http.requestJson({ method: "GET", path: "/status" });
|
|
15399
|
+
return res.ok ? mcpJson(res.data) : res.result;
|
|
15400
|
+
}
|
|
15401
|
+
};
|
|
15402
|
+
|
|
15403
|
+
// src/mcp/tools/store.ts
|
|
15404
|
+
import { mergeExtractedEntities } from "@pyx-memory/shared";
|
|
15405
|
+
|
|
15406
|
+
// src/mcp/extraction-prompt.ts
|
|
15407
|
+
var ENTITY_TYPES = ["PERSON", "ORGANIZATION", "CONCEPT", "TOOL", "LOCATION", "EVENT"];
|
|
15408
|
+
var RELATION_TYPES = [
|
|
15409
|
+
"USES",
|
|
15410
|
+
"OWNS",
|
|
15411
|
+
"DEPENDS_ON",
|
|
15412
|
+
"RELATED_TO",
|
|
15413
|
+
"CREATED_BY",
|
|
15414
|
+
"PART_OF",
|
|
15415
|
+
"IS_A",
|
|
15416
|
+
"WORKS_AT",
|
|
15417
|
+
"LOCATED_IN"
|
|
15418
|
+
];
|
|
15419
|
+
var ExtractionSchema = external_exports.object({
|
|
15420
|
+
entities: external_exports.array(
|
|
15421
|
+
external_exports.object({
|
|
15422
|
+
name: external_exports.string().min(1),
|
|
15423
|
+
type: external_exports.enum(ENTITY_TYPES)
|
|
15424
|
+
})
|
|
15425
|
+
),
|
|
15426
|
+
relations: external_exports.array(
|
|
15427
|
+
external_exports.object({
|
|
15428
|
+
source: external_exports.string().min(1),
|
|
15429
|
+
target: external_exports.string().min(1),
|
|
15430
|
+
type: external_exports.enum(RELATION_TYPES)
|
|
15431
|
+
})
|
|
15432
|
+
)
|
|
15433
|
+
});
|
|
15434
|
+
function buildExtractionPrompt(content) {
|
|
15435
|
+
return [
|
|
15436
|
+
"Extract graph facts as JSON only. No prose, no fences, no commentary.",
|
|
15437
|
+
`Schema: {"entities":[{"name":string,"type":EntityType}],"relations":[{"source":string,"target":string,"type":RelationType}]}.`,
|
|
15438
|
+
`EntityType values: ${ENTITY_TYPES.join(", ")}.`,
|
|
15439
|
+
`RelationType values: ${RELATION_TYPES.join(", ")}.`,
|
|
15440
|
+
"Include only entities/relations explicitly named or strongly implied in the content. Empty arrays are valid.",
|
|
15441
|
+
`Content: ${content}`
|
|
15442
|
+
].join("\n");
|
|
15443
|
+
}
|
|
15444
|
+
function parseExtractionResponse(raw) {
|
|
15445
|
+
let parsed;
|
|
15446
|
+
try {
|
|
15447
|
+
parsed = JSON.parse(raw);
|
|
15448
|
+
} catch (cause) {
|
|
15449
|
+
throw new Error(
|
|
15450
|
+
`MCP sampling returned non-JSON text (first 120 chars: ${raw.slice(0, 120).replace(/\n/g, "\\n")})`,
|
|
15451
|
+
{ cause: cause instanceof Error ? cause : void 0 }
|
|
15452
|
+
);
|
|
15453
|
+
}
|
|
15454
|
+
const result = ExtractionSchema.safeParse(parsed);
|
|
15455
|
+
if (!result.success) {
|
|
15456
|
+
throw new Error(`MCP sampling response failed schema validation: ${result.error.message}`);
|
|
15457
|
+
}
|
|
15458
|
+
return result.data;
|
|
15459
|
+
}
|
|
15460
|
+
|
|
15346
15461
|
// src/mcp/tools/store.ts
|
|
15347
15462
|
var entityTypes = ["PERSON", "ORGANIZATION", "CONCEPT", "TOOL", "LOCATION", "EVENT"];
|
|
15348
15463
|
var relationshipTypes = [
|
|
@@ -15356,7 +15471,7 @@ var relationshipTypes = [
|
|
|
15356
15471
|
"WORKS_AT",
|
|
15357
15472
|
"LOCATED_IN"
|
|
15358
15473
|
];
|
|
15359
|
-
var
|
|
15474
|
+
var inputShape7 = {
|
|
15360
15475
|
content: external_exports.string().min(1).describe("Concise factual statement to persist; decision, not deliberation."),
|
|
15361
15476
|
topic: external_exports.string().min(1).describe("Required metadata.topic for retrieval grouping."),
|
|
15362
15477
|
project: external_exports.string().min(1).describe("Required metadata.project namespace."),
|
|
@@ -15372,29 +15487,57 @@ var inputShape6 = {
|
|
|
15372
15487
|
name: external_exports.string().min(1).describe("Entity name as referenced in content."),
|
|
15373
15488
|
type: external_exports.enum(entityTypes).describe("Entity type.")
|
|
15374
15489
|
})
|
|
15375
|
-
).optional().describe(
|
|
15490
|
+
).optional().describe(
|
|
15491
|
+
"Named entities mentioned by the content; required when relationships are present. When omitted, the MCP tool may auto-extract via the connected client's LLM (MCP sampling) before sending to the server; set `extractEntities:false` to skip."
|
|
15492
|
+
),
|
|
15376
15493
|
relationships: external_exports.array(
|
|
15377
15494
|
external_exports.object({
|
|
15378
15495
|
source: external_exports.string().min(1).describe("Source entity name (must appear in entities array)."),
|
|
15379
15496
|
target: external_exports.string().min(1).describe("Target entity name (must appear in entities array)."),
|
|
15380
15497
|
type: external_exports.enum(relationshipTypes).describe("Relationship type.")
|
|
15381
15498
|
})
|
|
15382
|
-
).optional().describe(
|
|
15499
|
+
).optional().describe(
|
|
15500
|
+
"Edges between entities; source and target must be entity names from this request. When omitted, the MCP tool may auto-extract via the connected client's LLM (MCP sampling) before sending to the server; set `extractEntities:false` to skip."
|
|
15501
|
+
),
|
|
15502
|
+
extractEntities: external_exports.boolean().optional().describe(
|
|
15503
|
+
"Override client-side auto-extraction (MCP sampling): false to skip, true to require (errors if the connected client does not support sampling)."
|
|
15504
|
+
),
|
|
15383
15505
|
...scopeShape
|
|
15384
15506
|
};
|
|
15385
15507
|
var storeMemoryTool = {
|
|
15386
15508
|
name: "store_memory",
|
|
15387
15509
|
config: {
|
|
15388
15510
|
title: "Store pyx-memory entry",
|
|
15389
|
-
description: "Store one concise factual memory with required topic and project metadata. Provide entities/relationships when
|
|
15390
|
-
inputSchema:
|
|
15511
|
+
description: "Store one concise factual memory with required topic and project metadata. Provide entities/relationships explicitly when you want exact graph topology; when omitted, the MCP tool may auto-extract using your own LLM via MCP sampling before sending to the server.",
|
|
15512
|
+
inputSchema: inputShape7,
|
|
15391
15513
|
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true }
|
|
15392
15514
|
},
|
|
15393
|
-
handler: (deps) => async (raw) => {
|
|
15515
|
+
handler: (deps) => async (raw, ctx) => {
|
|
15394
15516
|
const args = raw;
|
|
15395
15517
|
const creds = await deps.readCredentials();
|
|
15396
15518
|
if (!creds.ok) return creds.result;
|
|
15397
15519
|
const http = createHttpClient(creds.credentials, deps.fetchImpl);
|
|
15520
|
+
let entities = args.entities;
|
|
15521
|
+
let relationships = args.relationships;
|
|
15522
|
+
const samplingAvailable = deps.samplingClient?.isAvailable() ?? false;
|
|
15523
|
+
const optedOut = args.extractEntities === false;
|
|
15524
|
+
const forced = args.extractEntities === true;
|
|
15525
|
+
if (forced && !samplingAvailable) {
|
|
15526
|
+
throw new Error(
|
|
15527
|
+
"extractEntities=true requested but the connected MCP client did not advertise the `sampling` capability. Pass entities/relationships explicitly, set extractEntities:false, or connect a sampling-capable client."
|
|
15528
|
+
);
|
|
15529
|
+
}
|
|
15530
|
+
if (deps.samplingClient && samplingAvailable && !optedOut && (entities?.length ?? 0) === 0) {
|
|
15531
|
+
const prompt = buildExtractionPrompt(args.content);
|
|
15532
|
+
const completion = await deps.samplingClient.complete(
|
|
15533
|
+
prompt,
|
|
15534
|
+
ctx?.signal ? { signal: ctx.signal } : void 0
|
|
15535
|
+
);
|
|
15536
|
+
const extracted = parseExtractionResponse(completion);
|
|
15537
|
+
const merged = mergeExtractedEntities(args.entities, args.relationships, extracted);
|
|
15538
|
+
entities = merged.entities;
|
|
15539
|
+
relationships = merged.relationships;
|
|
15540
|
+
}
|
|
15398
15541
|
const body = {
|
|
15399
15542
|
content: args.content,
|
|
15400
15543
|
type: args.type ?? "long-term",
|
|
@@ -15408,8 +15551,8 @@ var storeMemoryTool = {
|
|
|
15408
15551
|
agentId: args.agentId,
|
|
15409
15552
|
sessionId: args.sessionId,
|
|
15410
15553
|
parentId: args.parentId,
|
|
15411
|
-
entities
|
|
15412
|
-
relationships
|
|
15554
|
+
entities,
|
|
15555
|
+
relationships
|
|
15413
15556
|
};
|
|
15414
15557
|
const res = await http.requestJson({
|
|
15415
15558
|
method: "POST",
|
|
@@ -15422,7 +15565,7 @@ var storeMemoryTool = {
|
|
|
15422
15565
|
};
|
|
15423
15566
|
|
|
15424
15567
|
// src/mcp/tools/summarize.ts
|
|
15425
|
-
var
|
|
15568
|
+
var inputShape8 = {
|
|
15426
15569
|
entityName: external_exports.string().min(1).describe(
|
|
15427
15570
|
"Entity name to synthesize a profile for (PERSON, ORG, CONCEPT, TOOL, LOCATION, EVENT)."
|
|
15428
15571
|
),
|
|
@@ -15437,7 +15580,7 @@ var summarizeMemoryEntityTool = {
|
|
|
15437
15580
|
config: {
|
|
15438
15581
|
title: "Synthesize pyx-memory entity",
|
|
15439
15582
|
description: "Fetch or refresh a reusable synthesis/profile for an entity (person, customer, project, tool, organization). Default fetches existing; set refresh:true to rebuild.",
|
|
15440
|
-
inputSchema:
|
|
15583
|
+
inputSchema: inputShape8,
|
|
15441
15584
|
annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: true }
|
|
15442
15585
|
},
|
|
15443
15586
|
handler: (deps) => async (raw) => {
|
|
@@ -15472,24 +15615,30 @@ var ALL_TOOLS = [
|
|
|
15472
15615
|
listMemoriesTool,
|
|
15473
15616
|
deleteMemoryTool,
|
|
15474
15617
|
ingestMemoryFileTool,
|
|
15475
|
-
summarizeMemoryEntityTool
|
|
15618
|
+
summarizeMemoryEntityTool,
|
|
15619
|
+
statusTool
|
|
15476
15620
|
];
|
|
15477
15621
|
var ALL_TOOL_NAMES = ALL_TOOLS.map((t) => t.name);
|
|
15478
15622
|
|
|
15479
15623
|
// src/mcp/server.ts
|
|
15480
15624
|
async function runMcpServer(opts) {
|
|
15481
15625
|
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
15482
|
-
const version2 = opts.version ?? (true ? "0.
|
|
15626
|
+
const version2 = opts.version ?? (true ? "0.26.0" : "0.0.0-dev");
|
|
15483
15627
|
const server = new McpServer(
|
|
15484
15628
|
{ name: "pyx-memory", version: version2 },
|
|
15485
15629
|
{ instructions: PYX_MEMORY_INSTRUCTIONS, capabilities: { tools: {} } }
|
|
15486
15630
|
);
|
|
15631
|
+
const samplingClient = createSamplingClient(server);
|
|
15487
15632
|
for (const tool of ALL_TOOLS) {
|
|
15488
|
-
const handle = tool.handler({
|
|
15633
|
+
const handle = tool.handler({
|
|
15634
|
+
readCredentials: opts.readCredentials,
|
|
15635
|
+
fetchImpl,
|
|
15636
|
+
samplingClient
|
|
15637
|
+
});
|
|
15489
15638
|
server.registerTool(
|
|
15490
15639
|
tool.name,
|
|
15491
15640
|
tool.config,
|
|
15492
|
-
async (args) => handle(args)
|
|
15641
|
+
async (args, extra) => handle(args, { signal: extra?.signal })
|
|
15493
15642
|
);
|
|
15494
15643
|
}
|
|
15495
15644
|
const transport = new StdioServerTransport();
|
|
@@ -15661,6 +15810,7 @@ function mcpInstallClaudeCodeCommand(opts = {}) {
|
|
|
15661
15810
|
process.stdout.write(
|
|
15662
15811
|
`Installed pyx-memory MCP server in Claude Code (scope: ${scope}).
|
|
15663
15812
|
Restart Claude Code to make the tools available. No API key was written to .mcp.json \u2014 credentials live in the OS credential store.
|
|
15813
|
+
Graph entities/relationships auto-extract via your client's own LLM (MCP sampling) when you call store_memory without entities \u2014 no extra LLM keys to configure.
|
|
15664
15814
|
`
|
|
15665
15815
|
);
|
|
15666
15816
|
return EXIT.OK;
|
package/dist/dashboard.mjs
CHANGED
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
toGraphologyFormat,
|
|
12
12
|
transformGraphData,
|
|
13
13
|
unreachableHealth
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import "./chunk-
|
|
14
|
+
} from "./chunk-47O2JYAP.mjs";
|
|
15
|
+
import "./chunk-N6IRAUXH.mjs";
|
|
16
16
|
import "./chunk-7P6ASYW6.mjs";
|
|
17
17
|
export {
|
|
18
18
|
DashboardClient,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { StoreInput as StoreInput$1, MemoryEntry as MemoryEntry$1, MemorySearchParams as MemorySearchParams$1, MemorySearchResult as MemorySearchResult$1, MemoryType as MemoryType$1, PrincipalContext as PrincipalContext$1, MemoryStats as MemoryStats$1, WikiLintReport as WikiLintReport$1, ExtractedImageMeta as ExtractedImageMeta$1, IngestEntity as IngestEntity$1, IngestRelationship as IngestRelationship$1, IngestEvent as IngestEvent$1, GraphNode as GraphNode$1, GraphTraversalResult as GraphTraversalResult$1 } from '@pyx-memory/shared';
|
|
1
|
+
import { StoreInput as StoreInput$1, MemoryEntry as MemoryEntry$1, MemorySearchParams as MemorySearchParams$1, MemorySearchResult as MemorySearchResult$1, MemoryType as MemoryType$1, PrincipalContext as PrincipalContext$1, MemoryStats as MemoryStats$1, WikiLintReport as WikiLintReport$1, ExtractedImageMeta as ExtractedImageMeta$1, IngestEntity as IngestEntity$1, IngestRelationship as IngestRelationship$1, EntityExtractionResult as EntityExtractionResult$1, Topology as Topology$1, IngestEvent as IngestEvent$1, GraphNode as GraphNode$1, GraphTraversalResult as GraphTraversalResult$1 } from '@pyx-memory/shared';
|
|
2
2
|
|
|
3
3
|
/** Parameters for paginated entry listing. */
|
|
4
4
|
interface MemoryListParams {
|
|
@@ -19,6 +19,15 @@ interface MemoryListParams {
|
|
|
19
19
|
* and get the legacy tenant-only scope.
|
|
20
20
|
*/
|
|
21
21
|
principal?: PrincipalContext$1;
|
|
22
|
+
/**
|
|
23
|
+
* R1/R2 — opt-in list of `metadata._kind` values to include in the
|
|
24
|
+
* response. When omitted or empty, the default-set returns only entries
|
|
25
|
+
* with NO `_kind` system metadata (e.g. `_kind:'entity-summary'` rows
|
|
26
|
+
* are hidden). When supplied, the listed kinds are returned IN ADDITION
|
|
27
|
+
* to the default-set (additive, not replacement). See
|
|
28
|
+
* `docs/specs/topic-org-v025-f1-include-kinds/spec.md`.
|
|
29
|
+
*/
|
|
30
|
+
includeKinds?: string[];
|
|
22
31
|
}
|
|
23
32
|
/** Result of a paginated entry listing. */
|
|
24
33
|
interface MemoryListResult {
|
|
@@ -169,6 +178,35 @@ interface IngestFileOptions {
|
|
|
169
178
|
signal?: AbortSignal;
|
|
170
179
|
namespaceId?: string;
|
|
171
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Caller-supplied enrichment for the per-call store path. Mirrors
|
|
183
|
+
* {@link EnrichmentCallbacks} for file ingest — the SDK invokes the callback
|
|
184
|
+
* (against the caller's own LLM credentials) and merges the result into the
|
|
185
|
+
* ingest payload before POSTing, so the server holds no LLM keys.
|
|
186
|
+
*/
|
|
187
|
+
interface StoreEnrichmentCallbacks {
|
|
188
|
+
/**
|
|
189
|
+
* Extract entities + relationships from `content`. The caller's LLM does
|
|
190
|
+
* the work; the SDK passes the result through {@link mergeExtractedEntities}
|
|
191
|
+
* (caller-wins, case-insensitive) before sending to the server.
|
|
192
|
+
*
|
|
193
|
+
* Skipped when `entry.extractEntities === false`. When
|
|
194
|
+
* `entry.extractEntities === true` but this callback is not supplied,
|
|
195
|
+
* `MemoryClient.store()` throws a loud error rather than silently no-op'ing
|
|
196
|
+
* (same shape as the in-process Memory.store() guard).
|
|
197
|
+
*/
|
|
198
|
+
extractEntities?: (input: {
|
|
199
|
+
content: string;
|
|
200
|
+
metadata?: Record<string, unknown>;
|
|
201
|
+
signal?: AbortSignal;
|
|
202
|
+
}) => Promise<EntityExtractionResult$1>;
|
|
203
|
+
}
|
|
204
|
+
/** Options accepted by {@link MemoryClient.store}. */
|
|
205
|
+
interface StoreOptions {
|
|
206
|
+
enrichment?: StoreEnrichmentCallbacks;
|
|
207
|
+
/** Propagated into the `extractEntities` callback and the underlying fetch. */
|
|
208
|
+
signal?: AbortSignal;
|
|
209
|
+
}
|
|
172
210
|
/** Error thrown by MemoryClient when the server returns a non-success response. */
|
|
173
211
|
declare class MemoryServerError extends Error {
|
|
174
212
|
readonly status: number;
|
|
@@ -205,12 +243,22 @@ declare class MemoryClient implements ExtendedMemoryInterface {
|
|
|
205
243
|
/** Encode a path segment to prevent URL injection */
|
|
206
244
|
private encodePathSegment;
|
|
207
245
|
initialize(): Promise<void>;
|
|
208
|
-
store(entry: StoreInput$1): Promise<MemoryEntry$1>;
|
|
246
|
+
store(entry: StoreInput$1, options?: StoreOptions): Promise<MemoryEntry$1>;
|
|
209
247
|
search(params: MemorySearchParams$1): Promise<MemorySearchResult$1>;
|
|
210
248
|
get(id: string): Promise<MemoryEntry$1 | null>;
|
|
211
249
|
delete(id: string): Promise<boolean>;
|
|
212
250
|
clearSession(sessionId: string): Promise<number>;
|
|
213
251
|
stats(): Promise<MemoryStats$1>;
|
|
252
|
+
/**
|
|
253
|
+
* Fetch the running server's topology snapshot (build variant, declared
|
|
254
|
+
* role, embedding location, active model profile). Round-trips the
|
|
255
|
+
* server's `GET /status` envelope through {@link fetchApi}, surfacing any
|
|
256
|
+
* non-success response as {@link MemoryServerError}. Auth-header
|
|
257
|
+
* forwarding is unchanged from other client methods even though `/status`
|
|
258
|
+
* is server-side public — the server simply ignores credentials on that
|
|
259
|
+
* route.
|
|
260
|
+
*/
|
|
261
|
+
status(): Promise<Topology$1>;
|
|
214
262
|
shutdown(): Promise<void>;
|
|
215
263
|
list(params?: MemoryListParams): Promise<MemoryListResult>;
|
|
216
264
|
/**
|
|
@@ -318,12 +366,6 @@ declare const DEFAULTS: {
|
|
|
318
366
|
readonly MEMORY_SERVER_PORT: 7822;
|
|
319
367
|
};
|
|
320
368
|
|
|
321
|
-
interface ApiResponse<T> {
|
|
322
|
-
success: boolean;
|
|
323
|
-
data?: T;
|
|
324
|
-
error?: string;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
369
|
/** ISO 8601 timestamp string */
|
|
328
370
|
type Timestamp = string;
|
|
329
371
|
/** Unique agent identifier */
|
|
@@ -538,6 +580,8 @@ type StoreInput = Omit<MemoryEntry, 'id' | 'createdAt'> & {
|
|
|
538
580
|
entities?: IngestEntity[];
|
|
539
581
|
/** Agent-provided relationships for graph storage. */
|
|
540
582
|
relationships?: IngestRelationship[];
|
|
583
|
+
/** Override server-side auto-extraction for this store call. */
|
|
584
|
+
extractEntities?: boolean;
|
|
541
585
|
/** Graph-failure handling. Default: "throw" (loud) — see GraphFailureMode. */
|
|
542
586
|
graphFailureMode?: GraphFailureMode;
|
|
543
587
|
};
|
|
@@ -553,6 +597,8 @@ interface MemoryIngestRequest {
|
|
|
553
597
|
entities?: IngestEntity[];
|
|
554
598
|
/** Agent-provided relationships for graph storage. */
|
|
555
599
|
relationships?: IngestRelationship[];
|
|
600
|
+
/** Override server-side auto-extraction for this ingest request. */
|
|
601
|
+
extractEntities?: boolean;
|
|
556
602
|
/** Graph-failure handling. Default: "throw" (loud) — see GraphFailureMode. */
|
|
557
603
|
graphFailureMode?: GraphFailureMode;
|
|
558
604
|
/** Importance score (1-10). */
|
|
@@ -642,6 +688,89 @@ interface WikiLintReport {
|
|
|
642
688
|
}>;
|
|
643
689
|
}
|
|
644
690
|
|
|
691
|
+
/**
|
|
692
|
+
* Result shape returned by an LLM entity extractor — entities + relations.
|
|
693
|
+
*
|
|
694
|
+
* Lives in shared so both the server-side Memory.store() merge and any
|
|
695
|
+
* client-side enrichment path (`MemoryClient.store(..., { enrichment })`,
|
|
696
|
+
* the MCP `store_memory` tool that goes through MCP sampling) can speak the
|
|
697
|
+
* exact same structural type without crossing the core→client circular
|
|
698
|
+
* dependency.
|
|
699
|
+
*/
|
|
700
|
+
interface EntityExtractionResult {
|
|
701
|
+
entities: Array<{
|
|
702
|
+
name: string;
|
|
703
|
+
type: string;
|
|
704
|
+
}>;
|
|
705
|
+
relations: Array<{
|
|
706
|
+
source: string;
|
|
707
|
+
target: string;
|
|
708
|
+
type: string;
|
|
709
|
+
}>;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Merge caller-provided entities/relationships with LLM-extracted ones.
|
|
713
|
+
*
|
|
714
|
+
* Caller-wins on case-insensitive name conflict — the caller's casing is
|
|
715
|
+
* preserved and extracted duplicates are skipped. Extracted relationships
|
|
716
|
+
* are appended only when both endpoints resolve (case-insensitively) to a
|
|
717
|
+
* final entity name; the endpoint strings on the appended relationship are
|
|
718
|
+
* rewritten to the canonical (final) casing.
|
|
719
|
+
*
|
|
720
|
+
* Pure function. Single canonical merge used by Memory.store(),
|
|
721
|
+
* MemoryClient.store() (HTTP client enrichment callback), and the MCP
|
|
722
|
+
* store_memory tool — three call sites cannot drift.
|
|
723
|
+
*/
|
|
724
|
+
declare function mergeExtractedEntities(callerEntities: IngestEntity[] | undefined, callerRelationships: IngestRelationship[] | undefined, extracted: EntityExtractionResult): {
|
|
725
|
+
entities: IngestEntity[];
|
|
726
|
+
relationships: IngestRelationship[];
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
interface ApiResponse<T> {
|
|
730
|
+
success: boolean;
|
|
731
|
+
data?: T;
|
|
732
|
+
error?: string;
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Build variant of the running pyx-memory image. Detected from the actual
|
|
736
|
+
* availability of `@huggingface/transformers` at boot — `full` keeps the
|
|
737
|
+
* local-embedding stack bundled, `cloud` has it pruned (EMBEDDING_PROVIDER=http
|
|
738
|
+
* is mandatory). The detected value is authoritative; the optional
|
|
739
|
+
* `PYX_BUILD_VARIANT` env stamp is a sanity check that throws on drift.
|
|
740
|
+
*/
|
|
741
|
+
type TopologyServiceVariant = 'cloud' | 'full';
|
|
742
|
+
/**
|
|
743
|
+
* Where embedding work happens. `inline` means the server computes vectors
|
|
744
|
+
* in-process (EMBEDDING_PROVIDER=local); `remote` means it delegates to an
|
|
745
|
+
* HTTP embedding service (EMBEDDING_PROVIDER=http). The endpoint URL itself
|
|
746
|
+
* is internal infrastructure and is intentionally NOT exposed here.
|
|
747
|
+
*/
|
|
748
|
+
type TopologyEmbeddingLocation = 'inline' | 'remote';
|
|
749
|
+
/**
|
|
750
|
+
* Public topology snapshot returned by `GET /status`. Reflects actual runtime
|
|
751
|
+
* capability (variant detected from import probe, embedding location derived
|
|
752
|
+
* from configured provider) plus the active embedding profile. Never leaks
|
|
753
|
+
* embedding-service URLs or other infrastructure host fragments — those stay
|
|
754
|
+
* behind the admin surface.
|
|
755
|
+
*/
|
|
756
|
+
interface Topology {
|
|
757
|
+
service: {
|
|
758
|
+
variant: TopologyServiceVariant;
|
|
759
|
+
/**
|
|
760
|
+
* Operator-declared role label (`PYX_DEPLOYMENT_ROLE`). Free-form string.
|
|
761
|
+
* Structurally absent from the response when the env var is unset or empty.
|
|
762
|
+
*/
|
|
763
|
+
declaredRole?: string;
|
|
764
|
+
version: string;
|
|
765
|
+
};
|
|
766
|
+
embedding: {
|
|
767
|
+
location: TopologyEmbeddingLocation;
|
|
768
|
+
modelKey: string;
|
|
769
|
+
modelId: string;
|
|
770
|
+
dimensions: number;
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
|
|
645
774
|
/** Metadata for a single image extracted from a PDF. */
|
|
646
775
|
interface ExtractedImageMeta {
|
|
647
776
|
imageId: string;
|
|
@@ -907,4 +1036,4 @@ interface PrincipalContext {
|
|
|
907
1036
|
/** Sentinel tenant ID used in single-tenant deployments. */
|
|
908
1037
|
declare const SINGLE_TENANT_ID = "_single";
|
|
909
1038
|
|
|
910
|
-
export { type AgentId, type ApiResponse, type ConsolidationRunResult, DEFAULTS, EmbeddingProviderName, type EnrichmentCallbacks, type ExtendedMemoryInterface, type GraphFailureMode, type GraphNode, type GraphRelationship, type GraphTraversalResult, type IngestEntity, type IngestErrorEvent, type IngestEvent, type IngestFileOptions, type IngestHeartbeatEvent, type IngestProgressEvent, type IngestRelationship, type IngestResultEvent, type IngestStage, type IngestionResult, MemoryClient, type MemoryClientOptions, type MemoryEntry, type MemoryIngestRequest, type MemoryInterface, type MemoryListParams, type MemoryListResult, type MemoryLogFilters, type MemorySearchParams, type MemorySearchResult, MemoryServerError, type MemoryStats, MemoryType, type MoveEntriesFilter, MoveFailureReason, type MoveResult, type MoveTarget, NamespaceIsolation, type PrincipalContext, RAGStrategy, SINGLE_TENANT_ID, SensitivityLevel, type StoreInput, StoreTarget, type TemporalQueryFilters, type TenantScopeOptions, type Timestamp, VectorProvider, type WikiLintReport };
|
|
1039
|
+
export { type AgentId, type ApiResponse, type ConsolidationRunResult, DEFAULTS, EmbeddingProviderName, type EnrichmentCallbacks, type EntityExtractionResult, type ExtendedMemoryInterface, type GraphFailureMode, type GraphNode, type GraphRelationship, type GraphTraversalResult, type IngestEntity, type IngestErrorEvent, type IngestEvent, type IngestFileOptions, type IngestHeartbeatEvent, type IngestProgressEvent, type IngestRelationship, type IngestResultEvent, type IngestStage, type IngestionResult, MemoryClient, type MemoryClientOptions, type MemoryEntry, type MemoryIngestRequest, type MemoryInterface, type MemoryListParams, type MemoryListResult, type MemoryLogFilters, type MemorySearchParams, type MemorySearchResult, MemoryServerError, type MemoryStats, MemoryType, type MoveEntriesFilter, MoveFailureReason, type MoveResult, type MoveTarget, NamespaceIsolation, type PrincipalContext, RAGStrategy, SINGLE_TENANT_ID, SensitivityLevel, type StoreInput, StoreTarget, type TemporalQueryFilters, type TenantScopeOptions, type Timestamp, type Topology, type TopologyServiceVariant, VectorProvider, type WikiLintReport, mergeExtractedEntities };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MemoryClient,
|
|
3
3
|
MemoryServerError
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-N6IRAUXH.mjs";
|
|
5
5
|
import "./chunk-7P6ASYW6.mjs";
|
|
6
6
|
|
|
7
7
|
// ../shared/src/constants/defaults.ts
|
|
@@ -11,6 +11,31 @@ var DEFAULTS = {
|
|
|
11
11
|
MEMORY_SERVER_PORT: 7822
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
+
// ../shared/src/graph/extraction.ts
|
|
15
|
+
function mergeExtractedEntities(callerEntities, callerRelationships, extracted) {
|
|
16
|
+
const entities = [...callerEntities ?? []];
|
|
17
|
+
const relationships = [...callerRelationships ?? []];
|
|
18
|
+
const nameByLowercase = /* @__PURE__ */ new Map();
|
|
19
|
+
for (const entity of entities) {
|
|
20
|
+
const key = entity.name.toLowerCase();
|
|
21
|
+
if (!nameByLowercase.has(key)) nameByLowercase.set(key, entity.name);
|
|
22
|
+
}
|
|
23
|
+
for (const entity of extracted.entities) {
|
|
24
|
+
const key = entity.name.toLowerCase();
|
|
25
|
+
if (nameByLowercase.has(key)) continue;
|
|
26
|
+
entities.push(entity);
|
|
27
|
+
nameByLowercase.set(key, entity.name);
|
|
28
|
+
}
|
|
29
|
+
for (const relationship of extracted.relations) {
|
|
30
|
+
const source = nameByLowercase.get(relationship.source.toLowerCase());
|
|
31
|
+
const target = nameByLowercase.get(relationship.target.toLowerCase());
|
|
32
|
+
if (source && target) {
|
|
33
|
+
relationships.push({ ...relationship, source, target });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { entities, relationships };
|
|
37
|
+
}
|
|
38
|
+
|
|
14
39
|
// ../shared/src/types/isolation.ts
|
|
15
40
|
var NamespaceIsolation = {
|
|
16
41
|
SHARED: "shared",
|
|
@@ -88,5 +113,6 @@ export {
|
|
|
88
113
|
SINGLE_TENANT_ID,
|
|
89
114
|
SensitivityLevel,
|
|
90
115
|
StoreTarget,
|
|
91
|
-
VectorProvider
|
|
116
|
+
VectorProvider,
|
|
117
|
+
mergeExtractedEntities
|
|
92
118
|
};
|
package/dist/react.mjs
CHANGED
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
toGraphologyFormat,
|
|
12
12
|
transformGraphData,
|
|
13
13
|
unreachableHealth
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import "./chunk-
|
|
14
|
+
} from "./chunk-47O2JYAP.mjs";
|
|
15
|
+
import "./chunk-N6IRAUXH.mjs";
|
|
16
16
|
import "./chunk-7P6ASYW6.mjs";
|
|
17
17
|
|
|
18
18
|
// ../dashboard/src/hooks/use-consolidation-log.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyxmate/memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "SDK for pyx-memory — Memory as a Service for AI agents",
|
|
6
6
|
"license": "MIT",
|
|
@@ -57,7 +57,8 @@
|
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
60
|
-
"@napi-rs/keyring": "^1.3.0"
|
|
60
|
+
"@napi-rs/keyring": "^1.3.0",
|
|
61
|
+
"@pyx-memory/shared": "workspace:*"
|
|
61
62
|
},
|
|
62
63
|
"devDependencies": {
|
|
63
64
|
"tsup": "^8.4.0"
|