peon-mem 1.0.2 → 1.0.3
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 +21 -0
- package/dist/brain.js +15 -2
- package/dist/entities.js +25 -1
- package/dist/global-memory.js +6 -1
- package/dist/quality.js +48 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -48,6 +48,7 @@ prompt. It runs as a daemon on your machine, and nothing leaves it.
|
|
|
48
48
|
*The live monitor: 18k real beliefs rendered as stars. Type to make matching beliefs flare; click one to inspect it.*
|
|
49
49
|
|
|
50
50
|

|
|
51
|
+
|
|
51
52
|
*Ask the field: typing "wulver cluster" makes 400+ matching beliefs flare while the rest dim, and the camera flies to them.*
|
|
52
53
|
|
|
53
54
|
## Why "Peon"?
|
|
@@ -80,6 +81,26 @@ The short version: mem0 and Zep are memory platforms for products you build. Peo
|
|
|
80
81
|
for the coding agents you already use. It plugs into Claude Code or Codex in about five
|
|
81
82
|
minutes, and you can watch it think and audit every number it claims.
|
|
82
83
|
|
|
84
|
+
## Your agents share one brain
|
|
85
|
+
|
|
86
|
+
If you run more than one coding agent — Claude Code and Codex, say — they usually each live in
|
|
87
|
+
their own bubble. Whatever you work out with one is gone when you switch to the other.
|
|
88
|
+
|
|
89
|
+
Point them at the same project and Peon dissolves that wall. Both agents resolve to the same
|
|
90
|
+
`.peon/` brain (Peon canonicalizes the project path, so the hook, a direct MCP call, and Codex
|
|
91
|
+
all land on one store). So the memory flows between them:
|
|
92
|
+
|
|
93
|
+
- Codex works out how your build pipeline runs and records it. Next time you open Claude Code
|
|
94
|
+
in that repo, it's already in the injected context.
|
|
95
|
+
- Claude Code hits a gotcha and files it. Codex sees it the moment it calls `get_context` or
|
|
96
|
+
`search_memory`.
|
|
97
|
+
- The global brain sits above both, so your preferences and rules follow you into every agent,
|
|
98
|
+
in every project.
|
|
99
|
+
|
|
100
|
+
They aren't chatting in real time. It's a shared notebook both write in and both read from, so
|
|
101
|
+
a decision made in one agent shows up in the other without you re-explaining it. One project,
|
|
102
|
+
one memory, however many agents.
|
|
103
|
+
|
|
83
104
|
## Quickstart
|
|
84
105
|
|
|
85
106
|
Requirements: Node 20+, macOS or Linux. An [OpenRouter](https://openrouter.ai) API key is
|
package/dist/brain.js
CHANGED
|
@@ -53,12 +53,25 @@ export function resolveConflicts(records, now, protectGlobalScope = true) {
|
|
|
53
53
|
// to active, loser archived (recoverable).
|
|
54
54
|
const candidates = records.filter((r) => r.status === "active" || r.status === "conflicted");
|
|
55
55
|
const conflicts = detectMemoryConflicts(candidates);
|
|
56
|
-
if (conflicts.length === 0)
|
|
57
|
-
return { records: [...records], actions: [] };
|
|
58
56
|
const byId = new Map(records.map((r) => [r.id, r]));
|
|
59
57
|
const archived = new Set();
|
|
60
58
|
const reactivated = new Set();
|
|
61
59
|
const actions = [];
|
|
60
|
+
// Every belief that is part of a CURRENTLY-detected conflict. Anything still flagged
|
|
61
|
+
// "conflicted" but not in this set is an orphan — the consolidator (or an older, looser
|
|
62
|
+
// detector) benched it, but it no longer collides with anything. Left alone it stays out of
|
|
63
|
+
// recall forever; the July→August backlog was 100+ such orphans. Reactivate them below.
|
|
64
|
+
const inLiveConflict = new Set();
|
|
65
|
+
for (const c of conflicts) {
|
|
66
|
+
inLiveConflict.add(c.leftId);
|
|
67
|
+
inLiveConflict.add(c.rightId);
|
|
68
|
+
}
|
|
69
|
+
for (const r of records) {
|
|
70
|
+
if (r.status === "conflicted" && !inLiveConflict.has(r.id)) {
|
|
71
|
+
reactivated.add(r.id);
|
|
72
|
+
actions.push({ type: "resolve_conflict", detail: `reactivated stale conflict flag on "${r.content.slice(0, 40)}"`, affectedIds: [r.id] });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
62
75
|
for (const conflict of conflicts) {
|
|
63
76
|
const left = byId.get(conflict.leftId);
|
|
64
77
|
const right = byId.get(conflict.rightId);
|
package/dist/entities.js
CHANGED
|
@@ -18,6 +18,25 @@
|
|
|
18
18
|
const SRC_ROOTS = new Set(["src", "lib", "scripts", "test", "tests", "app", "apps", "packages", "dist", "bin"]);
|
|
19
19
|
const FILE_EXT_RE = /\.(ts|tsx|js|jsx|mjs|cjs|json|md|mdx|html|css|scss|py|ipynb|pdf|txt|yml|yaml|toml|sh|sql|rs|go|java|rb|c|cpp|h)$/i;
|
|
20
20
|
const IDENTIFIER_RE = /^[A-Za-z_$][\w$]*(?:[.#][A-Za-z_$][\w$]*)*$/;
|
|
21
|
+
// Bare common words that are never useful DOMAIN entities. Without this gate a lowercase token
|
|
22
|
+
// like "not" / "use" / "no" falls through the identifier branch and becomes a domain concept —
|
|
23
|
+
// then two unrelated beliefs that merely both contain that word "share an entity" and get
|
|
24
|
+
// false-flagged as conflicting. Only applies to plain lowercase words: file paths, product
|
|
25
|
+
// acronyms (BIRD, DTS-SQL), and code symbols carry caps/digits/separators and never land here.
|
|
26
|
+
const ENTITY_STOPWORDS = new Set([
|
|
27
|
+
"the", "this", "that", "these", "those", "a", "an", "it", "its", "we", "i", "you", "he", "she",
|
|
28
|
+
"they", "them", "our", "your", "their", "his", "her",
|
|
29
|
+
"if", "when", "then", "than", "for", "and", "but", "or", "nor", "so", "to", "in", "on", "of",
|
|
30
|
+
"at", "by", "as", "is", "are", "was", "were", "be", "been", "being", "do", "does", "did", "done",
|
|
31
|
+
"not", "no", "yes", "none", "null", "na", "nan", "true", "false",
|
|
32
|
+
"use", "used", "using", "avoid", "add", "added", "fix", "fixed", "make", "made", "set", "run",
|
|
33
|
+
"ran", "get", "got", "put", "new", "old", "now", "also", "with", "without", "from", "into",
|
|
34
|
+
"enable", "enabled", "disable", "disabled", "allow", "allowed", "deny", "forbidden",
|
|
35
|
+
"required", "optional", "about", "what", "which", "how", "why", "who", "where"
|
|
36
|
+
]);
|
|
37
|
+
function isEntityStopword(key) {
|
|
38
|
+
return ENTITY_STOPWORDS.has(key.toLowerCase());
|
|
39
|
+
}
|
|
21
40
|
/** Canonicalize one raw entity string. Returns null for junk (empty, too long, pure noise). */
|
|
22
41
|
export function canonicalizeEntity(raw) {
|
|
23
42
|
const s = (raw ?? "").trim().replace(/^[`'"]+|[`'"]+$/g, "").trim();
|
|
@@ -55,10 +74,15 @@ export function canonicalizeEntity(raw) {
|
|
|
55
74
|
return { key: s.toLowerCase(), name: s, kind: "concept", namespace: "domain" };
|
|
56
75
|
if (/[A-Z_]/.test(s.slice(1)))
|
|
57
76
|
return { key: s, name: s, kind: "symbol", namespace: "code" };
|
|
58
|
-
// lowercase single token (e.g. "vllm", "ollama") — treat as a domain concept
|
|
77
|
+
// lowercase single token (e.g. "vllm", "ollama") — treat as a domain concept, unless it's a
|
|
78
|
+
// bare common word ("not", "use", "no") that would only create noise and false conflicts.
|
|
79
|
+
if (isEntityStopword(s))
|
|
80
|
+
return null;
|
|
59
81
|
return { key: s.toLowerCase(), name: s, kind: "concept", namespace: "domain" };
|
|
60
82
|
}
|
|
61
83
|
// Multi-word phrase / proper noun → domain concept.
|
|
84
|
+
if (isEntityStopword(s))
|
|
85
|
+
return null;
|
|
62
86
|
return { key: s.toLowerCase(), name: s, kind: "concept", namespace: "domain" };
|
|
63
87
|
}
|
|
64
88
|
// Common capitalized words that START sentences / clauses — not domain entities.
|
package/dist/global-memory.js
CHANGED
|
@@ -9,7 +9,12 @@ export class PeonGlobalMemoryStore {
|
|
|
9
9
|
this.globalDir = globalDir;
|
|
10
10
|
}
|
|
11
11
|
static defaultDirectory() {
|
|
12
|
-
|
|
12
|
+
// PEON_GLOBAL_DIR relocates the global brain. Tests set it to a temp dir so they never read
|
|
13
|
+
// or write the real global store at ~/Library/Application Support/Peon/global — without this
|
|
14
|
+
// a tool created without an explicit globalMemoryDir falls back to the real store, which made
|
|
15
|
+
// cross-project isolation tests flaky and let test runs pollute the user's actual global brain.
|
|
16
|
+
const override = process.env.PEON_GLOBAL_DIR;
|
|
17
|
+
return override && override.trim() ? override.trim() : PeonGlobalMemoryStore.defaultGlobalDir;
|
|
13
18
|
}
|
|
14
19
|
static async open(options = {}) {
|
|
15
20
|
const store = new PeonGlobalMemoryStore(options.globalDir ?? PeonGlobalMemoryStore.defaultDirectory());
|
package/dist/quality.js
CHANGED
|
@@ -37,6 +37,13 @@ export function detectMemoryConflicts(records) {
|
|
|
37
37
|
const reason = opposingLanguageReason(left.content, right.content);
|
|
38
38
|
if (!reason)
|
|
39
39
|
continue;
|
|
40
|
+
// Same-topic gate. A shared entity + one opposing word-pair somewhere in two long beliefs
|
|
41
|
+
// is a weak signal: on a research brain, dozens of beliefs all mention "BIRD" and one says
|
|
42
|
+
// "use X" while an unrelated one says "avoid Y". They don't contradict — they're just both
|
|
43
|
+
// about BIRD. Require the two beliefs to actually be discussing the same thing before
|
|
44
|
+
// calling it a conflict: several shared entities, or real content overlap beyond the token.
|
|
45
|
+
if (!sameTopic(left, right))
|
|
46
|
+
continue;
|
|
40
47
|
conflicts.push({
|
|
41
48
|
entity,
|
|
42
49
|
leftId: left.id,
|
|
@@ -228,6 +235,47 @@ function sharedEntity(left, right) {
|
|
|
228
235
|
}
|
|
229
236
|
return undefined;
|
|
230
237
|
}
|
|
238
|
+
const TOPIC_STOP = new Set([
|
|
239
|
+
"the", "a", "an", "and", "or", "but", "to", "of", "in", "on", "for", "with", "is", "are", "was",
|
|
240
|
+
"were", "be", "as", "at", "by", "it", "this", "that", "we", "our", "use", "used", "using", "from",
|
|
241
|
+
"not", "no", "yes", "do", "does", "did", "so", "if", "then", "than", "when", "which", "what"
|
|
242
|
+
]);
|
|
243
|
+
/** Content tokens (lowercased, ≥3 chars, minus stopwords) — the topical fingerprint of a belief. */
|
|
244
|
+
function contentTokens(text) {
|
|
245
|
+
const out = new Set();
|
|
246
|
+
for (const raw of (text ?? "").toLowerCase().split(/[^a-z0-9]+/)) {
|
|
247
|
+
if (raw.length >= 3 && !TOPIC_STOP.has(raw))
|
|
248
|
+
out.add(raw);
|
|
249
|
+
}
|
|
250
|
+
return out;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* True when two beliefs are actually discussing the same thing — the precondition for their
|
|
254
|
+
* opposing language to be a real contradiction rather than a coincidence. Satisfied by either
|
|
255
|
+
* multiple shared entities (a strong same-subject signal) or meaningful content overlap
|
|
256
|
+
* (Jaccard of content tokens above a floor). Prevents "both mention BIRD, one says use / one
|
|
257
|
+
* says avoid, about unrelated things" from being flagged.
|
|
258
|
+
*/
|
|
259
|
+
function sameTopic(left, right) {
|
|
260
|
+
const le = new Set(left.entities.map(normalizeEntity));
|
|
261
|
+
const re = new Set(right.entities.map(normalizeEntity));
|
|
262
|
+
let sharedEntities = 0;
|
|
263
|
+
for (const e of le)
|
|
264
|
+
if (re.has(e))
|
|
265
|
+
sharedEntities += 1;
|
|
266
|
+
if (sharedEntities >= 2)
|
|
267
|
+
return true;
|
|
268
|
+
const lt = contentTokens(left.content);
|
|
269
|
+
const rt = contentTokens(right.content);
|
|
270
|
+
if (lt.size === 0 || rt.size === 0)
|
|
271
|
+
return false;
|
|
272
|
+
let inter = 0;
|
|
273
|
+
for (const t of lt)
|
|
274
|
+
if (rt.has(t))
|
|
275
|
+
inter += 1;
|
|
276
|
+
const jaccard = inter / (lt.size + rt.size - inter);
|
|
277
|
+
return jaccard >= 0.25;
|
|
278
|
+
}
|
|
231
279
|
function opposingLanguageReason(left, right) {
|
|
232
280
|
const leftText = normalizeMemory(left);
|
|
233
281
|
const rightText = normalizeMemory(right);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "peon-mem",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@modelcontextprotocol/sdk": "^1.23.0"
|
|
23
23
|
},
|
|
24
|
-
"description": "Local-first hierarchical memory brain for AI coding agents
|
|
24
|
+
"description": "Local-first hierarchical memory brain for AI coding agents — auto-capture, LLM consolidation into beliefs, hybrid retrieval, per-prompt injection, a living Neural Universe monitor, and a daily self-improvement loop. Works with Claude Code, Codex, and any MCP client.",
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"keywords": [
|
|
27
27
|
"memory",
|
|
@@ -55,4 +55,4 @@
|
|
|
55
55
|
"LICENSE"
|
|
56
56
|
],
|
|
57
57
|
"mcpName": "io.github.VineetV2/peon-mem"
|
|
58
|
-
}
|
|
58
|
+
}
|