@plur-ai/mcp 0.9.3 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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. **86.7% on LongMemEval** — on par with cloud memory services.
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
@@ -5,7 +5,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSy
5
5
  import { join } from "path";
6
6
  import { fileURLToPath } from "url";
7
7
  import { homedir } from "os";
8
- var VERSION = "0.9.3";
8
+ var VERSION = "0.9.5";
9
9
  var HELP = `plur-mcp v${VERSION} \u2014 persistent memory for AI agents
10
10
 
11
11
  Usage:
@@ -26,6 +26,33 @@ var MCP_SERVER_CONFIG = {
26
26
  command: "npx",
27
27
  args: ["-y", "@plur-ai/mcp@latest"]
28
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
+ }
29
56
  var CLI = "npx @plur-ai/cli";
30
57
  var PLUR_HOOKS = {
31
58
  // --- Session lifecycle ---
@@ -185,19 +212,35 @@ async function runInit() {
185
212
  let packsStatus = "no bundled packs found";
186
213
  if (existsSync(bundledPacksDir)) {
187
214
  const installed = plur.listPacks();
188
- const installedNames = new Set(installed.map((p) => p.name));
215
+ const installedByName = new Map(installed.map((p) => [p.name, p.manifest?.version]));
189
216
  const entries = readdirSync(bundledPacksDir).filter((e) => statSync(join(bundledPacksDir, e)).isDirectory());
190
217
  const newPacks = [];
218
+ const upgradedPacks = [];
191
219
  for (const entry of entries) {
192
- if (!installedNames.has(entry)) {
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)) {
193
224
  try {
194
225
  plur.installPack(join(bundledPacksDir, entry));
195
226
  newPacks.push(entry);
196
227
  } catch {
197
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
+ }
198
237
  }
199
238
  }
200
- packsStatus = newPacks.length > 0 ? `installed ${newPacks.join(", ")}` : `${entries.length} pack(s) already installed`;
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("; ");
201
244
  }
202
245
  results.push(`Packs: ${packsStatus}`);
203
246
  process.stdout.write(`PLUR initialized.
@@ -234,7 +277,7 @@ if (arg === "init") {
234
277
  process.exit(0);
235
278
  }
