@vib795/agent-memory 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,155 @@
1
+ ---
2
+ name: remember
3
+ version: 0.1.0
4
+ description: Capture durable project knowledge from the current conversation into the shared memory graph, so a later conversation in any window or repository already knows it. Use when the user says remember this, save this, note this for later, or /remember.
5
+ allowed-tools:
6
+ - Bash
7
+ - Read
8
+ - Write
9
+ triggers:
10
+ - remember this
11
+ - save this for later
12
+ - note this down
13
+ - remember
14
+ ---
15
+
16
+ # remember
17
+
18
+ Write down what this conversation established, so the next one does not have to
19
+ establish it again.
20
+
21
+ `/handoff` only fires when you switch windows. The best insight of a session dies if
22
+ the user simply closes the tab, so this exists as a deliberate mid-session capture.
23
+ It costs one request. Spend it well: write the knowledge, not the transcript.
24
+
25
+ Do the whole job in **one pass**. Compose, write one JSON file, make one terminal
26
+ call. Do not ask the user to confirm each node.
27
+
28
+ ---
29
+
30
+ ## Two forms
31
+
32
+ - **`/remember`** — you select the durable knowledge from the conversation so far.
33
+ - **`/remember <what>`** — the user named the thing. Write that, with full context
34
+ from the conversation, and write nothing unrelated to it.
35
+
36
+ ---
37
+
38
+ ## Step 1 — Select what is durable
39
+
40
+ <!-- extraction-rules:start -->
41
+ - A node is durable only if it will still be true next month. Task state is not
42
+ durable and belongs in a handoff file, not in the graph.
43
+ - Every `decision` node carries its why and its rejected alternatives, or it is not
44
+ written. Rationale is the thing that never survives re-explanation.
45
+ - Anything the conversation did not actually establish is `confidence: inferred`,
46
+ and the body says what would confirm it.
47
+ - Prefer updating an existing node over creating a near-duplicate. `write` returns
48
+ the existing id on a content-hash match.
49
+ - A decision that replaces a known prior sets `supersedes` to that prior's id.
50
+ - Constraints are the highest-value type. An environment restriction, a blocked
51
+ tool, a policy that forbids an approach: write it, because it is what stops a
52
+ future agent burning a retry loop on something that was never going to ship.
53
+ - Zero durable knowledge is a valid outcome. Writing nothing beats writing noise.
54
+ <!-- extraction-rules:end -->
55
+
56
+ Types:
57
+
58
+ | type | what it holds |
59
+ |---|---|
60
+ | `system` | how a thing works, anchored to a `path:line` |
61
+ | `decision` | what was chosen, why, and what was rejected |
62
+ | `convention` | how this codebase does something, and the gotcha |
63
+ | `constraint` | what the environment or the org forbids |
64
+
65
+ ---
66
+
67
+ ## Step 2 — Compose the JSON
68
+
69
+ One file, one array. Ids are kebab-case and name the subject, not the session.
70
+
71
+ ```json
72
+ {
73
+ "nodes": [
74
+ {
75
+ "id": "use-sessions",
76
+ "type": "decision",
77
+ "title": "Chose server sessions over JWT",
78
+ "body": "Why: revocation had to take effect immediately.\nRejected: short-TTL JWT, because logout would lag by the TTL.\nImplemented in src/auth/session.js:42.",
79
+ "confidence": "observed",
80
+ "edges": [{ "rel": "evidence-for", "dst": "auth-service" }]
81
+ }
82
+ ]
83
+ }
84
+ ```
85
+
86
+ Fields you may set: `id`, `type`, `title`, `body`, `repos`, `scope`, `confidence`,
87
+ `supersedes`, `edges`. Everything else is filled in for you.
88
+
89
+ - `repos` defaults to the current repository. Set `"scope": "global"` with an empty
90
+ `repos` for something true everywhere, such as an org-wide restriction.
91
+ - `edges` relations: `depends-on`, `applies-to`, `supersedes`, `contradicts`,
92
+ `evidence-for`. A `dst` that does not exist yet is allowed; it connects when that
93
+ note is written.
94
+ - Write the body for someone with zero context, and anchor it to a `path:line`.
95
+
96
+ ---
97
+
98
+ ## Step 3 — Write it (ONE terminal call)
99
+
100
+ **bash (macOS / Linux):**
101
+
102
+ ```bash
103
+ tmp="$(mktemp)"
104
+ cat > "$tmp" <<'JSON'
105
+ <the JSON from Step 2>
106
+ JSON
107
+ agent-memory write --from-json "$tmp" --source remember
108
+ rm -f "$tmp"
109
+ ```
110
+
111
+ **PowerShell (Windows / AVD):**
112
+
113
+ ```powershell
114
+ $tmp = [System.IO.Path]::GetTempFileName()
115
+ @'
116
+ <the JSON from Step 2>
117
+ '@ | Set-Content -Path $tmp -Encoding UTF8
118
+ agent-memory write --from-json $tmp --source remember
119
+ Remove-Item -Force $tmp
120
+ ```
121
+
122
+ Redaction runs inside `write`, before any bytes reach disk, and cannot be turned
123
+ off. You still must not paste a raw secret into the JSON: the guard is a backstop,
124
+ not a licence.
125
+
126
+ ---
127
+
128
+ ## Step 4 — Report
129
+
130
+ Print every id written, one per line, with what it is:
131
+
132
+ ```
133
+ created use-sessions [decision] Chose server sessions over JWT
134
+ updated auth-service [system] Auth uses server sessions
135
+ ```
136
+
137
+ Then stop. Do not summarize the conversation.
138
+
139
+ Warnings from `write` are worth surfacing verbatim:
140
+
141
+ - `title collision` means an existing note reads as the same thing under a different
142
+ id. Tell the user which two, and offer to merge or link them with `contradicts`.
143
+ - `redacted Nx <kind>` means the guard caught something. Say what kind was caught so
144
+ the user knows a secret was in play, never what the value was.
145
+
146
+ ---
147
+
148
+ ## Failure handling
149
+
150
+ - **Validation failed.** `write` reports every error at once. Fix them all and retry
151
+ once. If it fails again, print the errors and stop; do not guess at the schema.
152
+ - **`agent-memory: command not found`.** Say the package is not installed and print
153
+ the JSON in the chat so the work is not lost.
154
+ - **Nothing durable in the conversation.** Say so in one line. That is a correct
155
+ outcome, not a failure.
package/src/atomic.js ADDED
@@ -0,0 +1,57 @@
1
+ import { writeFileSync, renameSync, rmSync } from 'node:fs';
2
+
3
+ /**
4
+ * Write a file atomically.
5
+ *
6
+ * Deliberately dependency-free and importing nothing from this package: store.js
7
+ * already imports config.js, so putting this in either would make a cycle.
8
+ *
9
+ * Two properties matter, and both come from the same failure.
10
+ *
11
+ * The temp name is unique per process and per call. A fixed `<target>.tmp` looks
12
+ * atomic and is not: two processes writing the same file collide on that one path,
13
+ * so A writes tmp, B overwrites tmp, A renames and publishes B's bytes, then B
14
+ * renames and fails ENOENT. Two windows open at once is the normal working mode
15
+ * here, so that is the common case rather than an exotic one.
16
+ *
17
+ * The rename is retried briefly. On Windows a rename fails with EPERM or EBUSY when
18
+ * anything else holds the destination open for even a moment, and on a managed
19
+ * desktop that something is usually the antivirus scanner reading the file we just
20
+ * wrote. Retrying turns a hard failure into a pause nobody notices.
21
+ */
22
+
23
+ const RETRIES = 5;
24
+ const BACKOFF_MS = 20;
25
+
26
+ let counter = 0;
27
+
28
+ function sleep(ms) {
29
+ // Synchronous by design. Every caller sits inside a synchronous write path, and
30
+ // making them all async to absorb an antivirus hiccup is a poor trade.
31
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
32
+ }
33
+
34
+ export function tempName(target) {
35
+ counter += 1;
36
+ return `${target}.${process.pid}.${counter}.tmp`;
37
+ }
38
+
39
+ export function atomicWrite(target, contents) {
40
+ const tmp = tempName(target);
41
+ writeFileSync(tmp, contents, 'utf8');
42
+ for (let attempt = 0; ; attempt++) {
43
+ try {
44
+ renameSync(tmp, target);
45
+ return target;
46
+ } catch (err) {
47
+ const transient = err.code === 'EPERM' || err.code === 'EBUSY' || err.code === 'EACCES';
48
+ if (!transient || attempt >= RETRIES) {
49
+ // Never leave the temp file behind; a stray file under notes/ would be
50
+ // scanned on the next index run.
51
+ rmSync(tmp, { force: true });
52
+ throw err;
53
+ }
54
+ sleep(BACKOFF_MS * (attempt + 1));
55
+ }
56
+ }
57
+ }