@rubytech/create-maxy-code 0.1.151 → 0.1.152
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/lib/graph-search/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-search/dist/index.js +10 -9
- package/payload/platform/lib/graph-search/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-search/src/index.ts +10 -9
- package/payload/platform/package.json +2 -2
- package/payload/platform/plugins/admin/PLUGIN.md +1 -1
- package/payload/platform/plugins/admin/hooks/__tests__/pre-turn-graph-pass.test.sh +26 -129
- package/payload/platform/plugins/admin/hooks/pre-turn-graph-pass.sh +64 -131
- package/payload/platform/plugins/docs/PLUGIN.md +1 -1
- package/payload/platform/plugins/docs/references/admin-session.md +2 -2
- package/payload/platform/plugins/docs/references/internals.md +1 -64
- package/payload/platform/plugins/docs/references/platform.md +1 -1
- package/payload/platform/plugins/docs/references/plugins-guide.md +2 -2
- package/payload/platform/plugins/email/mcp/dist/lib/graph.d.ts +1 -1
- package/payload/platform/plugins/email/mcp/dist/lib/graph.d.ts.map +1 -1
- package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.js +25 -45
- package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.js.map +1 -1
- package/payload/platform/plugins/memory/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/memory/PLUGIN.md +8 -16
- package/payload/platform/plugins/memory/bin/conversation-archive-ingest.mjs +14 -11
- package/payload/platform/plugins/memory/mcp/dist/index.js +13 -269
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-citation-audit.d.ts +1 -24
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-citation-audit.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-citation-audit.js +8 -211
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-citation-audit.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/section-types.d.ts +99 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/section-types.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/section-types.js +56 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/section-types.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.d.ts +12 -32
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.js +19 -281
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.js +0 -3
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js +15 -14
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts +1 -76
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js +73 -329
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js.map +1 -1
- 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 +10 -12
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/vitest.config.ts +4 -3
- package/payload/platform/plugins/memory/references/graph-primitives.md +2 -2
- package/payload/platform/plugins/memory/references/schema-base.md +2 -7
- package/payload/platform/plugins/memory/skills/document-ingest/SKILL.md +71 -46
- package/payload/platform/plugins/scheduling/skills/briefing/SKILL.md +2 -2
- package/payload/platform/scripts/seed-neo4j.sh +0 -49
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +3 -2
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/templates/agents/admin/IDENTITY.md +1 -1
- package/payload/platform/templates/specialists/agents/citation-auditor.md +37 -0
- package/payload/platform/templates/specialists/agents/compiled-truth-rewriter.md +42 -0
- package/payload/platform/templates/specialists/agents/database-operator.md +2 -30
- package/payload/platform/templates/specialists/agents/research-assistant.md +2 -2
- package/payload/platform/templates/specialists/agents/typed-edge-classifier.md +37 -0
- package/payload/server/{adminuser-self-heal-YC47O34W.js → adminuser-self-heal-WSFCOEII.js} +1 -1
- package/payload/server/{chunk-T3DJD5QR.js → chunk-AXOL2PPG.js} +1 -1
- package/payload/server/{chunk-HYQNUVGO.js → chunk-PFF6I7KP.js} +1 -8
- package/payload/server/maxy-edge.js +2 -2
- package/payload/server/public/assets/{admin-CnLGltuE.js → admin-kh_oCHeS.js} +54 -54
- package/payload/server/public/assets/{data-MB2EWZUP.js → data-BHASh0_Y.js} +1 -1
- package/payload/server/public/assets/{graph-B2FnDRz-.js → graph-BhEI09tF.js} +1 -1
- package/payload/server/public/assets/graph-labels-DXlmm1og.js +1 -0
- package/payload/server/public/assets/{page-BJKRa6Db.js → page-CDWu1Aft.js} +3 -3
- package/payload/server/public/assets/{page-Bx2p3W1k.js → page-cyvshX4Z.js} +1 -1
- package/payload/server/public/data.html +3 -3
- package/payload/server/public/graph.html +3 -3
- package/payload/server/public/index.html +4 -4
- package/payload/server/server.js +319 -1286
- package/payload/platform/lib/graph-search/src/__tests__/query-expansion.test.ts +0 -96
- package/payload/platform/lib/graph-search/src/query-expansion.ts +0 -129
- package/payload/platform/lib/oauth-llm/src/index.ts +0 -583
- package/payload/platform/lib/oauth-llm/tsconfig.json +0 -8
- package/payload/platform/lib/screening-patterns/src/index.ts +0 -51
- package/payload/platform/lib/screening-patterns/tsconfig.json +0 -8
- package/payload/server/public/assets/graph-labels-DDYqDlu3.js +0 -1
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: memory
|
|
3
|
-
description: "Graph memory plugin. Provides memory-search (with optional `fields` projection for known-shape lookups), memory-
|
|
3
|
+
description: "Graph memory plugin. Provides memory-search (with optional `fields` projection for known-shape lookups), memory-write, memory-update, 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. 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 (`conversation-archive-derive-insights`) walks chunks of one named archive and emits per-row claim proposals for operator-gated wiring; `conversation-archive-enrich-rejection` records (or undoes) durable per-row rejections so already-triaged claims do not re-surface on re-runs. Ships four skills: `conversational-memory`, `document-ingest`, `conversation-archive` (source-agnostic transcript ingest for WhatsApp, Telegram, Signal, LinkedIn DMs, Zoom, meeting minutes, iMessage, Slack), and `conversation-archive-enrich` (per-row operator-gated insight derivation over a named archive's chunks)."
|
|
4
4
|
tools:
|
|
5
5
|
- name: memory-search
|
|
6
6
|
publicAllowlist: true
|
|
7
7
|
adminAllowlist: true
|
|
8
|
-
- name: memory-rank
|
|
9
|
-
publicAllowlist: false
|
|
10
|
-
adminAllowlist: false
|
|
11
8
|
- name: memory-write
|
|
12
9
|
publicAllowlist: false
|
|
13
10
|
adminAllowlist: true
|
|
@@ -26,9 +23,6 @@ tools:
|
|
|
26
23
|
- name: memory-ingest-web
|
|
27
24
|
publicAllowlist: false
|
|
28
25
|
adminAllowlist: false
|
|
29
|
-
- name: memory-classify
|
|
30
|
-
publicAllowlist: false
|
|
31
|
-
adminAllowlist: false
|
|
32
26
|
- name: memory-find-candidates
|
|
33
27
|
publicAllowlist: false
|
|
34
28
|
adminAllowlist: true
|
|
@@ -146,11 +140,9 @@ Provides read and write access to the Neo4j knowledge graph — search, structur
|
|
|
146
140
|
|
|
147
141
|
Tools are available via the `memory` MCP server.
|
|
148
142
|
|
|
149
|
-
##
|
|
150
|
-
|
|
151
|
-
`memory-rank` is a ranking tool built on top of `memory-search`. It retrieves a candidate pool from the graph using the same hybrid vector+BM25 pipeline, then passes the candidates to Claude Haiku along with a natural-language ranking criterion. The response is an ordered list with a short reasoning string per item explaining why each candidate landed where it did.
|
|
143
|
+
## Ranking
|
|
152
144
|
|
|
153
|
-
Ranking is
|
|
145
|
+
Ranking is in-turn agent work — call `memory-search` for a candidate pool of 15–30 rows (use `limit` to size the pool), then order them yourself against the operator's criterion. The agent has the same context the server-side ranker used to have plus the conversation history the server never saw, so the right place to rank is the calling agent's turn. Task 424 removed the `memory-rank` tool (server-side Haiku rerank against a `criteria` string) — the criterion now lives in your reasoning, not in a separate OAuth round-trip.
|
|
154
146
|
|
|
155
147
|
## Search Output Size
|
|
156
148
|
|
|
@@ -192,7 +184,7 @@ Graph hygiene is **agent-directed, case by case** — no autonomous rule engine,
|
|
|
192
184
|
|
|
193
185
|
## Brain-capture diagnostic — `memory-signals-recent`
|
|
194
186
|
|
|
195
|
-
Read-only window into the extractive graph writes produced under this account. Returns recent `:MENTIONS` edges (`:Message-[:MENTIONS]->Entity`) and `:Idea` nodes interleaved by `observedAt`. Default window is 24 hours; pass `windowHours` (max 720) and `limit` (max 200) to scan a longer arc. Use this when the operator asks "what has the brain captured today?", "show me recent mentions of X", or "what ideas did I leave on the table this week?". Writes are produced by `database-operator
|
|
187
|
+
Read-only window into the extractive graph writes produced under this account. Returns recent `:MENTIONS` edges (`:Message-[:MENTIONS]->Entity`) and `:Idea` nodes interleaved by `observedAt`. Default window is 24 hours; pass `windowHours` (max 720) and `limit` (max 200) to scan a longer arc. Use this when the operator asks "what has the brain captured today?", "show me recent mentions of X", or "what ideas did I leave on the table this week?". Writes are produced by `database-operator` per-write briefs admin dispatches — either inline mid-turn, or as part of the once-per-operator-prompt pre-turn graph pass orchestrated by admin (`platform/plugins/admin/hooks/pre-turn-graph-pass.sh`, Tasks 425 + 430 — 430 reshaped the hook so admin walks the turn and dispatches per-write, after the 425 shape was found to self-deny when operator prose pattern-matched as a writer instruction; 425 itself superseded the Task 412 Stop-hook variant, which superseded the original `signal-detector` specialist + throttled hook from Task 303).
|
|
196
188
|
|
|
197
189
|
## Conversational Memory
|
|
198
190
|
|
|
@@ -243,11 +235,11 @@ Document ingestion of any kind — PDFs, text, transcripts, web pages, single fi
|
|
|
243
235
|
|
|
244
236
|
### Pipeline
|
|
245
237
|
|
|
246
|
-
The skill drives a
|
|
238
|
+
The skill drives a two-tool pipeline with the dispatched specialist's in-turn work in between:
|
|
247
239
|
|
|
248
|
-
1. **`memory-ingest-extract`** — pulls text from PDF/markdown/plain-text and caches it under the `attachmentId`. No chunking — the chunker has moved upstream into
|
|
249
|
-
2.
|
|
250
|
-
3. **`memory-ingest`** — writes typed graph nodes (Position, Service, Credential, etc.) anchored to `UserProfile` / `LocalBusiness` / `Person` / `Organization` via natural ontology edges, plus `(KnowledgeDocument)-[:REFERENCES]->(typed)` links.
|
|
240
|
+
1. **`memory-ingest-extract`** — pulls text from PDF/markdown/plain-text and caches it under the `attachmentId`. No chunking — the chunker has moved upstream into in-turn section classification.
|
|
241
|
+
2. **In-turn classification (specialist's work)** — the dispatched specialist reads the cached text and the loaded ontology, then produces typed-section JSON (`{kind, title, body, properties, anchorEdge, related}`) directly in its turn. Every `kind` must come from the ontology label set the writer enforces — the writer rejects out-of-set kinds, so the specialist verifies before emitting. Task 424 deleted the `memory-classify` server tool that previously ran this step as a separate OAuth round-trip.
|
|
242
|
+
3. **`memory-ingest`** — writes typed graph nodes (Position, Service, Credential, etc.) anchored to `UserProfile` / `LocalBusiness` / `Person` / `Organization` via natural ontology edges, plus `(KnowledgeDocument)-[:REFERENCES]->(typed)` links. `Other` sections become generic `:Section:Other` nodes hanging off the document via `HAS_SECTION` (ontology-growth fallback) so free-form prose retrieval still works.
|
|
251
243
|
|
|
252
244
|
### Scope
|
|
253
245
|
|
|
@@ -106,15 +106,8 @@ const NEO4J_LIB_PATH = resolve(
|
|
|
106
106
|
"lib",
|
|
107
107
|
"neo4j.js",
|
|
108
108
|
);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
"plugins",
|
|
112
|
-
"memory",
|
|
113
|
-
"mcp",
|
|
114
|
-
"dist",
|
|
115
|
-
"lib",
|
|
116
|
-
"llm-classifier.js",
|
|
117
|
-
);
|
|
109
|
+
// LLM_CLASSIFIER_PATH removed by Task 424. Task 435 reintroduces per-chunk
|
|
110
|
+
// enrichment as in-turn specialist work.
|
|
118
111
|
const MEMORY_INGEST_PATH = resolve(
|
|
119
112
|
platformRoot,
|
|
120
113
|
"plugins",
|
|
@@ -467,7 +460,7 @@ async function main() {
|
|
|
467
460
|
let sessionize, toTurnText, findDeltaCursor;
|
|
468
461
|
let deriveConversationIdentity, deriveMessageContentHash;
|
|
469
462
|
let verifyParticipantElementIds;
|
|
470
|
-
let getSession,
|
|
463
|
+
let getSession, memoryIngest;
|
|
471
464
|
try {
|
|
472
465
|
({ getNormaliser } = await import(NORMALISERS_PATH));
|
|
473
466
|
({ sessionize } = await import(join(PIPELINE_PATH, "sessionize.js")));
|
|
@@ -477,7 +470,6 @@ async function main() {
|
|
|
477
470
|
await import(join(PIPELINE_PATH, "derive-keys.js")));
|
|
478
471
|
({ verifyParticipantElementIds } = await import(join(PIPELINE_PATH, "sender-bind.js")));
|
|
479
472
|
({ getSession } = await import(NEO4J_LIB_PATH));
|
|
480
|
-
({ classifyDocument } = await import(LLM_CLASSIFIER_PATH));
|
|
481
473
|
({ memoryIngest } = await import(MEMORY_INGEST_PATH));
|
|
482
474
|
} catch (err) {
|
|
483
475
|
fail("import", {
|
|
@@ -486,6 +478,17 @@ async function main() {
|
|
|
486
478
|
});
|
|
487
479
|
}
|
|
488
480
|
|
|
481
|
+
// Task 424 — per-session Haiku enrichment removed. Every session lands as an
|
|
482
|
+
// unenriched raw `:Conversation` chunk; topic summaries / keywords / per-chunk
|
|
483
|
+
// boundaries are reintroduced under Task 435 (specialist produces them
|
|
484
|
+
// in-turn). Until then, the driver still ingests the archive — operator gets
|
|
485
|
+
// the conversation in the graph, just without per-chunk enrichment.
|
|
486
|
+
const classifyDocument = async ({ documentText }) => ({
|
|
487
|
+
kind: "error",
|
|
488
|
+
reason: "Task 424 — server-side classifier removed; Task 435 introduces in-turn enrichment.",
|
|
489
|
+
documentText,
|
|
490
|
+
});
|
|
491
|
+
|
|
489
492
|
// Resolve owner AdminUser elementId from (accountId, userId). One Cypher
|
|
490
493
|
// hop replaces the deleted --owner-element-id flag — owner identity is env
|
|
491
494
|
// input, never an out-of-band flag.
|
|
@@ -6,7 +6,6 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { int } from "neo4j-driver";
|
|
8
8
|
import { memorySearch } from "./tools/memory-search.js";
|
|
9
|
-
import { memoryRank } from "./tools/memory-rank.js";
|
|
10
9
|
import { memoryWrite } from "./tools/memory-write.js";
|
|
11
10
|
import { loadSchema } from "./lib/schema-loader.js";
|
|
12
11
|
import { buildLiveSchemaSource, defaultSchemaCypherPath, } from "./lib/live-schema-source.js";
|
|
@@ -23,7 +22,6 @@ import { obsidianVaultImport, } from "./tools/obsidian-vault-import.js";
|
|
|
23
22
|
// `conversationIdentity`; the parent label tracks the dispatch.
|
|
24
23
|
// whatsapp-export-insight-pass deleted (Phase 2 deferred).
|
|
25
24
|
import { memoryIngestWeb } from "./tools/memory-ingest-web.js";
|
|
26
|
-
import { memoryClassify } from "./tools/memory-classify.js";
|
|
27
25
|
import { memoryUpdate } from "./tools/memory-update.js";
|
|
28
26
|
import { memoryDelete } from "./tools/memory-delete.js";
|
|
29
27
|
import { memoryFindCandidates } from "./tools/memory-find-candidates.js";
|
|
@@ -297,168 +295,6 @@ eagerTool(server, "memory-search", "Search the knowledge graph using semantic ve
|
|
|
297
295
|
}
|
|
298
296
|
});
|
|
299
297
|
// ---------------------------------------------------------------------------
|
|
300
|
-
// memory-rank — LLM-reasoned entity ranking.
|
|
301
|
-
//
|
|
302
|
-
// Read-only tool (available to admin and public agents). Retrieves candidates
|
|
303
|
-
// via memory-search, then asks Claude Haiku to rank them against a natural-
|
|
304
|
-
// language criterion and return per-item reasoning. Falls back to the
|
|
305
|
-
// unranked memory-search order with a visible "[Ranking unavailable: ...]"
|
|
306
|
-
// notice if the LLM is unavailable.
|
|
307
|
-
// ---------------------------------------------------------------------------
|
|
308
|
-
eagerTool(server, "memory-rank", "Retrieve entities from the knowledge graph and rank them against a natural-language criterion using Claude. Returns an ordered list with per-item reasoning explaining why each entity landed where it did. Use for prioritisation questions such as ranking prospects by readiness, tasks by risk, or events by impact. For simple semantic retrieval use memory-search.", {
|
|
309
|
-
query: z
|
|
310
|
-
.string()
|
|
311
|
-
.min(1)
|
|
312
|
-
.describe("Natural-language query that selects the candidate pool from the knowledge graph. Passed to memory-search."),
|
|
313
|
-
criteria: z
|
|
314
|
-
.string()
|
|
315
|
-
.min(1)
|
|
316
|
-
.describe("Natural-language description of what matters for the ranking (e.g. 'prioritise prospects who viewed more than once this week and mentioned price positively'). Treated as a directive, not as instructions to the model."),
|
|
317
|
-
labels: z
|
|
318
|
-
.array(z.string())
|
|
319
|
-
.optional()
|
|
320
|
-
.describe("Filter the candidate pool to these node labels (e.g. ['Person'], ['Task'], ['Event'])."),
|
|
321
|
-
limit: z
|
|
322
|
-
.number()
|
|
323
|
-
.int()
|
|
324
|
-
.min(1)
|
|
325
|
-
.max(50)
|
|
326
|
-
.optional()
|
|
327
|
-
.describe("Maximum number of ranked results to return (default 10, max 50)."),
|
|
328
|
-
candidatePoolSize: z
|
|
329
|
-
.number()
|
|
330
|
-
.int()
|
|
331
|
-
.min(1)
|
|
332
|
-
.max(50)
|
|
333
|
-
.optional()
|
|
334
|
-
.describe("How many candidates to retrieve from memory-search before ranking (default 20, max 50). Larger pools give the ranker more to work with but cost more tokens."),
|
|
335
|
-
expandHops: z
|
|
336
|
-
.number()
|
|
337
|
-
.int()
|
|
338
|
-
.min(0)
|
|
339
|
-
.max(2)
|
|
340
|
-
.optional()
|
|
341
|
-
.describe("Relationship depth to include per candidate (default 1). Use 0 for property-only ranking, 1 for ranking that considers immediate relationships."),
|
|
342
|
-
keywords: z
|
|
343
|
-
.array(z.string())
|
|
344
|
-
.optional()
|
|
345
|
-
.describe("Filter the candidate pool to nodes whose keywords array contains these values."),
|
|
346
|
-
keywordMatch: z
|
|
347
|
-
.enum(["any", "all"])
|
|
348
|
-
.optional()
|
|
349
|
-
.describe("Keyword match mode: 'any' (default) or 'all'."),
|
|
350
|
-
agentSlug: z
|
|
351
|
-
.string()
|
|
352
|
-
.optional()
|
|
353
|
-
.describe("Filter to nodes tagged for this agent slug. For admin use — public agents use server-level env var."),
|
|
354
|
-
}, async ({ query, criteria, labels, limit, candidatePoolSize, expandHops, keywords, keywordMatch, agentSlug: toolAgentSlug }) => {
|
|
355
|
-
if (!accountId)
|
|
356
|
-
return refuseNoAccount("memory-rank");
|
|
357
|
-
const effectiveAgentSlug = envAgentSlug ?? toolAgentSlug;
|
|
358
|
-
process.stderr.write(`[memory-rank] [${accountId}] invocation query=${JSON.stringify(query.slice(0, 60))} criteria=${JSON.stringify(criteria.slice(0, 60))} pool=${candidatePoolSize ?? "default"} limit=${limit ?? "default"}\n`);
|
|
359
|
-
try {
|
|
360
|
-
const response = await memoryRank({
|
|
361
|
-
query,
|
|
362
|
-
criteria,
|
|
363
|
-
labels,
|
|
364
|
-
accountId,
|
|
365
|
-
limit,
|
|
366
|
-
candidatePoolSize,
|
|
367
|
-
expandHops,
|
|
368
|
-
allowedScopes,
|
|
369
|
-
keywords,
|
|
370
|
-
keywordMatch,
|
|
371
|
-
agentSlug: effectiveAgentSlug,
|
|
372
|
-
keywordSubscriptions: envKeywordSubscriptions,
|
|
373
|
-
});
|
|
374
|
-
// Three distinct empty/near-empty cases with different meanings:
|
|
375
|
-
// 1. candidatePoolSize === 0 — memory-search found nothing. True empty result.
|
|
376
|
-
// 2. candidatePoolSize > 0, results.length === 0, no fallback — Haiku
|
|
377
|
-
// considered the candidates and said none matched the criterion. The
|
|
378
|
-
// agent must be told that candidates existed but were rejected, not
|
|
379
|
-
// that nothing was found.
|
|
380
|
-
// 3. candidatePoolSize > 0, results.length > 0 — normal ranked output
|
|
381
|
-
// (or ranked-fallback unranked output when fallbackReason is set).
|
|
382
|
-
if (response.candidatePoolSize === 0) {
|
|
383
|
-
return {
|
|
384
|
-
content: [
|
|
385
|
-
{
|
|
386
|
-
type: "text",
|
|
387
|
-
text: "No candidates found for this query.",
|
|
388
|
-
},
|
|
389
|
-
],
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
if (response.results.length === 0 && !response.fallbackReason) {
|
|
393
|
-
// Haiku ran successfully and returned rankings: []. Candidates existed
|
|
394
|
-
// but none matched the criterion. Report this honestly so the agent
|
|
395
|
-
// can relay it to the user instead of claiming nothing was found.
|
|
396
|
-
return {
|
|
397
|
-
content: [
|
|
398
|
-
{
|
|
399
|
-
type: "text",
|
|
400
|
-
text: `Considered ${response.candidatePoolSize} candidate${response.candidatePoolSize === 1 ? "" : "s"} but none matched the criterion.`,
|
|
401
|
-
},
|
|
402
|
-
],
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
const headerLines = [];
|
|
406
|
-
if (response.fallbackReason) {
|
|
407
|
-
headerLines.push(`[Ranking unavailable: ${response.fallbackReason}] — showing ${response.results.length} candidates in their original memory-search score order.`);
|
|
408
|
-
}
|
|
409
|
-
else {
|
|
410
|
-
headerLines.push(`Ranked ${response.results.length} of ${response.candidatePoolSize} candidates.`);
|
|
411
|
-
if (response.hallucinated > 0) {
|
|
412
|
-
headerLines.push(`(${response.hallucinated} ranker response${response.hallucinated === 1 ? "" : "s"} referenced candidates not in the pool and were discarded.)`);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
const body = response.results
|
|
416
|
-
.map((r) => {
|
|
417
|
-
const rankLine = r.rank !== undefined ? `#${r.rank} ` : "";
|
|
418
|
-
const reasoningLine = r.reasoning ? `\n reasoning: ${r.reasoning}` : "";
|
|
419
|
-
let text = `${rankLine}[${r.labels.join(", ")}] (id: ${r.nodeId}) (score: ${r.score.toFixed(3)})${reasoningLine}\n`;
|
|
420
|
-
for (const [k, v] of Object.entries(r.properties)) {
|
|
421
|
-
text += ` ${k}: ${String(v)}\n`;
|
|
422
|
-
}
|
|
423
|
-
if (r.related.length > 0) {
|
|
424
|
-
text += " Related:\n";
|
|
425
|
-
for (const rel of r.related) {
|
|
426
|
-
const arrow = rel.direction === "outgoing"
|
|
427
|
-
? `--[${rel.relationship}]-->`
|
|
428
|
-
: `<--[${rel.relationship}]--`;
|
|
429
|
-
text += ` ${arrow} [${rel.labels.join(", ")}]`;
|
|
430
|
-
const relProps = Object.entries(rel.properties)
|
|
431
|
-
.filter(([k]) => k !== "embedding" && k !== "accountId")
|
|
432
|
-
.map(([k, v]) => `${k}: ${String(v)}`)
|
|
433
|
-
.join(", ");
|
|
434
|
-
if (relProps)
|
|
435
|
-
text += ` {${relProps}}`;
|
|
436
|
-
text += "\n";
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
return text;
|
|
440
|
-
})
|
|
441
|
-
.join("\n---\n");
|
|
442
|
-
const output = `${headerLines.join("\n")}\n\n${body}`;
|
|
443
|
-
return {
|
|
444
|
-
content: [{ type: "text", text: output }],
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
catch (err) {
|
|
448
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
449
|
-
process.stderr.write(`[memory-rank] [${accountId}] unhandled error: ${msg}\n`);
|
|
450
|
-
return {
|
|
451
|
-
content: [
|
|
452
|
-
{
|
|
453
|
-
type: "text",
|
|
454
|
-
text: `Rank failed: ${msg}`,
|
|
455
|
-
},
|
|
456
|
-
],
|
|
457
|
-
isError: true,
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
});
|
|
461
|
-
// ---------------------------------------------------------------------------
|
|
462
298
|
// conversation-archive Phase 2: chunk-anchored insight derivation.
|
|
463
299
|
// Read-only tool. Walks the :Section chunks of one named conversation
|
|
464
300
|
// archive (`:ConversationArchive` keyed on `conversationIdentity` — Task
|
|
@@ -866,28 +702,12 @@ if (!readOnly) {
|
|
|
866
702
|
};
|
|
867
703
|
}
|
|
868
704
|
});
|
|
869
|
-
server.tool("memory-update", "Update properties on an existing node in the knowledge graph. Finds the node by element ID, verifies account ownership, sets the provided properties, and recomputes the embedding.
|
|
705
|
+
server.tool("memory-update", "Update properties on an existing node in the knowledge graph. Finds the node by element ID, verifies account ownership, sets the provided properties, and recomputes the embedding. To refresh `compiledTruth` / `compiledTruthPublic` on :Person / :Organization / :Concept / :Project / :LocalBusiness, dispatch the `compiled-truth-rewriter` specialist via the Task tool — admin sends the nodeId plus any `operatorHint` / `publicOperatorHint`; the specialist reads the node + 90-day timeline, computes the new summary, and writes it back via memory-update. Direct writes to compiled-truth properties via this tool are refused unless the caller is the compiled-truth-rewriter specialist (gated by MAXY_SPECIALIST env).", {
|
|
870
706
|
nodeId: z.string().describe("Element ID of the node to update (from memory-search results)"),
|
|
871
707
|
properties: z
|
|
872
708
|
.record(z.string(), z.unknown())
|
|
873
|
-
.describe("Properties to set or overwrite. Restricted keys (embedding, accountId, createdAt) are ignored. Compiled-truth fields are system-managed
|
|
874
|
-
|
|
875
|
-
.boolean()
|
|
876
|
-
.optional()
|
|
877
|
-
.describe("When true, run the compiled-truth rewriter against the node's most recent :TimelineEvent rows. Writes the admin compiledTruth, and (for :Organization / :Concept / :Project / :LocalBusiness, when the admin rewrite actually changes the text) also re-runs the rewriter in public mode and writes compiledTruthPublic. No-op admin rewrites skip the public pass. Valid on :Person / :Organization / :Concept / :Project / :LocalBusiness."),
|
|
878
|
-
forceRewrite: z
|
|
879
|
-
.boolean()
|
|
880
|
-
.optional()
|
|
881
|
-
.describe("Bypass the 7-day cooldown that protects operator-edited compiledTruth and compiledTruthPublic fields from automatic rewriter passes. Set only when the operator explicitly asks for a refresh."),
|
|
882
|
-
operatorHint: z
|
|
883
|
-
.string()
|
|
884
|
-
.optional()
|
|
885
|
-
.describe("Operator correction for the admin twin, supplied via chat. Only valid with rewriteCompiledTruth=true. Folded into the rewriter prompt with precedence over the timeline; the resulting write is stamped compiledTruthSource='operator', the 7-day cooldown does not block this pass, and the public-twin auto-fire is skipped so admin-side corrections do not leak unreviewed to the public surface. Cap: 1000 characters."),
|
|
886
|
-
publicOperatorHint: z
|
|
887
|
-
.string()
|
|
888
|
-
.optional()
|
|
889
|
-
.describe("Operator correction for the public twin, supplied via chat. Only valid with rewriteCompiledTruth=true AND a public-twin label (:Organization / :Concept / :Project / :LocalBusiness — :Person has no public twin). Folded into the public-mode rewriter prompt with precedence over the timeline; the public rewriter fires regardless of whether the admin pass changed the truth, the public-cooldown is bypassed, and the write is stamped compiledTruthPublicSource='operator'. Cap: 1000 characters."),
|
|
890
|
-
}, async ({ nodeId, properties, rewriteCompiledTruth, forceRewrite, operatorHint, publicOperatorHint }) => {
|
|
709
|
+
.describe("Properties to set or overwrite. Restricted keys (embedding, accountId, createdAt) are ignored. Compiled-truth fields are system-managed; only the compiled-truth-rewriter specialist may write them."),
|
|
710
|
+
}, async ({ nodeId, properties }) => {
|
|
891
711
|
if (!accountId)
|
|
892
712
|
return refuseNoAccount("memory-update");
|
|
893
713
|
try {
|
|
@@ -900,50 +720,12 @@ if (!readOnly) {
|
|
|
900
720
|
session: resolveSessionId(),
|
|
901
721
|
tool: "memory-update",
|
|
902
722
|
},
|
|
903
|
-
rewriteCompiledTruth,
|
|
904
|
-
forceRewrite,
|
|
905
|
-
operatorHint,
|
|
906
|
-
publicOperatorHint,
|
|
907
723
|
});
|
|
908
|
-
const rewriterLine = (() => {
|
|
909
|
-
if (!result.rewriter)
|
|
910
|
-
return "";
|
|
911
|
-
if (result.rewriter.kind === "ok") {
|
|
912
|
-
const sourceFragment = result.rewriter.source === "operator" ? " (operator-attributed)" : "";
|
|
913
|
-
return `\nAdmin compiled-truth rewritten${sourceFragment}: ${result.rewriter.charsBefore} → ${result.rewriter.charsAfter} chars.`;
|
|
914
|
-
}
|
|
915
|
-
if (result.rewriter.kind === "refused") {
|
|
916
|
-
return result.rewriter.reason === "operator-edit-cooldown"
|
|
917
|
-
? `\nAdmin rewriter refused: operator-edit cooldown (${result.rewriter.daysRemaining} day(s) remaining; pass forceRewrite=true to override).`
|
|
918
|
-
: `\nAdmin rewriter refused: no :TimelineEvent rows linked to this node.`;
|
|
919
|
-
}
|
|
920
|
-
if (result.rewriter.kind === "too-long") {
|
|
921
|
-
return `\nAdmin rewriter output exceeded ${result.rewriter.attemptedLen} chars after one retry; compiledTruth unchanged.`;
|
|
922
|
-
}
|
|
923
|
-
return `\nAdmin rewriter LLM call failed (cause=${result.rewriter.cause}); compiledTruth unchanged.`;
|
|
924
|
-
})();
|
|
925
|
-
const publicRewriterLine = (() => {
|
|
926
|
-
if (!result.publicRewriter)
|
|
927
|
-
return "";
|
|
928
|
-
if (result.publicRewriter.kind === "ok") {
|
|
929
|
-
const sourceFragment = result.publicRewriter.source === "operator" ? " (operator-attributed)" : "";
|
|
930
|
-
return `\nPublic compiled-truth rewritten${sourceFragment}: ${result.publicRewriter.charsBefore} → ${result.publicRewriter.charsAfter} chars.`;
|
|
931
|
-
}
|
|
932
|
-
if (result.publicRewriter.kind === "refused") {
|
|
933
|
-
return result.publicRewriter.reason === "operator-edit-cooldown"
|
|
934
|
-
? `\nPublic rewriter refused: operator-edit cooldown (${result.publicRewriter.daysRemaining} day(s) remaining; pass forceRewrite=true to override).`
|
|
935
|
-
: `\nPublic rewriter refused: no :TimelineEvent rows linked to this node.`;
|
|
936
|
-
}
|
|
937
|
-
if (result.publicRewriter.kind === "too-long") {
|
|
938
|
-
return `\nPublic rewriter output exceeded ${result.publicRewriter.attemptedLen} chars after one retry; compiledTruthPublic unchanged.`;
|
|
939
|
-
}
|
|
940
|
-
return `\nPublic rewriter LLM call failed (cause=${result.publicRewriter.cause}); compiledTruthPublic unchanged.`;
|
|
941
|
-
})();
|
|
942
724
|
return {
|
|
943
725
|
content: [
|
|
944
726
|
{
|
|
945
727
|
type: "text",
|
|
946
|
-
text: `Node updated: [${result.labels.join(", ")}] (ID: ${result.nodeId})
|
|
728
|
+
text: `Node updated: [${result.labels.join(", ")}] (ID: ${result.nodeId})`,
|
|
947
729
|
},
|
|
948
730
|
],
|
|
949
731
|
};
|
|
@@ -1075,11 +857,11 @@ if (!readOnly) {
|
|
|
1075
857
|
};
|
|
1076
858
|
}
|
|
1077
859
|
});
|
|
1078
|
-
server.tool("memory-ingest-extract", "Extract text from an uploaded file and cache it server-side for memory-
|
|
860
|
+
server.tool("memory-ingest-extract", "Extract text from an uploaded file and cache it server-side for memory-ingest. " +
|
|
1079
861
|
"Supports PDF (via pdftotext), plain text, and markdown. " +
|
|
1080
862
|
"Rejects CSV (structured data — use memory-write instead) and images (metadata-only). " +
|
|
1081
863
|
"Returns file metadata + a short preview; the full text lives in the in-process cache keyed by attachmentId. " +
|
|
1082
|
-
"
|
|
864
|
+
"The dispatched specialist reads the cached text and produces the typed-section JSON in-turn from the loaded ontology, then calls memory-ingest directly.", {
|
|
1083
865
|
storagePath: z.string().describe("Absolute path to the stored file on disk"),
|
|
1084
866
|
filename: z.string().describe("Original filename as uploaded"),
|
|
1085
867
|
mimeType: z.string().describe("MIME type of the file"),
|
|
@@ -1106,45 +888,8 @@ if (!readOnly) {
|
|
|
1106
888
|
};
|
|
1107
889
|
}
|
|
1108
890
|
});
|
|
1109
|
-
server.tool("memory-classify", "Classify an unstructured document into typed sections via Claude Haiku. " +
|
|
1110
|
-
"Reads the cached text written by memory-ingest-extract (same attachmentId), runs the classifier against " +
|
|
1111
|
-
"the live ontology label set, and returns a JSON structure ready for memory-ingest. Every returned `kind` is " +
|
|
1112
|
-
"verified server-side against the ontology — invalid labels become `UNMAPPED` so the writer falls back to " +
|
|
1113
|
-
"generic Section nodes with a logged ontology gap. When Haiku is unavailable, returns `{kind: \"fallback\"}` " +
|
|
1114
|
-
"with a reason — the skill should treat the whole document as one UNMAPPED Section.", {
|
|
1115
|
-
attachmentId: z.string().describe("UUID of the file attachment — must match a prior memory-ingest-extract call"),
|
|
1116
|
-
anchorDescription: z.string().describe("Short human sentence describing the document subject the classifier should anchor sections to. " +
|
|
1117
|
-
"Examples: 'subject = UserProfile (the account owner)'; 'subject = LocalBusiness (the operator's business)'; " +
|
|
1118
|
-
"'subject = Person {name: \"Jane Smith\"} (a third party)'."),
|
|
1119
|
-
}, async ({ attachmentId, anchorDescription }) => {
|
|
1120
|
-
if (!accountId)
|
|
1121
|
-
return refuseNoAccount("memory-classify");
|
|
1122
|
-
try {
|
|
1123
|
-
const result = await memoryClassify({
|
|
1124
|
-
accountId,
|
|
1125
|
-
attachmentId,
|
|
1126
|
-
anchorDescription,
|
|
1127
|
-
liveSchemaSource: liveSchemaRuntime.source,
|
|
1128
|
-
});
|
|
1129
|
-
return {
|
|
1130
|
-
content: [{
|
|
1131
|
-
type: "text",
|
|
1132
|
-
text: JSON.stringify(result),
|
|
1133
|
-
}],
|
|
1134
|
-
};
|
|
1135
|
-
}
|
|
1136
|
-
catch (err) {
|
|
1137
|
-
return {
|
|
1138
|
-
content: [{
|
|
1139
|
-
type: "text",
|
|
1140
|
-
text: `memory-classify failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1141
|
-
}],
|
|
1142
|
-
isError: true,
|
|
1143
|
-
};
|
|
1144
|
-
}
|
|
1145
|
-
});
|
|
1146
891
|
server.tool("memory-ingest", "Write a classified document into the graph as typed nodes anchored to the document subject. " +
|
|
1147
|
-
"Requires prior
|
|
892
|
+
"Requires a prior memory-ingest-extract call for the text cache; the typed-section JSON in the `sections` argument is produced by the dispatched specialist in-turn from the loaded ontology. " +
|
|
1148
893
|
"Each section becomes either a typed graph node (Position, Service, Credential, etc.) anchored to the named " +
|
|
1149
894
|
"subject via the natural ontology edge plus a (KnowledgeDocument)-[:REFERENCES]->(typed) link, or — for " +
|
|
1150
895
|
"UNMAPPED sections — a generic :Section node hanging off the document via HAS_SECTION (legacy fallback). " +
|
|
@@ -1160,8 +905,8 @@ if (!readOnly) {
|
|
|
1160
905
|
sections: z.array(z.object({
|
|
1161
906
|
kind: z.string().describe("Section kind from the closed enumeration (Position/Education/Chapter/Parties/.../Other), or a standalone-node kind like 'Project'."),
|
|
1162
907
|
title: z.string(),
|
|
1163
|
-
body: z.string().describe("
|
|
1164
|
-
summary: z.string().describe("1-3 sentence summary of the section
|
|
908
|
+
body: z.string().describe("Body text for this section — the specialist slices documentText.slice(sourceStart, sourceEnd) and stamps it here. The writer treats it as the section's content."),
|
|
909
|
+
summary: z.string().describe("1-3 sentence summary of the section, ≤500 chars; stored as :Section.summary."),
|
|
1165
910
|
sourceStart: z.number().int().nonnegative().describe("Whole-document char offset where the section begins (inclusive). Translated to whole-doc coords by the chunked-classify path."),
|
|
1166
911
|
sourceEnd: z.number().int().nonnegative().describe("Whole-document char offset where the section ends (exclusive)."),
|
|
1167
912
|
properties: z.record(z.string(), z.unknown()),
|
|
@@ -1181,7 +926,7 @@ if (!readOnly) {
|
|
|
1181
926
|
merge: z.boolean().optional(),
|
|
1182
927
|
})).optional(),
|
|
1183
928
|
classifierReason: z.string().optional().describe("Required when kind === 'Other' — one-line description of what the section is about, stamped on :Section:Other.classifierReason for ontology-growth review."),
|
|
1184
|
-
})).describe("Sections
|
|
929
|
+
})).describe("Sections in reading order, produced in-turn by the dispatched specialist from the extract cache. The writer chains them via :NEXT in this order."),
|
|
1185
930
|
documentEdges: z.array(z.object({
|
|
1186
931
|
type: z.string(),
|
|
1187
932
|
direction: z.enum(["outgoing", "incoming"]),
|
|
@@ -1392,8 +1137,7 @@ if (!readOnly) {
|
|
|
1392
1137
|
// source.
|
|
1393
1138
|
server.tool("memory-ingest-web", "Adapter for web-content ingestion. Accepts a URL and its pre-fetched readable content " +
|
|
1394
1139
|
"(the agent calls WebFetch first, then passes the text here), writes content to a temp file, and delegates " +
|
|
1395
|
-
"to memory-ingest-extract — caching the text under a freshly-generated attachmentId. The
|
|
1396
|
-
"memory-classify and memory-ingest using that attachmentId, exactly as for a file upload. " +
|
|
1140
|
+
"to memory-ingest-extract — caching the text under a freshly-generated attachmentId. The dispatched specialist then produces typed-section JSON in-turn from the loaded ontology and calls memory-ingest, exactly as for a file upload. " +
|
|
1397
1141
|
"If the URL was previously ingested, returns the existing document info so the agent can inform the user.", {
|
|
1398
1142
|
url: z.string().describe("The web page URL being ingested"),
|
|
1399
1143
|
content: z.string().describe("The readable text content of the web page (pre-fetched by the agent via WebFetch)"),
|
|
@@ -1651,11 +1395,11 @@ if (!readOnly) {
|
|
|
1651
1395
|
let text = `Edited: ${result.filename} (${result.oldSizeBytes} → ${result.newSizeBytes} bytes)`;
|
|
1652
1396
|
if (result.cachePopulated) {
|
|
1653
1397
|
text += `\nExtract cache populated: ${result.extractTextLength} chars.`;
|
|
1654
|
-
text += `\
|
|
1398
|
+
text += `\nProduce typed-section JSON in-turn from the loaded ontology and call memory-ingest to re-sync the knowledge graph.`;
|
|
1655
1399
|
}
|
|
1656
1400
|
else {
|
|
1657
1401
|
text += `\nWARNING: File updated on disk but extract cache could not be populated.`;
|
|
1658
|
-
text += `\nCall memory-ingest-extract manually, then
|
|
1402
|
+
text += `\nCall memory-ingest-extract manually, then produce typed-section JSON in-turn and call memory-ingest.`;
|
|
1659
1403
|
}
|
|
1660
1404
|
return { content: [{ type: "text", text }] };
|
|
1661
1405
|
}
|