@pyxmate/memory 0.35.0 → 0.36.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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  MemoryClient
3
- } from "./chunk-C2KBUNUM.mjs";
3
+ } from "./chunk-EXUSQ2GR.mjs";
4
4
 
5
5
  // ../dashboard/src/aggregations/consolidation-analytics.ts
6
6
  function analyzeConsolidationLog(entries) {
@@ -171,21 +171,15 @@ var DashboardClient = class extends MemoryClient {
171
171
  `/api/memory/graph/relationships?limit=${limit}`
172
172
  );
173
173
  }
174
- async graphFull(limit = 50) {
175
- const relationshipLimit = limit * 4;
176
- const [nodesResponse, relsResponse] = await Promise.all([
177
- this.fetchApi(
178
- `/api/memory/graph/nodes?limit=${limit}`
179
- ),
180
- this.graphRelationships(relationshipLimit).catch(() => ({
181
- relationships: [],
182
- totalCount: 0
183
- }))
184
- ]);
185
- return {
186
- nodes: nodesResponse.nodes,
187
- relationships: relsResponse.relationships
188
- };
174
+ /**
175
+ * Coherent graph projection (server ≥0.34): edges + ALL their endpoint nodes
176
+ * + true store totals. The former nodes+relationships pair fetch (graphFull)
177
+ * could not feed a graph view — links whose endpoints fell outside the
178
+ * arbitrary N-node sample were silently dropped, collapsing dense graphs to
179
+ * a handful of rendered links.
180
+ */
181
+ async graphSubgraph(edges = 2e3) {
182
+ return this.fetchApi(`/api/memory/graph/subgraph?edges=${edges}`);
189
183
  }
190
184
  async fetchHealthRaw() {
191
185
  return this.fetchApi("/health");
@@ -105,6 +105,7 @@ var MemoryClient = class {
105
105
  searchParams.set("eventTimeEnd", params.eventTimeRange[1]);
106
106
  }
107
107
  if (params.asOf) searchParams.set("asOf", params.asOf);
108
+ if (params.anchorTime) searchParams.set("anchorTime", params.anchorTime);
108
109
  if (params.abstentionThreshold != null)
109
110
  searchParams.set("abstentionThreshold", String(params.abstentionThreshold));
110
111
  return this.fetchApi(`/api/memory/search?${searchParams}`);
@@ -555,7 +555,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
555
555
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
556
556
 
557
557
  // src/mcp/instructions.ts
558
- 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. When content names people, organizations, tools, places, events, or key concepts, you (the caller) must extract and pass both entities and relationships \u2014 the server does not auto-extract them. Edges matter as much as nodes: without relationships the graph cannot connect related memories. Use file ingest for documents/images worth persisting; images require a description.`;
558
+ 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. Pass eventTime (ISO-8601, when the fact happened or took effect) for any fact that can change or go stale \u2014 job/status changes, decisions that supersede earlier ones, dated events; recency ordering, dated ("as of") queries, and stale-vs-current conflict resolution all key off it. When a question names a time \u2014 explicit or relative ("last year", "\uB450 \uB2EC \uC804") \u2014 resolve it to an absolute ISO-8601 timestamp yourself and pass it as search anchorTime: results then rank by proximity to that time instead of now, without excluding anything. When content names people, organizations, tools, places, events, or key concepts, you (the caller) must extract and pass both entities and relationships \u2014 the server does not auto-extract them. Edges matter as much as nodes: without relationships the graph cannot connect related memories. Use file ingest for documents/images worth persisting; images require a description.`;
559
559
 
560
560
  // src/mcp/sampling.ts
561
561
  function createSamplingClient(server) {
@@ -1024,6 +1024,9 @@ var inputShape5 = {
1024
1024
  eventTimeStart: z8.string().optional().describe("Inclusive event-time start (ISO-8601); must be paired with eventTimeEnd."),
1025
1025
  eventTimeEnd: z8.string().optional().describe("Inclusive event-time end (ISO-8601); must be paired with eventTimeStart."),
1026
1026
  asOf: z8.string().optional().describe("Only include memories ingested before this ISO-8601 timestamp."),
1027
+ anchorTime: z8.string().optional().describe(
1028
+ 'Soft recency ANCHOR (ISO-8601) \u2014 ranks results by proximity to this time instead of now; never excludes anything. When the question names a relative time ("two months ago", "last year", "3\uB144 \uC804"), resolve it against the current date yourself and pass the absolute timestamp here; prefer this over eventTimeStart/End unless a strict window is required, because hard filters drop last-known-before facts.'
1029
+ ),
1027
1030
  ...scopeShape
1028
1031
  };
1029
1032
  var searchMemoriesTool = {
@@ -1055,7 +1058,8 @@ var searchMemoriesTool = {
1055
1058
  abstentionThreshold: args.abstentionThreshold,
1056
1059
  eventTimeStart: args.eventTimeStart,
1057
1060
  eventTimeEnd: args.eventTimeEnd,
1058
- asOf: args.asOf
1061
+ asOf: args.asOf,
1062
+ anchorTime: args.anchorTime
1059
1063
  },
1060
1064
  scope: args
1061
1065
  });
@@ -1175,7 +1179,9 @@ var inputShape7 = {
1175
1179
  "Storage targets. Include 'graph' when you provide entities/relationships or want zero graph write counts reported."
1176
1180
  ),
1177
1181
  importance: z10.number().int().min(1).max(10).optional().describe("Importance 1\u201310."),
1178
- eventTime: z10.string().optional().describe("ISO-8601 timestamp of when the event happened (bi-temporal)."),
1182
+ eventTime: z10.string().optional().describe(
1183
+ 'ISO-8601 time the fact happened or took effect (bi-temporal). Pass for any fact that can change or go stale \u2014 recency ordering, dated ("as of") queries, and stale-vs-current conflict resolution key off it.'
1184
+ ),
1179
1185
  source: z10.string().optional().describe("Free-form origin (filename, URL, conversation id)."),
1180
1186
  agentId: z10.string().optional().describe("Agent identifier stored alongside the entry."),
1181
1187
  sessionId: z10.string().optional().describe("Session identifier for grouping."),
@@ -1330,7 +1336,7 @@ var ALL_TOOL_NAMES = ALL_TOOLS.map((t) => t.name);
1330
1336
  // src/mcp/server.ts
1331
1337
  async function runMcpServer(opts) {
1332
1338
  const fetchImpl = opts.fetchImpl ?? fetch;
1333
- const version = opts.version ?? (true ? "0.35.0" : "0.0.0-dev");
1339
+ const version = opts.version ?? (true ? "0.36.0" : "0.0.0-dev");
1334
1340
  const server = new McpServer(
1335
1341
  { name: "pyx-memory", version },
1336
1342
  { instructions: PYX_MEMORY_INSTRUCTIONS, capabilities: { tools: {} } }
@@ -1,4 +1,4 @@
1
- import { MemoryType, MemoryEntry, MemoryStats, GraphRelationship, GraphNode } from '@pyx-memory/shared';
1
+ import { MemoryType, MemoryEntry, GraphNode, GraphRelationship, MemoryStats } from '@pyx-memory/shared';
2
2
  import { MemoryClient } from '@pyx-memory/client';
3
3
 
4
4
  interface RawHealthResponse {
@@ -13,6 +13,15 @@ interface RawHealthResponse {
13
13
  storageUsedBytes: number;
14
14
  };
15
15
  }
16
+ /** GET /api/memory/graph/subgraph — edges + ALL their endpoint nodes + true store totals. */
17
+ interface SubgraphResponse {
18
+ nodes: GraphNode[];
19
+ relationships: GraphRelationship[];
20
+ categoryTotals: Record<string, number>;
21
+ totalNodes: number;
22
+ totalEdges: number;
23
+ truncated: boolean;
24
+ }
16
25
  interface HealthData {
17
26
  status: 'ok' | 'degraded' | 'unreachable';
18
27
  uptime: number;
@@ -175,10 +184,14 @@ declare class DashboardClient extends MemoryClient {
175
184
  relationships: GraphRelationship[];
176
185
  totalCount: number;
177
186
  }>;
178
- graphFull(limit?: number): Promise<{
179
- nodes: GraphNode[];
180
- relationships: GraphRelationship[];
181
- }>;
187
+ /**
188
+ * Coherent graph projection (server ≥0.34): edges + ALL their endpoint nodes
189
+ * + true store totals. The former nodes+relationships pair fetch (graphFull)
190
+ * could not feed a graph view — links whose endpoints fell outside the
191
+ * arbitrary N-node sample were silently dropped, collapsing dense graphs to
192
+ * a handful of rendered links.
193
+ */
194
+ graphSubgraph(edges?: number): Promise<SubgraphResponse>;
182
195
  fetchHealthRaw(): Promise<RawHealthResponse>;
183
196
  }
184
197
 
@@ -213,4 +226,4 @@ declare class Poller<T> {
213
226
  private emit;
214
227
  }
215
228
 
216
- export { type ConsolidationAnalytics, type ConsolidationLogEntry, type D3ForceData, type D3ForceLink, type D3ForceNode, DashboardClient, type EntryFilters, type GraphVizData, type GraphVizEdge, type GraphVizNode, type GraphologyData, type GraphologyEdge, type GraphologyNode, type HealthData, type HookResult, type MemoryMetrics, type PaginatedEntries, Poller, type PollerOptions, type PollerState, type RawHealthResponse, type TransformOptions, type TypeDistribution, analyzeConsolidationLog, computeMetrics, computeTypeDistribution, enrichHealth, formatBytes, formatUptime, toD3ForceFormat, toGraphologyFormat, transformGraphData, unreachableHealth };
229
+ export { type ConsolidationAnalytics, type ConsolidationLogEntry, type D3ForceData, type D3ForceLink, type D3ForceNode, DashboardClient, type EntryFilters, type GraphVizData, type GraphVizEdge, type GraphVizNode, type GraphologyData, type GraphologyEdge, type GraphologyNode, type HealthData, type HookResult, type MemoryMetrics, type PaginatedEntries, Poller, type PollerOptions, type PollerState, type RawHealthResponse, type SubgraphResponse, type TransformOptions, type TypeDistribution, analyzeConsolidationLog, computeMetrics, computeTypeDistribution, enrichHealth, formatBytes, formatUptime, toD3ForceFormat, toGraphologyFormat, transformGraphData, unreachableHealth };
@@ -11,8 +11,8 @@ import {
11
11
  toGraphologyFormat,
12
12
  transformGraphData,
13
13
  unreachableHealth
14
- } from "./chunk-KQV3VYZO.mjs";
15
- import "./chunk-C2KBUNUM.mjs";
14
+ } from "./chunk-DEMDFWI4.mjs";
15
+ import "./chunk-EXUSQ2GR.mjs";
16
16
  import "./chunk-KSTI4M52.mjs";
17
17
  export {
18
18
  DashboardClient,
package/dist/index.d.ts CHANGED
@@ -554,6 +554,15 @@ interface MemorySearchParams {
554
554
  eventTimeRange?: [string, string];
555
555
  /** Filter results to entries ingested before this cutoff (ISO 8601). Falls back to createdAt when ingestTime is null. */
556
556
  asOf?: string;
557
+ /**
558
+ * Soft recency ANCHOR (ISO 8601) — never a filter. Candidates are ranked by
559
+ * proximity to this time instead of now, and conflicting assertions
560
+ * re-order around it. Resolving relative-time expressions ("two months
561
+ * ago", "3년 전") to an absolute timestamp is the CALLER's side of the
562
+ * agentic contract — language understanding never enters the engine. Wins
563
+ * over the query-text year parse (hybrid strategy).
564
+ */
565
+ anchorTime?: string;
557
566
  /** Confidence threshold below which the system recommends abstention (0.0–1.0). Default: 0.3. */
558
567
  abstentionThreshold?: number;
559
568
  /**
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  MemoryClient,
3
3
  MemoryServerError
4
- } from "./chunk-C2KBUNUM.mjs";
4
+ } from "./chunk-EXUSQ2GR.mjs";
5
5
  import {
6
6
  DEFAULTS,
7
7
  DEPRECATED_RAG_STRATEGIES,
package/dist/react.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { HookResult, ConsolidationAnalytics, TransformOptions, GraphVizData, EntryFilters, PaginatedEntries, HealthData, TypeDistribution } from './dashboard.js';
2
- export { ConsolidationLogEntry, D3ForceData, D3ForceLink, D3ForceNode, DashboardClient, GraphVizEdge, GraphVizNode, GraphologyData, GraphologyEdge, GraphologyNode, MemoryMetrics, Poller, PollerOptions, PollerState, RawHealthResponse, analyzeConsolidationLog, computeMetrics, computeTypeDistribution, enrichHealth, formatBytes, formatUptime, toD3ForceFormat, toGraphologyFormat, transformGraphData, unreachableHealth } from './dashboard.js';
2
+ export { ConsolidationLogEntry, D3ForceData, D3ForceLink, D3ForceNode, DashboardClient, GraphVizEdge, GraphVizNode, GraphologyData, GraphologyEdge, GraphologyNode, MemoryMetrics, Poller, PollerOptions, PollerState, RawHealthResponse, SubgraphResponse, analyzeConsolidationLog, computeMetrics, computeTypeDistribution, enrichHealth, formatBytes, formatUptime, toD3ForceFormat, toGraphologyFormat, transformGraphData, unreachableHealth } from './dashboard.js';
3
3
  import { MemoryStats } from '@pyx-memory/shared';
4
4
  import '@pyx-memory/client';
5
5
 
@@ -7,8 +7,16 @@ declare function useConsolidationLog(serverUrl: string, limit?: number, interval
7
7
 
8
8
  interface UseKnowledgeGraphOptions extends TransformOptions {
9
9
  intervalMs?: number;
10
- limit?: number;
10
+ /** Edge render budget forwarded to the subgraph endpoint (server caps at 10k). */
11
+ edges?: number;
11
12
  }
13
+ /**
14
+ * Polls the coherent subgraph (server ≥0.34): every relationship arrives with
15
+ * both endpoint nodes hydrated, so no link is dropped for a missing endpoint.
16
+ * nodeCount/edgeCount/nodeTypes report true store totals — including isolated
17
+ * entities the connected projection doesn't render — while nodes/edges are the
18
+ * rendered projection.
19
+ */
12
20
  declare function useKnowledgeGraph(serverUrl: string, opts?: UseKnowledgeGraphOptions): HookResult<GraphVizData>;
13
21
 
14
22
  declare function useMemoryEntries(serverUrl: string, filters?: EntryFilters, intervalMs?: number): HookResult<PaginatedEntries>;
package/dist/react.mjs CHANGED
@@ -11,8 +11,8 @@ import {
11
11
  toGraphologyFormat,
12
12
  transformGraphData,
13
13
  unreachableHealth
14
- } from "./chunk-KQV3VYZO.mjs";
15
- import "./chunk-C2KBUNUM.mjs";
14
+ } from "./chunk-DEMDFWI4.mjs";
15
+ import "./chunk-EXUSQ2GR.mjs";
16
16
  import "./chunk-KSTI4M52.mjs";
17
17
 
18
18
  // ../dashboard/src/hooks/use-consolidation-log.ts
@@ -75,16 +75,22 @@ function useConsolidationLog(serverUrl, limit = 20, intervalMs = DEFAULT_POLL_IN
75
75
  // ../dashboard/src/hooks/use-knowledge-graph.ts
76
76
  import { useCallback as useCallback3, useMemo as useMemo2 } from "react";
77
77
  function useKnowledgeGraph(serverUrl, opts = {}) {
78
- const { intervalMs = DEFAULT_POLL_INTERVAL_MS, limit = 50, minWeight, maxNodes } = opts;
78
+ const { intervalMs = DEFAULT_POLL_INTERVAL_MS, edges, minWeight, maxNodes } = opts;
79
79
  const client = useMemo2(() => new DashboardClient(serverUrl), [serverUrl]);
80
80
  const stableTransformOpts = useMemo2(
81
81
  () => ({ minWeight, maxNodes }),
82
82
  [minWeight, maxNodes]
83
83
  );
84
84
  const fetcher = useCallback3(async () => {
85
- const { nodes, relationships } = await client.graphFull(limit);
86
- return transformGraphData(nodes, relationships, stableTransformOpts);
87
- }, [client, limit, stableTransformOpts]);
85
+ const subgraph = await client.graphSubgraph(edges);
86
+ const viz = transformGraphData(subgraph.nodes, subgraph.relationships, stableTransformOpts);
87
+ return {
88
+ ...viz,
89
+ nodeCount: subgraph.totalNodes,
90
+ edgeCount: subgraph.totalEdges,
91
+ nodeTypes: subgraph.categoryTotals
92
+ };
93
+ }, [client, edges, stableTransformOpts]);
88
94
  return usePolling(fetcher, { intervalMs });
89
95
  }
90
96
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyxmate/memory",
3
- "version": "0.35.0",
3
+ "version": "0.36.0",
4
4
  "type": "module",
5
5
  "description": "SDK for pyx-memory — Memory as a Service for AI agents",
6
6
  "license": "MIT",