@rubytech/create-realagent-code 0.1.144 → 0.1.147
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 +154 -4
- package/payload/platform/plugins/admin/PLUGIN.md +1 -2
- package/payload/platform/plugins/admin/hooks/__tests__/session-end-retrospective.test.sh +2 -2
- package/payload/platform/plugins/admin/hooks/post-turn-graph-pass.sh +251 -0
- package/payload/platform/plugins/admin/hooks/session-end-retrospective.sh +6 -3
- package/payload/platform/plugins/admin/skills/datetime/SKILL.md +63 -5
- package/payload/platform/plugins/docs/references/internals.md +16 -0
- package/payload/platform/plugins/docs/references/memory-guide.md +10 -0
- package/payload/platform/plugins/docs/references/platform.md +1 -1
- package/payload/platform/plugins/memory/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/memory/PLUGIN.md +25 -3
- package/payload/platform/plugins/memory/mcp/dist/index.js +309 -11
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.js +33 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-gate-label.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-gate-label.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-gate-label.test.js +30 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-gate-label.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-prune-revisions.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-prune-revisions.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-prune-revisions.test.js +51 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-prune-revisions.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/relative-date.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/relative-date.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/relative-date.test.js +68 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/relative-date.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/timeline-extractor.test.js +41 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/timeline-extractor.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-revision.d.ts +43 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-revision.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-revision.js +33 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-revision.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.d.ts +18 -5
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.js +64 -17
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/index.d.ts +44 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/index.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/index.js +14 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/index.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-citation-audit.d.ts +26 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-citation-audit.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-citation-audit.js +219 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-citation-audit.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-dead-edges.d.ts +19 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-dead-edges.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-dead-edges.js +39 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-dead-edges.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-orphans.d.ts +19 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-orphans.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-orphans.js +42 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-orphans.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-prune-revisions.d.ts +21 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-prune-revisions.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-prune-revisions.js +27 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-prune-revisions.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-stale-truth.d.ts +18 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-stale-truth.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-stale-truth.js +60 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-stale-truth.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.d.ts +20 -3
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.js +47 -12
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/relative-date.d.ts +13 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/relative-date.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/relative-date.js +191 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/relative-date.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/timeline-extractor.d.ts +7 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/timeline-extractor.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/timeline-extractor.js +37 -9
- package/payload/platform/plugins/memory/mcp/dist/lib/timeline-extractor.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-compiled-truth-history.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-compiled-truth-history.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-compiled-truth-history.test.js +81 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-compiled-truth-history.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-report-tools.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-report-tools.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-report-tools.test.js +115 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-report-tools.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.js +1 -0
- 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/__tests__/memory-write-emit.test.js +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-compiled-truth-history.d.ts +19 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-compiled-truth-history.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-compiled-truth-history.js +50 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-compiled-truth-history.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-dream-run.d.ts +45 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-dream-run.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-dream-run.js +170 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-dream-run.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-list.d.ts +28 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-list.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-list.js +64 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-list.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-read-latest.d.ts +24 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-read-latest.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-read-latest.js +49 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-read-latest.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-write.d.ts +34 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-write.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-write.js +99 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-write.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-review-queue.d.ts +29 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-review-queue.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-review-queue.js +66 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-review-queue.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js +122 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts +49 -13
- 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 +184 -16
- 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 +53 -10
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/vitest.config.ts +6 -0
- package/payload/platform/plugins/memory/references/schema-base.md +4 -0
- package/payload/platform/plugins/memory/skills/conversation-archive/SKILL.md +3 -4
- package/payload/platform/plugins/scheduling/skills/briefing/SKILL.md +13 -2
- package/payload/platform/scripts/seed-neo4j.sh +1 -1
- package/payload/platform/templates/agents/admin/IDENTITY.md +9 -1
- package/payload/platform/templates/specialists/agents/database-operator.md +30 -2
- package/payload/premium-plugins/real-agent/agents/listing-curator.md +39 -10
- package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-brochure/SKILL.md +1 -0
- package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-brochure/references/build.md +10 -1
- package/payload/premium-plugins/real-agent/plugins/brochures/skills/property-brochure/references/images.md +104 -0
- package/payload/server/server.js +195 -91
- package/payload/platform/plugins/admin/hooks/signal-detector-stop.sh +0 -309
- package/payload/platform/plugins/admin/skills/commitment-followthrough/SKILL.md +0 -60
- package/payload/platform/templates/specialists/agents/signal-detector.md +0 -129
|
@@ -297,6 +297,22 @@ This makes the failure visible to the agent, which can relay it to the user. Deg
|
|
|
297
297
|
|
|
298
298
|
---
|
|
299
299
|
|
|
300
|
+
## Reports — durable workflow output (Task 332)
|
|
301
|
+
|
|
302
|
+
The `:Report` label is the platform's durable shape for workflow output the operator may want back later — daily briefings, dream cycle runs, ad-hoc analyses. Three MCP tools own the surface, all on the memory plugin:
|
|
303
|
+
|
|
304
|
+
- `memory-report-write` — append-only writer. Validates body ≤ 10,000 chars, embeds title+body, and CREATEs a `:Report` node. Idempotent on `(accountId, title, occurredAt-within-same-minute)` — a second call with the same title in the same minute returns the existing node instead of duplicating. Parented to the active `:Conversation` via `:PRODUCED` when `CONVERSATION_NODE_ID` is set (the chat-driven default); falls back to the account's `:AdminUser` so the graph-hierarchy doctrine holds even outside a conversation.
|
|
305
|
+
- `memory-report-read-latest` — fetches the newest `:Report` (default `limit=1`) tagged with a given keyword. The expected route for any operator phrasing of "latest X", "last night's X", "show me X report".
|
|
306
|
+
- `memory-report-list` — metadata-only paginated listing (newest first), with optional `keyword` and `sourceWorkflow` filters. Use to scan the catalogue without paying for full bodies.
|
|
307
|
+
|
|
308
|
+
Every operation emits one log line: `[reports] op=<write|read-latest|list> reportId=<short> keywords=<csv> ms=<n>` (with `idempotent=1` on a write that resolved to an existing node, `hits=<n>` on reads, `total=<n>` on list).
|
|
309
|
+
|
|
310
|
+
Routing is not classifier-side. The admin agent's `IDENTITY.md` carries the rule under **Recalling reports**: "latest <X>" / "last night's <X>" / "show me <X> report" → first tool call is `memory-report-read-latest`. The intent classifier (Task 304's `retrievalClass`) already differentiates temporal vs entity vs event reads; reports route off the literal phrase, not a new class.
|
|
311
|
+
|
|
312
|
+
The first caller is the `briefing` skill (`platform/plugins/scheduling/skills/briefing/SKILL.md`), which persists each run as a `:Report` with `title: "Daily briefing <YYYY-MM-DD>"`, `keywords: ["daily-briefing", "<YYYY-MM-DD>"]`, `sourceWorkflow: "daily-briefing"`. Dream-cycle (Task 327) and ad-hoc analyses are expected to follow the same pattern.
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
300
316
|
## Graph Expansion
|
|
301
317
|
|
|
302
318
|
After the top results are selected (by combined score or by LLM ranking), each result node is expanded by traversing its immediate relationships.
|
|
@@ -116,6 +116,16 @@ Every node also carries a provenance stamp — which agent wrote it, in which se
|
|
|
116
116
|
|
|
117
117
|
**Two write surfaces, one substrate.** General agents write through schema-aware helpers — {{productName}} can record a new contact, a new commitment, a new preference without ever typing a database query, and the helper enforces the connection-and-provenance rule above structurally. The graph-steward role (the specialist {{productName}} dispatches when you ask for graph hygiene — "merge those two duplicate contacts," "wire those four tasks to the meeting," "rename the legacy label across the graph") additionally has a raw Cypher write tool for the multi-step operations the helpers cannot express. The steward role internalises the same connection-and-provenance discipline in its prompt; a post-write audit emits a warning on every breach so the same rules apply to both surfaces. Both paths feed the same hourly orphan trend and the same forensic provenance fields — read-side, you cannot tell the two apart, and that is the point.
|
|
118
118
|
|
|
119
|
+
## Public-facing summaries for customer-readable subjects
|
|
120
|
+
|
|
121
|
+
Some entities in your graph are knowable by people outside your team — companies you work with, projects you've delivered, the business itself. For those entities ({{productName}} treats `:Organization`, `:Concept`, `:Project`, and `:LocalBusiness` this way), {{productName}} maintains two summaries: a private one only you and your specialist agents see, and a customer-facing public one your public agents are allowed to surface.
|
|
122
|
+
|
|
123
|
+
Whenever {{productName}} updates the private summary on one of these entities, it automatically rewrites the public summary in the same step using a separate prompt that strips operator-voice ("needs follow-up", "action: chase next week"), internal sentiment, and anything that reads like a note-to-self. The two summaries stay in lockstep without you doing anything.
|
|
124
|
+
|
|
125
|
+
If you want to write the public summary yourself — for instance, because the auto-generated version misses something you want customers to see — just tell {{productName}} the wording you want for the public summary on that entity, and {{productName}} will write it directly. It stays locked in for seven days; after that, the next automatic refresh can take over again, unless you re-pin it.
|
|
126
|
+
|
|
127
|
+
People entries (`:Person`) are deliberately excluded from this dual-summary system. Notes about contacts are private by definition and never get a public-facing form.
|
|
128
|
+
|
|
119
129
|
## Privacy
|
|
120
130
|
|
|
121
131
|
All memory is stored on your local Raspberry Pi. The Neo4j database never leaves your network. {{productName}} does not sync memory to any cloud service or third party.
|
|
@@ -54,7 +54,7 @@ Roles are installed during setup and listed when {{productName}} introduces itse
|
|
|
54
54
|
|
|
55
55
|
{{productName}} maintains a graph database (Neo4j) of everything you've told it. People, conversations, preferences, and context are stored as connected nodes. When you ask {{productName}} something, it searches this graph to retrieve relevant context before responding.
|
|
56
56
|
|
|
57
|
-
**The recording loop.**
|
|
57
|
+
**The recording loop.** Once your turn finishes and the reply is on screen, a Stop hook (`post-turn-graph-pass.sh`) fires the `database-operator` specialist as a background dispatch. The dispatch carries the full conversation as `[role: text]` pairs plus the list of every node already written under this conversation, fetched via `GET /api/admin/post-turn-context`. The specialist decides what new graph rows the conversation supports — mentions of named entities, ideas, learnings, contacts, tasks, anything the schema admits — and writes them. Duplicate writes between this background pass and any inline write the admin agent dispatched mid-turn are accepted and cleaned up by the dream-cycle / hygiene sweeps. There is no throttle and no idle gate: one dispatch per `end_turn`. The admin agent can also dispatch `database-operator` inline at its own discretion when a write must complete before the assistant response ends. To disable the background pass for an account, remove the `post-turn-graph-pass.sh` Stop-hook entry from that account's `~/.<brand>/.claude/settings.json`; there is no toggle.
|
|
58
58
|
|
|
59
59
|
The memory graph is stored on your Pi. It never leaves your network.
|
|
60
60
|
|
|
@@ -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-rank, memory-write, and memory-
|
|
3
|
+
"description": "Graph memory plugin. Provides memory-search (with optional `fields` projection for known-shape lookups), memory-rank, 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 (memory-classify + memory-ingest) supports two modes: `document` (default) for unstructured PDF/web content → :KnowledgeDocument (keyed on `attachmentId`) + :Section, and `chat` for conversation transcripts → :KnowledgeDocument (keyed on `conversationIdentity`) + :Section chunks; one parent label, 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
|
"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-rank, memory-write, and memory-
|
|
3
|
+
description: "Graph memory plugin. Provides memory-search (with optional `fields` projection for known-shape lookups), memory-rank, 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 (memory-classify + memory-ingest) supports two modes: `document` (default) for unstructured PDF/web content → :KnowledgeDocument (keyed on `attachmentId`) + :Section, and `chat` for conversation transcripts → :KnowledgeDocument (keyed on `conversationIdentity`) + :Section chunks; one parent label, 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
|
|
@@ -65,6 +65,18 @@ tools:
|
|
|
65
65
|
- name: conversation-archive-enrich-rejection
|
|
66
66
|
publicAllowlist: false
|
|
67
67
|
adminAllowlist: false
|
|
68
|
+
- name: memory-report-write
|
|
69
|
+
publicAllowlist: false
|
|
70
|
+
adminAllowlist: true
|
|
71
|
+
- name: memory-report-read-latest
|
|
72
|
+
publicAllowlist: false
|
|
73
|
+
adminAllowlist: true
|
|
74
|
+
- name: memory-report-list
|
|
75
|
+
publicAllowlist: false
|
|
76
|
+
adminAllowlist: true
|
|
77
|
+
- name: memory-compiled-truth-history
|
|
78
|
+
publicAllowlist: false
|
|
79
|
+
adminAllowlist: true
|
|
68
80
|
- name: conversation-list
|
|
69
81
|
publicAllowlist: false
|
|
70
82
|
adminAllowlist: true
|
|
@@ -95,6 +107,12 @@ tools:
|
|
|
95
107
|
- name: conversation-memory-expunge
|
|
96
108
|
publicAllowlist: false
|
|
97
109
|
adminAllowlist: true
|
|
110
|
+
- name: memory-dream-run
|
|
111
|
+
publicAllowlist: false
|
|
112
|
+
adminAllowlist: true
|
|
113
|
+
- name: memory-review-queue
|
|
114
|
+
publicAllowlist: false
|
|
115
|
+
adminAllowlist: true
|
|
98
116
|
hidden:
|
|
99
117
|
- session-compact
|
|
100
118
|
- session-compact-status
|
|
@@ -168,9 +186,13 @@ Graph hygiene is **agent-directed, case by case** — no autonomous rule engine,
|
|
|
168
186
|
|
|
169
187
|
**Why soft-delete.** On 2026-04-20 an autonomous orphan-delete rule wiped 19 nodes in one `graph-prune-run` call; properties were unrecoverable (Neo4j Community has no PITR, no APOC). The cron, the `graph-prune-run` tool, and the rule engine have been removed. An earlier fix added the `:Trashed` label primitive so every soft-delete caller (`memory-delete`, `contact-delete`, the admin `/graph` page's trash button) runs the same shared `trashNode` helper — relationships preserved, unique keys snapshotted into `_trashedKeys`, audit lines `[trash:marked] by=<surface>` for cross-cut provenance.
|
|
170
188
|
|
|
171
|
-
##
|
|
189
|
+
## Dream-cycle maintenance — `memory-dream-run` / `memory-review-queue`
|
|
190
|
+
|
|
191
|
+
`memory-dream-run` is the operator-initiated graph-hygiene sweep — four phases (orphan detection on `:Person`/`:Organization`/`:Concept`/`:Project`; stale compiled-truth scan on `compiledTruthUpdatedAt`; mark edges to `:Trashed` targets with `staleTarget: true`; LLM-bounded citation audit on `:TimelineEvent`). Each run writes a `:Report` (via `memory-report-write` from Task 332) summarising counts; per-phase log lines `[dream-cycle:phase-<n>]` join on the run id. Failure of any phase logs `[dream-cycle:phase-<n>:error]` and continues — subsequent phases still run. Pass `phases: [4]` to run only the citation audit (e.g. after a bulk ingest); `maxEventsPerRun` defaults to 200 to cap LLM cost, with a resume cursor stored as `:Report.lastEventCursor` so subsequent runs continue from the last processed event. `memory-review-queue` is the read-only surface for `:OrphanCandidate` and `:CitationProposal` queues — counts plus a small sample per queue. Phase 5 (backlinks) and phase 6 (tag consistency) are deferred to follow-up tasks. Task 327; absorbs Task 330.
|
|
192
|
+
|
|
193
|
+
## Brain-capture diagnostic — `memory-signals-recent`
|
|
172
194
|
|
|
173
|
-
Read-only window into the
|
|
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`, either inline at admin discretion or via the once-per-turn background pass (`platform/plugins/admin/hooks/post-turn-graph-pass.sh`, Task 412 — supersedes the original `signal-detector` specialist + throttled hook from Task 303).
|
|
174
196
|
|
|
175
197
|
## Conversational Memory
|
|
176
198
|
|
|
@@ -46,6 +46,12 @@ import { conversationArchiveEnrichRejection } from "./tools/conversation-archive
|
|
|
46
46
|
import { fetchImageAsContent } from "./tools/image-fetch.js";
|
|
47
47
|
import { memoryTypedEdgePass } from "./tools/memory-typed-edge-pass.js";
|
|
48
48
|
import { sessionRetrospectiveSkipRate } from "./tools/session-retrospective-skip-rate.js";
|
|
49
|
+
import { memoryReportWrite } from "./tools/memory-report-write.js";
|
|
50
|
+
import { memoryReportReadLatest } from "./tools/memory-report-read-latest.js";
|
|
51
|
+
import { memoryReportList } from "./tools/memory-report-list.js";
|
|
52
|
+
import { memoryDreamRun } from "./tools/memory-dream-run.js";
|
|
53
|
+
import { memoryReviewQueue } from "./tools/memory-review-queue.js";
|
|
54
|
+
import { memoryCompiledTruthHistory } from "./tools/memory-compiled-truth-history.js";
|
|
49
55
|
import { getSession, closeDriver } from "./lib/neo4j.js";
|
|
50
56
|
import { embed } from "./lib/embeddings.js";
|
|
51
57
|
import { notTrashed } from "../../../../lib/graph-trash/dist/index.js";
|
|
@@ -581,6 +587,165 @@ server.tool("conversation-archive-enrich-rejection", "Record or undo a per-row r
|
|
|
581
587
|
}
|
|
582
588
|
});
|
|
583
589
|
process.stderr.write("[memory-mcp] registered tool=conversation-archive-enrich-rejection\n");
|
|
590
|
+
// ---------------------------------------------------------------------------
|
|
591
|
+
// :Report read surface (Task 332). Both tools are read-only and available
|
|
592
|
+
// to admin agents without the !readOnly gate. Routing for "latest X" /
|
|
593
|
+
// "show me X report" intents lives in the admin IDENTITY.md clause and in
|
|
594
|
+
// these tools' descriptions.
|
|
595
|
+
// ---------------------------------------------------------------------------
|
|
596
|
+
eagerTool(server, "memory-report-read-latest", "Return the most recent :Report node tagged with the given keyword. Reports are the durable output of admin workflows (daily briefing, dream cycle, ad-hoc analyses) — keyword-routed so 'latest briefing' or 'last night's dream cycle' resolve deterministically. Default limit=1 returns just the freshest match; raise it to scan a window. Prefer this over memory-search when the operator says 'latest', 'last', 'most recent', or 'show me the X report'.", {
|
|
597
|
+
keyword: z
|
|
598
|
+
.string()
|
|
599
|
+
.min(1)
|
|
600
|
+
.describe("Keyword to match against the report's keywords array (e.g. 'daily-briefing', 'dream-cycle'). Any-match — a report with this keyword in its array qualifies."),
|
|
601
|
+
limit: z
|
|
602
|
+
.number()
|
|
603
|
+
.int()
|
|
604
|
+
.min(1)
|
|
605
|
+
.max(20)
|
|
606
|
+
.optional()
|
|
607
|
+
.describe("Maximum number of reports to return, newest first. Default 1."),
|
|
608
|
+
}, async ({ keyword, limit }) => {
|
|
609
|
+
if (!accountId)
|
|
610
|
+
return refuseNoAccount("memory-report-read-latest");
|
|
611
|
+
try {
|
|
612
|
+
const result = await memoryReportReadLatest({ accountId, keyword, limit });
|
|
613
|
+
if (result.results.length === 0) {
|
|
614
|
+
return {
|
|
615
|
+
content: [
|
|
616
|
+
{
|
|
617
|
+
type: "text",
|
|
618
|
+
text: `No reports found tagged with keyword '${keyword}'.`,
|
|
619
|
+
},
|
|
620
|
+
],
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
const body = result.results
|
|
624
|
+
.map((r) => {
|
|
625
|
+
const wf = r.sourceWorkflow ? ` (workflow=${r.sourceWorkflow})` : "";
|
|
626
|
+
return `# ${r.title}${wf}\nreportId: ${r.reportId}\noccurredAt: ${r.occurredAt}\nkeywords: ${r.keywords.join(", ")}\n\n${r.body}`;
|
|
627
|
+
})
|
|
628
|
+
.join("\n\n---\n\n");
|
|
629
|
+
return { content: [{ type: "text", text: body }] };
|
|
630
|
+
}
|
|
631
|
+
catch (err) {
|
|
632
|
+
return {
|
|
633
|
+
content: [
|
|
634
|
+
{
|
|
635
|
+
type: "text",
|
|
636
|
+
text: `memory-report-read-latest failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
637
|
+
},
|
|
638
|
+
],
|
|
639
|
+
isError: true,
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
eagerTool(server, "memory-report-list", "List :Report nodes, newest first, with optional keyword and sourceWorkflow filters. Returns metadata only (title, keywords, sourceWorkflow, occurredAt, body length) — no body — so the agent can scan the catalogue without paying for full bodies. Use memory-report-read-latest to fetch the body of the single most recent match for a keyword.", {
|
|
644
|
+
keyword: z
|
|
645
|
+
.string()
|
|
646
|
+
.optional()
|
|
647
|
+
.describe("Optional keyword filter (any-match against the report's keywords array)."),
|
|
648
|
+
sourceWorkflow: z
|
|
649
|
+
.string()
|
|
650
|
+
.optional()
|
|
651
|
+
.describe("Optional exact-match filter on the originating workflow (e.g. 'daily-briefing', 'dream-cycle')."),
|
|
652
|
+
limit: z
|
|
653
|
+
.number()
|
|
654
|
+
.int()
|
|
655
|
+
.min(1)
|
|
656
|
+
.max(100)
|
|
657
|
+
.optional()
|
|
658
|
+
.describe("Page size, newest first. Default 20, max 100."),
|
|
659
|
+
offset: z
|
|
660
|
+
.number()
|
|
661
|
+
.int()
|
|
662
|
+
.min(0)
|
|
663
|
+
.optional()
|
|
664
|
+
.describe("Zero-based offset for pagination. Default 0."),
|
|
665
|
+
}, async ({ keyword, sourceWorkflow, limit, offset }) => {
|
|
666
|
+
if (!accountId)
|
|
667
|
+
return refuseNoAccount("memory-report-list");
|
|
668
|
+
try {
|
|
669
|
+
const result = await memoryReportList({
|
|
670
|
+
accountId,
|
|
671
|
+
keyword,
|
|
672
|
+
sourceWorkflow,
|
|
673
|
+
limit,
|
|
674
|
+
offset,
|
|
675
|
+
});
|
|
676
|
+
if (result.results.length === 0) {
|
|
677
|
+
return {
|
|
678
|
+
content: [
|
|
679
|
+
{
|
|
680
|
+
type: "text",
|
|
681
|
+
text: `No reports found${keyword ? ` for keyword '${keyword}'` : ""}${sourceWorkflow ? ` (workflow='${sourceWorkflow}')` : ""}.`,
|
|
682
|
+
},
|
|
683
|
+
],
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
const header = `Showing ${result.results.length} of ${result.total} reports.`;
|
|
687
|
+
const rows = result.results
|
|
688
|
+
.map((r) => {
|
|
689
|
+
const wf = r.sourceWorkflow ? ` workflow=${r.sourceWorkflow}` : "";
|
|
690
|
+
return `- ${r.occurredAt} ${r.title} [${r.keywords.join(",")}]${wf} body=${r.bodyChars}c id=${r.reportId.slice(0, 8)} elementId=${r.nodeId}`;
|
|
691
|
+
})
|
|
692
|
+
.join("\n");
|
|
693
|
+
return {
|
|
694
|
+
content: [{ type: "text", text: `${header}\n${rows}` }],
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
catch (err) {
|
|
698
|
+
return {
|
|
699
|
+
content: [
|
|
700
|
+
{
|
|
701
|
+
type: "text",
|
|
702
|
+
text: `memory-report-list failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
703
|
+
},
|
|
704
|
+
],
|
|
705
|
+
isError: true,
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
process.stderr.write("[memory-mcp] registered tool=memory-report-read-latest\n");
|
|
710
|
+
process.stderr.write("[memory-mcp] registered tool=memory-report-list\n");
|
|
711
|
+
eagerTool(server, "memory-compiled-truth-history", "Return the audit history of `compiledTruth` values for one entity (:Person | :Organization | :Concept), newest first, capped at 20 revisions. Each row carries the truth text, when it was written, and whether the rewriter or an operator authored it. Use when the operator asks 'how has my understanding of X changed over time?' or 'when did the agent introduce this claim about X?' — pass the entity's elementId and present the diff between adjacent revisions in chat.", {
|
|
712
|
+
nodeId: z
|
|
713
|
+
.string()
|
|
714
|
+
.describe("elementId of the :Person | :Organization | :Concept whose revision history is being read."),
|
|
715
|
+
}, async ({ nodeId }) => {
|
|
716
|
+
if (!accountId)
|
|
717
|
+
return refuseNoAccount("memory-compiled-truth-history");
|
|
718
|
+
try {
|
|
719
|
+
const result = await memoryCompiledTruthHistory({ accountId, nodeId });
|
|
720
|
+
if (result.revisions.length === 0) {
|
|
721
|
+
return {
|
|
722
|
+
content: [
|
|
723
|
+
{
|
|
724
|
+
type: "text",
|
|
725
|
+
text: `No compiled-truth revisions found for nodeId=${nodeId.slice(0, 12)}.`,
|
|
726
|
+
},
|
|
727
|
+
],
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
const header = `Showing ${result.revisions.length} revision(s) (newest first) for nodeId=${nodeId.slice(0, 12)}.`;
|
|
731
|
+
const rows = result.revisions
|
|
732
|
+
.map((r, i) => `## #${i + 1} ${r.writtenAt} source=${r.source} revisionId=${r.revisionId.slice(0, 8)}\n${r.truth}`)
|
|
733
|
+
.join("\n\n");
|
|
734
|
+
return { content: [{ type: "text", text: `${header}\n\n${rows}` }] };
|
|
735
|
+
}
|
|
736
|
+
catch (err) {
|
|
737
|
+
return {
|
|
738
|
+
content: [
|
|
739
|
+
{
|
|
740
|
+
type: "text",
|
|
741
|
+
text: `memory-compiled-truth-history failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
742
|
+
},
|
|
743
|
+
],
|
|
744
|
+
isError: true,
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
process.stderr.write("[memory-mcp] registered tool=memory-compiled-truth-history\n");
|
|
584
749
|
if (!readOnly) {
|
|
585
750
|
eagerTool(server, "memory-write", "Create a new node in the knowledge graph with automatic embedding computation. Every node must be created with at least one relationship — call memory-search first to find target elementIds. Writes targeting :Person, :UserProfile, :AdminUser, :Organization, :LocalBusiness, :CloudflareTunnel, or :CloudflareHostname additionally require an inbound :PRODUCED edge from :Task, :Conversation, or :Message. For autonomous workflows that own a :Task lifecycle, pass the Task's elementId as `producedByTaskId`. For direct asks during a conversation, omit `producedByTaskId` — the platform auto-injects the edge from the active conversation.", {
|
|
586
751
|
labels: z
|
|
@@ -643,20 +808,82 @@ if (!readOnly) {
|
|
|
643
808
|
};
|
|
644
809
|
}
|
|
645
810
|
});
|
|
646
|
-
server
|
|
811
|
+
eagerTool(server, "memory-report-write", "Persist a workflow's output as a durable :Report node. Reports are append-only, keyword-routed, and embedded for hybrid search. Use at the end of any operator-initiated workflow whose output the operator will want to recall later — daily briefing, dream cycle, ad-hoc analyses. Idempotent on (title, occurredAt-within-same-minute): a second call with the same title in the same minute returns the existing report instead of duplicating. The platform auto-parents the report to the active conversation (or to the account's :AdminUser when no conversation context exists).", {
|
|
812
|
+
title: z
|
|
813
|
+
.string()
|
|
814
|
+
.min(1)
|
|
815
|
+
.describe("Short report title (e.g. 'Daily briefing 2026-05-25', 'Dream cycle 2026-05-25 14:30'). Used for idempotency together with occurredAt — same title in the same minute deduplicates."),
|
|
816
|
+
body: z
|
|
817
|
+
.string()
|
|
818
|
+
.max(10_000)
|
|
819
|
+
.describe("Report body in markdown. Maximum 10,000 characters; trim or summarise above the cap."),
|
|
820
|
+
keywords: z
|
|
821
|
+
.array(z.string().min(1))
|
|
822
|
+
.min(1)
|
|
823
|
+
.describe("Keyword tags used by memory-report-read-latest routing. Always include the workflow slug (e.g. 'daily-briefing', 'dream-cycle') plus the date in 'YYYY-MM-DD' form. Add topic keywords when the report focuses on one subject."),
|
|
824
|
+
sourceWorkflow: z
|
|
825
|
+
.string()
|
|
826
|
+
.optional()
|
|
827
|
+
.describe("Originating workflow slug for indexed lookup (e.g. 'daily-briefing', 'dream-cycle'). Recommended when the report comes from a named skill or scheduled workflow."),
|
|
828
|
+
occurredAt: z
|
|
829
|
+
.string()
|
|
830
|
+
.optional()
|
|
831
|
+
.describe("ISO 8601 timestamp the report describes. Defaults to now(). Override only when persisting a backdated report."),
|
|
832
|
+
}, async ({ title, body, keywords, sourceWorkflow, occurredAt }) => {
|
|
833
|
+
if (!accountId)
|
|
834
|
+
return refuseNoAccount("memory-report-write");
|
|
835
|
+
try {
|
|
836
|
+
const result = await memoryReportWrite({
|
|
837
|
+
accountId,
|
|
838
|
+
title,
|
|
839
|
+
body,
|
|
840
|
+
keywords,
|
|
841
|
+
sourceWorkflow,
|
|
842
|
+
occurredAt,
|
|
843
|
+
conversationNodeId: process.env.CONVERSATION_NODE_ID,
|
|
844
|
+
});
|
|
845
|
+
const status = result.created
|
|
846
|
+
? `Report created`
|
|
847
|
+
: `Report already existed (idempotent: ${result.idempotencyReason ?? "match"})`;
|
|
848
|
+
return {
|
|
849
|
+
content: [
|
|
850
|
+
{
|
|
851
|
+
type: "text",
|
|
852
|
+
text: `${status} — reportId: ${result.reportId} elementId: ${result.nodeId}`,
|
|
853
|
+
},
|
|
854
|
+
],
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
catch (err) {
|
|
858
|
+
return {
|
|
859
|
+
content: [
|
|
860
|
+
{
|
|
861
|
+
type: "text",
|
|
862
|
+
text: `memory-report-write failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
863
|
+
},
|
|
864
|
+
],
|
|
865
|
+
isError: true,
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
});
|
|
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. For :Person / :Organization / :Concept / :Project / :LocalBusiness, pass rewriteCompiledTruth=true to recompute the admin compiledTruth summary; when the label is in the public-twin set (:Organization / :Concept / :Project / :LocalBusiness) and the admin pass changes the truth, the public-twin compiledTruthPublic auto-rewrites in the same call. Pass compiledTruthPublic to override the public twin directly via chat. Direct writes to compiledTruth / compiledTruthUpdatedAt / compiledTruthSource / compiledTruthPublic / compiledTruthPublicUpdatedAt / compiledTruthPublicSource via the properties bag are refused — use the dedicated arguments.", {
|
|
647
870
|
nodeId: z.string().describe("Element ID of the node to update (from memory-search results)"),
|
|
648
871
|
properties: z
|
|
649
872
|
.record(z.string(), z.unknown())
|
|
650
|
-
.describe("Properties to set or overwrite. Restricted keys (embedding, accountId, createdAt) are ignored.
|
|
873
|
+
.describe("Properties to set or overwrite. Restricted keys (embedding, accountId, createdAt) are ignored. Compiled-truth fields are system-managed and rejected loudly here — use rewriteCompiledTruth or the compiledTruthPublic argument."),
|
|
651
874
|
rewriteCompiledTruth: z
|
|
652
875
|
.boolean()
|
|
653
876
|
.optional()
|
|
654
|
-
.describe("When true, run the compiled-truth rewriter against the node's most recent :TimelineEvent rows and
|
|
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."),
|
|
655
878
|
forceRewrite: z
|
|
656
879
|
.boolean()
|
|
657
880
|
.optional()
|
|
658
|
-
.describe("Bypass the 7-day cooldown that protects
|
|
659
|
-
|
|
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
|
+
compiledTruthPublic: z
|
|
883
|
+
.string()
|
|
884
|
+
.optional()
|
|
885
|
+
.describe("Operator override for the public-facing compiled-truth twin. Writes the supplied string verbatim, stamps source='operator', and resets the public-twin cooldown. Length cap: 2000 characters. Only valid on :Organization / :Concept / :Project / :LocalBusiness (:Person has no public twin)."),
|
|
886
|
+
}, async ({ nodeId, properties, rewriteCompiledTruth, forceRewrite, compiledTruthPublic }) => {
|
|
660
887
|
if (!accountId)
|
|
661
888
|
return refuseNoAccount("memory-update");
|
|
662
889
|
try {
|
|
@@ -671,28 +898,55 @@ if (!readOnly) {
|
|
|
671
898
|
},
|
|
672
899
|
rewriteCompiledTruth,
|
|
673
900
|
forceRewrite,
|
|
901
|
+
compiledTruthPublic,
|
|
674
902
|
});
|
|
675
903
|
const rewriterLine = (() => {
|
|
676
904
|
if (!result.rewriter)
|
|
677
905
|
return "";
|
|
678
906
|
if (result.rewriter.kind === "ok") {
|
|
679
|
-
return `\
|
|
907
|
+
return `\nAdmin compiled-truth rewritten: ${result.rewriter.charsBefore} → ${result.rewriter.charsAfter} chars.`;
|
|
680
908
|
}
|
|
681
909
|
if (result.rewriter.kind === "refused") {
|
|
682
910
|
return result.rewriter.reason === "operator-edit-cooldown"
|
|
683
|
-
? `\
|
|
684
|
-
: `\
|
|
911
|
+
? `\nAdmin rewriter refused: operator-edit cooldown (${result.rewriter.daysRemaining} day(s) remaining; pass forceRewrite=true to override).`
|
|
912
|
+
: `\nAdmin rewriter refused: no :TimelineEvent rows linked to this node.`;
|
|
685
913
|
}
|
|
686
914
|
if (result.rewriter.kind === "too-long") {
|
|
687
|
-
return `\
|
|
915
|
+
return `\nAdmin rewriter output exceeded ${result.rewriter.attemptedLen} chars after one retry; compiledTruth unchanged.`;
|
|
916
|
+
}
|
|
917
|
+
return `\nAdmin rewriter LLM call failed (cause=${result.rewriter.cause}); compiledTruth unchanged.`;
|
|
918
|
+
})();
|
|
919
|
+
const publicRewriterLine = (() => {
|
|
920
|
+
if (!result.publicRewriter)
|
|
921
|
+
return "";
|
|
922
|
+
if (result.publicRewriter.kind === "ok") {
|
|
923
|
+
return `\nPublic compiled-truth rewritten: ${result.publicRewriter.charsBefore} → ${result.publicRewriter.charsAfter} chars.`;
|
|
924
|
+
}
|
|
925
|
+
if (result.publicRewriter.kind === "refused") {
|
|
926
|
+
return result.publicRewriter.reason === "operator-edit-cooldown"
|
|
927
|
+
? `\nPublic rewriter refused: operator-edit cooldown (${result.publicRewriter.daysRemaining} day(s) remaining; pass forceRewrite=true to override).`
|
|
928
|
+
: `\nPublic rewriter refused: no :TimelineEvent rows linked to this node.`;
|
|
929
|
+
}
|
|
930
|
+
if (result.publicRewriter.kind === "too-long") {
|
|
931
|
+
return `\nPublic rewriter output exceeded ${result.publicRewriter.attemptedLen} chars after one retry; compiledTruthPublic unchanged.`;
|
|
932
|
+
}
|
|
933
|
+
return `\nPublic rewriter LLM call failed (cause=${result.publicRewriter.cause}); compiledTruthPublic unchanged.`;
|
|
934
|
+
})();
|
|
935
|
+
const overrideLine = (() => {
|
|
936
|
+
if (!result.publicOverride)
|
|
937
|
+
return "";
|
|
938
|
+
if (result.publicOverride.kind === "applied") {
|
|
939
|
+
return `\ncompiledTruthPublic overridden by operator: ${result.publicOverride.chars} chars.`;
|
|
688
940
|
}
|
|
689
|
-
return
|
|
941
|
+
return result.publicOverride.reason === "wrong-label"
|
|
942
|
+
? `\ncompiledTruthPublic override refused: label is not in the public-twin set (:Organization / :Concept / :Project / :LocalBusiness).`
|
|
943
|
+
: `\ncompiledTruthPublic override refused: exceeds 2000 character cap.`;
|
|
690
944
|
})();
|
|
691
945
|
return {
|
|
692
946
|
content: [
|
|
693
947
|
{
|
|
694
948
|
type: "text",
|
|
695
|
-
text: `Node updated: [${result.labels.join(", ")}] (ID: ${result.nodeId})${rewriterLine}`,
|
|
949
|
+
text: `Node updated: [${result.labels.join(", ")}] (ID: ${result.nodeId})${rewriterLine}${publicRewriterLine}${overrideLine}`,
|
|
696
950
|
},
|
|
697
951
|
],
|
|
698
952
|
};
|
|
@@ -2152,6 +2406,50 @@ server.tool("session-retrospective-skip-rate", "Return the ratio of recent sessi
|
|
|
2152
2406
|
await dbSession.close();
|
|
2153
2407
|
}
|
|
2154
2408
|
});
|
|
2409
|
+
// ===================================================================
|
|
2410
|
+
// Dream-cycle (Task 327, absorbs 330)
|
|
2411
|
+
// ===================================================================
|
|
2412
|
+
// Report MCP tools (Task 332) are registered earlier in this file.
|
|
2413
|
+
// Dream-run calls the same writer in-process for the per-run :Report.
|
|
2414
|
+
server.tool("memory-dream-run", "Operator-initiated dream-cycle: graph-hygiene sweep across five phases " +
|
|
2415
|
+
"(orphan detection, stale compiled-truth, edges to :Trashed targets, citation audit on :TimelineEvent, " +
|
|
2416
|
+
"prune compiled-truth revisions to newest 20 per entity). " +
|
|
2417
|
+
"Writes a :Report node at the end. Failure of any phase logs and continues. " +
|
|
2418
|
+
"Pass phases=[4] to run only the citation audit (e.g. after a bulk ingest), or phases=[5] for revision prune only.", {
|
|
2419
|
+
operatorId: z.string().optional().describe("Identifier of the operator who triggered the run; appears in the report and log line."),
|
|
2420
|
+
phases: z.array(z.number().int().min(1).max(5)).optional().describe("Subset of phases to run. Default [1,2,3,4,5]. Order is fixed."),
|
|
2421
|
+
maxEventsPerRun: z.number().int().positive().optional().describe("Phase 4 cap on :TimelineEvent rows scanned per run. Default 200."),
|
|
2422
|
+
}, async ({ operatorId, phases, maxEventsPerRun }) => {
|
|
2423
|
+
if (!accountId)
|
|
2424
|
+
return refuseNoAccount("memory-dream-run");
|
|
2425
|
+
try {
|
|
2426
|
+
const result = await memoryDreamRun({ accountId, operatorId, phases, maxEventsPerRun });
|
|
2427
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2428
|
+
}
|
|
2429
|
+
catch (err) {
|
|
2430
|
+
return {
|
|
2431
|
+
content: [{ type: "text", text: `memory-dream-run failed: ${err instanceof Error ? err.message : String(err)}` }],
|
|
2432
|
+
isError: true,
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
});
|
|
2436
|
+
server.tool("memory-review-queue", "Read-only summary of the dream-cycle's review queues (:OrphanCandidate, :CitationProposal). " +
|
|
2437
|
+
"Returns per-queue counts and a small sample so the operator can see whether to triage.", {
|
|
2438
|
+
sampleSize: z.number().int().positive().optional().describe("Sample rows per queue. Default 5, max 25."),
|
|
2439
|
+
}, async ({ sampleSize }) => {
|
|
2440
|
+
if (!accountId)
|
|
2441
|
+
return refuseNoAccount("memory-review-queue");
|
|
2442
|
+
try {
|
|
2443
|
+
const result = await memoryReviewQueue({ accountId, sampleSize });
|
|
2444
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
2445
|
+
}
|
|
2446
|
+
catch (err) {
|
|
2447
|
+
return {
|
|
2448
|
+
content: [{ type: "text", text: `memory-review-queue failed: ${err instanceof Error ? err.message : String(err)}` }],
|
|
2449
|
+
isError: true,
|
|
2450
|
+
};
|
|
2451
|
+
}
|
|
2452
|
+
});
|
|
2155
2453
|
// Cleanup on exit
|
|
2156
2454
|
process.on("SIGINT", async () => {
|
|
2157
2455
|
liveSchemaRuntime.cache.stop();
|