@pyxmate/memory 1.8.0 → 1.9.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/dist/agent-contract.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare const PYX_MEMORY_INSTRUCTIONS = "Use pyx-memory for durable memory across sessions, proactively \u2014 do not wait to be told. SEARCH before assuming: at the start of a task, and before proposing an approach or re-deriving a past decision, search memory for the project and topic; resolve relative times (\"last year\", \"\uB450 \uB2EC \uC804\") to an absolute ISO-8601 anchorTime so results rank by proximity to that time. STORE when durable state settles \u2014 usually before your final response or handoff; if nothing durable changed, store nothing. Otherwise capture a fact (correction, bug fix + root cause, design/architecture decision + reasoning, integration/API/auth/endpoint detail, gotcha, preference, \"remember this\"), a reusable process once stable, a milestone snapshot (type episodic, not a running log), or a changed decision (store the new state with eventTime, never a silent overwrite, so lineage traces it) \u2014 concise facts not deliberation, each with topic and project. Pass eventTime (ISO-8601, when the fact took effect) for anything that can change or go stale; recency ordering, \"as of\" queries, and stale-vs-current resolution all key off it. After a memory informs your work, call reinforce so it stays in the quick/medium tiers (idle never revives it). When the user corrects you, call record_correction; before a task, call fetch_applicable_corrections (pyx never auto-applies them). When content names people, organizations, tools, places, events, or key concepts, pass entities and relationships \u2014 you build the graph, the server does not extract it; a multi-entity store with no connecting edge is refused. Match search effort to need: quick (default, strongest) for routine recall, deep for full/archived history; use lineage to trace how a fact changed. userId/teamId/agentId and callerAccessLevel are attribution filters and sensitivity redaction, not a security isolation boundary \u2014
|
|
1
|
+
declare const PYX_MEMORY_INSTRUCTIONS = "Use pyx-memory for durable memory across sessions, proactively \u2014 do not wait to be told. SEARCH before assuming: at the start of a task, and before proposing an approach or re-deriving a past decision, search memory for the project and topic; resolve relative times (\"last year\", \"\uB450 \uB2EC \uC804\") to an absolute ISO-8601 anchorTime so results rank by proximity to that time. STORE when durable state settles \u2014 usually before your final response or handoff; if nothing durable changed, store nothing. Otherwise capture a fact (correction, bug fix + root cause, design/architecture decision + reasoning, integration/API/auth/endpoint detail, gotcha, preference, \"remember this\"), a reusable process once stable, a milestone snapshot (type episodic, not a running log), or a changed decision (store the new state with eventTime, never a silent overwrite, so lineage traces it) \u2014 concise facts not deliberation, each with topic and project. Pass eventTime (ISO-8601, when the fact took effect) for anything that can change or go stale; recency ordering, \"as of\" queries, and stale-vs-current resolution all key off it. After a memory informs your work, call reinforce so it stays in the quick/medium tiers (idle never revives it). When the user corrects you, call record_correction; before a task, call fetch_applicable_corrections (pyx never auto-applies them). When content names people, organizations, tools, places, events, or key concepts, pass entities and relationships \u2014 you build the graph, the server does not extract it; a multi-entity store with no connecting edge is refused. Match search effort to need: quick (default, strongest) for routine recall, deep for full/archived history; use lineage to trace how a fact changed. userId/teamId/agentId and callerAccessLevel are attribution filters and sensitivity redaction, not a security isolation boundary \u2014 tenant/namespace isolation is enforced server-side (namespaces + ReBAC grants; see docs/access-control.md).";
|
|
2
2
|
declare const PERSISTENT_MEMORY_SECTION: string;
|
|
3
3
|
declare function buildDesignGuide({ appName }: {
|
|
4
4
|
appName: string;
|
package/dist/agent-contract.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/contract/index.ts
|
|
2
|
-
var PYX_MEMORY_INSTRUCTIONS = `Use pyx-memory for durable memory across sessions, proactively \u2014 do not wait to be told. SEARCH before assuming: at the start of a task, and before proposing an approach or re-deriving a past decision, search memory for the project and topic; resolve relative times ("last year", "\uB450 \uB2EC \uC804") to an absolute ISO-8601 anchorTime so results rank by proximity to that time. STORE when durable state settles \u2014 usually before your final response or handoff; if nothing durable changed, store nothing. Otherwise capture a fact (correction, bug fix + root cause, design/architecture decision + reasoning, integration/API/auth/endpoint detail, gotcha, preference, "remember this"), a reusable process once stable, a milestone snapshot (type episodic, not a running log), or a changed decision (store the new state with eventTime, never a silent overwrite, so lineage traces it) \u2014 concise facts not deliberation, each with topic and project. Pass eventTime (ISO-8601, when the fact took effect) for anything that can change or go stale; recency ordering, "as of" queries, and stale-vs-current resolution all key off it. After a memory informs your work, call reinforce so it stays in the quick/medium tiers (idle never revives it). When the user corrects you, call record_correction; before a task, call fetch_applicable_corrections (pyx never auto-applies them). When content names people, organizations, tools, places, events, or key concepts, pass entities and relationships \u2014 you build the graph, the server does not extract it; a multi-entity store with no connecting edge is refused. Match search effort to need: quick (default, strongest) for routine recall, deep for full/archived history; use lineage to trace how a fact changed. userId/teamId/agentId and callerAccessLevel are attribution filters and sensitivity redaction, not a security isolation boundary \u2014
|
|
2
|
+
var PYX_MEMORY_INSTRUCTIONS = `Use pyx-memory for durable memory across sessions, proactively \u2014 do not wait to be told. SEARCH before assuming: at the start of a task, and before proposing an approach or re-deriving a past decision, search memory for the project and topic; resolve relative times ("last year", "\uB450 \uB2EC \uC804") to an absolute ISO-8601 anchorTime so results rank by proximity to that time. STORE when durable state settles \u2014 usually before your final response or handoff; if nothing durable changed, store nothing. Otherwise capture a fact (correction, bug fix + root cause, design/architecture decision + reasoning, integration/API/auth/endpoint detail, gotcha, preference, "remember this"), a reusable process once stable, a milestone snapshot (type episodic, not a running log), or a changed decision (store the new state with eventTime, never a silent overwrite, so lineage traces it) \u2014 concise facts not deliberation, each with topic and project. Pass eventTime (ISO-8601, when the fact took effect) for anything that can change or go stale; recency ordering, "as of" queries, and stale-vs-current resolution all key off it. After a memory informs your work, call reinforce so it stays in the quick/medium tiers (idle never revives it). When the user corrects you, call record_correction; before a task, call fetch_applicable_corrections (pyx never auto-applies them). When content names people, organizations, tools, places, events, or key concepts, pass entities and relationships \u2014 you build the graph, the server does not extract it; a multi-entity store with no connecting edge is refused. Match search effort to need: quick (default, strongest) for routine recall, deep for full/archived history; use lineage to trace how a fact changed. userId/teamId/agentId and callerAccessLevel are attribution filters and sensitivity redaction, not a security isolation boundary \u2014 tenant/namespace isolation is enforced server-side (namespaces + ReBAC grants; see docs/access-control.md).`;
|
|
3
3
|
var PERSISTENT_MEMORY_SECTION = [
|
|
4
4
|
"## Persistent Memory",
|
|
5
5
|
"",
|
|
@@ -8,38 +8,38 @@ var PERSISTENT_MEMORY_SECTION = [
|
|
|
8
8
|
"Search before assuming:",
|
|
9
9
|
"",
|
|
10
10
|
"- At the start of a new conversation or task, search for the project name and task topic.",
|
|
11
|
-
"- Before suggesting an approach,
|
|
11
|
+
"- Before suggesting an approach, search for prior decisions that might contradict it.",
|
|
12
12
|
"- When the user references prior work, search for it.",
|
|
13
|
-
"- When investigating a bug, search for prior occurrences.",
|
|
14
|
-
'- When a question names a time \u2014 explicit or relative ("last year", "\uB450 \uB2EC \uC804") \u2014 resolve it to an absolute ISO-8601 timestamp and pass it as search `anchorTime`: results rank by proximity to that time
|
|
15
|
-
"- Match search `effort` to
|
|
16
|
-
'- To trace how a fact changed
|
|
13
|
+
"- When investigating a bug, search for prior occurrences and fixes.",
|
|
14
|
+
'- When a question names a time \u2014 explicit or relative ("last year", "\uB450 \uB2EC \uC804") \u2014 resolve it to an absolute ISO-8601 timestamp and pass it as search `anchorTime`: results rank by proximity to that time, without excluding other results.',
|
|
15
|
+
"- Match search `effort` to risk: use `quick` for routine recall; use `deep` when quick returns nothing, when prior state may be archived/superseded, or when full history matters.",
|
|
16
|
+
'- To trace how a fact changed ("what did X use before Y", how it evolved), call `lineage` with `subject` + `relation` or an `entryId` instead of stitching together searches.',
|
|
17
|
+
"- Recalled memories reflect what was true when written \u2014 if one names a file, function, flag, or version, verify it still exists before acting on it.",
|
|
17
18
|
"",
|
|
18
|
-
"Reinforce
|
|
19
|
+
"Reinforce and correct:",
|
|
19
20
|
"",
|
|
20
|
-
"- After a retrieved memory actually informs your work (you cited it or acted on it), call `reinforce` on that memory. Recall
|
|
21
|
-
"",
|
|
22
|
-
"
|
|
23
|
-
"",
|
|
24
|
-
"- When the user corrects a mistake you made, call `record_correction` (what was wrong, what to do instead, and when it applies). Before starting a task, call `fetch_applicable_corrections` with the task shape to retrieve the corrections that match it, then decide which to follow \u2014 pyx never auto-applies them. Isolation: `userId`/`teamId` are attribution and legacy filters, not authorization or read gates. Use `agentId` only for trusted per-agent pool isolation (hard filter when set; omit to share a pool); on hosted MaaS/remote MCP it is a within-tenant filter, not a cross-identity security boundary. For per-user/team privacy inside a shared tenant, use namespaces + ReBAC grants via `/api/admin/*` + `ADMIN_API_KEY` + `TENANT_MODE=multi` (private namespace per identity; grant everyone for shared). `callerAccessLevel` is sensitivity redaction, not isolation. Trust boundary: stdio/self-host/companion callers may pass scope per call; hosted MaaS/remote MCP derive identity from the project/token and strip or ignore caller-supplied identity until verified-JWT multi-tenant remote MCP exists.",
|
|
21
|
+
"- After a retrieved memory actually informs your work (you cited it or acted on it), call `reinforce` on that memory. Recall keeps useful memories in the `quick`/`medium` tiers.",
|
|
22
|
+
"- When the user corrects a mistake, call `record_correction` with what was wrong, what to do instead, and when it applies.",
|
|
23
|
+
"- Before starting a task, call `fetch_applicable_corrections` with the task shape; pyx never auto-applies them, so decide which corrections fit.",
|
|
25
24
|
"",
|
|
26
25
|
"Store when durable state settles \u2014 usually before a final response or handoff. Treat that moment as a check, not an automatic write: sparse and high-value beats dense and noisy, so if nothing durable changed, store nothing. When it did, capture only what applies:",
|
|
27
26
|
"",
|
|
28
|
-
'- Facts (default) \u2014 corrections
|
|
29
|
-
"- Process (`type: long-term`) \u2014 a reusable workflow, convention, or playbook a fresh session should follow. Store it once
|
|
30
|
-
"- Trajectory (`type: episodic`) \u2014 only at a real milestone, handoff, or user-requested checkpoint: one dated snapshot of what changed, where it
|
|
31
|
-
"- Evolution \u2014 when a prior fact or decision changes, store the new state with its `eventTime` rather than overwriting the old one, so `lineage` can trace how and why it evolved. Route explicit
|
|
27
|
+
'- Facts (default) \u2014 corrections/preferences; bug fixes with root cause and fix; architecture/design decisions with reasoning; integration/API/auth/endpoint details; gotchas with what broke and the fix; explicit "remember this" requests. One fact per memory.',
|
|
28
|
+
"- Process (`type: long-term`) \u2014 a reusable workflow, convention, or playbook a fresh session should follow. Store it once stabilized or adopted.",
|
|
29
|
+
"- Trajectory (`type: episodic`) \u2014 only at a real milestone, handoff, or user-requested checkpoint: one dated snapshot of what changed, where it stands, and the open frontier. Not a running log.",
|
|
30
|
+
"- Evolution \u2014 when a prior fact or decision changes, store the new state with its `eventTime` rather than overwriting the old one, so `lineage` can trace how and why it evolved. Route explicit corrections through `record_correction`.",
|
|
32
31
|
"",
|
|
33
32
|
"Memory rules:",
|
|
34
33
|
"",
|
|
35
34
|
"- Always include `topic` and `project`.",
|
|
36
35
|
"- Store concise facts and decisions, not deliberation.",
|
|
37
|
-
"- Keep one memory per concept
|
|
38
|
-
"- Do not store ephemeral file contents, raw logs, or step-by-step progress as it happens
|
|
39
|
-
'- Pass `eventTime` (ISO-8601, when the fact happened or took effect) for
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"
|
|
36
|
+
"- Keep one memory per concept; do not bundle unrelated facts.",
|
|
37
|
+
"- Do not store ephemeral file contents, raw logs, or step-by-step progress as it happens. High-frequency streams (wearable/health samples, sensor readings, metrics) must be aggregated into the dated fact or summary they support (with `eventTime`), never stored as one memory or graph node per sample.",
|
|
38
|
+
'- Pass `eventTime` (ISO-8601, when the fact happened or took effect) for facts 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 resolution key off it.',
|
|
39
|
+
"- Graph is a retrieval structure. When content names people, organizations, tools, places, events, or key concepts, pass `entities` and `relationships` when you can (caller-wins). On a multi-entity store, include at least one relationship connecting them, or use `triples` so edges are explicit. Relationships matter as much as entities for traversal. Entity-free memories are valid.",
|
|
40
|
+
'- Use specific durable entities ("sleep apnea", "morning run routine"). Never create a catch-all hub node ("me", "health", "work") linked to everything; mega-hubs degrade traversal. Bounded `IS_A` category nodes are the exception.',
|
|
41
|
+
'- For countable categories the user may later enumerate ("fitness classes", "streaming services", "pets"), add a canonical category `CONCEPT` node and an `IS_A` edge from each member to it (`"yoga"` IS_A `"fitness classes"`).',
|
|
42
|
+
"- Ingest documents/images only when they are worth persisting; images require a short description so they are searchable."
|
|
43
43
|
].join("\n");
|
|
44
44
|
function buildDesignGuide({ appName }) {
|
|
45
45
|
const name = appName.trim() || "your app";
|
package/dist/cli/pyx-mem.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
PERSISTENT_MEMORY_SECTION,
|
|
4
4
|
buildDesignGuide
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-AXH45N7T.mjs";
|
|
6
6
|
|
|
7
7
|
// src/cli/exit-codes.ts
|
|
8
8
|
var EXIT = {
|
|
@@ -798,7 +798,7 @@ function createProxyServer(client, version, uploadLocalFile) {
|
|
|
798
798
|
return server;
|
|
799
799
|
}
|
|
800
800
|
async function runMcpProxyServer(opts) {
|
|
801
|
-
const version = opts.version ?? (true ? "1.
|
|
801
|
+
const version = opts.version ?? (true ? "1.9.0" : "0.0.0-dev");
|
|
802
802
|
const read = await opts.readCredentials();
|
|
803
803
|
if (!read.ok) {
|
|
804
804
|
const text = read.result.content.map((c) => c.type === "text" ? c.text : "").join(" ").trim();
|
|
@@ -832,34 +832,9 @@ async function runMcpProxyServer(opts) {
|
|
|
832
832
|
}
|
|
833
833
|
}
|
|
834
834
|
|
|
835
|
-
// src/cli/commands/mcp.ts
|
|
836
|
-
async function mcpCommand() {
|
|
837
|
-
const readCredentials = createReadCredentials(() => getDefaultKeychain());
|
|
838
|
-
const onStdinEnd = new Promise((resolve3) => {
|
|
839
|
-
process.stdin.once("end", resolve3);
|
|
840
|
-
process.stdin.once("close", resolve3);
|
|
841
|
-
});
|
|
842
|
-
try {
|
|
843
|
-
await Promise.race([runMcpProxyServer({ readCredentials }), onStdinEnd]);
|
|
844
|
-
return EXIT.OK;
|
|
845
|
-
} catch (err) {
|
|
846
|
-
process.stderr.write(`Internal error: ${err instanceof Error ? err.message : String(err)}
|
|
847
|
-
`);
|
|
848
|
-
return EXIT.USAGE;
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
function resolveMcpMode(flags) {
|
|
852
|
-
if (flags.bundled === true) {
|
|
853
|
-
return {
|
|
854
|
-
ok: false,
|
|
855
|
-
error: "The bundled stdio MCP server was removed in v1.3.0. `pyx-mem mcp` now uses the hosted zero-touch path (server-owned tools + instructions; your key stays in the OS keychain). Drop `--bundled`. For a self-hosted server, point at it with `pyx-mem login --endpoint <url>` first."
|
|
856
|
-
};
|
|
857
|
-
}
|
|
858
|
-
return { ok: true };
|
|
859
|
-
}
|
|
860
|
-
|
|
861
835
|
// src/cli/commands/mcp-install.ts
|
|
862
836
|
import { spawnSync as nodeSpawnSync } from "child_process";
|
|
837
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
863
838
|
import { homedir } from "os";
|
|
864
839
|
import { join } from "path";
|
|
865
840
|
|
|
@@ -1047,6 +1022,11 @@ function findLegacyPyxRange(lines, mask) {
|
|
|
1047
1022
|
}
|
|
1048
1023
|
return null;
|
|
1049
1024
|
}
|
|
1025
|
+
function hasPyxMemoryRules(content) {
|
|
1026
|
+
const lines = content.replace(/\r\n/g, "\n").split("\n");
|
|
1027
|
+
const mask = fenceMask(lines);
|
|
1028
|
+
return findCompleteManagedRange(lines, mask) !== null || findOrphanBeginLine(lines, mask) !== -1 || findLegacyPyxRange(lines, mask) !== null;
|
|
1029
|
+
}
|
|
1050
1030
|
function spliceBlock(lines, from, toExclusive, blockLines) {
|
|
1051
1031
|
const before = lines.slice(0, from);
|
|
1052
1032
|
const after = lines.slice(toExclusive);
|
|
@@ -1449,6 +1429,24 @@ function installMemoryRules(opts) {
|
|
|
1449
1429
|
});
|
|
1450
1430
|
process.stdout.write(result.message);
|
|
1451
1431
|
}
|
|
1432
|
+
function reconcileMemoryRules(opts = {}) {
|
|
1433
|
+
const home = opts._homeDir ?? homedir();
|
|
1434
|
+
for (const segs of Object.values(RULES_FILE_TARGETS)) {
|
|
1435
|
+
const filePath = join(home, ...segs);
|
|
1436
|
+
try {
|
|
1437
|
+
if (!existsSync3(filePath)) continue;
|
|
1438
|
+
if (!hasPyxMemoryRules(readFileSync3(filePath, "utf8"))) continue;
|
|
1439
|
+
const result = writeMemoryRulesFile({ filePath, section: PERSISTENT_MEMORY_SECTION });
|
|
1440
|
+
if (result.action !== "unchanged") {
|
|
1441
|
+
process.stderr.write(
|
|
1442
|
+
`pyx-memory: refreshed the "## Persistent Memory" rules in ${filePath}
|
|
1443
|
+
`
|
|
1444
|
+
);
|
|
1445
|
+
}
|
|
1446
|
+
} catch {
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1452
1450
|
function validateScope(scope) {
|
|
1453
1451
|
if (VALID_SCOPES.has(scope)) return true;
|
|
1454
1452
|
process.stderr.write(`Error: invalid --scope \`${scope}\`. Expected: local | user | project.
|
|
@@ -1476,8 +1474,35 @@ function writeJsonAndReport(filePath, agentLabel, entry, opts = {}) {
|
|
|
1476
1474
|
return result.exitCode;
|
|
1477
1475
|
}
|
|
1478
1476
|
|
|
1477
|
+
// src/cli/commands/mcp.ts
|
|
1478
|
+
async function mcpCommand() {
|
|
1479
|
+
reconcileMemoryRules();
|
|
1480
|
+
const readCredentials = createReadCredentials(() => getDefaultKeychain());
|
|
1481
|
+
const onStdinEnd = new Promise((resolve3) => {
|
|
1482
|
+
process.stdin.once("end", resolve3);
|
|
1483
|
+
process.stdin.once("close", resolve3);
|
|
1484
|
+
});
|
|
1485
|
+
try {
|
|
1486
|
+
await Promise.race([runMcpProxyServer({ readCredentials }), onStdinEnd]);
|
|
1487
|
+
return EXIT.OK;
|
|
1488
|
+
} catch (err) {
|
|
1489
|
+
process.stderr.write(`Internal error: ${err instanceof Error ? err.message : String(err)}
|
|
1490
|
+
`);
|
|
1491
|
+
return EXIT.USAGE;
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
function resolveMcpMode(flags) {
|
|
1495
|
+
if (flags.bundled === true) {
|
|
1496
|
+
return {
|
|
1497
|
+
ok: false,
|
|
1498
|
+
error: "The bundled stdio MCP server was removed in v1.3.0. `pyx-mem mcp` now uses the hosted zero-touch path (server-owned tools + instructions; your key stays in the OS keychain). Drop `--bundled`. For a self-hosted server, point at it with `pyx-mem login --endpoint <url>` first."
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
return { ok: true };
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1479
1504
|
// src/cli/commands/scaffold.ts
|
|
1480
|
-
import { existsSync as
|
|
1505
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
1481
1506
|
import { basename as basename2, join as join2, resolve as resolve2 } from "path";
|
|
1482
1507
|
var SERVER_IMAGE = "ghcr.io/pyx-corp/pyx-memory-v1:latest";
|
|
1483
1508
|
var DOCKER_COMPOSE = `services:
|
|
@@ -1619,7 +1644,7 @@ function buildFiles(targetDir, appName) {
|
|
|
1619
1644
|
function scaffoldCommand(args = {}) {
|
|
1620
1645
|
const target = resolveTarget(args);
|
|
1621
1646
|
if (target === null) return EXIT.USAGE;
|
|
1622
|
-
if (
|
|
1647
|
+
if (existsSync4(target.targetDir) && !statSync2(target.targetDir).isDirectory()) {
|
|
1623
1648
|
process.stderr.write(`Error: target exists and is not a directory: ${target.targetDir}
|
|
1624
1649
|
`);
|
|
1625
1650
|
return EXIT.USAGE;
|
|
@@ -1629,7 +1654,7 @@ function scaffoldCommand(args = {}) {
|
|
|
1629
1654
|
const skipped = [];
|
|
1630
1655
|
for (const file of buildFiles(target.targetDir, target.appName)) {
|
|
1631
1656
|
const relativePath = basename2(file.path);
|
|
1632
|
-
if (
|
|
1657
|
+
if (existsSync4(file.path)) {
|
|
1633
1658
|
skipped.push(relativePath);
|
|
1634
1659
|
continue;
|
|
1635
1660
|
}
|