236
279
  if (arg === "serve" || arg === void 0) {
237
- const { runStdio } = await import("./server-SKNKYZ7O.js");
280
+ const { runStdio } = await import("./server-MAFJ365R.js");
238
281
  runStdio().catch((err) => {
239
282
  console.error("Failed to start PLUR MCP server:", err);
240
283
  process.exit(1);
@@ -244,3 +287,7 @@ if (arg === "serve" || arg === void 0) {
244
287
  Run plur-mcp --help for usage.`);
245
288
  process.exit(1);
246
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 results = await plur.recallHybrid(args.query, {
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
- return {
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: "hybrid"
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.",
@@ -938,24 +1053,38 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
938
1053
  },
939
1054
  {
940
1055
  name: "plur_stores_add",
941
- description: "Register an additional engram store at a filesystem path with a scope identifier",
1056
+ description: "Register an additional engram store. Either filesystem (path) or remote (url+token, e.g. PLUR Enterprise).",
942
1057
  annotations: { title: "Add store", destructiveHint: false, idempotentHint: true },
943
1058
  inputSchema: {
944
1059
  type: "object",
945
1060
  properties: {
946
- path: { type: "string", description: "Filesystem path to engrams.yaml" },
947
- scope: { type: "string", description: "Scope identifier (e.g. space:1-datafund, module:trading)" },
948
- shared: { type: "boolean", description: "Whether this store is git-committed / team-visible" },
1061
+ path: { type: "string", description: "Filesystem path to engrams.yaml (omit if registering a remote store)" },
1062
+ url: { type: "string", description: "Remote store base URL, e.g. https://plur.datafund.io/sse \u2014 pair with token" },
1063
+ token: { type: "string", description: "Bearer token (JWT or plur_sk_... API key) for remote stores" },
1064
+ scope: { type: "string", description: "Scope identifier (e.g. space:1-datafund, group:plur/plur-ai/engineering)" },
1065
+ shared: { type: "boolean", description: "Whether this store is git-committed / team-visible (remote stores default true)" },
949
1066
  readonly: { type: "boolean", description: "Whether this store is read-only (e.g. purchased packs)" }
950
1067
  },
951
- required: ["path", "scope"]
1068
+ required: ["scope"]
952
1069
  },
953
1070
  handler: async (args, plur) => {
954
- plur.addStore(args.path, args.scope, {
1071
+ const path = args.path;
1072
+ const url = args.url;
1073
+ const token = args.token;
1074
+ if (!path && !url) return { error: "Either path or url must be provided" };
1075
+ if (path && url) return { error: "Provide path OR url, not both" };
1076
+ plur.addStore(path ?? "", args.scope, {
955
1077
  shared: args.shared,
956
- readonly: args.readonly
1078
+ readonly: args.readonly,
1079
+ url,
1080
+ token
957
1081
  });
958
- return { success: true, path: args.path, scope: args.scope };
1082
+ return {
1083
+ success: true,
1084
+ ...path ? { path } : { url },
1085
+ scope: args.scope,
1086
+ kind: url ? "remote" : "filesystem"
1087
+ };
959
1088
  }
960
1089
  },
961
1090
  {
@@ -1290,7 +1419,7 @@ Include at least one engram_suggestion if ANYTHING was learned. An empty suggest
1290
1419
 
1291
1420
  // src/server.ts
1292
1421
  import { z } from "zod";
1293
- var VERSION = "0.9.3";
1422
+ var VERSION = "0.9.5";
1294
1423
  var INSTRUCTIONS = `PLUR is your persistent memory. Corrections, preferences, and conventions persist across sessions as engrams.
1295
1424
 
1296
1425
  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,6 +1,6 @@
1
1
  {
2
2
  "name": "@plur-ai/mcp",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "plur-mcp": "dist/index.js"
@@ -13,7 +13,7 @@
13
13
  "dependencies": {
14
14
  "@modelcontextprotocol/sdk": "^1.12.0",
15
15
  "zod": "^3.23.0",
16
- "@plur-ai/core": "0.9.3"
16
+ "@plur-ai/core": "0.9.5"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@types/node": "^25.5.0"
@@ -1,16 +1,21 @@
1
1
  ---
2
2
  name: Effective Memory
3
- description: Make your AI agent remember what matters. Session boundaries, learning triggers, feedback loops, and anti-patterns the habits that turn raw memory into compounding intelligence.
4
- version: "1.0.0"
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
5
  creator: plur-ai
6
6
  license: MIT
7
- tags: [memory, learning, best-practices, session-management, feedback]
7
+ tags: [memory, learning, best-practices, session-management, feedback, safety, verification, discipline, time]
8
8
  x-datacore:
9
9
  id: effective-memory
10
10
  injection_policy: on_match
11
- match_terms: [memory, learn, remember, session, feedback, engram, forget, correction, preference]
11
+ match_terms: [memory, learn, remember, session, feedback, engram, forget, correction, preference, recall, verification, safety, plur]
12
12
  domain: plur.best-practices
13
- engram_count: 8
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.
14
19
  ---
15
20
 
16
21
  # Effective Memory
@@ -19,8 +24,35 @@ Your agent has memory. These habits make it actually useful.
19
24
 
20
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.
21
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
+
22
29
  ## Install
23
30
 
24
31
  ```bash
25
- npx @plur-ai/cli packs install effective-memory
32
+ npx @plur-ai/cli@0.9.4 packs install effective-memory
26
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. |
@@ -1,121 +1,332 @@
1
1
  engrams:
2
+ # === Session lifecycle ===
3
+
2
4
  - id: ENG-PACK-EM-001
3
- version: 1
5
+ version: 2
4
6
  status: active
5
7
  type: procedural
6
8
  scope: global
7
9
  visibility: public
8
- statement: "Every session must bookend with plur_session_start (beginning) and plur_session_end (end). Session start injects relevant engrams into context. Session end captures what was learned, what failed, and what to continue. Without bookends, the agent starts every conversation from zero."
9
- rationale: "Session boundaries are the minimum viable memory habit. Everything else builds on this."
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.
10
20
  domain: plur.session
11
21
  tags: [session, lifecycle, start, end]
12
- activation: { retrieval_strength: 0.95, storage_strength: 0.95, frequency: 0, last_accessed: "2026-04-22" }
22
+ activation: { retrieval_strength: 0.95, storage_strength: 0.95, frequency: 0, last_accessed: "2026-05-04" }
13
23
  dual_coding:
14
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."
15
25
  analogy: "Like reading your notes before a meeting vs walking in cold every time."
16
26
  pack: effective-memory
27
+ polarity: do
28
+ commitment: locked
29
+
30
+ # === Capture: corrections and learn-triggers ===
17
31
 
18
32
  - id: ENG-PACK-EM-002
19
- version: 1
33
+ version: 2
20
34
  status: active
21
35
  type: behavioral
22
36
  scope: global
23
37
  visibility: public
24
- statement: "When the user corrects you, call plur_learn immediately — before continuing the task. Corrections are the highest-value engrams because they prevent repeated mistakes. Don't wait until end of session. Don't batch them. Learn the correction the instant it happens."
25
- rationale: "Corrections decay from working memory fast. Capturing them immediately is the difference between learning once and being corrected forever."
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.
26
50
  domain: plur.learning
27
- tags: [correction, learn, immediate]
28
- activation: { retrieval_strength: 0.95, storage_strength: 0.95, frequency: 0, last_accessed: "2026-04-22" }
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" }
29
53
  dual_coding:
30
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."
31
55
  pack: effective-memory
56
+ polarity: do
57
+ commitment: locked
32
58
 
33
59
  - id: ENG-PACK-EM-003
34
- version: 1
60
+ version: 2
35
61
  status: active
36
62
  type: behavioral
37
63
  scope: global
38
64
  visibility: public
39
- statement: "Call plur_feedback on injected engrams — positive when helpful, negative when wrong or stale. Feedback trains the injection algorithm. Without it, irrelevant engrams keep appearing and relevant ones get buried. This is how memory gets smarter, not just bigger."
40
- rationale: "Feedback is the training signal for retrieval quality. Memory without feedback is a search engine that never improves its ranking."
41
- domain: plur.feedback
42
- tags: [feedback, relevance, training]
43
- activation: { retrieval_strength: 0.9, storage_strength: 0.9, frequency: 0, last_accessed: "2026-04-22" }
44
- dual_coding:
45
- example: "Session starts, 8 engrams injected. 2 were directly useful for the task — call plur_feedback with positive. 1 was about a project you finished last month — call plur_feedback with negative. The rest were fine but not remarkable — skip or neutral."
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" }
46
78
  pack: effective-memory
79
+ polarity: do
80
+ commitment: decided
81
+
82
+ # === Recall: don't confabulate ===
47
83
 
48
84
  - id: ENG-PACK-EM-004
49
- version: 1
85
+ version: 2
50
86
  status: active
51
- type: behavioral
87
+ type: procedural
52
88
  scope: global
53
89
  visibility: public
54
- statement: "Learn preferences, not just corrections. When the user says 'always X', 'never Y', 'I prefer Z' — that is a preference engram. Preferences shape how you work, not just what you know. They are high-value because they affect every future interaction."
55
- rationale: "Preferences are the difference between a tool and an assistant that knows you."
56
- domain: plur.learning
57
- tags: [preference, learn, personalization]
58
- activation: { retrieval_strength: 0.9, storage_strength: 0.9, frequency: 0, last_accessed: "2026-04-22" }
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" }
59
104
  dual_coding:
60
- example: "User: 'I like terse responses, skip the summaries.' Call plur_learn with type behavioral. Next session, this preference is injected and you're already concise."
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."
61
107
  pack: effective-memory
108
+ polarity: do
109
+ commitment: locked
110
+
111
+ # === Feedback loop ===
62
112
 
63
113
  - id: ENG-PACK-EM-005
64
- version: 1
114
+ version: 2
65
115
  status: active
66
116
  type: behavioral
67
117
  scope: global
68
118
  visibility: public
69
- statement: "Do NOT learn trivial facts, session-specific state, or things derivable from the codebase. Bad engrams: 'the user said hello', 'we are working on file X', 'function foo is on line 42'. Good engrams: 'this API returns snake_case despite the docs saying camelCase', 'user prefers DCA over single entries', 'deploy requires VPN connection first'."
70
- rationale: "Memory quality matters more than quantity. Noisy engrams dilute injection relevance and waste context window."
71
- domain: plur.learning
72
- tags: [quality, anti-pattern, noise]
73
- activation: { retrieval_strength: 0.9, storage_strength: 0.9, frequency: 0, last_accessed: "2026-04-22" }
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."
74
135
  pack: effective-memory
136
+ polarity: do
137
+ commitment: locked
138
+
139
+ # === Engram quality ===
75
140
 
76
141
  - id: ENG-PACK-EM-006
77
- version: 1
142
+ version: 2
78
143
  status: active
79
- type: procedural
144
+ type: behavioral
80
145
  scope: global
81
146
  visibility: public
82
- statement: "Before answering factual questions about past work, decisions, or conventions — call plur_recall_hybrid first. The answer may already be in memory. This prevents contradicting past decisions, rediscovering known patterns, and giving generic answers when specific learned context exists."
83
- rationale: "Recall before reasoning. The most common memory failure is not forgetting — it's not checking."
84
- domain: plur.recall
85
- tags: [recall, search, before-answering]
86
- activation: { retrieval_strength: 0.9, storage_strength: 0.9, frequency: 0, last_accessed: "2026-04-22" }
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" }
87
163
  dual_coding:
88
- 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."
89
- analogy: "Like checking your notes before answering a question in a meeting, instead of guessing."
164
+ example: "User: 'I like terse responses, skip the summaries.' high-value engram (preference). User: 'okay, sounds good' no engram (no rule, just acknowledgement)."
90
165
  pack: effective-memory
166
+ polarity: do
167
+ commitment: locked
168
+
169
+ # === Long-horizon agents ===
91
170
 
92
171
  - id: ENG-PACK-EM-007
93
- version: 1
172
+ version: 2
94
173
  status: active
95
174
  type: architectural
96
175
  scope: global
97
176
  visibility: public
98
- statement: "Long-horizon agents — agents that run repeatedly on the same domain across days or weeks — MUST call plur_timeline at startup to read past episodes. Without timeline context, the agent rediscovers the same blockers, repeats failed approaches, and loses coherence across runs. This is 'coherence failure' — distinct from intelligence failure and the dominant failure mode for long-running agents."
99
- rationale: "A doctor with amnesia is competent each visit but never builds on the previous one. Timeline is the patient chart."
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.
100
188
  domain: plur.agents
101
- tags: [timeline, long-horizon, coherence, episodic]
102
- activation: { retrieval_strength: 0.9, storage_strength: 0.9, frequency: 0, last_accessed: "2026-04-22" }
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" }
103
191
  dual_coding:
104
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."
105
193
  analogy: "Like having a doctor with anterograde amnesia treating the same patient daily — competent each visit but never builds on the previous one."
106
194
  pack: effective-memory
195
+ polarity: do
196
+ commitment: locked
197
+
198
+ # === Trust observation over stale memory ===
107
199
 
108
200
  - id: ENG-PACK-EM-008
109
- version: 1
201
+ version: 2
110
202
  status: active
111
203
  type: behavioral
112
204
  scope: global
113
205
  visibility: public
114
- statement: "When memory and current reality conflict, trust what you observe now — then update the stale engram. Call plur_learn with the correction and plur_feedback with negative on the old engram. Memory is a starting point for reasoning, not a substitute for verification."
115
- rationale: "Stale memory acted on without verification causes worse outcomes than no memory at all."
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.
116
215
  domain: plur.maintenance
117
216
  tags: [staleness, verification, update, conflict]
118
- activation: { retrieval_strength: 0.9, storage_strength: 0.9, frequency: 0, last_accessed: "2026-04-22" }
217
+ activation: { retrieval_strength: 0.9, storage_strength: 0.9, frequency: 0, last_accessed: "2026-05-04" }
119
218
  dual_coding:
120
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."
121
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