@rubytech/create-maxy-code 0.1.230 → 0.1.232
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/package.json +1 -1
- package/payload/platform/neo4j/schema.cypher +20 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +39 -1
- package/payload/platform/plugins/docs/references/graph.md +12 -0
- package/payload/platform/plugins/docs/references/memory-guide.md +26 -0
- package/payload/platform/plugins/docs/superpowers/plans/2026-06-01-memory-edge.md +589 -0
- package/payload/platform/plugins/memory/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/memory/PLUGIN.md +7 -1
- package/payload/platform/plugins/memory/mcp/dist/index.js +41 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-compiled-truth-history.test.js +72 -26
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-compiled-truth-history.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-edge.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-edge.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-edge.test.js +164 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-edge.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-timeline-learned-at.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-timeline-learned-at.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-timeline-learned-at.test.js +30 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-timeline-learned-at.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-compiled-truth-history.d.ts +17 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-compiled-truth-history.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-compiled-truth-history.js +33 -13
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-compiled-truth-history.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-edge.d.ts +21 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-edge.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-edge.js +51 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-edge.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
- package/payload/platform/plugins/memory/skills/challenge/SKILL.md +52 -0
- package/payload/platform/plugins/memory/skills/connect/SKILL.md +56 -0
- package/payload/platform/plugins/memory/skills/emerge/SKILL.md +87 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +26 -4
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/templates/specialists/agents/archive-ingest-operator.md +1 -1
- package/payload/platform/templates/specialists/agents/database-operator.md +1 -1
- package/payload/platform/templates/specialists/agents/librarian.md +1 -1
- package/payload/server/{chunk-EXIPYITB.js → chunk-63M3ILJ6.js} +1 -0
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/public/assets/admin-DfeXRW1m.js +1 -0
- package/payload/server/public/index.html +1 -1
- package/payload/server/server.js +1 -1
- package/payload/server/public/assets/admin-BKawc0eQ.js +0 -1
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
# memory-edge Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Add a `memory-edge` MCP tool to the memory plugin that creates or deletes a typed directed edge between two pre-existing Neo4j nodes that belong to the same account.
|
|
6
|
+
|
|
7
|
+
**Architecture:** New `tools/memory-edge.ts` exports a single `memoryEdge()` function. It opens one Neo4j session, runs two ownership-check queries (one per endpoint), then runs a MERGE or DELETE query with the relationship type interpolated (backtick-escaped) into Cypher. The tool is registered with `server.tool` in `index.ts` and listed in `PLUGIN.md` and three specialist agent templates.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript, Neo4j driver (`getSession` from `lib/neo4j.js`), Vitest for unit tests.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
### Task 1: Create `tools/memory-edge.ts` with core logic
|
|
14
|
+
|
|
15
|
+
**Files:**
|
|
16
|
+
- Create: `maxy-code/platform/plugins/memory/mcp/src/tools/memory-edge.ts`
|
|
17
|
+
|
|
18
|
+
- [ ] **Step 1: Write the failing test** (see Task 2 — implement this file first so tests have something to import, then add tests)
|
|
19
|
+
|
|
20
|
+
- [ ] **Step 2: Create the file**
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { getSession } from "../lib/neo4j.js";
|
|
24
|
+
|
|
25
|
+
export interface MemoryEdgeParams {
|
|
26
|
+
action: "create" | "delete";
|
|
27
|
+
fromId: string;
|
|
28
|
+
toId: string;
|
|
29
|
+
relationshipType: string;
|
|
30
|
+
properties?: Record<string, unknown>;
|
|
31
|
+
accountId: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type MemoryEdgeResult =
|
|
35
|
+
| { created: boolean }
|
|
36
|
+
| { deleted: boolean }
|
|
37
|
+
| { error: "foreign-node"; elementId: string }
|
|
38
|
+
| { error: "reserved-type"; type: string };
|
|
39
|
+
|
|
40
|
+
export async function memoryEdge(params: MemoryEdgeParams): Promise<MemoryEdgeResult> {
|
|
41
|
+
const { action, fromId, toId, accountId } = params;
|
|
42
|
+
|
|
43
|
+
const rawType = params.relationshipType.toUpperCase().replace(/`/g, "");
|
|
44
|
+
|
|
45
|
+
if (rawType.startsWith("_")) {
|
|
46
|
+
process.stderr.write(
|
|
47
|
+
`[memory-edge] action=${action} from=${fromId.slice(0, 8)} to=${toId.slice(0, 8)} type=${rawType} accountId=${accountId?.slice(0, 8) ?? "none"} result=rejected reason=reserved-type type=${rawType}\n`,
|
|
48
|
+
);
|
|
49
|
+
return { error: "reserved-type", type: rawType };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const session = getSession();
|
|
53
|
+
try {
|
|
54
|
+
const fromCheck = await session.run(
|
|
55
|
+
`MATCH (n) WHERE elementId(n) = $nodeId AND n.accountId = $accountId RETURN elementId(n) AS id`,
|
|
56
|
+
{ nodeId: fromId, accountId },
|
|
57
|
+
);
|
|
58
|
+
if (fromCheck.records.length === 0) {
|
|
59
|
+
process.stderr.write(
|
|
60
|
+
`[memory-edge] action=${action} from=${fromId.slice(0, 8)} to=${toId.slice(0, 8)} type=${rawType} accountId=${accountId?.slice(0, 8) ?? "none"} result=rejected reason=foreign-node elementId=${fromId}\n`,
|
|
61
|
+
);
|
|
62
|
+
return { error: "foreign-node", elementId: fromId };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const toCheck = await session.run(
|
|
66
|
+
`MATCH (n) WHERE elementId(n) = $nodeId AND n.accountId = $accountId RETURN elementId(n) AS id`,
|
|
67
|
+
{ nodeId: toId, accountId },
|
|
68
|
+
);
|
|
69
|
+
if (toCheck.records.length === 0) {
|
|
70
|
+
process.stderr.write(
|
|
71
|
+
`[memory-edge] action=${action} from=${fromId.slice(0, 8)} to=${toId.slice(0, 8)} type=${rawType} accountId=${accountId?.slice(0, 8) ?? "none"} result=rejected reason=foreign-node elementId=${toId}\n`,
|
|
72
|
+
);
|
|
73
|
+
return { error: "foreign-node", elementId: toId };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (action === "create") {
|
|
77
|
+
const props = params.properties ?? {};
|
|
78
|
+
const q = `
|
|
79
|
+
MATCH (a) WHERE elementId(a) = $fromId
|
|
80
|
+
MATCH (b) WHERE elementId(b) = $toId
|
|
81
|
+
MERGE (a)-[r:\`${rawType}\`]->(b)
|
|
82
|
+
ON CREATE SET r += $props, r.createdAt = datetime()
|
|
83
|
+
RETURN r
|
|
84
|
+
`;
|
|
85
|
+
const result = await session.run(q, { fromId, toId, props });
|
|
86
|
+
const created = result.summary.counters.updates().relationshipsCreated > 0;
|
|
87
|
+
process.stderr.write(
|
|
88
|
+
`[memory-edge] action=create from=${fromId.slice(0, 8)} to=${toId.slice(0, 8)} type=${rawType} accountId=${accountId?.slice(0, 8) ?? "none"} result=${created ? "created" : "idempotent"}\n`,
|
|
89
|
+
);
|
|
90
|
+
return { created };
|
|
91
|
+
} else {
|
|
92
|
+
const q = `
|
|
93
|
+
MATCH (a)-[r:\`${rawType}\`]->(b)
|
|
94
|
+
WHERE elementId(a) = $fromId AND elementId(b) = $toId
|
|
95
|
+
DELETE r
|
|
96
|
+
`;
|
|
97
|
+
const result = await session.run(q, { fromId, toId });
|
|
98
|
+
const deleted = result.summary.counters.updates().relationshipsDeleted > 0;
|
|
99
|
+
process.stderr.write(
|
|
100
|
+
`[memory-edge] action=delete from=${fromId.slice(0, 8)} to=${toId.slice(0, 8)} type=${rawType} accountId=${accountId?.slice(0, 8) ?? "none"} result=${deleted ? "deleted" : "absent"}\n`,
|
|
101
|
+
);
|
|
102
|
+
return { deleted };
|
|
103
|
+
}
|
|
104
|
+
} finally {
|
|
105
|
+
await session.close();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
- [ ] **Step 3: TypeScript check** (run after Task 2 tests pass)
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
cd maxy-code/platform/plugins/memory/mcp && npx tsc --noEmit 2>&1 | head -20
|
|
114
|
+
```
|
|
115
|
+
Expected: no errors for this file (other pre-existing errors are acceptable if present before this change).
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
### Task 2: Unit tests for `memory-edge`
|
|
120
|
+
|
|
121
|
+
**Files:**
|
|
122
|
+
- Create: `maxy-code/platform/plugins/memory/mcp/src/tools/__tests__/memory-edge.test.ts`
|
|
123
|
+
|
|
124
|
+
- [ ] **Step 1: Write the test file**
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// @vitest-environment node
|
|
128
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
129
|
+
|
|
130
|
+
// Stage session run results in call order. Each entry in the array is the
|
|
131
|
+
// return value for one session.run() call.
|
|
132
|
+
function makeSession(runResults: Array<{ records: unknown[]; summary?: unknown }>) {
|
|
133
|
+
let i = 0;
|
|
134
|
+
return {
|
|
135
|
+
run: vi.fn().mockImplementation(async () => {
|
|
136
|
+
const res = runResults[i++] ?? { records: [] };
|
|
137
|
+
return res;
|
|
138
|
+
}),
|
|
139
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function ownerOk(id: string) {
|
|
144
|
+
return { records: [{ get: (_: string) => id }] };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function ownerFail() {
|
|
148
|
+
return { records: [] };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function mergeResult(created: number) {
|
|
152
|
+
return {
|
|
153
|
+
records: [{ get: (_: string) => ({}) }],
|
|
154
|
+
summary: { counters: { updates: () => ({ relationshipsCreated: created, relationshipsDeleted: 0 }) } },
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function deleteResult(deleted: number) {
|
|
159
|
+
return {
|
|
160
|
+
records: [],
|
|
161
|
+
summary: { counters: { updates: () => ({ relationshipsCreated: 0, relationshipsDeleted: deleted }) } },
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const ACCOUNT_ID = "acct-1";
|
|
166
|
+
const FROM_ID = "4:eid:node-a";
|
|
167
|
+
const TO_ID = "4:eid:node-b";
|
|
168
|
+
|
|
169
|
+
let mockSession: ReturnType<typeof makeSession>;
|
|
170
|
+
|
|
171
|
+
vi.mock("../../lib/neo4j.js", () => ({
|
|
172
|
+
getSession: () => mockSession,
|
|
173
|
+
}));
|
|
174
|
+
|
|
175
|
+
import { memoryEdge } from "../memory-edge.js";
|
|
176
|
+
|
|
177
|
+
beforeEach(() => {
|
|
178
|
+
vi.clearAllMocks();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Suppress stderr noise during tests
|
|
182
|
+
beforeEach(() => {
|
|
183
|
+
vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe("memory-edge — create", () => {
|
|
187
|
+
it("creates edge on first call — returned created: true", async () => {
|
|
188
|
+
mockSession = makeSession([ownerOk(FROM_ID), ownerOk(TO_ID), mergeResult(1)]);
|
|
189
|
+
const result = await memoryEdge({
|
|
190
|
+
action: "create",
|
|
191
|
+
fromId: FROM_ID,
|
|
192
|
+
toId: TO_ID,
|
|
193
|
+
relationshipType: "RELATES_TO",
|
|
194
|
+
accountId: ACCOUNT_ID,
|
|
195
|
+
});
|
|
196
|
+
expect(result).toEqual({ created: true });
|
|
197
|
+
expect(mockSession.run).toHaveBeenCalledTimes(3);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("idempotent on second call — returned created: false", async () => {
|
|
201
|
+
mockSession = makeSession([ownerOk(FROM_ID), ownerOk(TO_ID), mergeResult(0)]);
|
|
202
|
+
const result = await memoryEdge({
|
|
203
|
+
action: "create",
|
|
204
|
+
fromId: FROM_ID,
|
|
205
|
+
toId: TO_ID,
|
|
206
|
+
relationshipType: "RELATES_TO",
|
|
207
|
+
accountId: ACCOUNT_ID,
|
|
208
|
+
});
|
|
209
|
+
expect(result).toEqual({ created: false });
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("uppercases the relationship type before the Cypher call", async () => {
|
|
213
|
+
mockSession = makeSession([ownerOk(FROM_ID), ownerOk(TO_ID), mergeResult(1)]);
|
|
214
|
+
await memoryEdge({
|
|
215
|
+
action: "create",
|
|
216
|
+
fromId: FROM_ID,
|
|
217
|
+
toId: TO_ID,
|
|
218
|
+
relationshipType: "relates_to",
|
|
219
|
+
accountId: ACCOUNT_ID,
|
|
220
|
+
});
|
|
221
|
+
const mergeCall = mockSession.run.mock.calls[2][0] as string;
|
|
222
|
+
expect(mergeCall).toContain("`RELATES_TO`");
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe("memory-edge — delete", () => {
|
|
227
|
+
it("deletes a present edge — returned deleted: true", async () => {
|
|
228
|
+
mockSession = makeSession([ownerOk(FROM_ID), ownerOk(TO_ID), deleteResult(1)]);
|
|
229
|
+
const result = await memoryEdge({
|
|
230
|
+
action: "delete",
|
|
231
|
+
fromId: FROM_ID,
|
|
232
|
+
toId: TO_ID,
|
|
233
|
+
relationshipType: "RELATES_TO",
|
|
234
|
+
accountId: ACCOUNT_ID,
|
|
235
|
+
});
|
|
236
|
+
expect(result).toEqual({ deleted: true });
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("absent edge is a no-op — returned deleted: false", async () => {
|
|
240
|
+
mockSession = makeSession([ownerOk(FROM_ID), ownerOk(TO_ID), deleteResult(0)]);
|
|
241
|
+
const result = await memoryEdge({
|
|
242
|
+
action: "delete",
|
|
243
|
+
fromId: FROM_ID,
|
|
244
|
+
toId: TO_ID,
|
|
245
|
+
relationshipType: "RELATES_TO",
|
|
246
|
+
accountId: ACCOUNT_ID,
|
|
247
|
+
});
|
|
248
|
+
expect(result).toEqual({ deleted: false });
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
describe("memory-edge — account ownership", () => {
|
|
253
|
+
it("rejects when fromId does not belong to the account — foreign-node error, no mutation", async () => {
|
|
254
|
+
mockSession = makeSession([ownerFail()]);
|
|
255
|
+
const result = await memoryEdge({
|
|
256
|
+
action: "create",
|
|
257
|
+
fromId: FROM_ID,
|
|
258
|
+
toId: TO_ID,
|
|
259
|
+
relationshipType: "RELATES_TO",
|
|
260
|
+
accountId: ACCOUNT_ID,
|
|
261
|
+
});
|
|
262
|
+
expect(result).toEqual({ error: "foreign-node", elementId: FROM_ID });
|
|
263
|
+
expect(mockSession.run).toHaveBeenCalledTimes(1);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("rejects when toId does not belong to the account — foreign-node error, no mutation", async () => {
|
|
267
|
+
mockSession = makeSession([ownerOk(FROM_ID), ownerFail()]);
|
|
268
|
+
const result = await memoryEdge({
|
|
269
|
+
action: "create",
|
|
270
|
+
fromId: FROM_ID,
|
|
271
|
+
toId: TO_ID,
|
|
272
|
+
relationshipType: "RELATES_TO",
|
|
273
|
+
accountId: ACCOUNT_ID,
|
|
274
|
+
});
|
|
275
|
+
expect(result).toEqual({ error: "foreign-node", elementId: TO_ID });
|
|
276
|
+
expect(mockSession.run).toHaveBeenCalledTimes(2);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe("memory-edge — reserved type guard", () => {
|
|
281
|
+
it("rejects underscore-prefixed type before opening a session", async () => {
|
|
282
|
+
mockSession = makeSession([]);
|
|
283
|
+
const result = await memoryEdge({
|
|
284
|
+
action: "create",
|
|
285
|
+
fromId: FROM_ID,
|
|
286
|
+
toId: TO_ID,
|
|
287
|
+
relationshipType: "_INTERNAL",
|
|
288
|
+
accountId: ACCOUNT_ID,
|
|
289
|
+
});
|
|
290
|
+
expect(result).toEqual({ error: "reserved-type", type: "_INTERNAL" });
|
|
291
|
+
expect(mockSession.run).not.toHaveBeenCalled();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it("uppercase-coerces before the reserved check — _soft_delete is also rejected", async () => {
|
|
295
|
+
mockSession = makeSession([]);
|
|
296
|
+
const result = await memoryEdge({
|
|
297
|
+
action: "create",
|
|
298
|
+
fromId: FROM_ID,
|
|
299
|
+
toId: TO_ID,
|
|
300
|
+
relationshipType: "_soft_delete",
|
|
301
|
+
accountId: ACCOUNT_ID,
|
|
302
|
+
});
|
|
303
|
+
expect(result).toEqual({ error: "reserved-type", type: "_SOFT_DELETE" });
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
- [ ] **Step 2: Run the tests — expect PASS**
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
cd maxy-code/platform/plugins/memory/mcp && npx vitest run src/tools/__tests__/memory-edge.test.ts 2>&1
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Expected: all tests pass (the implementation in Task 1 is already complete).
|
|
315
|
+
|
|
316
|
+
- [ ] **Step 3: Run the full test suite — no regressions**
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
cd maxy-code/platform/plugins/memory/mcp && npx vitest run 2>&1 | tail -20
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Expected: same pass count as before this sprint (pre-existing failures are acceptable if they existed before).
|
|
323
|
+
|
|
324
|
+
- [ ] **Step 4: Commit**
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
cd maxy-code/platform/plugins/memory/mcp && git add src/tools/memory-edge.ts src/tools/__tests__/memory-edge.test.ts
|
|
328
|
+
git commit -m "$(cat <<'EOF'
|
|
329
|
+
feat: add memory-edge tool — create/delete typed edges between existing nodes
|
|
330
|
+
|
|
331
|
+
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
332
|
+
Session: 3104d4b4-d03e-4339-b65e-6014223c069e
|
|
333
|
+
EOF
|
|
334
|
+
)"
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
### Task 3: Register in `index.ts`
|
|
340
|
+
|
|
341
|
+
**Files:**
|
|
342
|
+
- Modify: `maxy-code/platform/plugins/memory/mcp/src/index.ts`
|
|
343
|
+
|
|
344
|
+
- [ ] **Step 1: Add the import** after the `memoryUpdate` import line (around line 36)
|
|
345
|
+
|
|
346
|
+
Find:
|
|
347
|
+
```typescript
|
|
348
|
+
import { memoryUpdate } from "./tools/memory-update.js";
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Add immediately after:
|
|
352
|
+
```typescript
|
|
353
|
+
import { memoryEdge } from "./tools/memory-edge.js";
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
- [ ] **Step 2: Add the `server.tool` registration** after the closing brace of the `memory-update` block (after line 1111 — search for the block ending with `"Update failed:"`)
|
|
357
|
+
|
|
358
|
+
Find this exact closing sequence of the memory-update registration:
|
|
359
|
+
```typescript
|
|
360
|
+
isError: true,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
eagerTool(server,
|
|
367
|
+
"session-compact-status",
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
Replace with:
|
|
371
|
+
```typescript
|
|
372
|
+
isError: true,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
server.tool(
|
|
379
|
+
"memory-edge",
|
|
380
|
+
"Create or delete a typed directed edge between two nodes that already exist in the knowledge graph. Both nodes must belong to the same account. Use action='create' to MERGE the edge (idempotent — returns {created:false} if it already exists) or action='delete' to remove it (returns {deleted:false} if already absent). relationshipType is uppercase-coerced; types starting with underscore are reserved and rejected. Properties are stamped on the edge on CREATE only.",
|
|
381
|
+
{
|
|
382
|
+
action: z.enum(["create", "delete"]).describe("'create' MERGEs the edge (idempotent); 'delete' removes it (no-op if absent)."),
|
|
383
|
+
fromId: z.string().describe("elementId of the source node (from memory-search results)."),
|
|
384
|
+
toId: z.string().describe("elementId of the target node (from memory-search results)."),
|
|
385
|
+
relationshipType: z.string().describe("Relationship type label — e.g. RELATES_TO, MENTIONS, WORKED_WITH. Uppercase-coerced. Underscore-prefixed types (_SOFT_DELETE, etc.) are reserved and rejected."),
|
|
386
|
+
properties: z.record(z.string(), z.unknown()).optional().describe("Properties to set on the relationship on CREATE. Ignored on DELETE and on idempotent CREATE (MERGE-matched)."),
|
|
387
|
+
},
|
|
388
|
+
async ({ action, fromId, toId, relationshipType, properties }: {
|
|
389
|
+
action: "create" | "delete";
|
|
390
|
+
fromId: string;
|
|
391
|
+
toId: string;
|
|
392
|
+
relationshipType: string;
|
|
393
|
+
properties?: Record<string, unknown>;
|
|
394
|
+
}) => {
|
|
395
|
+
if (!accountId) return refuseNoAccount("memory-edge");
|
|
396
|
+
try {
|
|
397
|
+
const result = await memoryEdge({
|
|
398
|
+
action,
|
|
399
|
+
fromId,
|
|
400
|
+
toId,
|
|
401
|
+
relationshipType,
|
|
402
|
+
properties,
|
|
403
|
+
accountId,
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
if ("error" in result) {
|
|
407
|
+
return {
|
|
408
|
+
content: [{ type: "text" as const, text: `memory-edge rejected: ${JSON.stringify(result)}` }],
|
|
409
|
+
isError: true,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const detail =
|
|
414
|
+
"created" in result
|
|
415
|
+
? result.created
|
|
416
|
+
? "Edge created."
|
|
417
|
+
: "Edge already exists (idempotent)."
|
|
418
|
+
: result.deleted
|
|
419
|
+
? "Edge deleted."
|
|
420
|
+
: "Edge was not present (no-op).";
|
|
421
|
+
|
|
422
|
+
return { content: [{ type: "text" as const, text: detail }] };
|
|
423
|
+
} catch (err) {
|
|
424
|
+
return {
|
|
425
|
+
content: [{ type: "text" as const, text: `memory-edge failed: ${err instanceof Error ? err.message : String(err)}` }],
|
|
426
|
+
isError: true,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
eagerTool(server,
|
|
433
|
+
"session-compact-status",
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
- [ ] **Step 3: TypeScript check**
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
cd maxy-code/platform/plugins/memory/mcp && npx tsc --noEmit 2>&1 | head -20
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Expected: no new errors introduced by this change.
|
|
443
|
+
|
|
444
|
+
- [ ] **Step 4: Commit**
|
|
445
|
+
|
|
446
|
+
```bash
|
|
447
|
+
git add maxy-code/platform/plugins/memory/mcp/src/index.ts
|
|
448
|
+
git commit -m "$(cat <<'EOF'
|
|
449
|
+
feat: register memory-edge in memory MCP server index.ts
|
|
450
|
+
|
|
451
|
+
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
452
|
+
Session: 3104d4b4-d03e-4339-b65e-6014223c069e
|
|
453
|
+
EOF
|
|
454
|
+
)"
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
### Task 4: Update `PLUGIN.md` and `graph.md`
|
|
460
|
+
|
|
461
|
+
**Files:**
|
|
462
|
+
- Modify: `maxy-code/platform/plugins/memory/PLUGIN.md`
|
|
463
|
+
- Modify: `maxy-code/platform/plugins/docs/references/graph.md`
|
|
464
|
+
|
|
465
|
+
- [ ] **Step 1: Add tool entry to `PLUGIN.md`** (tool list, after the `memory-update` entry)
|
|
466
|
+
|
|
467
|
+
Find:
|
|
468
|
+
```yaml
|
|
469
|
+
- name: memory-update
|
|
470
|
+
publicAllowlist: false
|
|
471
|
+
adminAllowlist: true
|
|
472
|
+
- name: memory-update-by-name
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
Replace with:
|
|
476
|
+
```yaml
|
|
477
|
+
- name: memory-update
|
|
478
|
+
publicAllowlist: false
|
|
479
|
+
adminAllowlist: true
|
|
480
|
+
- name: memory-edge
|
|
481
|
+
publicAllowlist: false
|
|
482
|
+
adminAllowlist: true
|
|
483
|
+
- name: memory-update-by-name
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
- [ ] **Step 2: Update `PLUGIN.md` description prose**
|
|
487
|
+
|
|
488
|
+
Find the `description:` field's first sentence fragment (around "memory-write, memory-update,"):
|
|
489
|
+
```
|
|
490
|
+
memory-write, memory-update, memory-update-by-name
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
Replace with:
|
|
494
|
+
```
|
|
495
|
+
memory-write, memory-update, memory-edge (create/delete typed directed edges between pre-existing nodes), memory-update-by-name
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
- [ ] **Step 3: Add edge-write section to `graph.md`** — append at end of file
|
|
499
|
+
|
|
500
|
+
```markdown
|
|
501
|
+
|
|
502
|
+
## Direct edge management
|
|
503
|
+
|
|
504
|
+
`memory-edge` creates or deletes a typed directed edge between two nodes that already exist in the graph. Both nodes must belong to the same account — mismatched or foreign nodes are rejected with a structured error before any mutation runs.
|
|
505
|
+
|
|
506
|
+
**Create:** MERGE is idempotent. First call returns `{created: true}`; a repeated call with the same endpoints and type returns `{created: false}`. Properties supplied on the call are stamped onto the relationship on CREATE only; a subsequent idempotent hit does not overwrite them.
|
|
507
|
+
|
|
508
|
+
**Delete:** If the edge is present it is deleted and `{deleted: true}` is returned. If absent, the call is a no-op and returns `{deleted: false}`.
|
|
509
|
+
|
|
510
|
+
`relationshipType` is uppercase-coerced. Types that start with an underscore (e.g. `_SOFT_DELETE`) are reserved for platform internals and are rejected.
|
|
511
|
+
|
|
512
|
+
Typical flow: call `memory-search` for each endpoint to retrieve their `elementId` values, then call `memory-edge action=create relationshipType=RELATES_TO fromId=<id> toId=<id>`. The new edge appears when you hop-expand either endpoint on the `/graph` canvas.
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
- [ ] **Step 4: Commit**
|
|
516
|
+
|
|
517
|
+
```bash
|
|
518
|
+
git add maxy-code/platform/plugins/memory/PLUGIN.md maxy-code/platform/plugins/docs/references/graph.md
|
|
519
|
+
git commit -m "$(cat <<'EOF'
|
|
520
|
+
docs: add memory-edge to PLUGIN.md tool list and graph.md edge-write section
|
|
521
|
+
|
|
522
|
+
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
523
|
+
Session: 3104d4b4-d03e-4339-b65e-6014223c069e
|
|
524
|
+
EOF
|
|
525
|
+
)"
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
### Task 5: Update specialist agent templates
|
|
531
|
+
|
|
532
|
+
**Files:**
|
|
533
|
+
- Modify: `maxy-code/platform/templates/specialists/agents/database-operator.md`
|
|
534
|
+
- Modify: `maxy-code/platform/templates/specialists/agents/archive-ingest-operator.md`
|
|
535
|
+
- Modify: `maxy-code/platform/templates/specialists/agents/librarian.md`
|
|
536
|
+
|
|
537
|
+
- [ ] **Step 1: Update `database-operator.md`**
|
|
538
|
+
|
|
539
|
+
Find:
|
|
540
|
+
```
|
|
541
|
+
tools: Read, mcp__plugin_memory_memory__memory-write, mcp__plugin_memory_memory__memory-update, mcp__plugin_memory_memory__memory-update-by-name,
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
Replace with:
|
|
545
|
+
```
|
|
546
|
+
tools: Read, mcp__plugin_memory_memory__memory-write, mcp__plugin_memory_memory__memory-update, mcp__plugin_memory_memory__memory-edge, mcp__plugin_memory_memory__memory-update-by-name,
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
(Add `mcp__plugin_memory_memory__memory-edge` after `memory-update` and before `memory-update-by-name`.)
|
|
550
|
+
|
|
551
|
+
- [ ] **Step 2: Update `archive-ingest-operator.md`**
|
|
552
|
+
|
|
553
|
+
Find:
|
|
554
|
+
```
|
|
555
|
+
tools: Read, mcp__plugin_memory_memory__memory-write, mcp__plugin_memory_memory__memory-update, mcp__plugin_memory_memory__memory-search,
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
Replace with:
|
|
559
|
+
```
|
|
560
|
+
tools: Read, mcp__plugin_memory_memory__memory-write, mcp__plugin_memory_memory__memory-update, mcp__plugin_memory_memory__memory-edge, mcp__plugin_memory_memory__memory-search,
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
- [ ] **Step 3: Update `librarian.md`**
|
|
564
|
+
|
|
565
|
+
Find:
|
|
566
|
+
```
|
|
567
|
+
tools: mcp__plugin_memory_memory__memory-ingest-extract, mcp__plugin_memory_memory__memory-ingest, mcp__plugin_memory_memory__memory-ingest-web, mcp__plugin_memory_memory__memory-archive-write, mcp__plugin_memory_memory__memory-write, mcp__plugin_memory_memory__memory-update, mcp__plugin_memory_memory__memory-search,
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
Replace with:
|
|
571
|
+
```
|
|
572
|
+
tools: mcp__plugin_memory_memory__memory-ingest-extract, mcp__plugin_memory_memory__memory-ingest, mcp__plugin_memory_memory__memory-ingest-web, mcp__plugin_memory_memory__memory-archive-write, mcp__plugin_memory_memory__memory-write, mcp__plugin_memory_memory__memory-update, mcp__plugin_memory_memory__memory-edge, mcp__plugin_memory_memory__memory-search,
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
- [ ] **Step 4: Commit**
|
|
576
|
+
|
|
577
|
+
```bash
|
|
578
|
+
git add \
|
|
579
|
+
maxy-code/platform/templates/specialists/agents/database-operator.md \
|
|
580
|
+
maxy-code/platform/templates/specialists/agents/archive-ingest-operator.md \
|
|
581
|
+
maxy-code/platform/templates/specialists/agents/librarian.md
|
|
582
|
+
git commit -m "$(cat <<'EOF'
|
|
583
|
+
feat: add memory-edge to write-specialist agent templates
|
|
584
|
+
|
|
585
|
+
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
586
|
+
Session: 3104d4b4-d03e-4339-b65e-6014223c069e
|
|
587
|
+
EOF
|
|
588
|
+
)"
|
|
589
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memory",
|
|
3
|
-
"description": "Graph memory plugin. Provides memory-search (with optional `fields` projection for known-shape lookups), memory-write, memory-update, memory-update-by-name (repair surface that resolves an elementId from a (label, name, accountId) tuple when search cannot return one), memory-lookup-by-name (deterministic read surface that returns nodes by case-insensitive exact `name` match, optionally constrained by labels, bypassing the memory-search ranking stack so a present entity is never missed because a diluted query ranked it below the cut), and the :Report surface (memory-report-write / memory-report-read-latest / memory-report-list) for reading from, writing to, and updating the Neo4j knowledge graph. Includes conversational memory — organic preference learning, evidence-backed recall, and transparent 'what do you know about me?' responses. Document ingestion goes through memory-ingest; the dispatched specialist produces typed-section JSON in-turn from the loaded ontology. Operator-solicited reclassify of an already-stored thread-shaped KD (today: email-thread KDs) is exposed as `kd-classify <attachmentId>` — runs a body-growth gate (proceed iff body grew ≥25% since the last classify, or first-ever classify), stages the body to a temp file under `$ACCOUNT_DIR/tmp/`, and returns a dispatch envelope for the `librarian`; on success `memory-ingest` replaces the prior :Section children and stamps `lastClassifiedAt` + `lastClassifiedBodyLength` on the parent KD. Two modes: `document` (default) for unstructured PDF/web content → :KnowledgeDocument (keyed on `attachmentId`) + :Section, and `chat` for conversation transcripts → :ConversationArchive (keyed on `conversationIdentity`) + :Section chunks; two parent labels, two writer paths chosen by which identity property is set. Conversation-archive Phase 2 splits across two read-only tools (Task 433): `conversation-archive-list-chunks` pages chunk bodies for the dispatched specialist to read in-turn; `conversation-archive-derive-insights` converts the specialist's per-chunk claims into operator-facing proposals with per-kind cypher. `conversation-archive-enrich-rejection` records (or undoes) durable per-row rejections so already-triaged claims do not re-surface on re-runs. Ships five skills: `conversational-memory`, `document-ingest`, `conversation-archive` (source-agnostic transcript ingest for WhatsApp, Telegram, Signal, LinkedIn DMs, Zoom, meeting minutes, iMessage, Slack), `conversation-archive-mcp` (operator-initiated MCP-driven ingest for Granola, Otter, Circleback meetings — sibling of `conversation-archive` sharing the same writer surface and identity formula), and `conversation-archive-enrich` (per-row operator-gated insight derivation over a named archive's chunks).",
|
|
3
|
+
"description": "Graph memory plugin. Provides memory-search (with optional `fields` projection for known-shape lookups), memory-write, memory-update, memory-edge (create/delete typed directed edges between pre-existing nodes), memory-update-by-name (repair surface that resolves an elementId from a (label, name, accountId) tuple when search cannot return one), memory-lookup-by-name (deterministic read surface that returns nodes by case-insensitive exact `name` match, optionally constrained by labels, bypassing the memory-search ranking stack so a present entity is never missed because a diluted query ranked it below the cut), and the :Report surface (memory-report-write / memory-report-read-latest / memory-report-list) for reading from, writing to, and updating the Neo4j knowledge graph. Includes conversational memory — organic preference learning, evidence-backed recall, and transparent 'what do you know about me?' responses. Document ingestion goes through memory-ingest; the dispatched specialist produces typed-section JSON in-turn from the loaded ontology. Operator-solicited reclassify of an already-stored thread-shaped KD (today: email-thread KDs) is exposed as `kd-classify <attachmentId>` — runs a body-growth gate (proceed iff body grew ≥25% since the last classify, or first-ever classify), stages the body to a temp file under `$ACCOUNT_DIR/tmp/`, and returns a dispatch envelope for the `librarian`; on success `memory-ingest` replaces the prior :Section children and stamps `lastClassifiedAt` + `lastClassifiedBodyLength` on the parent KD. Two modes: `document` (default) for unstructured PDF/web content → :KnowledgeDocument (keyed on `attachmentId`) + :Section, and `chat` for conversation transcripts → :ConversationArchive (keyed on `conversationIdentity`) + :Section chunks; two parent labels, two writer paths chosen by which identity property is set. Conversation-archive Phase 2 splits across two read-only tools (Task 433): `conversation-archive-list-chunks` pages chunk bodies for the dispatched specialist to read in-turn; `conversation-archive-derive-insights` converts the specialist's per-chunk claims into operator-facing proposals with per-kind cypher. `conversation-archive-enrich-rejection` records (or undoes) durable per-row rejections so already-triaged claims do not re-surface on re-runs. Ships five skills: `conversational-memory`, `document-ingest`, `conversation-archive` (source-agnostic transcript ingest for WhatsApp, Telegram, Signal, LinkedIn DMs, Zoom, meeting minutes, iMessage, Slack), `conversation-archive-mcp` (operator-initiated MCP-driven ingest for Granola, Otter, Circleback meetings — sibling of `conversation-archive` sharing the same writer surface and identity formula), and `conversation-archive-enrich` (per-row operator-gated insight derivation over a named archive's chunks). Ships three thinking-tool skills: `challenge` (adversarial retrieval — strongest counter-evidence from the graph for a given claim), `connect` (bridge-finding between two topics via shared graph neighbors), and `emerge` (clusters uncategorised KnowledgeDocument and Section nodes into operator-approved Concept proposals).",
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Rubytech LLC"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: memory
|
|
3
|
-
description: "Graph memory plugin. Provides memory-search (with optional `fields` projection for known-shape lookups), memory-write, memory-update, memory-update-by-name (repair surface that resolves an elementId from a (label, name, accountId) tuple when search cannot return one), memory-lookup-by-name (deterministic read surface that returns nodes by case-insensitive exact `name` match, optionally constrained by labels, bypassing the memory-search ranking stack so a present entity is never missed because a diluted query ranked it below the cut), and the :Report surface (memory-report-write / memory-report-read-latest / memory-report-list) for reading from, writing to, and updating the Neo4j knowledge graph. Includes conversational memory — organic preference learning, evidence-backed recall, and transparent 'what do you know about me?' responses. Document ingestion goes through memory-ingest; the dispatched specialist produces typed-section JSON in-turn from the loaded ontology. Operator-solicited reclassify of an already-stored thread-shaped KD (today: email-thread KDs) is exposed as `kd-classify <attachmentId>` — runs a body-growth gate (proceed iff body grew ≥25% since the last classify, or first-ever classify), stages the body to a temp file under `$ACCOUNT_DIR/tmp/`, and returns a dispatch envelope for the `librarian`; on success `memory-ingest` replaces the prior :Section children and stamps `lastClassifiedAt` + `lastClassifiedBodyLength` on the parent KD. Two modes: `document` (default) for unstructured PDF/web content → :KnowledgeDocument (keyed on `attachmentId`) + :Section, and `chat` for conversation transcripts → :ConversationArchive (keyed on `conversationIdentity`) + :Section chunks; two parent labels, two writer paths chosen by which identity property is set. Conversation-archive Phase 2 splits across two read-only tools (Task 433): `conversation-archive-list-chunks` pages chunk bodies for the dispatched specialist to read in-turn; `conversation-archive-derive-insights` converts the specialist's per-chunk claims into operator-facing proposals with per-kind cypher. `conversation-archive-enrich-rejection` records (or undoes) durable per-row rejections so already-triaged claims do not re-surface on re-runs. Ships five skills: `conversational-memory`, `document-ingest`, `conversation-archive` (source-agnostic transcript ingest for WhatsApp, Telegram, Signal, LinkedIn DMs, Zoom, meeting minutes, iMessage, Slack), `conversation-archive-mcp` (operator-initiated MCP-driven ingest for Granola, Otter, Circleback meetings — sibling of `conversation-archive` sharing the same writer surface and identity formula), and `conversation-archive-enrich` (per-row operator-gated insight derivation over a named archive's chunks)."
|
|
3
|
+
description: "Graph memory plugin. Provides memory-search (with optional `fields` projection for known-shape lookups), memory-write, memory-update, memory-edge (create/delete typed directed edges between pre-existing nodes), memory-update-by-name (repair surface that resolves an elementId from a (label, name, accountId) tuple when search cannot return one), memory-lookup-by-name (deterministic read surface that returns nodes by case-insensitive exact `name` match, optionally constrained by labels, bypassing the memory-search ranking stack so a present entity is never missed because a diluted query ranked it below the cut), and the :Report surface (memory-report-write / memory-report-read-latest / memory-report-list) for reading from, writing to, and updating the Neo4j knowledge graph. Includes conversational memory — organic preference learning, evidence-backed recall, and transparent 'what do you know about me?' responses. Document ingestion goes through memory-ingest; the dispatched specialist produces typed-section JSON in-turn from the loaded ontology. Operator-solicited reclassify of an already-stored thread-shaped KD (today: email-thread KDs) is exposed as `kd-classify <attachmentId>` — runs a body-growth gate (proceed iff body grew ≥25% since the last classify, or first-ever classify), stages the body to a temp file under `$ACCOUNT_DIR/tmp/`, and returns a dispatch envelope for the `librarian`; on success `memory-ingest` replaces the prior :Section children and stamps `lastClassifiedAt` + `lastClassifiedBodyLength` on the parent KD. Two modes: `document` (default) for unstructured PDF/web content → :KnowledgeDocument (keyed on `attachmentId`) + :Section, and `chat` for conversation transcripts → :ConversationArchive (keyed on `conversationIdentity`) + :Section chunks; two parent labels, two writer paths chosen by which identity property is set. Conversation-archive Phase 2 splits across two read-only tools (Task 433): `conversation-archive-list-chunks` pages chunk bodies for the dispatched specialist to read in-turn; `conversation-archive-derive-insights` converts the specialist's per-chunk claims into operator-facing proposals with per-kind cypher. `conversation-archive-enrich-rejection` records (or undoes) durable per-row rejections so already-triaged claims do not re-surface on re-runs. Ships five skills: `conversational-memory`, `document-ingest`, `conversation-archive` (source-agnostic transcript ingest for WhatsApp, Telegram, Signal, LinkedIn DMs, Zoom, meeting minutes, iMessage, Slack), `conversation-archive-mcp` (operator-initiated MCP-driven ingest for Granola, Otter, Circleback meetings — sibling of `conversation-archive` sharing the same writer surface and identity formula), and `conversation-archive-enrich` (per-row operator-gated insight derivation over a named archive's chunks). Ships three thinking-tool skills: `challenge` (adversarial retrieval — strongest counter-evidence from the graph for a given claim), `connect` (bridge-finding between two topics via shared graph neighbors), and `emerge` (clusters uncategorised KnowledgeDocument and Section nodes into operator-approved Concept proposals)."
|
|
4
4
|
tools:
|
|
5
5
|
- name: memory-search
|
|
6
6
|
publicAllowlist: true
|
|
@@ -11,6 +11,9 @@ tools:
|
|
|
11
11
|
- name: memory-update
|
|
12
12
|
publicAllowlist: false
|
|
13
13
|
adminAllowlist: true
|
|
14
|
+
- name: memory-edge
|
|
15
|
+
publicAllowlist: false
|
|
16
|
+
adminAllowlist: true
|
|
14
17
|
- name: memory-update-by-name
|
|
15
18
|
publicAllowlist: false
|
|
16
19
|
adminAllowlist: true
|
|
@@ -135,6 +138,9 @@ skills:
|
|
|
135
138
|
- skills/conversation-archive-enrich/SKILL.md
|
|
136
139
|
- skills/concept-synthesis/SKILL.md
|
|
137
140
|
- skills/archive-crawler/SKILL.md
|
|
141
|
+
- skills/challenge/SKILL.md
|
|
142
|
+
- skills/connect/SKILL.md
|
|
143
|
+
- skills/emerge/SKILL.md
|
|
138
144
|
always: true
|
|
139
145
|
embed: false
|
|
140
146
|
mcp:
|