@rubytech/create-maxy-code 0.1.230 → 0.1.233

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 (106) hide show
  1. package/dist/__tests__/rss-sampler-installer.test.js +27 -0
  2. package/dist/index.js +61 -0
  3. package/package.json +1 -1
  4. package/payload/platform/neo4j/schema.cypher +20 -0
  5. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +39 -1
  6. package/payload/platform/plugins/docs/references/graph.md +12 -0
  7. package/payload/platform/plugins/docs/references/memory-guide.md +26 -0
  8. package/payload/platform/plugins/docs/superpowers/plans/2026-06-01-memory-edge.md +589 -0
  9. package/payload/platform/plugins/memory/.claude-plugin/plugin.json +1 -1
  10. package/payload/platform/plugins/memory/PLUGIN.md +12 -2
  11. package/payload/platform/plugins/memory/mcp/dist/index.js +64 -7
  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.js +16 -0
  14. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.js.map +1 -1
  15. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-backlinks.test.d.ts +2 -0
  16. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-backlinks.test.d.ts.map +1 -0
  17. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-backlinks.test.js +24 -0
  18. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-backlinks.test.js.map +1 -0
  19. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-tag-normalisation.test.d.ts +2 -0
  20. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-tag-normalisation.test.d.ts.map +1 -0
  21. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-tag-normalisation.test.js +51 -0
  22. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/phase-tag-normalisation.test.js.map +1 -0
  23. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-revision.d.ts +31 -0
  24. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-revision.d.ts.map +1 -1
  25. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-revision.js.map +1 -1
  26. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/index.d.ts +7 -7
  27. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/index.js +7 -7
  28. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-backlinks.d.ts +15 -0
  29. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-backlinks.d.ts.map +1 -0
  30. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-backlinks.js +37 -0
  31. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-backlinks.js.map +1 -0
  32. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-tag-normalisation.d.ts +25 -0
  33. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-tag-normalisation.d.ts.map +1 -0
  34. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-tag-normalisation.js +90 -0
  35. package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-tag-normalisation.js.map +1 -0
  36. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.js +117 -0
  37. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.js.map +1 -1
  38. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-compiled-truth-history.test.js +72 -26
  39. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-compiled-truth-history.test.js.map +1 -1
  40. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-edge.test.d.ts +2 -0
  41. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-edge.test.d.ts.map +1 -0
  42. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-edge.test.js +164 -0
  43. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-edge.test.js.map +1 -0
  44. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-inline-rewrite.test.d.ts +2 -0
  45. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-inline-rewrite.test.d.ts.map +1 -0
  46. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-inline-rewrite.test.js +66 -0
  47. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest-inline-rewrite.test.js.map +1 -0
  48. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-timeline-learned-at.test.d.ts +2 -0
  49. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-timeline-learned-at.test.d.ts.map +1 -0
  50. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-timeline-learned-at.test.js +30 -0
  51. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-timeline-learned-at.test.js.map +1 -0
  52. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.d.ts +25 -0
  53. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.d.ts.map +1 -1
  54. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js +26 -0
  55. package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js.map +1 -1
  56. package/payload/platform/plugins/memory/mcp/dist/tools/memory-compiled-truth-history.d.ts +17 -1
  57. package/payload/platform/plugins/memory/mcp/dist/tools/memory-compiled-truth-history.d.ts.map +1 -1
  58. package/payload/platform/plugins/memory/mcp/dist/tools/memory-compiled-truth-history.js +33 -13
  59. package/payload/platform/plugins/memory/mcp/dist/tools/memory-compiled-truth-history.js.map +1 -1
  60. package/payload/platform/plugins/memory/mcp/dist/tools/memory-dream-run.d.ts +6 -3
  61. package/payload/platform/plugins/memory/mcp/dist/tools/memory-dream-run.d.ts.map +1 -1
  62. package/payload/platform/plugins/memory/mcp/dist/tools/memory-dream-run.js +32 -5
  63. package/payload/platform/plugins/memory/mcp/dist/tools/memory-dream-run.js.map +1 -1
  64. package/payload/platform/plugins/memory/mcp/dist/tools/memory-edge.d.ts +21 -0
  65. package/payload/platform/plugins/memory/mcp/dist/tools/memory-edge.d.ts.map +1 -0
  66. package/payload/platform/plugins/memory/mcp/dist/tools/memory-edge.js +51 -0
  67. package/payload/platform/plugins/memory/mcp/dist/tools/memory-edge.js.map +1 -0
  68. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts +37 -0
  69. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts.map +1 -1
  70. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +72 -0
  71. package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
  72. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
  73. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +1 -0
  74. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
  75. package/payload/platform/plugins/memory/skills/challenge/SKILL.md +52 -0
  76. package/payload/platform/plugins/memory/skills/connect/SKILL.md +56 -0
  77. package/payload/platform/plugins/memory/skills/emerge/SKILL.md +87 -0
  78. package/payload/platform/plugins/scheduling/mcp/dist/index.js +1 -1
  79. package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
  80. package/payload/platform/scripts/rss-sampler.sh +58 -0
  81. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
  82. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -0
  83. package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
  84. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +4 -0
  85. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  86. package/payload/platform/services/claude-session-manager/dist/http-server.js +30 -4
  87. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  88. package/payload/platform/services/claude-session-manager/dist/index.js +23 -0
  89. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  90. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +14 -2
  91. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  92. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +27 -0
  93. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  94. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +6 -0
  95. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
  96. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +2 -0
  97. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
  98. package/payload/platform/templates/specialists/agents/archive-ingest-operator.md +1 -1
  99. package/payload/platform/templates/specialists/agents/database-operator.md +1 -1
  100. package/payload/platform/templates/specialists/agents/librarian.md +1 -1
  101. package/payload/server/{chunk-EXIPYITB.js → chunk-63M3ILJ6.js} +1 -0
  102. package/payload/server/maxy-edge.js +1 -1
  103. package/payload/server/public/assets/admin-DfeXRW1m.js +1 -0
  104. package/payload/server/public/index.html +1 -1
  105. package/payload/server/server.js +1 -1
  106. package/payload/server/public/assets/admin-BKawc0eQ.js +0 -1
