@rubytech/create-maxy-code 0.1.143 → 0.1.146

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.
Files changed (131) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/neo4j/schema.cypher +154 -4
  3. package/payload/platform/plugins/admin/PLUGIN.md +1 -1
  4. package/payload/platform/plugins/admin/hooks/post-turn-graph-pass.sh +251 -0
  5. package/payload/platform/plugins/admin/skills/datetime/SKILL.md +63 -5
  6. package/payload/platform/plugins/docs/references/internals.md +16 -0
  7. package/payload/platform/plugins/docs/references/memory-guide.md +10 -0
  8. package/payload/platform/plugins/docs/references/platform.md +1 -1
  9. package/payload/platform/plugins/memory/.claude-plugin/plugin.json +1 -1
  10. package/payload/platform/plugins/memory/PLUGIN.md +25 -3
  11. package/payload/platform/plugins/memory/mcp/dist/index.js +309 -11
  12. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  13. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.d.ts +2 -0
  14. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.d.ts.map +1 -0
  15. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.js +33 -0
  16. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.js.map +1 -0
  17. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-gate-label.test.d.ts +2 -0
  18. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-gate-label.test.d.ts.map +1 -0
  19. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-gate-label.test.js +30 -0
  20. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-gate-label.test.js.map +1 -0
  21. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-prune-revisions.test.d.ts +2 -0
  22. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-prune-revisions.test.d.ts.map +1 -0
  23. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-prune-revisions.test.js +51 -0
  24. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-prune-revisions.test.js.map +1 -0
  25. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/relative-date.test.d.ts +2 -0
  26. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/relative-date.test.d.ts.map +1 -0
  27. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/relative-date.test.js +68 -0
  28. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/relative-date.test.js.map +1 -0
  29. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/timeline-extractor.test.js +41 -0
  30. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/timeline-extractor.test.js.map +1 -1
  31. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-revision.d.ts +43 -0
  32. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-revision.d.ts.map +1 -0
  33. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-revision.js +33 -0
  34. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-revision.js.map +1 -0
  35. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.d.ts +18 -5
  36. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.d.ts.map +1 -1
  37. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.js +64 -17
  38. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.js.map +1 -1
  39. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/index.d.ts +44 -0
  40. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/index.d.ts.map +1 -0
  41. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/index.js +14 -0
  42. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/index.js.map +1 -0
  43. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-citation-audit.d.ts +26 -0
  44. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-citation-audit.d.ts.map +1 -0
  45. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-citation-audit.js +219 -0
  46. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-citation-audit.js.map +1 -0
  47. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-dead-edges.d.ts +19 -0
  48. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-dead-edges.d.ts.map +1 -0
  49. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-dead-edges.js +39 -0
  50. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-dead-edges.js.map +1 -0
  51. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-orphans.d.ts +19 -0
  52. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-orphans.d.ts.map +1 -0
  53. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-orphans.js +42 -0
  54. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-orphans.js.map +1 -0
  55. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-prune-revisions.d.ts +21 -0
  56. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-prune-revisions.d.ts.map +1 -0
  57. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-prune-revisions.js +27 -0
  58. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-prune-revisions.js.map +1 -0
  59. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-stale-truth.d.ts +18 -0
  60. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-stale-truth.d.ts.map +1 -0
  61. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-stale-truth.js +60 -0
  62. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-stale-truth.js.map +1 -0
  63. package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.d.ts +20 -3
  64. package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.d.ts.map +1 -1
  65. package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.js +47 -12
  66. package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.js.map +1 -1
  67. package/payload/platform/plugins/memory/mcp/dist/lib/relative-date.d.ts +13 -0
  68. package/payload/platform/plugins/memory/mcp/dist/lib/relative-date.d.ts.map +1 -0
  69. package/payload/platform/plugins/memory/mcp/dist/lib/relative-date.js +191 -0
  70. package/payload/platform/plugins/memory/mcp/dist/lib/relative-date.js.map +1 -0
  71. package/payload/platform/plugins/memory/mcp/dist/lib/timeline-extractor.d.ts +7 -1
  72. package/payload/platform/plugins/memory/mcp/dist/lib/timeline-extractor.d.ts.map +1 -1
  73. package/payload/platform/plugins/memory/mcp/dist/lib/timeline-extractor.js +37 -9
  74. package/payload/platform/plugins/memory/mcp/dist/lib/timeline-extractor.js.map +1 -1
  75. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-compiled-truth-history.test.d.ts +2 -0
  76. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-compiled-truth-history.test.d.ts.map +1 -0
  77. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-compiled-truth-history.test.js +81 -0
  78. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-compiled-truth-history.test.js.map +1 -0
  79. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-report-tools.test.d.ts +2 -0
  80. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-report-tools.test.d.ts.map +1 -0
  81. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-report-tools.test.js +115 -0
  82. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-report-tools.test.js.map +1 -0
  83. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.js +1 -0
  84. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.js.map +1 -1
  85. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.js +1 -0
  86. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.js.map +1 -1
  87. package/payload/platform/plugins/memory/mcp/dist/tools/memory-compiled-truth-history.d.ts +19 -0
  88. package/payload/platform/plugins/memory/mcp/dist/tools/memory-compiled-truth-history.d.ts.map +1 -0
  89. package/payload/platform/plugins/memory/mcp/dist/tools/memory-compiled-truth-history.js +50 -0
  90. package/payload/platform/plugins/memory/mcp/dist/tools/memory-compiled-truth-history.js.map +1 -0
  91. package/payload/platform/plugins/memory/mcp/dist/tools/memory-dream-run.d.ts +45 -0
  92. package/payload/platform/plugins/memory/mcp/dist/tools/memory-dream-run.d.ts.map +1 -0
  93. package/payload/platform/plugins/memory/mcp/dist/tools/memory-dream-run.js +170 -0
  94. package/payload/platform/plugins/memory/mcp/dist/tools/memory-dream-run.js.map +1 -0
  95. package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-list.d.ts +28 -0
  96. package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-list.d.ts.map +1 -0
  97. package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-list.js +64 -0
  98. package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-list.js.map +1 -0
  99. package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-read-latest.d.ts +24 -0
  100. package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-read-latest.d.ts.map +1 -0
  101. package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-read-latest.js +49 -0
  102. package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-read-latest.js.map +1 -0
  103. package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-write.d.ts +34 -0
  104. package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-write.d.ts.map +1 -0
  105. package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-write.js +99 -0
  106. package/payload/platform/plugins/memory/mcp/dist/tools/memory-report-write.js.map +1 -0
  107. package/payload/platform/plugins/memory/mcp/dist/tools/memory-review-queue.d.ts +29 -0
  108. package/payload/platform/plugins/memory/mcp/dist/tools/memory-review-queue.d.ts.map +1 -0
  109. package/payload/platform/plugins/memory/mcp/dist/tools/memory-review-queue.js +66 -0
  110. package/payload/platform/plugins/memory/mcp/dist/tools/memory-review-queue.js.map +1 -0
  111. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.d.ts.map +1 -1
  112. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js +122 -2
  113. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js.map +1 -1
  114. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts +49 -13
  115. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts.map +1 -1
  116. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js +184 -16
  117. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js.map +1 -1
  118. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
  119. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +53 -10
  120. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
  121. package/payload/platform/plugins/memory/mcp/vitest.config.ts +6 -0
  122. package/payload/platform/plugins/memory/references/schema-base.md +4 -0
  123. package/payload/platform/plugins/memory/skills/conversation-archive/SKILL.md +3 -4
  124. package/payload/platform/plugins/scheduling/skills/briefing/SKILL.md +13 -2
  125. package/payload/platform/scripts/seed-neo4j.sh +1 -1
  126. package/payload/platform/templates/agents/admin/IDENTITY.md +9 -1
  127. package/payload/platform/templates/specialists/agents/database-operator.md +30 -2
  128. package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
  129. package/payload/server/server.js +195 -91
  130. package/payload/platform/plugins/admin/hooks/signal-detector-stop.sh +0 -309
  131. package/payload/platform/templates/specialists/agents/signal-detector.md +0 -129
