agent-working-memory 0.14.5 → 0.14.6

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.
@@ -28,8 +28,12 @@ export interface SetupContext {
28
28
  hasDist: boolean;
29
29
  /** Hook sidecar secret token */
30
30
  hookSecret: string;
31
- /** Hook sidecar port */
31
+ /** Hook sidecar port (preferred; the sidecar walks upward from here when busy) */
32
32
  hookPort: string;
33
+ /** How many ports upward from hookPort the sidecar may try (0.14.2) */
34
+ hookPortRange: string;
35
+ /** Install the UserPromptSubmit prime hook (0.14.6; `--no-prime` turns it off) */
36
+ installPrime: boolean;
33
37
  /** Whether to use global scope */
34
38
  isGlobal: boolean;
35
39
  /** Windows platform */
@@ -70,6 +74,13 @@ export interface CLIAdapter {
70
74
  /** Configure hooks (if supported). Returns action summary. */
71
75
  writeHooks(ctx: SetupContext, skip: boolean): string;
72
76
 
73
- /** Validate that the setup is healthy. */
74
- diagnose(ctx: SetupContext): DiagnosticResult[];
77
+ /** Validate that the setup is healthy. May probe the network, hence possibly async. */
78
+ diagnose(ctx: SetupContext): DiagnosticResult[] | Promise<DiagnosticResult[]>;
79
+
80
+ /**
81
+ * The env block of an AWM server entry this adapter previously wrote, if any.
82
+ * `awm setup` re-runs preserve these values unless a flag overrides them, so an
83
+ * upgrade never repoints the database or renames the agent (0.14.6).
84
+ */
85
+ readExistingEnv?(isGlobal: boolean, cwd: string): Record<string, string> | null;
75
86
  }
package/src/cli.ts CHANGED
@@ -74,7 +74,10 @@ Setup options:
74
74
  --no-instructions Skip instruction file (CLAUDE.md, AGENTS.md, etc.)
75
75
  --no-claude-md Alias for --no-instructions
76
76
  --no-hooks Skip hook installation
77
- --hook-port PORT Sidecar port for hooks (default: 8401)
77
+ --no-prime Skip the UserPromptSubmit prime hook (auto-inject memories per prompt)
78
+ --hook-port PORT Preferred sidecar port (default: 8401; each session walks upward when busy)
79
+ --hook-port-range N Ports to try upward from --hook-port (default: 10)
80
+ Re-running setup keeps your existing agent id, db path, port and any env you added.
78
81
 
79
82
  Examples:
80
83
  awm setup --global Claude Code, global (recommended)
@@ -95,7 +98,9 @@ async function setup() {
95
98
  let skipInstructions = false;
96
99
  let isGlobal = false;
97
100
  let skipHooks = false;
98
- let hookPort = '8401';
101
+ let installPrime = true;
102
+ let hookPort: string | undefined;
103
+ let hookPortRange: string | undefined;
99
104
 
100
105
  for (let i = 1; i < args.length; i++) {
101
106
  if (args[i] === '--agent-id' && args[i + 1]) {
@@ -106,8 +111,12 @@ async function setup() {
106
111
  skipInstructions = true;
107
112
  } else if (args[i] === '--no-hooks') {
108
113
  skipHooks = true;
114
+ } else if (args[i] === '--no-prime') {
115
+ installPrime = false;
109
116
  } else if (args[i] === '--hook-port' && args[i + 1]) {
110
117
  hookPort = args[++i];
118
+ } else if (args[i] === '--hook-port-range' && args[i + 1]) {
119
+ hookPortRange = args[++i];
111
120
  } else if (args[i] === '--global') {
112
121
  isGlobal = true;
113
122
  } else if (!args[i].startsWith('--')) {
@@ -133,8 +142,10 @@ async function setup() {
133
142
  isGlobal = true;
134
143
  }
135
144
 
136
- // Build context
137
- const ctx = buildSetupContext({ agentId, dbPath, isGlobal, hookPort });
145
+ // Build context. A previous install's values survive unless a flag overrides them
146
+ // (0.14.6) — an upgrade must never repoint the database or rename the agent.
147
+ const existingEnv = adapter.readExistingEnv?.(isGlobal, process.cwd()) ?? null;
148
+ const ctx = buildSetupContext({ agentId, dbPath, isGlobal, hookPort, hookPortRange, installPrime, existingEnv });
138
149
 
139
150
  // Run adapter
140
151
  const configAction = adapter.writeMcpConfig(ctx);
@@ -157,6 +168,7 @@ AWM configured for ${adapter.name}${isGlobal ? ' (global)' : ''}
157
168
  Next steps:
158
169
  1. Restart ${adapter.name} to pick up the MCP server
159
170
  2. Memory tools will appear automatically${adapter.id === 'codex' ? ' (verify with /mcp)' : ''}
171
+ 3. \`awm doctor\` checks the install and lists the live sidecars
160
172
  `.trim());
161
173
  }
162
174
 
@@ -197,7 +209,9 @@ async function doctor() {
197
209
  }
198
210
 
199
211
  console.log(` ${adapter.name}:`);
200
- const results = adapter.diagnose(ctx);
212
+ const existingEnv = adapter.readExistingEnv?.(true, process.cwd()) ?? null;
213
+ const ctx = buildSetupContext({ isGlobal: true, existingEnv });
214
+ const results = await adapter.diagnose(ctx);
201
215
  for (const r of results) {
202
216
  const icon = r.status === 'ok' ? '+' : r.status === 'warn' ? '~' : 'x';
203
217
  console.log(` [${icon}] ${r.check}: ${r.message}`);
@@ -0,0 +1,28 @@
1
+ // Copyright 2026 Robert Winter / Complete Ideas
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ /**
5
+ * Fallback agent identity when AWM_AGENT_ID / WORKER_NAME are unset.
6
+ *
7
+ * Derived from the project directory so a plain `claude` launch still binds to
8
+ * the right store: `Personal-Projects` → 'personal'; everything else → 'work'.
9
+ * The AWM package itself lives under Personal-Projects, so an AgentSynapse
10
+ * checkout is guarded first — a stray server cwd must not mis-bind.
11
+ *
12
+ * ONE definition, three consumers that must agree:
13
+ * - src/mcp.ts — what the server actually binds to
14
+ * - src/adapters/common.ts — what `awm setup` reports and seeds
15
+ * - the shipped hook scripts (src/adapters/hook-scripts.ts) carry a
16
+ * line-for-line JavaScript mirror, because a hook cannot import from dist.
17
+ * tests/adapters/hook-scripts.test.ts asserts the mirror agrees with this.
18
+ */
19
+ export function deriveAgentFromDir(dir: string): string {
20
+ const d = String(dir ?? '').replace(/\\/g, '/');
21
+ if (/\/AgentSynapse\//i.test(d)) return 'work';
22
+ return /\/Personal-Projects(\/|$)/i.test(d) ? 'personal' : 'work';
23
+ }
24
+
25
+ /** The agent id a server process would resolve to for `dir`, honouring the env first. */
26
+ export function resolveAgentId(dir: string, env: NodeJS.ProcessEnv = process.env): string {
27
+ return env.AWM_AGENT_ID ?? env.WORKER_NAME ?? deriveAgentFromDir(dir);
28
+ }
@@ -36,7 +36,12 @@ import { packRecallByBudget, estimateTokens, type PackedRecall } from '../core/t
36
36
 
37
37
  /** Shape returned by the sidecar's `activate` dependency. */
38
38
  export interface PrimeCandidate {
39
- engram: { id: string; concept: string; content: string; memoryClass?: string; validTo?: string | null };
39
+ engram: {
40
+ id: string; concept: string; content: string; memoryClass?: string;
41
+ validTo?: string | null;
42
+ /** Used for the age label; Date from the store, string over HTTP. */
43
+ createdAt?: Date | string | null;
44
+ };
40
45
  score: number;
41
46
  summary?: string;
42
47
  confidence?: number;
@@ -64,21 +69,31 @@ export interface PrimeResult {
64
69
  export const PRIME_DEFAULTS = {
65
70
  maxTokens: 600,
66
71
  /**
67
- * 0.25 = the "balanced" threshold AWM's own recall docs recommend for
68
- * acting on a memory. Priming is acting on it without being asked, so it
69
- * should not be looser than the value the docs suggest for deliberate use.
72
+ * 0.10 (was 0.25 until 0.14.6). The confidence this gates on describes the
73
+ * SHAPE of the score distribution — how far the top results stand out from
74
+ * the rest — not relevance. Measured 2026-09-11 on a 30k-memory store: at
75
+ * 0.25 two specific prompts with one clear winner (top score 0.26 and 0.38)
76
+ * were silenced while a vague prompt passed at 0.92. Those specific prompts
77
+ * are exactly what priming exists for. 0.10 passed all three and still
78
+ * abstains on the garbage case (confidence ≈ 0.05).
70
79
  */
71
- minConfidence: 0.25,
80
+ minConfidence: 0.10,
72
81
  minScore: 0.10,
73
82
  } as const;
74
83
 
75
84
  /** One injected line. Deliberately terser than the MCP recall format — this is
76
85
  * context the agent didn't ask for, so it should read as a brief note, not a
77
86
  * report. The id is retained so the agent can act on it (feedback, supersede). */
78
- function formatPrimeLine(c: PrimeCandidate): string {
87
+ function formatPrimeLine(c: PrimeCandidate, now: number = Date.now()): string {
79
88
  const body = c.summary ?? c.engram.content;
80
89
  const validity = c.engram.validTo ? ` [valid until ${c.engram.validTo}]` : '';
81
- return `- ${c.engram.concept}${validity} [${c.engram.id}]: ${body}`;
90
+ // Class + age make the volatility rubric mechanical: a 90-day-old working
91
+ // memory reads differently from a 2-day-old canonical one, and the agent
92
+ // should not have to look either up before deciding how much to trust it.
93
+ const cls = c.engram.memoryClass ?? 'working';
94
+ const created = c.engram.createdAt ? new Date(c.engram.createdAt as any).getTime() : NaN;
95
+ const age = Number.isFinite(created) ? ` · ${Math.max(0, Math.round((now - created) / 86_400_000))}d` : '';
96
+ return `- [${cls}${age}] ${c.engram.concept}${validity} [${c.engram.id}]: ${body}`;
82
97
  }
83
98
 
84
99
  /**
package/src/mcp.ts CHANGED
@@ -30,6 +30,7 @@
30
30
  */
31
31
 
32
32
  import { readFileSync } from 'node:fs';
33
+ import { deriveAgentFromDir as deriveAgentFromDirShared } from './core/agent-id.js';
33
34
  import { resolve, basename } from 'node:path';
34
35
  import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
35
36
 
@@ -111,17 +112,10 @@ const BACKEND: StoreBackend = getConfiguredBackend();
111
112
  const DB_PATH = process.env.AWM_DB_PATH ?? (BACKEND === 'pglite' ? 'memory-pglite' : 'memory.db');
112
113
 
113
114
  // Fallback agent selection when AWM_AGENT_ID/WORKER_NAME are unset: derive from
114
- // the project directory so plain `claude` launches still bind to the right
115
- // store. Personal-Projects -> 'personal'; everything else -> 'work' (the
116
- // primary store). MUST stay in sync with the SessionStart hook
117
- // (~/.claude/hooks/awm-session-start.ps1) so the hook's restore and the
118
- // server's reads/writes never diverge. Guard the AWM package's own path
119
- // (it lives under Personal-Projects) so a stray server cwd can't mis-bind.
120
- function deriveAgentFromDir(): string {
121
- const dir = (process.env.CLAUDE_PROJECT_DIR ?? process.cwd()).replace(/\\/g, '/');
122
- if (/\/AgentSynapse\//i.test(dir)) return 'work';
123
- return /\/Personal-Projects(\/|$)/i.test(dir) ? 'personal' : 'work';
124
- }
115
+ // the project directory (core/agent-id.ts — one definition shared with `awm setup`
116
+ // and mirrored in the shipped hook scripts, so the server, the installer and the
117
+ // hooks always name the same pool for a given directory).
118
+ const deriveAgentFromDir = () => deriveAgentFromDirShared(process.env.CLAUDE_PROJECT_DIR ?? process.cwd());
125
119
  const AGENT_ID = process.env.AWM_AGENT_ID ?? process.env.WORKER_NAME ?? deriveAgentFromDir();
126
120
  const HOOK_PORT = parseInt(process.env.AWM_HOOK_PORT ?? '8401', 10);
127
121
  // 0.14.2: ports to try upward from HOOK_PORT when it is busy (see sidecar.ts).