@plur-ai/mcp 0.9.2 → 0.9.4
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 +1 -1
- package/dist/index.d.ts +30 -0
- package/dist/index.js +72 -3
- package/dist/{server-3O37RXPQ.js → server-W73LKOH7.js} +120 -5
- package/package.json +4 -3
- package/packs/effective-memory/SKILL.md +58 -0
- package/packs/effective-memory/engrams.yaml +332 -0
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@ Next session starts → relevant ones injected → agent remembers
|
|
|
42
42
|
You rate the result → engram strengthens → quality improves
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
Knowledge is stored as **engrams** — small assertions that strengthen with use and decay when irrelevant. Search is fully local (BM25 + embeddings), so memory recall costs nothing and works offline.
|
|
45
|
+
Knowledge is stored as **engrams** — small assertions that strengthen with use and decay when irrelevant. Search is fully local (BM25 + embeddings), so memory recall costs nothing and works offline. [Benchmark methodology →](https://plur.ai/benchmark.html)
|
|
46
46
|
|
|
47
47
|
## Tools
|
|
48
48
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Compare two semver strings (e.g. "1.0.0" vs "1.1.0"). Returns negative if
|
|
4
|
+
* a < b, 0 if equal, positive if a > b. Tolerates missing patch segments
|
|
5
|
+
* (treats "1.0" as "1.0.0"). Strips prerelease suffixes — "1.0.0-rc1" is
|
|
6
|
+
* compared as "1.0.0", which means a prerelease compares EQUAL to its base
|
|
7
|
+
* release. Adequate for the controlled pack ecosystem; for prerelease
|
|
8
|
+
* support add a dedicated semver lib.
|
|
9
|
+
*
|
|
10
|
+
* Non-numeric leading segments (e.g. "v1.0.0", "abc.1.0") parse as 0 — so
|
|
11
|
+
* "v1.0.0" compares as [0,0,0]. This is intentional: we'd rather accept the
|
|
12
|
+
* pack and treat it as version-zero than throw. The `extractManifestVersion`
|
|
13
|
+
* regex normally strips the leading `v`; this is a defense-in-depth fallback.
|
|
14
|
+
*
|
|
15
|
+
* Calendar versioning (e.g. "2025.04") parses correctly as numeric segments,
|
|
16
|
+
* but a calendar-versioned pack will compare as far-future against semver-
|
|
17
|
+
* versioned bundled packs and never receive upgrades. Packs ship semver.
|
|
18
|
+
*/
|
|
19
|
+
declare function compareSemver(a: string, b: string): number;
|
|
20
|
+
/**
|
|
21
|
+
* Extract the `version` field from a pack's SKILL.md frontmatter without
|
|
22
|
+
* loading the whole pack. Returns null when SKILL.md is missing, the
|
|
23
|
+
* frontmatter has no version, or the version is nested under another key.
|
|
24
|
+
*
|
|
25
|
+
* Accepts both quoted (`version: "1.0.0"`) and unquoted (`version: 1.0.0`)
|
|
26
|
+
* forms. Rejects nested keys (`metadata:\n version: 1.0.0`) — the regex
|
|
27
|
+
* anchors to start-of-line so a leading space breaks the match.
|
|
28
|
+
*/
|
|
29
|
+
declare function extractManifestVersion(skillMdPath: string): string | null;
|
|
30
|
+
|
|
31
|
+
export { compareSemver, extractManifestVersion };
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
5
5
|
import { join } from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
6
7
|
import { homedir } from "os";
|
|
7
|
-
var VERSION = "0.9.
|
|
8
|
+
var VERSION = "0.9.4";
|
|
8
9
|
var HELP = `plur-mcp v${VERSION} \u2014 persistent memory for AI agents
|
|
9
10
|
|
|
10
11
|
Usage:
|
|
@@ -25,6 +26,33 @@ var MCP_SERVER_CONFIG = {
|
|
|
25
26
|
command: "npx",
|
|
26
27
|
args: ["-y", "@plur-ai/mcp@latest"]
|
|
27
28
|
};
|
|
29
|
+
function compareSemver(a, b) {
|
|
30
|
+
const stripPrerelease = (v) => v.split("-")[0].split("+")[0];
|
|
31
|
+
const stripV = (v) => v.replace(/^v/i, "");
|
|
32
|
+
const parse = (v) => stripV(stripPrerelease(v)).split(".").map((n) => {
|
|
33
|
+
const parsed = parseInt(n, 10);
|
|
34
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
35
|
+
});
|
|
36
|
+
const pa = parse(a);
|
|
37
|
+
const pb = parse(b);
|
|
38
|
+
for (let i = 0; i < Math.max(pa.length, pb.length, 3); i++) {
|
|
39
|
+
const ai = pa[i] ?? 0;
|
|
40
|
+
const bi = pb[i] ?? 0;
|
|
41
|
+
if (ai !== bi) return ai - bi;
|
|
42
|
+
}
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
function extractManifestVersion(skillMdPath) {
|
|
46
|
+
try {
|
|
47
|
+
const content = readFileSync(skillMdPath, "utf8");
|
|
48
|
+
const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/m);
|
|
49
|
+
if (!fmMatch) return null;
|
|
50
|
+
const versionMatch = fmMatch[1].match(/^version:\s*"?([^"\n]+)"?\s*$/m);
|
|
51
|
+
return versionMatch ? versionMatch[1].trim() : null;
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
28
56
|
var CLI = "npx @plur-ai/cli";
|
|
29
57
|
var PLUR_HOOKS = {
|
|
30
58
|
// --- Session lifecycle ---
|
|
@@ -178,6 +206,43 @@ async function runInit() {
|
|
|
178
206
|
results.push(`Hooks: ${hooksStatus}`);
|
|
179
207
|
const claudeMdStatus = installClaudeMd();
|
|
180
208
|
results.push(`CLAUDE.md: ${claudeMdStatus}`);
|
|
209
|
+
const { Plur } = await import("@plur-ai/core");
|
|
210
|
+
const plur = new Plur({ path: paths.root });
|
|
211
|
+
const bundledPacksDir = join(fileURLToPath(import.meta.url), "..", "..", "packs");
|
|
212
|
+
let packsStatus = "no bundled packs found";
|
|
213
|
+
if (existsSync(bundledPacksDir)) {
|
|
214
|
+
const installed = plur.listPacks();
|
|
215
|
+
const installedByName = new Map(installed.map((p) => [p.name, p.manifest?.version]));
|
|
216
|
+
const entries = readdirSync(bundledPacksDir).filter((e) => statSync(join(bundledPacksDir, e)).isDirectory());
|
|
217
|
+
const newPacks = [];
|
|
218
|
+
const upgradedPacks = [];
|
|
219
|
+
for (const entry of entries) {
|
|
220
|
+
const bundledManifestPath = join(bundledPacksDir, entry, "SKILL.md");
|
|
221
|
+
const bundledVersion = existsSync(bundledManifestPath) ? extractManifestVersion(bundledManifestPath) : null;
|
|
222
|
+
const installedVersion = installedByName.get(entry);
|
|
223
|
+
if (!installedByName.has(entry)) {
|
|
224
|
+
try {
|
|
225
|
+
plur.installPack(join(bundledPacksDir, entry));
|
|
226
|
+
newPacks.push(entry);
|
|
227
|
+
} catch {
|
|
228
|
+
}
|
|
229
|
+
} else if (bundledVersion && (!installedVersion || compareSemver(bundledVersion, installedVersion) > 0)) {
|
|
230
|
+
try {
|
|
231
|
+
plur.installPack(join(bundledPacksDir, entry));
|
|
232
|
+
upgradedPacks.push(
|
|
233
|
+
`${entry} ${installedVersion ?? "unknown"}\u2192${bundledVersion}`
|
|
234
|
+
);
|
|
235
|
+
} catch {
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const segments = [];
|
|
240
|
+
if (newPacks.length > 0) segments.push(`installed ${newPacks.join(", ")}`);
|
|
241
|
+
if (upgradedPacks.length > 0) segments.push(`upgraded ${upgradedPacks.join(", ")}`);
|
|
242
|
+
if (segments.length === 0) segments.push(`${entries.length} pack(s) already up-to-date`);
|
|
243
|
+
packsStatus = segments.join("; ");
|
|
244
|
+
}
|
|
245
|
+
results.push(`Packs: ${packsStatus}`);
|
|
181
246
|
process.stdout.write(`PLUR initialized.
|
|
182
247
|
|
|
183
248
|
Architecture: PLUR is a global tool \u2014 one MCP server, one engram
|
|
@@ -212,7 +277,7 @@ if (arg === "init") {
|
|
|
212
277
|
process.exit(0);
|
|
213
278
|
}
|
|
214
279
|
if (arg === "serve" || arg === void 0) {
|
|
215
|
-
const { runStdio } = await import("./server-
|
|
280
|
+
const { runStdio } = await import("./server-W73LKOH7.js");
|
|
216
281
|
runStdio().catch((err) => {
|
|
217
282
|
console.error("Failed to start PLUR MCP server:", err);
|
|
218
283
|
process.exit(1);
|
|
@@ -222,3 +287,7 @@ if (arg === "serve" || arg === void 0) {
|
|
|
222
287
|
Run plur-mcp --help for usage.`);
|
|
223
288
|
process.exit(1);
|
|
224
289
|
}
|
|
290
|
+
export {
|
|
291
|
+
compareSemver,
|
|
292
|
+
extractManifestVersion
|
|
293
|
+
};
|
|
@@ -119,7 +119,8 @@ function getToolDefinitions() {
|
|
|
119
119
|
commitment: { type: "string", enum: ["exploring", "leaning", "decided", "locked"], description: "Commitment level (default: leaning)" },
|
|
120
120
|
locked_reason: { type: "string", description: "Reason for locking (when commitment=locked)" },
|
|
121
121
|
memory_class: { type: "string", enum: ["semantic", "episodic", "procedural", "metacognitive"], description: "Memory classification (auto-set from type if omitted)" },
|
|
122
|
-
session_episode_id: { type: "string", description: "Link to current session episode for episodic anchoring" }
|
|
122
|
+
session_episode_id: { type: "string", description: "Link to current session episode for episodic anchoring" },
|
|
123
|
+
pinned: { type: "boolean", description: "Always-load flag. If true, this engram bypasses the keyword-relevance gate and is eligible for injection on every session, regardless of overlap with the user task. Use sparingly: meta-rules, safety conventions, core operating principles only." }
|
|
123
124
|
},
|
|
124
125
|
required: ["statement"]
|
|
125
126
|
},
|
|
@@ -141,6 +142,7 @@ function getToolDefinitions() {
|
|
|
141
142
|
locked_reason: args.locked_reason,
|
|
142
143
|
memory_class: args.memory_class,
|
|
143
144
|
session_episode_id: args.session_episode_id,
|
|
145
|
+
pinned: args.pinned,
|
|
144
146
|
llm
|
|
145
147
|
};
|
|
146
148
|
try {
|
|
@@ -150,6 +152,7 @@ function getToolDefinitions() {
|
|
|
150
152
|
statement: result.engram.statement,
|
|
151
153
|
scope: result.engram.scope,
|
|
152
154
|
type: result.engram.type,
|
|
155
|
+
pinned: result.engram.pinned === true,
|
|
153
156
|
decision: result.decision,
|
|
154
157
|
existing_id: result.existing_id,
|
|
155
158
|
tensions: result.tensions
|
|
@@ -218,12 +221,13 @@ function getToolDefinitions() {
|
|
|
218
221
|
handler: async (args, plur) => {
|
|
219
222
|
const budget = args.budget;
|
|
220
223
|
const effectiveLimit = budget?.max_results ?? args.limit ?? 20;
|
|
221
|
-
const
|
|
224
|
+
const meta = await plur.recallHybridWithMeta(args.query, {
|
|
222
225
|
scope: args.scope,
|
|
223
226
|
domain: args.domain,
|
|
224
227
|
limit: effectiveLimit,
|
|
225
228
|
min_strength: args.min_strength
|
|
226
229
|
});
|
|
230
|
+
const results = meta.engrams;
|
|
227
231
|
let truncated = false;
|
|
228
232
|
let boundedResults = results;
|
|
229
233
|
if (budget?.max_results && results.length > budget.max_results) {
|
|
@@ -245,7 +249,7 @@ function getToolDefinitions() {
|
|
|
245
249
|
boundedResults = withinBudget;
|
|
246
250
|
}
|
|
247
251
|
const includeEpisodes = args.include_episodes === true;
|
|
248
|
-
|
|
252
|
+
const response = {
|
|
249
253
|
results: boundedResults.map((e) => {
|
|
250
254
|
const raw = e;
|
|
251
255
|
const base = {
|
|
@@ -264,8 +268,12 @@ function getToolDefinitions() {
|
|
|
264
268
|
}),
|
|
265
269
|
count: boundedResults.length,
|
|
266
270
|
truncated,
|
|
267
|
-
mode:
|
|
271
|
+
mode: meta.mode
|
|
268
272
|
};
|
|
273
|
+
if (meta.mode === "hybrid-degraded") {
|
|
274
|
+
response.warning = `Embedding layer unavailable \u2014 results are BM25-only. Run plur_doctor for diagnosis. Last error: ${meta.embedderError ?? "unknown"}`;
|
|
275
|
+
}
|
|
276
|
+
return response;
|
|
269
277
|
}
|
|
270
278
|
},
|
|
271
279
|
{
|
|
@@ -376,6 +384,37 @@ function getToolDefinitions() {
|
|
|
376
384
|
}
|
|
377
385
|
}
|
|
378
386
|
},
|
|
387
|
+
{
|
|
388
|
+
name: "plur_pin",
|
|
389
|
+
description: "Toggle the always-load (pinned) flag on an engram. Pinned engrams bypass the keyword-relevance gate at injection time and are eligible for loading on every session, regardless of overlap with the user task. Use sparingly \u2014 meta-rules, safety conventions, core operating principles. Pass {id, pinned:true} to pin or {id, pinned:false} to unpin. List current pinned with {list:true}.",
|
|
390
|
+
annotations: { title: "Pin", destructiveHint: false, idempotentHint: true },
|
|
391
|
+
inputSchema: {
|
|
392
|
+
type: "object",
|
|
393
|
+
properties: {
|
|
394
|
+
id: { type: "string", description: "Engram ID to pin or unpin" },
|
|
395
|
+
pinned: { type: "boolean", description: "Target value (default true)" },
|
|
396
|
+
list: { type: "boolean", description: "If true, just return the current set of pinned engrams (no mutation)" }
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
handler: async (args, plur) => {
|
|
400
|
+
if (args.list === true) {
|
|
401
|
+
const pinned = plur.listPinned();
|
|
402
|
+
return {
|
|
403
|
+
count: pinned.length,
|
|
404
|
+
pinned: pinned.map((e) => ({ id: e.id, statement: e.statement, scope: e.scope, domain: e.domain }))
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
if (!args.id) throw new Error("Provide id (or list:true to list pinned)");
|
|
408
|
+
const target = args.pinned ?? true;
|
|
409
|
+
const updated = plur.setPinned(args.id, target);
|
|
410
|
+
if (!updated) throw new Error(`Engram not found: ${args.id}`);
|
|
411
|
+
return {
|
|
412
|
+
id: updated.id,
|
|
413
|
+
statement: updated.statement,
|
|
414
|
+
pinned: updated.pinned === true
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
},
|
|
379
418
|
{
|
|
380
419
|
name: "plur_forget",
|
|
381
420
|
description: "Retire an engram by ID or search term \u2014 marks it as no longer active without deleting history",
|
|
@@ -810,6 +849,82 @@ function getToolDefinitions() {
|
|
|
810
849
|
};
|
|
811
850
|
}
|
|
812
851
|
},
|
|
852
|
+
{
|
|
853
|
+
name: "plur_doctor",
|
|
854
|
+
description: "Diagnose the PLUR install. Reports whether the embedding model loaded, whether hybrid search is fully operational, and what to do if it is degraded. Run this first when recall feels off.",
|
|
855
|
+
annotations: { title: "Doctor", readOnlyHint: false, idempotentHint: false },
|
|
856
|
+
inputSchema: {
|
|
857
|
+
type: "object",
|
|
858
|
+
properties: {
|
|
859
|
+
retry: { type: "boolean", description: "If true, reset cached embedder failure state and retry the model load before reporting" }
|
|
860
|
+
}
|
|
861
|
+
},
|
|
862
|
+
handler: async (args, plur) => {
|
|
863
|
+
if (args.retry === true) {
|
|
864
|
+
plur.resetEmbedder();
|
|
865
|
+
}
|
|
866
|
+
const status = plur.status();
|
|
867
|
+
const before = plur.embedderStatus();
|
|
868
|
+
if (!before.disabled) {
|
|
869
|
+
try {
|
|
870
|
+
await plur.recallSemantic("plur doctor probe", { limit: 1 });
|
|
871
|
+
} catch {
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
const after = plur.embedderStatus();
|
|
875
|
+
const checks = [];
|
|
876
|
+
checks.push({
|
|
877
|
+
check: "engram store",
|
|
878
|
+
ok: status.engram_count > 0,
|
|
879
|
+
detail: `${status.engram_count} engrams across ${status.pack_count} packs at ${status.storage_root}`
|
|
880
|
+
});
|
|
881
|
+
if (after.disabled) {
|
|
882
|
+
checks.push({
|
|
883
|
+
check: "embedder available",
|
|
884
|
+
ok: true,
|
|
885
|
+
detail: `Disabled on purpose \u2014 ${after.disabledReason ?? "embeddings disabled"}`
|
|
886
|
+
});
|
|
887
|
+
checks.push({
|
|
888
|
+
check: "hybrid search operational",
|
|
889
|
+
ok: true,
|
|
890
|
+
detail: "Running in BM25-only mode (embeddings opted out)"
|
|
891
|
+
});
|
|
892
|
+
} else {
|
|
893
|
+
checks.push({
|
|
894
|
+
check: "embedder available",
|
|
895
|
+
ok: after.available && after.loaded,
|
|
896
|
+
detail: after.loaded ? "BGE-small-en-v1.5 loaded" : after.lastError ? `Failed to load: ${after.lastError}` : "Not yet loaded \u2014 first call may have raced; try again or use retry:true"
|
|
897
|
+
});
|
|
898
|
+
const hybridOk = after.available && after.loaded;
|
|
899
|
+
checks.push({
|
|
900
|
+
check: "hybrid search operational",
|
|
901
|
+
ok: hybridOk,
|
|
902
|
+
detail: hybridOk ? "Hybrid search will use BM25 + embeddings (fully functional)" : "Hybrid search will silently degrade to BM25-only \u2014 semantic recall disabled"
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
const remediation = [];
|
|
906
|
+
if (!after.disabled && !(after.available && after.loaded)) {
|
|
907
|
+
remediation.push(
|
|
908
|
+
"Embedding model is not loaded. Common causes:",
|
|
909
|
+
" \u2022 First-run download not yet completed (try: plur_doctor with retry:true)",
|
|
910
|
+
" \u2022 Network blocked HuggingFace Hub fetch \u2014 check connectivity to huggingface.co",
|
|
911
|
+
" \u2022 pnpm hoisting issue: @huggingface/transformers must resolve onnxruntime-node from the package root, not a workspace package",
|
|
912
|
+
" \u2022 Corrupt model cache: a half-completed download leaves a broken cache that fails every subsequent load. Delete `~/.cache/huggingface/hub/models--Xenova--bge-small-en-v1.5/` and retry \u2014 the model will redownload on next call.",
|
|
913
|
+
" \u2022 Manual fix: from the @plur-ai/core package directory, run a script that imports @huggingface/transformers and calls pipeline() to trigger the download",
|
|
914
|
+
" \u2022 Or opt out: set PLUR_DISABLE_EMBEDDINGS=1, or write `embeddings: { enabled: false }` to ~/.plur/config.yaml \u2014 hybrid search will run BM25-only"
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
return {
|
|
918
|
+
ok: checks.every((c) => c.ok),
|
|
919
|
+
checks,
|
|
920
|
+
embedder: {
|
|
921
|
+
before_probe: before,
|
|
922
|
+
after_probe: after
|
|
923
|
+
},
|
|
924
|
+
remediation: remediation.length > 0 ? remediation : ["All checks passed \u2014 PLUR is healthy."]
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
},
|
|
813
928
|
{
|
|
814
929
|
name: "plur_session_start",
|
|
815
930
|
description: "Start a session \u2014 inject relevant engrams for your task. Call at the beginning of every session.",
|
|
@@ -1290,7 +1405,7 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
|
|
|
1290
1405
|
|
|
1291
1406
|
// src/server.ts
|
|
1292
1407
|
import { z } from "zod";
|
|
1293
|
-
var VERSION = "0.9.
|
|
1408
|
+
var VERSION = "0.9.4";
|
|
1294
1409
|
var INSTRUCTIONS = `PLUR is your persistent memory. Corrections, preferences, and conventions persist across sessions as engrams.
|
|
1295
1410
|
|
|
1296
1411
|
PLUR is a GLOBAL tool \u2014 one MCP server, one engram store (~/.plur/), available in every project. Multi-project scoping uses domain/scope fields on engrams, not separate installations.
|
package/package.json
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plur-ai/mcp",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"plur-mcp": "dist/index.js"
|
|
7
7
|
},
|
|
8
8
|
"main": "dist/index.js",
|
|
9
9
|
"files": [
|
|
10
|
-
"dist"
|
|
10
|
+
"dist",
|
|
11
|
+
"packs"
|
|
11
12
|
],
|
|
12
13
|
"dependencies": {
|
|
13
14
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
14
15
|
"zod": "^3.23.0",
|
|
15
|
-
"@plur-ai/core": "0.9.
|
|
16
|
+
"@plur-ai/core": "0.9.4"
|
|
16
17
|
},
|
|
17
18
|
"devDependencies": {
|
|
18
19
|
"@types/node": "^25.5.0"
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Effective Memory
|
|
3
|
+
description: The essential habits for an AI agent with memory — session bookends, learning triggers, verification, safety, and the operational discipline that turns raw recall into compounding intelligence. Pinned, always-injected.
|
|
4
|
+
version: "1.1.0"
|
|
5
|
+
creator: plur-ai
|
|
6
|
+
license: MIT
|
|
7
|
+
tags: [memory, learning, best-practices, session-management, feedback, safety, verification, discipline, time]
|
|
8
|
+
x-datacore:
|
|
9
|
+
id: effective-memory
|
|
10
|
+
injection_policy: on_match
|
|
11
|
+
match_terms: [memory, learn, remember, session, feedback, engram, forget, correction, preference, recall, verification, safety, plur]
|
|
12
|
+
domain: plur.best-practices
|
|
13
|
+
engram_count: 12
|
|
14
|
+
# Note: every engram in this pack carries `pinned: true` at the engram
|
|
15
|
+
# level, which is the actual "always-inject" mechanism (introduced in
|
|
16
|
+
# PLUR 0.9.4). Pack-level injection_policy: pinned is not a recognized
|
|
17
|
+
# value in 0.9.4 — keep it on_match here so loaders that respect it
|
|
18
|
+
# behave predictably; the per-engram pinned flags do the work.
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Effective Memory
|
|
22
|
+
|
|
23
|
+
Your agent has memory. These habits make it actually useful.
|
|
24
|
+
|
|
25
|
+
Without them, memory is a growing pile of assertions nobody retrieves. With them, memory compounds — each session builds on the last, corrections stick, and the agent gets measurably better over time.
|
|
26
|
+
|
|
27
|
+
This pack is **pinned** in PLUR 0.9.4+. Engrams here bypass keyword gating and are always eligible for injection at session start. They cover the meta-rules every agent needs regardless of domain: how to capture corrections, when to recall before answering, what "verified" means, how to stay safe with destructive actions, and why never to type a weekday from memory.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx @plur-ai/cli@0.9.4 packs install effective-memory
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
(In 0.9.4+, `plur init` auto-installs this pack — manual install is rarely needed.)
|
|
36
|
+
|
|
37
|
+
## What's inside
|
|
38
|
+
|
|
39
|
+
12 engrams covering:
|
|
40
|
+
|
|
41
|
+
- **Capture** — call `plur_learn` immediately on corrections; detect correction-shaped phrases.
|
|
42
|
+
- **Recall** — `plur_recall_hybrid` before factual answers; don't confabulate.
|
|
43
|
+
- **Session lifecycle** — bookend with `plur_session_start` / `plur_session_end`; `plur_feedback` on injected engrams; `plur_timeline` for long-horizon agents.
|
|
44
|
+
- **Verification** — artifact-first; never bulk-mark as done from narrative text.
|
|
45
|
+
- **Safety** — irreversible actions need actual user confirmation and one-item dry-runs.
|
|
46
|
+
- **Discipline** — read before edit; don't ask "want to continue?" mid-task.
|
|
47
|
+
- **Time** — never type a day-of-week from memory.
|
|
48
|
+
|
|
49
|
+
## Why pinned
|
|
50
|
+
|
|
51
|
+
Pinned engrams (introduced in PLUR 0.9.4) bypass the keyword-relevance gate in `scoreEngram` and per-pack/per-domain caps in `fillTokenBudget`. They are always eligible for injection regardless of how the user's query keywords overlap with the engram statement. Use this for cross-cutting meta-rules only; pinning everything defeats the purpose.
|
|
52
|
+
|
|
53
|
+
## Versioning
|
|
54
|
+
|
|
55
|
+
| Version | Changes |
|
|
56
|
+
|---|---|
|
|
57
|
+
| 1.1.0 | Consolidated `plur-required` meta-rules into this pack. All engrams now `pinned: true`. Added verification, safety, discipline, and time-handling rules. Engram count 8 → 12. |
|
|
58
|
+
| 1.0.0 | Initial pack — 8 engrams covering session bookends, learning triggers, and feedback loops. |
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
engrams:
|
|
2
|
+
# === Session lifecycle ===
|
|
3
|
+
|
|
4
|
+
- id: ENG-PACK-EM-001
|
|
5
|
+
version: 2
|
|
6
|
+
status: active
|
|
7
|
+
type: procedural
|
|
8
|
+
scope: global
|
|
9
|
+
visibility: public
|
|
10
|
+
pinned: true
|
|
11
|
+
statement: >-
|
|
12
|
+
Every session must bookend with plur_session_start (beginning) and
|
|
13
|
+
plur_session_end (end). Session start injects relevant engrams into
|
|
14
|
+
context. Session end captures what was learned, what failed, and what
|
|
15
|
+
to continue. Without bookends, the agent starts every conversation
|
|
16
|
+
from zero.
|
|
17
|
+
rationale: >-
|
|
18
|
+
Session boundaries are the minimum viable memory habit. Everything
|
|
19
|
+
else builds on them.
|
|
20
|
+
domain: plur.session
|
|
21
|
+
tags: [session, lifecycle, start, end]
|
|
22
|
+
activation: { retrieval_strength: 0.95, storage_strength: 0.95, frequency: 0, last_accessed: "2026-05-04" }
|
|
23
|
+
dual_coding:
|
|
24
|
+
example: "User says 'fix the auth bug'. Agent calls plur_session_start('fixing auth bug') — gets injected with 'OAuth tokens expire after 1h, not 24h' from last week's session. Fixes it in 5 minutes instead of rediscovering the issue."
|
|
25
|
+
analogy: "Like reading your notes before a meeting vs walking in cold every time."
|
|
26
|
+
pack: effective-memory
|
|
27
|
+
polarity: do
|
|
28
|
+
commitment: locked
|
|
29
|
+
|
|
30
|
+
# === Capture: corrections and learn-triggers ===
|
|
31
|
+
|
|
32
|
+
- id: ENG-PACK-EM-002
|
|
33
|
+
version: 2
|
|
34
|
+
status: active
|
|
35
|
+
type: behavioral
|
|
36
|
+
scope: global
|
|
37
|
+
visibility: public
|
|
38
|
+
pinned: true
|
|
39
|
+
statement: >-
|
|
40
|
+
When the user corrects you, call plur_learn IMMEDIATELY — before
|
|
41
|
+
continuing the task. Acknowledging the correction in prose is not
|
|
42
|
+
enough; that lesson disappears with context-window compression. Don't
|
|
43
|
+
batch corrections, don't wait until end of session — capture them the
|
|
44
|
+
instant they happen.
|
|
45
|
+
rationale: >-
|
|
46
|
+
Corrections are the highest-value engrams because they prevent
|
|
47
|
+
repeated mistakes. Without immediate plur_learn the same correction
|
|
48
|
+
will need to be given again next session — and self-observation of
|
|
49
|
+
the correction moment is the primary input gap of memory systems.
|
|
50
|
+
domain: plur.learning
|
|
51
|
+
tags: [correction, learn, immediate, capture, plur_learn]
|
|
52
|
+
activation: { retrieval_strength: 0.95, storage_strength: 0.95, frequency: 0, last_accessed: "2026-05-04" }
|
|
53
|
+
dual_coding:
|
|
54
|
+
example: "User: 'No, use snake_case not camelCase in this repo.' Agent immediately calls plur_learn('This repo uses snake_case, not camelCase') before proceeding with the fix."
|
|
55
|
+
pack: effective-memory
|
|
56
|
+
polarity: do
|
|
57
|
+
commitment: locked
|
|
58
|
+
|
|
59
|
+
- id: ENG-PACK-EM-003
|
|
60
|
+
version: 2
|
|
61
|
+
status: active
|
|
62
|
+
type: behavioral
|
|
63
|
+
scope: global
|
|
64
|
+
visibility: public
|
|
65
|
+
pinned: true
|
|
66
|
+
statement: >-
|
|
67
|
+
Treat correction-shaped phrases as engram triggers. When the user
|
|
68
|
+
uses "actually", "no, what I meant", "wait", "I want X not Y", "from
|
|
69
|
+
now on", "the right way is", "you misunderstood", or similar, pause
|
|
70
|
+
and ask whether the rule belongs in plur_learn before continuing.
|
|
71
|
+
rationale: >-
|
|
72
|
+
Many corrections are subtle — they don't say "remember this" but
|
|
73
|
+
they establish a durable rule. The phrase pattern is a reliable
|
|
74
|
+
signal that a learnable rule has just been stated.
|
|
75
|
+
domain: plur.learning
|
|
76
|
+
tags: [correction-detection, plur_learn, signal, meta]
|
|
77
|
+
activation: { retrieval_strength: 0.9, storage_strength: 0.9, frequency: 0, last_accessed: "2026-05-04" }
|
|
78
|
+
pack: effective-memory
|
|
79
|
+
polarity: do
|
|
80
|
+
commitment: decided
|
|
81
|
+
|
|
82
|
+
# === Recall: don't confabulate ===
|
|
83
|
+
|
|
84
|
+
- id: ENG-PACK-EM-004
|
|
85
|
+
version: 2
|
|
86
|
+
status: active
|
|
87
|
+
type: procedural
|
|
88
|
+
scope: global
|
|
89
|
+
visibility: public
|
|
90
|
+
pinned: true
|
|
91
|
+
statement: >-
|
|
92
|
+
Before answering factual questions about the user's project, codebase,
|
|
93
|
+
preferences, infrastructure, or past decisions, call plur_recall_hybrid
|
|
94
|
+
first. The answer may already be in memory. Do not fabricate or compose
|
|
95
|
+
answers from prior-conversation context — that context is often stale
|
|
96
|
+
or invented.
|
|
97
|
+
rationale: >-
|
|
98
|
+
Confabulation is the failure mode when memory exists but isn't
|
|
99
|
+
queried. Recall before reasoning. The most common memory failure
|
|
100
|
+
is not forgetting — it's not checking.
|
|
101
|
+
domain: plur.recall
|
|
102
|
+
tags: [recall, search, before-answering, plur_recall_hybrid, confabulation]
|
|
103
|
+
activation: { retrieval_strength: 0.95, storage_strength: 0.95, frequency: 0, last_accessed: "2026-05-04" }
|
|
104
|
+
dual_coding:
|
|
105
|
+
example: "User asks 'how do we deploy to production?' — call plur_recall_hybrid('deployment production') before answering. You might find 'deploy requires git push to nightshift server, not GitHub' from a previous session."
|
|
106
|
+
analogy: "Like checking your notes before answering a question in a meeting, instead of guessing."
|
|
107
|
+
pack: effective-memory
|
|
108
|
+
polarity: do
|
|
109
|
+
commitment: locked
|
|
110
|
+
|
|
111
|
+
# === Feedback loop ===
|
|
112
|
+
|
|
113
|
+
- id: ENG-PACK-EM-005
|
|
114
|
+
version: 2
|
|
115
|
+
status: active
|
|
116
|
+
type: behavioral
|
|
117
|
+
scope: global
|
|
118
|
+
visibility: public
|
|
119
|
+
pinned: true
|
|
120
|
+
statement: >-
|
|
121
|
+
Call plur_feedback on injected engrams — positive when helpful,
|
|
122
|
+
negative when wrong or stale. Feedback trains the injection
|
|
123
|
+
algorithm. Without it, irrelevant engrams keep appearing and
|
|
124
|
+
relevant ones get buried. This is how memory gets smarter, not
|
|
125
|
+
just bigger.
|
|
126
|
+
rationale: >-
|
|
127
|
+
Feedback is the training signal for retrieval quality. Memory
|
|
128
|
+
without feedback is a search engine that never improves its
|
|
129
|
+
ranking.
|
|
130
|
+
domain: plur.feedback
|
|
131
|
+
tags: [feedback, relevance, training, plur_feedback]
|
|
132
|
+
activation: { retrieval_strength: 0.9, storage_strength: 0.9, frequency: 0, last_accessed: "2026-05-04" }
|
|
133
|
+
dual_coding:
|
|
134
|
+
example: "Session starts, 8 engrams injected. 2 were directly useful for the task — call plur_feedback positive. 1 was about a project you finished last month — call plur_feedback negative. The rest fine but unremarkable — skip or neutral."
|
|
135
|
+
pack: effective-memory
|
|
136
|
+
polarity: do
|
|
137
|
+
commitment: locked
|
|
138
|
+
|
|
139
|
+
# === Engram quality ===
|
|
140
|
+
|
|
141
|
+
- id: ENG-PACK-EM-006
|
|
142
|
+
version: 2
|
|
143
|
+
status: active
|
|
144
|
+
type: behavioral
|
|
145
|
+
scope: global
|
|
146
|
+
visibility: public
|
|
147
|
+
pinned: true
|
|
148
|
+
statement: >-
|
|
149
|
+
Learn preferences and durable rules — not trivial facts,
|
|
150
|
+
session-specific state, or things derivable from the codebase. Good
|
|
151
|
+
engrams: "this API returns snake_case despite the docs saying
|
|
152
|
+
camelCase", "user prefers DCA over single entries", "deploy requires
|
|
153
|
+
VPN connection first". Bad engrams: "the user said hello", "we are
|
|
154
|
+
working on file X", "function foo is on line 42".
|
|
155
|
+
rationale: >-
|
|
156
|
+
Memory quality matters more than quantity. Noisy engrams dilute
|
|
157
|
+
injection relevance and waste context window. Preferences are the
|
|
158
|
+
difference between a tool and an assistant that knows you;
|
|
159
|
+
derivable facts are noise.
|
|
160
|
+
domain: plur.learning
|
|
161
|
+
tags: [quality, preference, anti-pattern, noise, personalization]
|
|
162
|
+
activation: { retrieval_strength: 0.9, storage_strength: 0.9, frequency: 0, last_accessed: "2026-05-04" }
|
|
163
|
+
dual_coding:
|
|
164
|
+
example: "User: 'I like terse responses, skip the summaries.' → high-value engram (preference). User: 'okay, sounds good' → no engram (no rule, just acknowledgement)."
|
|
165
|
+
pack: effective-memory
|
|
166
|
+
polarity: do
|
|
167
|
+
commitment: locked
|
|
168
|
+
|
|
169
|
+
# === Long-horizon agents ===
|
|
170
|
+
|
|
171
|
+
- id: ENG-PACK-EM-007
|
|
172
|
+
version: 2
|
|
173
|
+
status: active
|
|
174
|
+
type: architectural
|
|
175
|
+
scope: global
|
|
176
|
+
visibility: public
|
|
177
|
+
pinned: true
|
|
178
|
+
statement: >-
|
|
179
|
+
Long-horizon agents — agents that run repeatedly on the same domain
|
|
180
|
+
across days or weeks — MUST call plur_timeline at startup to read
|
|
181
|
+
past episodes. Without timeline context, the agent rediscovers the
|
|
182
|
+
same blockers, repeats failed approaches, and loses coherence across
|
|
183
|
+
runs. This is "coherence failure" — distinct from intelligence
|
|
184
|
+
failure and the dominant failure mode for long-running agents.
|
|
185
|
+
rationale: >-
|
|
186
|
+
A doctor with amnesia is competent each visit but never builds on
|
|
187
|
+
the previous one. Timeline is the patient chart.
|
|
188
|
+
domain: plur.agents
|
|
189
|
+
tags: [timeline, long-horizon, coherence, episodic, plur_timeline]
|
|
190
|
+
activation: { retrieval_strength: 0.9, storage_strength: 0.9, frequency: 0, last_accessed: "2026-05-04" }
|
|
191
|
+
dual_coding:
|
|
192
|
+
example: "Project manager agent runs Monday, escalates blocker to teammate, no response. Without timeline: Tuesday it escalates again. With timeline: Tuesday it sees Monday's episode, knows escalation failed, tries different approach."
|
|
193
|
+
analogy: "Like having a doctor with anterograde amnesia treating the same patient daily — competent each visit but never builds on the previous one."
|
|
194
|
+
pack: effective-memory
|
|
195
|
+
polarity: do
|
|
196
|
+
commitment: locked
|
|
197
|
+
|
|
198
|
+
# === Trust observation over stale memory ===
|
|
199
|
+
|
|
200
|
+
- id: ENG-PACK-EM-008
|
|
201
|
+
version: 2
|
|
202
|
+
status: active
|
|
203
|
+
type: behavioral
|
|
204
|
+
scope: global
|
|
205
|
+
visibility: public
|
|
206
|
+
pinned: true
|
|
207
|
+
statement: >-
|
|
208
|
+
When memory and current reality conflict, trust what you observe
|
|
209
|
+
now — then update the stale engram. Call plur_learn with the
|
|
210
|
+
correction and plur_feedback negative on the old engram. Memory
|
|
211
|
+
is a starting point for reasoning, not a substitute for verification.
|
|
212
|
+
rationale: >-
|
|
213
|
+
Stale memory acted on without verification causes worse outcomes
|
|
214
|
+
than no memory at all.
|
|
215
|
+
domain: plur.maintenance
|
|
216
|
+
tags: [staleness, verification, update, conflict]
|
|
217
|
+
activation: { retrieval_strength: 0.9, storage_strength: 0.9, frequency: 0, last_accessed: "2026-05-04" }
|
|
218
|
+
dual_coding:
|
|
219
|
+
example: "Memory says 'deploy to server at 10.0.0.5'. You SSH and it times out. Don't keep trying — the IP changed. Verify, update the engram, then proceed."
|
|
220
|
+
pack: effective-memory
|
|
221
|
+
polarity: do
|
|
222
|
+
commitment: locked
|
|
223
|
+
|
|
224
|
+
# === Verification: what "done" means ===
|
|
225
|
+
|
|
226
|
+
- id: ENG-PACK-EM-009
|
|
227
|
+
version: 1
|
|
228
|
+
status: active
|
|
229
|
+
type: behavioral
|
|
230
|
+
scope: global
|
|
231
|
+
visibility: public
|
|
232
|
+
pinned: true
|
|
233
|
+
statement: >-
|
|
234
|
+
For coding/work tasks, "verify if done" means checking the actual
|
|
235
|
+
artifact (code, file, deployment, commit, infrastructure), not
|
|
236
|
+
keyword-matching against narrative text (journals, notes,
|
|
237
|
+
briefings). Per-item verification against the deliverable. Never
|
|
238
|
+
bulk-action without per-item artifact-level evidence.
|
|
239
|
+
rationale: >-
|
|
240
|
+
Narrative text mentions topics in passing. Bulk action that depends
|
|
241
|
+
on keyword matches has been observed to mark genuinely-incomplete
|
|
242
|
+
work as done, hiding state from the user.
|
|
243
|
+
domain: agent.verification
|
|
244
|
+
tags: [verification, artifact-first, no-bulk-action]
|
|
245
|
+
activation: { retrieval_strength: 0.95, storage_strength: 1, frequency: 0, last_accessed: "2026-05-04" }
|
|
246
|
+
pack: effective-memory
|
|
247
|
+
polarity: do
|
|
248
|
+
commitment: locked
|
|
249
|
+
|
|
250
|
+
# === Safety: irreversibility & consent ===
|
|
251
|
+
|
|
252
|
+
- id: ENG-PACK-EM-010
|
|
253
|
+
version: 1
|
|
254
|
+
status: active
|
|
255
|
+
type: behavioral
|
|
256
|
+
scope: global
|
|
257
|
+
visibility: public
|
|
258
|
+
pinned: true
|
|
259
|
+
statement: >-
|
|
260
|
+
"User confirms" steps in plans, sensitive-file actions, and
|
|
261
|
+
destructive operations require an actual user message in the
|
|
262
|
+
conversation. Self-authorization, prior-session approval, configured
|
|
263
|
+
allowlists, or inferred consent do NOT satisfy. Before any
|
|
264
|
+
irreversible action (delete, force-push, drop table, chmod near
|
|
265
|
+
$HOME, mass-rename, mass-cancel) run a one-item dry-run first and
|
|
266
|
+
surface the result for the user.
|
|
267
|
+
rationale: >-
|
|
268
|
+
Auto-confirming "user confirms" steps defeats the purpose of having
|
|
269
|
+
them — they exist precisely for moments where automation should
|
|
270
|
+
pause. One-item-first turns a potentially catastrophic mistake into
|
|
271
|
+
a single easy revert.
|
|
272
|
+
domain: agent.safety
|
|
273
|
+
tags: [confirmation, consent, safety, sensitive, irreversible, dry-run, bulk]
|
|
274
|
+
activation: { retrieval_strength: 0.95, storage_strength: 1, frequency: 0, last_accessed: "2026-05-04" }
|
|
275
|
+
pack: effective-memory
|
|
276
|
+
polarity: do
|
|
277
|
+
commitment: locked
|
|
278
|
+
|
|
279
|
+
# === Discipline: read first, ask less ===
|
|
280
|
+
|
|
281
|
+
- id: ENG-PACK-EM-011
|
|
282
|
+
version: 1
|
|
283
|
+
status: active
|
|
284
|
+
type: behavioral
|
|
285
|
+
scope: global
|
|
286
|
+
visibility: public
|
|
287
|
+
pinned: true
|
|
288
|
+
statement: >-
|
|
289
|
+
Read the target file before editing it; don't edit based on imagined
|
|
290
|
+
content or remembered structure. Read → diff → write. Separately,
|
|
291
|
+
don't ask the user "want to continue?", "should I proceed?", or
|
|
292
|
+
"shall I keep going?" mid-task. Keep working until blocked, the task
|
|
293
|
+
is done, or you hit a genuine decision-point (destructive action,
|
|
294
|
+
new direction, sensitive choice). The user already said yes by
|
|
295
|
+
giving the task.
|
|
296
|
+
rationale: >-
|
|
297
|
+
Memory of file content drifts during a session — edits to imagined
|
|
298
|
+
content corrupt the file or introduce silent regressions.
|
|
299
|
+
"Want to continue?" prompts add friction without information; they
|
|
300
|
+
train the user to ignore agent prompts, which then bleeds into
|
|
301
|
+
ignoring the asks that DO matter.
|
|
302
|
+
domain: agent.discipline
|
|
303
|
+
tags: [read-before-edit, discipline, flow, prompts, agent-behavior]
|
|
304
|
+
activation: { retrieval_strength: 0.9, storage_strength: 1, frequency: 0, last_accessed: "2026-05-04" }
|
|
305
|
+
pack: effective-memory
|
|
306
|
+
polarity: do
|
|
307
|
+
commitment: locked
|
|
308
|
+
|
|
309
|
+
# === Time: don't hallucinate dates ===
|
|
310
|
+
|
|
311
|
+
- id: ENG-PACK-EM-012
|
|
312
|
+
version: 1
|
|
313
|
+
status: active
|
|
314
|
+
type: behavioral
|
|
315
|
+
scope: global
|
|
316
|
+
visibility: public
|
|
317
|
+
pinned: true
|
|
318
|
+
statement: >-
|
|
319
|
+
Never type a day-of-week from memory. LLMs systematically
|
|
320
|
+
hallucinate weekdays for dates outside their training cutoff or for
|
|
321
|
+
any specific date. Always verify via a date utility (system clock,
|
|
322
|
+
datacore.date, `date`, or `python -c "from datetime import date;
|
|
323
|
+
..."`) before writing a date+weekday into any file or message.
|
|
324
|
+
rationale: >-
|
|
325
|
+
Wrong weekdays in calendar entries, journal headers, and scheduled
|
|
326
|
+
tasks cause real misalignment. The check costs ~50ms.
|
|
327
|
+
domain: agent.time
|
|
328
|
+
tags: [dates, weekday, time, hallucination]
|
|
329
|
+
activation: { retrieval_strength: 0.9, storage_strength: 1, frequency: 0, last_accessed: "2026-05-04" }
|
|
330
|
+
pack: effective-memory
|
|
331
|
+
polarity: do
|
|
332
|
+
commitment: locked
|