@@ -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-update tools 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).",
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-update tools 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)."
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
- ## Signal-detector diagnostic — `memory-signals-recent`
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 writes the `signal-detector` specialist has 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?". The signal-detector specialist itself is dispatched only by the admin-agent Stop hook at `platform/plugins/admin/hooks/signal-detector-stop.sh`never by operator intent and never via `<specialist-domains>` routing. Task 303.
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.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, pass rewriteCompiledTruth=true to recompute the compiled-truth summary from the most recent :TimelineEvent rows; direct writes to compiledTruth / compiledTruthUpdatedAt / compiledTruthSource are refused.", {
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. compiledTruth / compiledTruthUpdatedAt / compiledTruthSource are system-managed and rejected loudly — use rewriteCompiledTruth instead."),
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 set the result on compiledTruth. Only valid on :Person / :Organization / :Concept."),
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 an operator-edited compiledTruth from automatic rewriter passes. Set only when the operator explicitly asks for a refresh."),
659
- }, async ({ nodeId, properties, rewriteCompiledTruth, forceRewrite }) => {
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 `\nCompiled-truth rewritten: ${result.rewriter.charsBefore} → ${result.rewriter.charsAfter} chars.`;
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
- ? `\nRewriter refused: operator-edit cooldown (${result.rewriter.daysRemaining} day(s) remaining; pass forceRewrite=true to override).`
684
- : `\nRewriter refused: no :TimelineEvent rows linked to this node.`;
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 `\nRewriter output exceeded ${result.rewriter.attemptedLen} chars after one retry; compiledTruth unchanged.`;
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 `\nRewriter LLM call failed (cause=${result.rewriter.cause}); compiledTruth unchanged.`;
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();