agent-working-memory 0.15.7 → 0.15.9

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.
@@ -54,7 +54,7 @@ const adapter: CLIAdapter = {
54
54
 
55
55
  if (skip) return '.cursorrules: skipped (--no-instructions)';
56
56
 
57
- return upsertAwmSection(rulesPath, AWM_INSTRUCTION_CONTENT, { titleIfNew: '# Agent Working Memory' });
57
+ return upsertAwmSection(rulesPath, AWM_INSTRUCTION_CONTENT, { titleIfNew: '# Agent Working Memory', force: ctx.forceInstructions });
58
58
  },
59
59
 
60
60
  writeHooks(_ctx: SetupContext, _skip: boolean): string {
@@ -56,6 +56,7 @@ const adapter: CLIAdapter = {
56
56
  return upsertAwmSection(instrPath, AWM_INSTRUCTION_CONTENT, {
57
57
  titleIfNew: '# Agent Working Memory',
58
58
  suffix: HTTP_ADDENDUM,
59
+ force: ctx.forceInstructions,
59
60
  });
60
61
  },
61
62
 
@@ -9,8 +9,8 @@
9
9
  * the selected adapter.
10
10
  */
11
11
 
12
- export interface SetupContext {
13
- /** Set when setup moved a store out of the installed package. The old file is left in place. */
12
+ export interface SetupContext {
13
+ /** Set when setup moved a store out of the installed package. The old file is left in place. */
14
14
  rescuedDbFrom?: string | null;
15
15
  /** Current working directory */
16
16
  cwd: string;
@@ -36,6 +36,12 @@ export interface SetupContext {
36
36
  hookPortRange: string;
37
37
  /** Install the UserPromptSubmit prime hook (0.14.6; `--no-prime` turns it off) */
38
38
  installPrime: boolean;
39
+ /**
40
+ * Replace a legacy, unmarked AWM instruction section instead of backing it up and
41
+ * refusing (`--force`). Off by default: an unmarked section cannot be told apart from
42
+ * the user's own notes, and replacing one measured 169 of 381 lines lost on a real install.
43
+ */
44
+ forceInstructions: boolean;
39
45
  /** Whether to use global scope */
40
46
  isGlobal: boolean;
41
47
  /** Windows platform */
package/src/cli.ts CHANGED
@@ -76,6 +76,9 @@ Setup options:
76
76
  --no-claude-md Alias for --no-instructions
77
77
  --no-hooks Skip hook installation
78
78
  --no-prime Skip the UserPromptSubmit prime hook (auto-inject memories per prompt)
79
+ --force Replace a legacy instruction section that has no generated markers.
80
+ Without it setup backs the file up and refuses, because your own
81
+ notes inside that section cannot be told apart from generated text.
79
82
  --hook-port PORT Preferred sidecar port (default: 8401; each session walks upward when busy)
80
83
  --hook-port-range N Ports to try upward from --hook-port (default: 10)
81
84
  Re-running setup keeps your existing agent id, db path, port and any env you added.
@@ -100,6 +103,7 @@ async function setup() {
100
103
  let isGlobal = false;
101
104
  let skipHooks = false;
102
105
  let installPrime = true;
106
+ let forceInstructions = false;
103
107
  let hookPort: string | undefined;
104
108
  let hookPortRange: string | undefined;
105
109
 
@@ -114,6 +118,8 @@ async function setup() {
114
118
  skipHooks = true;
115
119
  } else if (args[i] === '--no-prime') {
116
120
  installPrime = false;
121
+ } else if (args[i] === '--force') {
122
+ forceInstructions = true;
117
123
  } else if (args[i] === '--hook-port' && args[i + 1]) {
118
124
  hookPort = args[++i];
119
125
  } else if (args[i] === '--hook-port-range' && args[i + 1]) {
@@ -147,7 +153,7 @@ async function setup() {
147
153
  // (0.14.6) — an upgrade must never repoint the database or rename the agent.
148
154
  const existingEnv = adapter.readExistingEnv?.(isGlobal, process.cwd()) ?? null;
149
155
  // `awm setup claude-code` -> client=claude-code, cursor -> client=cursor, and so on.
150
- const ctx = buildSetupContext({ agentId, dbPath, isGlobal, hookPort, hookPortRange, installPrime, existingEnv, client: target });
156
+ const ctx = buildSetupContext({ agentId, dbPath, isGlobal, hookPort, hookPortRange, installPrime, forceInstructions, existingEnv, client: target });
151
157
 
152
158
  // Run adapter
153
159
  const configAction = adapter.writeMcpConfig(ctx);
@@ -288,6 +288,24 @@ export async function performWrite(
288
288
  && superseder.supersededBy == null) {
289
289
  result = await reinforceMatched(store, superseder, noveltyResult, salience, input.content, input.concept);
290
290
  }
291
+ } else if (matched.stage === 'archived' && matched.supersededBy == null && !matched.retracted) {
292
+ // R1b (2026-09-22 fix, evidence in tests/core/reinforcement-floor-bug.test.ts
293
+ // and real production data from the /freshdesk-support hourly sweep "family of
294
+ // stubs" incident): a same-concept match that consolidation's Phase 6.5
295
+ // redundancy-prune archived (it archives a "loser" near-duplicate WITHOUT
296
+ // merging its confidence into a survivor — see engine/consolidation.ts) is
297
+ // still the same fact being restated. A fresh matching write is exactly the
298
+ // "repeat" signal R1 exists to act on. Without this branch an archived match
299
+ // is a permanent dead end: isHealthy above requires stage==='active', so it can
300
+ // never be reinforced again, and it isn't superseded either — every later
301
+ // matching write fell through to create() forever, producing an
302
+ // ever-growing, never-consolidating family of near-duplicate archived stubs
303
+ // that still rank in recall (archived != excluded from search) and crowd out
304
+ // the genuinely current one. REVIVE it: reinforce as usual, then flip it back
305
+ // to active so it behaves like any other reinforced engram going forward.
306
+ result = await reinforceMatched(store, matched, noveltyResult, salience, input.content, input.concept);
307
+ await store.updateStage(matched.id, 'active');
308
+ result.engram.stage = 'active';
291
309
  }
292
310
  }
293
311
  }
@@ -491,24 +509,24 @@ async function createNewEngram(
491
509
 
492
510
  const tags = [...(input.tags ?? [])];
493
511
  if (isLowSalience && !tags.includes('low-salience')) tags.push('low-salience');
494
-
495
- // Which CLIENT wrote this: `client=claude-code`, `client=claude-desktop`, `client=cursor`.
496
- //
497
- // Deliberately not called `surface`: memory_whoami already reports a `surface` field
498
- // meaning the TRANSPORT (mcp | http), and one word for two concepts is how you lose an
499
- // hour six months from now. Transport is how the request arrived; client is which
500
- // application it arrived from.
501
- // AWM_CLIENT is set by the thing that launches the server (the Claude Code plugin, the
502
- // Desktop extension, `awm setup`), never by the caller, so it records where a memory came
503
- // from rather than what the writer claims.
504
- //
505
- // Captured by default rather than behind a flag, because provenance is one-way: a tag can
506
- // be ignored or stripped later, but the origin of a memory written without one is gone for
507
- // good. An explicit `client=` in the caller's tags always wins.
508
- const client = (process.env.AWM_CLIENT ?? '').trim();
509
- if (client && !tags.some(t => t.toLowerCase().startsWith('client='))) {
510
- tags.push(`client=${client}`);
511
- }
512
+
513
+ // Which CLIENT wrote this: `client=claude-code`, `client=claude-desktop`, `client=cursor`.
514
+ //
515
+ // Deliberately not called `surface`: memory_whoami already reports a `surface` field
516
+ // meaning the TRANSPORT (mcp | http), and one word for two concepts is how you lose an
517
+ // hour six months from now. Transport is how the request arrived; client is which
518
+ // application it arrived from.
519
+ // AWM_CLIENT is set by the thing that launches the server (the Claude Code plugin, the
520
+ // Desktop extension, `awm setup`), never by the caller, so it records where a memory came
521
+ // from rather than what the writer claims.
522
+ //
523
+ // Captured by default rather than behind a flag, because provenance is one-way: a tag can
524
+ // be ignored or stripped later, but the origin of a memory written without one is gone for
525
+ // good. An explicit `client=` in the caller's tags always wins.
526
+ const client = (process.env.AWM_CLIENT ?? '').trim();
527
+ if (client && !tags.some(t => t.toLowerCase().startsWith('client='))) {
528
+ tags.push(`client=${client}`);
529
+ }
512
530
 
513
531
  // Auto-tag meta-tags (default-OFF, AWM_AUTOTAG=1). extractMetaTags emits
514
532
  // `entity:<ProperNoun>` and `cat:<category>` tags from concept+content. These