@@ -0,0 +1,27 @@
1
+ // Static-grep contracts for Task 600 installer additions:
2
+ // swap provisioning and rss-sampler service registration.
3
+ //
4
+ // Why static-grep, not behaviour: the install branches spawn (sudo, systemctl,
5
+ // dphys-swapfile), so end-to-end reproduction belongs to the manual smoke path.
6
+ // The unit suite pins the branch points so a future refactor that removes them
7
+ // fails loudly here.
8
+ import test from "node:test";
9
+ import assert from "node:assert/strict";
10
+ import { readFileSync } from "node:fs";
11
+ import { fileURLToPath } from "node:url";
12
+ import { dirname, resolve } from "node:path";
13
+ const here = dirname(fileURLToPath(import.meta.url));
14
+ const INDEX_TS = resolve(here, "../../src/index.ts");
15
+ const SRC = readFileSync(INDEX_TS, "utf-8");
16
+ test("installSystemDeps Linux branch installs dphys-swapfile and writes CONF_SWAPSIZE=2048", () => {
17
+ assert.match(SRC, /apt-get.*install.*dphys-swapfile/s, "apt install dphys-swapfile must be present");
18
+ assert.match(SRC, /CONF_SWAPSIZE=2048/, "swap size must be 2048MB");
19
+ assert.match(SRC, /CONF_MAXSWAP=4096/, "swap max must be 4096MB");
20
+ assert.match(SRC, /dphys-swapfile.*restart|restart.*dphys-swapfile/s, "dphys-swapfile service must be restarted");
21
+ });
22
+ test("installService Linux branch writes rss-sampler user unit and enables it", () => {
23
+ assert.match(SRC, /rss-sampler\.service/, "rss-sampler.service unit must be written");
24
+ assert.match(SRC, /rss-sampler\.sh/, "rss-sampler.sh must appear in ExecStart");
25
+ assert.match(SRC, /rss-sampler.*enable|enable.*rss-sampler/s, "rss-sampler must be enabled");
26
+ assert.match(SRC, /rss-sampler installed/, "rss-sampler install must emit a logFile entry");
27
+ });
package/dist/index.js CHANGED
@@ -709,6 +709,42 @@ function installSystemDeps() {
709
709
  // so deployPayload's writeChromiumBinaryPathFile and installService's
710
710
  // buildMaxyUnitFile both have a real absolute path to thread through.
711
711
  ensureNonSnapChromium();
712
+ // Task 600 — provision swap so the OOM-killer has a buffer to work in
713
+ // instead of silently freezing the box. dphys-swapfile is the standard
714
+ // Raspberry Pi OS swap manager. CONF_MAXSWAP raised to 4096 so the
715
+ // operator can manually grow the swap file without reinstalling.
716
+ // Best-effort: a failure here warns but does not abort the install.
717
+ try {
718
+ console.log(" [privileged] apt-get install dphys-swapfile");
719
+ shell("apt-get", ["install", "-y", "dphys-swapfile"], { sudo: true, bestEffort: true });
720
+ const desiredSwapConf = "CONF_SWAPSIZE=2048\nCONF_MAXSWAP=4096\n";
721
+ const swapConfPath = "/etc/dphys-swapfile";
722
+ let swapCurrent = "";
723
+ if (existsSync(swapConfPath)) {
724
+ try {
725
+ swapCurrent = readFileSync(swapConfPath, "utf-8");
726
+ }
727
+ catch {
728
+ swapCurrent = "";
729
+ }
730
+ }
731
+ if (swapCurrent === desiredSwapConf) {
732
+ logFile(" swap: already configured (CONF_SWAPSIZE=2048 CONF_MAXSWAP=4096)");
733
+ }
734
+ else {
735
+ const swapTmpPath = `/tmp/dphys-swapfile.${process.pid}`;
736
+ writeFileSync(swapTmpPath, desiredSwapConf);
737
+ console.log(" [privileged] install /etc/dphys-swapfile (CONF_SWAPSIZE=2048 CONF_MAXSWAP=4096)");
738
+ shell("cp", [swapTmpPath, swapConfPath], { sudo: true });
739
+ spawnSync("rm", ["-f", swapTmpPath]);
740
+ console.log(" [privileged] systemctl restart dphys-swapfile");
741
+ shell("systemctl", ["restart", "dphys-swapfile"], { sudo: true, bestEffort: true });
742
+ logFile(" swap: configured CONF_SWAPSIZE=2048 CONF_MAXSWAP=4096 and restarted dphys-swapfile");
743
+ }
744
+ }
745
+ catch (err) {
746
+ console.error(` WARNING: failed to provision swap: ${err instanceof Error ? err.message : String(err)}`);
747
+ }
712
748
  // Hostname resolution — four sources, in priority order:
713
749
  // 1. --hostname flag (unconditional — the caller is the authority)
714
750
  // 2. OS detection on same-brand upgrade (service exists → keep whatever is currently set)
@@ -3084,6 +3120,28 @@ function installService() {
3084
3120
  const claudePtysSliceName = "claude-ptys.slice";
3085
3121
  writeFileSync(join(serviceDir, claudePtysSliceName), buildClaudePtysSliceUnitFile());
3086
3122
  logFile(` ${claudePtysSliceName}: Task 250 slice for claude-session-*.scope cohort`);
3123
+ // Task 600 — per-brand RSS sampler: a long-running user service that
3124
+ // periodically writes per-claude.exe RSS + aggregate RSS to disk.
3125
+ // Named per-brand (BRAND.hostname prefix) so two brands on the same
3126
+ // device each own their own sampler writing to their own log path.
3127
+ const rssSamplerUnitName = `${BRAND.hostname}-rss-sampler.service`;
3128
+ const rssSamplerScriptPath = resolve(INSTALL_DIR, "platform/scripts/rss-sampler.sh");
3129
+ const rssSamplerLogPath = join(persistDir, "rss-sampler.log");
3130
+ const rssSamplerUnit = `[Unit]
3131
+ Description=${BRAND.productName} RSS Sampler
3132
+
3133
+ [Service]
3134
+ Type=simple
3135
+ ExecStart=/bin/bash ${rssSamplerScriptPath}
3136
+ Environment=LOG_PATH=${rssSamplerLogPath}
3137
+ Restart=on-failure
3138
+ RestartSec=10
3139
+
3140
+ [Install]
3141
+ WantedBy=default.target
3142
+ `;
3143
+ writeFileSync(join(serviceDir, rssSamplerUnitName), rssSamplerUnit);
3144
+ logFile(` rss-sampler installed: ${rssSamplerUnitName} LOG_PATH=${rssSamplerLogPath}`);
3087
3145
  // the edge service: always-on front door that owns the public
3088
3146
  // port (PORT) and the VNC stack (Xtigervnc + websockify). Its lifecycle is
3089
3147
  // independent of the main brand service, so an in-place upgrade triggered
@@ -3294,11 +3352,14 @@ WantedBy=multi-user.target
3294
3352
  spawnSync("systemctl", ["--user", "enable", edgeUnitShort], { stdio: "inherit" });
3295
3353
  spawnSync("systemctl", ["--user", "enable", claudeSessionManagerUnitShort], { stdio: "inherit" });
3296
3354
  spawnSync("systemctl", ["--user", "enable", unitName], { stdio: "inherit" });
3355
+ spawnSync("systemctl", ["--user", "enable", `${BRAND.hostname}-rss-sampler`], { stdio: "inherit" });
3297
3356
  // edge first: binds public port + starts VNC stack. Then claude-session-manager
3298
3357
  // (so the main brand unit's Requires= is satisfied). Then main brand service.
3358
+ // rss-sampler is independent — start last, failure is non-blocking.
3299
3359
  spawnSync("systemctl", ["--user", "restart", edgeUnitShort], { stdio: "inherit" });
3300
3360
  spawnSync("systemctl", ["--user", "restart", claudeSessionManagerUnitShort], { stdio: "inherit" });
3301
3361
  spawnSync("systemctl", ["--user", "restart", unitName], { stdio: "inherit" });
3362
+ spawnSync("systemctl", ["--user", "restart", `${BRAND.hostname}-rss-sampler`], { stdio: "inherit" });
3302
3363
  // Session manager runs in its own systemd user unit and can boot-fail silently
3303
3364
  // (e.g. mcp-block-missing). The web server below comes up regardless, so the
3304
3365
  // install terminal would otherwise report success while every API call 503s.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy-code",
3
- "version": "0.1.230",
3
+ "version": "0.1.233",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy-code": "./dist/index.js"
@@ -1566,6 +1566,13 @@ OPTIONS {
1566
1566
  // mentioned | met | quoted | attended | transacted
1567
1567
  // Extractor refuses on ambiguity rather than collapsing to "other".
1568
1568
  //
1569
+ // `learnedAt` — datetime(), transaction-time: when this event was ingested.
1570
+ // Distinct from `occurredAt` (valid-time: when the fact was true in the world).
1571
+ // Enables bi-temporal queries: "events ingested on date X about something that
1572
+ // happened in 1990". Backfilled from `createdAt` on pre-existing nodes; nodes
1573
+ // without `createdAt` fall back to `datetime()` (safe approximation).
1574
+ // Task 597.
1575
+ //
1569
1576
  // `compiledTruth` source:
1570
1577
  // compiledTruthSource = "rewriter" (default) — compiled-truth-rewriter
1571
1578
  // library wrote it.
@@ -1596,6 +1603,9 @@ FOR (t:TimelineEvent) ON (t.occurredAt);
1596
1603
  CREATE INDEX timeline_event_kind IF NOT EXISTS
1597
1604
  FOR (t:TimelineEvent) ON (t.kind);
1598
1605
 
1606
+ CREATE INDEX timeline_event_learned_at IF NOT EXISTS
1607
+ FOR (t:TimelineEvent) ON (t.learnedAt);
1608
+
1599
1609
  CREATE VECTOR INDEX timeline_event_embedding IF NOT EXISTS
1600
1610
  FOR (t:TimelineEvent) ON (t.embedding)
1601
1611
  OPTIONS {
@@ -1605,6 +1615,16 @@ OPTIONS {
1605
1615
  }
1606
1616
  };
1607
1617
 
1618
+ // Task 597 migration — backfill learnedAt on :TimelineEvent nodes written
1619
+ // before transaction-time was tracked. Uses createdAt as the best available
1620
+ // proxy; nodes without createdAt get datetime() (safe: they predate the field).
1621
+ // CALL IN TRANSACTIONS avoids a single large write lock on installs with many nodes.
1622
+ CALL {
1623
+ MATCH (t:TimelineEvent) WHERE t.learnedAt IS NULL
1624
+ WITH t LIMIT 500
1625
+ SET t.learnedAt = coalesce(t.createdAt, datetime())
1626
+ } IN TRANSACTIONS OF 500 ROWS;
1627
+
1608
1628
  // `compiledTruthUpdatedAt` indices for stale-detection scans
1609
1629
  // (e.g. "find :Person with truth older than 7 days that has new events").
1610
1630
  CREATE INDEX person_compiled_truth_updated_at IF NOT EXISTS
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: platform-architecture
3
3
  description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
4
- content-hash: sha256:7ccf57afeb67e23745bc86265d7a65790fd4fc6cba0a659a8fb6ee3f69027e6d
4
+ content-hash: sha256:40fe99e94ab4ff0540965423bb0c7aa6016d438a5503f06aa3ab6608773c694e
5
5
  brand: maxy-code
6
6
  product-name: Maxy
7
7
  ---
@@ -1489,6 +1489,16 @@ Ask naturally:
1489
1489
  - "What did I last discuss about the Acme proposal?"
1490
1490
  - "Who have I met from the fintech conference?"
1491
1491
 
1492
+ ## Thinking tools
1493
+
1494
+ Three slash commands that apply analysis to what's already in your graph:
1495
+
1496
+ **`/challenge <claim>`** — stress-tests an assertion. Maxy searches your graph for nodes that contradict or qualify the claim — nodes that assert the opposite, name exceptions, or add significant caveats — and presents the strongest counter-case it finds. If nothing in your graph challenges the claim, it says so rather than inventing one. Results cite node IDs and relevance scores so you can inspect the sources directly.
1497
+
1498
+ **`/connect <topic-A> <topic-B>`** — finds the bridge. Maxy searches both topics, collects their immediate graph neighborhoods, and looks for nodes they share. If a direct bridge exists it names it in one sentence. If not, it surfaces the closest approach — the two nodes that are semantically nearest across the two sides — and proposes the connection you could draw.
1499
+
1500
+ **`/emerge`** — names the unnamed clusters. Maxy retrieves your KnowledgeDocument and Section nodes that are not yet connected to a Concept node, groups them by shared theme, and proposes a Concept name for each cluster. You approve or skip each proposal one at a time; nothing is written without your confirmation. Clusters of fewer than three nodes are listed at the end as "too small to cluster."
1501
+
1492
1502
  ## Listing and counting
1493
1503
 
1494
1504
  Maxy answers relational questions — "list all my people", "how many tasks do I have", "find the person with email X", "show me the 20 most recently created nodes" — via direct read-only Cypher against your Neo4j. This is faster and more precise than semantic search when the question is "the exact set where", not "things similar to".
@@ -1507,6 +1517,22 @@ The page reads only your own brand's Neo4j — a Maxy device and a Real Agent de
1507
1517
 
1508
1518
  **Typo-proof cypher.** When Maxy runs direct Cypher to answer a relational question, the query is checked against your Neo4j's live label and relationship-type taxonomy before it executes. Cypher that references an unknown name (an edge or label that does not exist in your graph) is rejected for writes and flagged with a warnings header for reads, so Maxy never silently acts on a query that targeted the wrong set of nodes. You should not see this — it runs invisibly — but it is the safety net that stops a fabricated edge name from producing "empty" results that are really just unreachable. Before acting on a bulk operation Maxy surfaces the result count and a sample; if it ever describes a cypher rejection, that means its first attempt was malformed and it corrected itself.
1509
1519
 
1520
+ ## Bi-temporal timeline events
1521
+
1522
+ Every factual statement Maxy extracts from your conversations is stored as a `:TimelineEvent` node. Each event carries two separate timestamps:
1523
+
1524
+ - **`occurredAt`** (valid-time) — when the fact was true in the world. Set from the text itself; can reference a date in the past ("Alice joined in 1990" stores `occurredAt = 1990-01-01`).
1525
+ - **`learnedAt`** (transaction-time) — when the system ingested this event. Always the wall-clock time of the write; never back-dated.
1526
+
1527
+ This distinction lets you ask two qualitatively different questions:
1528
+
1529
+ - *"What happened to Alice in 1990?"* — query by `occurredAt`.
1530
+ - *"What did Maxy learn about Alice last Tuesday?"* — query by `learnedAt`.
1531
+
1532
+ `memory-compiled-truth-history` returns both fields for every timeline event on an entity under the `timelineEvents` array, alongside the compiled-truth revision history in the `revisions` array.
1533
+
1534
+ **Backfill:** Timeline events written before this feature was added have `learnedAt` set to their `createdAt` value by the schema migration. Events without `createdAt` (very old nodes) receive the migration run time as an approximation.
1535
+
1510
1536
  ## Write doctrine
1511
1537
 
1512
1538
  Every new node in Maxy's graph is created with at least one connection to an existing node. A contact connects to the conversation or organisation it came from; a task connects to the session that raised it or the entities it will affect; a session summary connects to the conversation it summarises. A node with no connection is noise — it cannot be attributed, traversed, or explained — so the graph refuses to create one. If Maxy ever tries to record something without a link, the write is rejected and Maxy asks you to clarify where it belongs.
@@ -2572,6 +2598,18 @@ switch them off. Flipping it off again also drops them from any already-
2572
2598
  expanded neighbourhood so a click near a `ToolCall` does not re-introduce
2573
2599
  it.
2574
2600
 
2601
+ ## Direct edge management
2602
+
2603
+ `memory-edge` creates or deletes a typed directed edge between two nodes that already exist in the graph. Both nodes must belong to the same account — mismatched or foreign nodes are rejected with a structured error before any mutation runs.
2604
+
2605
+ **Create:** MERGE is idempotent. First call returns `{created: true}`; a repeated call with the same endpoints and type returns `{created: false}`. Properties supplied on the call are stamped onto the relationship on CREATE only; a subsequent idempotent hit does not overwrite them.
2606
+
2607
+ **Delete:** If the edge is present it is deleted and `{deleted: true}` is returned. If absent, the call is a no-op and returns `{deleted: false}`.
2608
+
2609
+ `relationshipType` is uppercase-coerced. Types that start with an underscore (e.g. `_SOFT_DELETE`) are reserved for platform internals and are rejected.
2610
+
2611
+ Typical flow: call `memory-search` for each endpoint to retrieve their `elementId` values, then call `memory-edge action=create relationshipType=RELATES_TO fromId=<id> toId=<id>`. The new edge appears when you hop-expand either endpoint on the `/graph` canvas.
2612
+
2575
2613
  ---
2576
2614
  # Neo4j Edge Types
2577
2615
  Source: https://docs.getmaxy.com/neo4j.md
@@ -147,3 +147,15 @@ the 90% domain-navigation path stays clean without having to remember to
147
147
  switch them off. Flipping it off again also drops them from any already-
148
148
  expanded neighbourhood so a click near a `ToolCall` does not re-introduce
149
149
  it.
150
+
151
+ ## Direct edge management
152
+
153
+ `memory-edge` creates or deletes a typed directed edge between two nodes that already exist in the graph. Both nodes must belong to the same account — mismatched or foreign nodes are rejected with a structured error before any mutation runs.
154
+
155
+ **Create:** MERGE is idempotent. First call returns `{created: true}`; a repeated call with the same endpoints and type returns `{created: false}`. Properties supplied on the call are stamped onto the relationship on CREATE only; a subsequent idempotent hit does not overwrite them.
156
+
157
+ **Delete:** If the edge is present it is deleted and `{deleted: true}` is returned. If absent, the call is a no-op and returns `{deleted: false}`.
158
+
159
+ `relationshipType` is uppercase-coerced. Types that start with an underscore (e.g. `_SOFT_DELETE`) are reserved for platform internals and are rejected.
160
+
161
+ Typical flow: call `memory-search` for each endpoint to retrieve their `elementId` values, then call `memory-edge action=create relationshipType=RELATES_TO fromId=<id> toId=<id>`. The new edge appears when you hop-expand either endpoint on the `/graph` canvas.
@@ -90,6 +90,16 @@ Ask naturally:
90
90
  - "What did I last discuss about the Acme proposal?"
91
91
  - "Who have I met from the fintech conference?"
92
92
 
93
+ ## Thinking tools
94
+
95
+ Three slash commands that apply analysis to what's already in your graph:
96
+
97
+ **`/challenge <claim>`** — stress-tests an assertion. {{productName}} searches your graph for nodes that contradict or qualify the claim — nodes that assert the opposite, name exceptions, or add significant caveats — and presents the strongest counter-case it finds. If nothing in your graph challenges the claim, it says so rather than inventing one. Results cite node IDs and relevance scores so you can inspect the sources directly.
98
+
99
+ **`/connect <topic-A> <topic-B>`** — finds the bridge. {{productName}} searches both topics, collects their immediate graph neighborhoods, and looks for nodes they share. If a direct bridge exists it names it in one sentence. If not, it surfaces the closest approach — the two nodes that are semantically nearest across the two sides — and proposes the connection you could draw.
100
+
101
+ **`/emerge`** — names the unnamed clusters. {{productName}} retrieves your KnowledgeDocument and Section nodes that are not yet connected to a Concept node, groups them by shared theme, and proposes a Concept name for each cluster. You approve or skip each proposal one at a time; nothing is written without your confirmation. Clusters of fewer than three nodes are listed at the end as "too small to cluster."
102
+
93
103
  ## Listing and counting
94
104
 
95
105
  {{productName}} answers relational questions — "list all my people", "how many tasks do I have", "find the person with email X", "show me the 20 most recently created nodes" — via direct read-only Cypher against your Neo4j. This is faster and more precise than semantic search when the question is "the exact set where", not "things similar to".
@@ -108,6 +118,22 @@ The page reads only your own brand's Neo4j — a Maxy device and a Real Agent de
108
118
 
109
119
  **Typo-proof cypher.** When {{productName}} runs direct Cypher to answer a relational question, the query is checked against your Neo4j's live label and relationship-type taxonomy before it executes. Cypher that references an unknown name (an edge or label that does not exist in your graph) is rejected for writes and flagged with a warnings header for reads, so {{productName}} never silently acts on a query that targeted the wrong set of nodes. You should not see this — it runs invisibly — but it is the safety net that stops a fabricated edge name from producing "empty" results that are really just unreachable. Before acting on a bulk operation {{productName}} surfaces the result count and a sample; if it ever describes a cypher rejection, that means its first attempt was malformed and it corrected itself.
110
120
 
121
+ ## Bi-temporal timeline events
122
+
123
+ Every factual statement {{productName}} extracts from your conversations is stored as a `:TimelineEvent` node. Each event carries two separate timestamps:
124
+
125
+ - **`occurredAt`** (valid-time) — when the fact was true in the world. Set from the text itself; can reference a date in the past ("Alice joined in 1990" stores `occurredAt = 1990-01-01`).
126
+ - **`learnedAt`** (transaction-time) — when the system ingested this event. Always the wall-clock time of the write; never back-dated.
127
+
128
+ This distinction lets you ask two qualitatively different questions:
129
+
130
+ - *"What happened to Alice in 1990?"* — query by `occurredAt`.
131
+ - *"What did {{productName}} learn about Alice last Tuesday?"* — query by `learnedAt`.
132
+
133
+ `memory-compiled-truth-history` returns both fields for every timeline event on an entity under the `timelineEvents` array, alongside the compiled-truth revision history in the `revisions` array.
134
+
135
+ **Backfill:** Timeline events written before this feature was added have `learnedAt` set to their `createdAt` value by the schema migration. Events without `createdAt` (very old nodes) receive the migration run time as an approximation.
136
+
111
137
  ## Write doctrine
112
138
 
113
139
  Every new node in {{productName}}'s graph is created with at least one connection to an existing node. A contact connects to the conversation or organisation it came from; a task connects to the session that raised it or the entities it will affect; a session summary connects to the conversation it summarises. A node with no connection is noise — it cannot be attributed, traversed, or explained — so the graph refuses to create one. If {{productName}} ever tries to record something without a link, the write is rejected and {{productName}} asks you to clarify where it belongs.