@rubytech/create-maxy-code 0.1.132 → 0.1.138
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/dist/__tests__/installer-specialist-registration.test.js +5 -40
- package/dist/index.js +24 -28
- package/dist/specialist-registration.js +0 -15
- package/package.json +1 -1
- package/payload/platform/lib/graph-search/dist/boosts.d.ts +39 -0
- package/payload/platform/lib/graph-search/dist/boosts.d.ts.map +1 -0
- package/payload/platform/lib/graph-search/dist/boosts.js +57 -0
- package/payload/platform/lib/graph-search/dist/boosts.js.map +1 -0
- package/payload/platform/lib/graph-search/dist/dedup.d.ts +29 -0
- package/payload/platform/lib/graph-search/dist/dedup.d.ts.map +1 -0
- package/payload/platform/lib/graph-search/dist/dedup.js +97 -0
- package/payload/platform/lib/graph-search/dist/dedup.js.map +1 -0
- package/payload/platform/lib/graph-search/dist/index.d.ts +70 -8
- package/payload/platform/lib/graph-search/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-search/dist/index.js +323 -62
- package/payload/platform/lib/graph-search/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-search/dist/query-expansion.d.ts +37 -0
- package/payload/platform/lib/graph-search/dist/query-expansion.d.ts.map +1 -0
- package/payload/platform/lib/graph-search/dist/query-expansion.js +101 -0
- package/payload/platform/lib/graph-search/dist/query-expansion.js.map +1 -0
- package/payload/platform/lib/graph-search/dist/route.d.ts +29 -0
- package/payload/platform/lib/graph-search/dist/route.d.ts.map +1 -0
- package/payload/platform/lib/graph-search/dist/route.js +53 -0
- package/payload/platform/lib/graph-search/dist/route.js.map +1 -0
- package/payload/platform/lib/graph-search/dist/rrf-fusion.d.ts +31 -0
- package/payload/platform/lib/graph-search/dist/rrf-fusion.d.ts.map +1 -0
- package/payload/platform/lib/graph-search/dist/rrf-fusion.js +57 -0
- package/payload/platform/lib/graph-search/dist/rrf-fusion.js.map +1 -0
- package/payload/platform/lib/graph-search/src/__tests__/boosts.test.ts +59 -0
- package/payload/platform/lib/graph-search/src/__tests__/dedup.test.ts +83 -0
- package/payload/platform/lib/graph-search/src/__tests__/query-expansion.test.ts +96 -0
- package/payload/platform/lib/graph-search/src/__tests__/route.test.ts +62 -0
- package/payload/platform/lib/graph-search/src/__tests__/rrf-fusion.test.ts +39 -0
- package/payload/platform/lib/graph-search/src/boosts.ts +61 -0
- package/payload/platform/lib/graph-search/src/dedup.ts +108 -0
- package/payload/platform/lib/graph-search/src/index.ts +407 -65
- package/payload/platform/lib/graph-search/src/query-expansion.ts +129 -0
- package/payload/platform/lib/graph-search/src/route.ts +70 -0
- package/payload/platform/lib/graph-search/src/rrf-fusion.ts +62 -0
- package/payload/platform/neo4j/schema.cypher +80 -1
- package/payload/platform/plugins/admin/hooks/session-end-retrospective.sh +4 -2
- package/payload/platform/plugins/admin/mcp/dist/index.js +6 -4
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/docs/references/internals.md +36 -10
- package/payload/platform/plugins/docs/references/neo4j.md +63 -0
- package/payload/platform/plugins/docs/references/session-retrospective.md +11 -4
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js +2 -2
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/index.js +114 -5
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-gate-property.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-gate-property.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-gate-property.test.js +25 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-gate-property.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/timeline-extractor.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/timeline-extractor.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/timeline-extractor.test.js +70 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/timeline-extractor.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/typed-edge-schema.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/typed-edge-schema.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/typed-edge-schema.test.js +50 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/typed-edge-schema.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.d.ts +33 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.js +99 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.d.ts +21 -0
- 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 +24 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/timeline-extractor.d.ts +13 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/timeline-extractor.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/timeline-extractor.js +151 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/timeline-extractor.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.d.ts +55 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.js +290 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.d.ts +19 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.js +102 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-fields.test.js +18 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-fields.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.js +5 -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 +2 -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-search.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js +14 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.d.ts +29 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.js +22 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts +31 -1
- 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 +182 -50
- 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 +76 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.d.ts +30 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.js +129 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/scripts/backfill-typed-edges.ts +72 -0
- package/payload/platform/plugins/memory/mcp/scripts/generate-edge-docs.ts +75 -0
- package/payload/platform/plugins/memory/mcp/vitest.config.ts +5 -0
- package/payload/platform/plugins/memory/references/schema-base.md +44 -0
- package/payload/platform/scripts/seed-neo4j.sh +49 -0
- package/payload/platform/templates/agents/admin/IDENTITY.md +4 -3
- package/payload/platform/templates/specialists/agents/database-operator.md +9 -1
- package/payload/server/{adminuser-self-heal-QAWOZ3JV.js → adminuser-self-heal-YC47O34W.js} +2 -1
- package/payload/server/{chunk-JSBRDJBE.js → chunk-HYQNUVGO.js} +9 -1
- package/payload/server/{chunk-HCYM5FLU.js → chunk-T3DJD5QR.js} +3 -1
- package/payload/server/maxy-edge.js +3 -2
- package/payload/server/public/assets/{admin-FcRHAL-3.js → admin-BM9aXUUh.js} +1 -1
- package/payload/server/public/assets/{data-Ds37mflX.js → data-Ds2ECtfP.js} +1 -1
- package/payload/server/public/assets/{graph-CmWRhaiS.js → graph-BzxObKMM.js} +1 -1
- package/payload/server/public/assets/{graph-labels-Ch2r00Gt.js → graph-labels-Dwgep_nr.js} +1 -1
- package/payload/server/public/assets/{page-BcHhJXUt.js → page-BlkBsjP2.js} +1 -1
- package/payload/server/public/assets/page-h_SA5SVQ.js +51 -0
- package/payload/server/public/data.html +3 -3
- package/payload/server/public/graph.html +3 -3
- package/payload/server/public/index.html +4 -4
- package/payload/server/server.js +803 -76
- package/payload/server/public/assets/page-BOtNny_4.js +0 -51
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-query expansion via Haiku (Task 308).
|
|
3
|
+
*
|
|
4
|
+
* Generates 3-5 paraphrases of the user query so the hybrid pipeline runs
|
|
5
|
+
* each paraphrase against vector + BM25 in parallel and unions the
|
|
6
|
+
* candidate sets before fusion. Recovers recall on terse queries where the
|
|
7
|
+
* embedding's nearest-neighbour is unstable.
|
|
8
|
+
*
|
|
9
|
+
* The expansion runs through `@rubytech/oauth-llm` against Claude Haiku —
|
|
10
|
+
* the same OAuth-bearer path the admin classifiers use; no new SDK
|
|
11
|
+
* dependency and no Anthropic-API-key path. Cache is in-process, keyed by
|
|
12
|
+
* (accountId, normalisedQuery, retrievalClass), with a 1-hour TTL.
|
|
13
|
+
*
|
|
14
|
+
* Graceful degrade: if the LLM call fails, returns just the original
|
|
15
|
+
* query. The caller's hybrid pipeline still runs — just on one query
|
|
16
|
+
* instead of N — so missing paraphrases are a recall regression, not a
|
|
17
|
+
* fault.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { callOauthLlm } from "../../oauth-llm/dist/index.js";
|
|
21
|
+
|
|
22
|
+
const CACHE_TTL_MS = 60 * 60 * 1000;
|
|
23
|
+
const HAIKU_MODEL = "claude-haiku-4-5";
|
|
24
|
+
const MAX_PARAPHRASES = 5;
|
|
25
|
+
|
|
26
|
+
interface CacheEntry {
|
|
27
|
+
expansions: string[];
|
|
28
|
+
expiresAt: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const cache = new Map<string, CacheEntry>();
|
|
32
|
+
|
|
33
|
+
export function clearExpansionCache(): void {
|
|
34
|
+
cache.clear();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ExpandQueryParams {
|
|
38
|
+
query: string;
|
|
39
|
+
retrievalClass?: string;
|
|
40
|
+
accountId?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Override the cache or skip it entirely (useful in tests). When
|
|
43
|
+
* `false`, the cache is read but never written; when `true` (default),
|
|
44
|
+
* both reads and writes are active.
|
|
45
|
+
*/
|
|
46
|
+
useCache?: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Return `[query, ...paraphrases]` (original always first). On LLM
|
|
51
|
+
* failure returns `[query]` only and writes a single stderr line so the
|
|
52
|
+
* operator can see expansion is degrading without breaking search.
|
|
53
|
+
*/
|
|
54
|
+
export async function expandQuery(
|
|
55
|
+
params: ExpandQueryParams,
|
|
56
|
+
): Promise<string[]> {
|
|
57
|
+
const { query, retrievalClass = "general", accountId = "", useCache = true } = params;
|
|
58
|
+
const normalised = query.trim().toLowerCase();
|
|
59
|
+
const cacheKey = `${accountId}::${retrievalClass}::${normalised}`;
|
|
60
|
+
|
|
61
|
+
if (useCache) {
|
|
62
|
+
const hit = cache.get(cacheKey);
|
|
63
|
+
if (hit && hit.expiresAt > Date.now()) {
|
|
64
|
+
return [query, ...hit.expansions];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const systemPrompt = [
|
|
69
|
+
"You generate paraphrases of a user query for hybrid graph search.",
|
|
70
|
+
"Return 3-5 alternative phrasings that preserve the user's intent but vary the surface form (synonyms, word order, entity-vs-attribute framing).",
|
|
71
|
+
"Do not change the entity names. Do not invent constraints the user did not state.",
|
|
72
|
+
"Output ONLY a JSON object via the provided tool; never free text.",
|
|
73
|
+
].join("\n");
|
|
74
|
+
|
|
75
|
+
const classHint = retrievalClass === "general"
|
|
76
|
+
? ""
|
|
77
|
+
: `The retrieval class is '${retrievalClass}' — tailor paraphrases accordingly.`;
|
|
78
|
+
const userMessage = [
|
|
79
|
+
`Original query: ${query}`,
|
|
80
|
+
classHint,
|
|
81
|
+
].filter((s) => s.length > 0).join("\n");
|
|
82
|
+
|
|
83
|
+
const result = await callOauthLlm({
|
|
84
|
+
model: HAIKU_MODEL,
|
|
85
|
+
system: systemPrompt,
|
|
86
|
+
userMessage,
|
|
87
|
+
maxTokens: 512,
|
|
88
|
+
timeoutMs: 15_000,
|
|
89
|
+
tools: [
|
|
90
|
+
{
|
|
91
|
+
name: "emit_paraphrases",
|
|
92
|
+
description: "Emit 3-5 paraphrases of the user query.",
|
|
93
|
+
input_schema: {
|
|
94
|
+
type: "object",
|
|
95
|
+
properties: {
|
|
96
|
+
paraphrases: {
|
|
97
|
+
type: "array",
|
|
98
|
+
items: { type: "string" },
|
|
99
|
+
minItems: 3,
|
|
100
|
+
maxItems: MAX_PARAPHRASES,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
required: ["paraphrases"],
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
toolChoiceName: "emit_paraphrases",
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (result.kind !== "ok-tool") {
|
|
111
|
+
process.stderr.write(
|
|
112
|
+
`[graph-search:expansion-failed] cause=${result.kind === "fallback" ? result.cause : "unexpected"} query=${query.slice(0, 60).replace(/\s+/g, " ")}\n`,
|
|
113
|
+
);
|
|
114
|
+
return [query];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const raw = (result.input as { paraphrases?: unknown }).paraphrases;
|
|
118
|
+
if (!Array.isArray(raw)) return [query];
|
|
119
|
+
const expansions = raw
|
|
120
|
+
.filter((p): p is string => typeof p === "string" && p.trim().length > 0)
|
|
121
|
+
.map((p) => p.trim())
|
|
122
|
+
.slice(0, MAX_PARAPHRASES);
|
|
123
|
+
|
|
124
|
+
if (useCache) {
|
|
125
|
+
cache.set(cacheKey, { expansions, expiresAt: Date.now() + CACHE_TTL_MS });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return [query, ...expansions];
|
|
129
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrieval-class routing (Task 308).
|
|
3
|
+
*
|
|
4
|
+
* Consumes the `retrievalClass` field produced by Task 304's
|
|
5
|
+
* gateway-classifier (landed) and returns a per-class index plan: which
|
|
6
|
+
* labels to filter to, which fusion weights to favour, and whether to
|
|
7
|
+
* skip the lookup entirely.
|
|
8
|
+
*
|
|
9
|
+
* No new Haiku call here — this module is a pure deterministic mapping
|
|
10
|
+
* over the gateway's existing classification.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type RetrievalClass =
|
|
14
|
+
| "entity"
|
|
15
|
+
| "temporal"
|
|
16
|
+
| "event"
|
|
17
|
+
| "general"
|
|
18
|
+
| "none";
|
|
19
|
+
|
|
20
|
+
export interface IndexPlan {
|
|
21
|
+
/** When true, the caller short-circuits and returns no results. */
|
|
22
|
+
skip: boolean;
|
|
23
|
+
/** Restrict vector + BM25 to these labels. Undefined means no filter. */
|
|
24
|
+
labelFilter?: string[];
|
|
25
|
+
/** Weight on the vector half of the (weighted-sum) fusion. */
|
|
26
|
+
vectorWeight: number;
|
|
27
|
+
/** Weight on the BM25 half of the (weighted-sum) fusion. */
|
|
28
|
+
bm25Weight: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Return the index plan for a retrieval class. Unknown values fall back
|
|
33
|
+
* to `general` rather than throwing — the routing is a soft hint and the
|
|
34
|
+
* caller's old behaviour is the safe default.
|
|
35
|
+
*/
|
|
36
|
+
export function pickIndexMix(retrievalClass: string | undefined): IndexPlan {
|
|
37
|
+
switch (retrievalClass) {
|
|
38
|
+
case "entity":
|
|
39
|
+
return {
|
|
40
|
+
skip: false,
|
|
41
|
+
labelFilter: ["Person", "Company", "Concept"],
|
|
42
|
+
vectorWeight: 0.85,
|
|
43
|
+
bm25Weight: 0.15,
|
|
44
|
+
};
|
|
45
|
+
case "temporal":
|
|
46
|
+
return {
|
|
47
|
+
skip: false,
|
|
48
|
+
labelFilter: ["Event"],
|
|
49
|
+
vectorWeight: 0.2,
|
|
50
|
+
bm25Weight: 0.8,
|
|
51
|
+
};
|
|
52
|
+
case "event":
|
|
53
|
+
return {
|
|
54
|
+
skip: false,
|
|
55
|
+
labelFilter: ["Event"],
|
|
56
|
+
vectorWeight: 0,
|
|
57
|
+
bm25Weight: 1,
|
|
58
|
+
};
|
|
59
|
+
case "none":
|
|
60
|
+
return { skip: true, vectorWeight: 0, bm25Weight: 0 };
|
|
61
|
+
case "general":
|
|
62
|
+
default:
|
|
63
|
+
if (retrievalClass && retrievalClass !== "general") {
|
|
64
|
+
process.stderr.write(
|
|
65
|
+
`[graph-search:unknown-class] rc=${retrievalClass} fallback=general\n`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return { skip: false, vectorWeight: 0.7, bm25Weight: 0.3 };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reciprocal Rank Fusion (Task 308).
|
|
3
|
+
*
|
|
4
|
+
* Replaces the weighted-sum fusion (`0.7 * vector + 0.3 * bm25_norm`) when
|
|
5
|
+
* the `MAXY_GS_RRF` flag is on. RRF is robust to score-distribution drift
|
|
6
|
+
* between the vector and BM25 indexes — it sums `1 / (k + rank_i)` for each
|
|
7
|
+
* node across the ranked lists it appears in. Higher rank (lower index) →
|
|
8
|
+
* larger contribution; the constant `k` (default 60, per the gbrain
|
|
9
|
+
* default) smooths the long tail.
|
|
10
|
+
*
|
|
11
|
+
* The weighted-sum baseline stays as the fallback so flag flips are
|
|
12
|
+
* reversible without code edits.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface FusionCandidate {
|
|
16
|
+
nodeId: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Fuse multiple ranked lists by Reciprocal Rank Fusion.
|
|
21
|
+
*
|
|
22
|
+
* Each input list is treated as already sorted (best first). For each
|
|
23
|
+
* candidate, score = sum over lists of `1 / (k + rank)`. Returns the union
|
|
24
|
+
* sorted by descending RRF score.
|
|
25
|
+
*
|
|
26
|
+
* Ties on RRF score are broken by the candidate's earliest appearance
|
|
27
|
+
* across the input lists (first-seen wins), which is deterministic given
|
|
28
|
+
* a deterministic input order.
|
|
29
|
+
*/
|
|
30
|
+
export function fuseRrf<T extends FusionCandidate>(
|
|
31
|
+
rankedLists: T[][],
|
|
32
|
+
k = 60,
|
|
33
|
+
): Array<T & { rrfScore: number }> {
|
|
34
|
+
const scores = new Map<string, number>();
|
|
35
|
+
const representatives = new Map<string, T>();
|
|
36
|
+
const firstSeen = new Map<string, number>();
|
|
37
|
+
let order = 0;
|
|
38
|
+
|
|
39
|
+
for (const list of rankedLists) {
|
|
40
|
+
for (let rank = 0; rank < list.length; rank++) {
|
|
41
|
+
const candidate = list[rank];
|
|
42
|
+
const contribution = 1 / (k + rank);
|
|
43
|
+
const prior = scores.get(candidate.nodeId) ?? 0;
|
|
44
|
+
scores.set(candidate.nodeId, prior + contribution);
|
|
45
|
+
if (!representatives.has(candidate.nodeId)) {
|
|
46
|
+
representatives.set(candidate.nodeId, candidate);
|
|
47
|
+
firstSeen.set(candidate.nodeId, order++);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const out: Array<T & { rrfScore: number }> = [];
|
|
53
|
+
for (const [nodeId, rrfScore] of scores) {
|
|
54
|
+
const rep = representatives.get(nodeId)!;
|
|
55
|
+
out.push({ ...rep, rrfScore });
|
|
56
|
+
}
|
|
57
|
+
out.sort((a, b) => {
|
|
58
|
+
if (b.rrfScore !== a.rrfScore) return b.rrfScore - a.rrfScore;
|
|
59
|
+
return (firstSeen.get(a.nodeId)! - firstSeen.get(b.nodeId)!);
|
|
60
|
+
});
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
// Maxy Phase 0 — Neo4j Graph Schema
|
|
3
3
|
// Schema.org compliant node types
|
|
4
4
|
// ============================================================
|
|
5
|
+
//
|
|
6
|
+
// This file declares INDEXES and CONSTRAINTS only. The legal
|
|
7
|
+
// (sourceLabel, EDGE_TYPE, targetLabel) triples for the typed-edge
|
|
8
|
+
// auto-extraction pass (Task 305) live in
|
|
9
|
+
// `platform/plugins/memory/mcp/src/lib/typed-edge-schema.ts`
|
|
10
|
+
// as the TYPED_EDGE_ALLOWLIST constant. The Haiku prompt, the inline
|
|
11
|
+
// validator, and the public docs are all generated from that one
|
|
12
|
+
// constant; do not duplicate the enumeration here.
|
|
5
13
|
|
|
6
14
|
// ----------------------------------------------------------
|
|
7
15
|
// Constraints
|
|
@@ -361,7 +369,12 @@ ON EACH [n.name, n.firstName, n.lastName, n.givenName, n.familyName,
|
|
|
361
369
|
n.email, n.note, n.label, n.value, n.message, n.preview, n.tagline,
|
|
362
370
|
n.subject, n.bodyPreview, n.fromName, n.fromAddress, n.agentAddress, n.screeningReason,
|
|
363
371
|
n.authority, n.contactValue, n.toolName,
|
|
364
|
-
n.displayName, n.slug, n.role
|
|
372
|
+
n.displayName, n.slug, n.role,
|
|
373
|
+
n.compiledTruth];
|
|
374
|
+
// `compiledTruth` (Task 306) is the authoritative-summary property the
|
|
375
|
+
// compiled-truth + timeline knowledge model writes on Person/Company/Concept.
|
|
376
|
+
// Indexed here so Task 308's compiledTruth boost path also benefits from BM25
|
|
377
|
+
// hits against the summary text. No-op until 306 writes the property.
|
|
365
378
|
|
|
366
379
|
// Project node — a standalone creative-output node distinct from
|
|
367
380
|
// :Section. Anchored via (:UserProfile)-[:CREATED]->(:Project), with optional
|
|
@@ -1345,3 +1358,69 @@ OPTIONS {
|
|
|
1345
1358
|
`vector.similarity_function`: 'cosine'
|
|
1346
1359
|
}
|
|
1347
1360
|
};
|
|
1361
|
+
|
|
1362
|
+
// ----------------------------------------------------------
|
|
1363
|
+
// Compiled-truth + timeline knowledge model (Task 306) —
|
|
1364
|
+
// gbrain's "compiled truth above the rule, append-only timeline
|
|
1365
|
+
// below" pattern. Every :Person / :Organization / :Concept carries
|
|
1366
|
+
// an agent-rewritten summary (`compiledTruth`, ≤2000 chars) plus
|
|
1367
|
+
// a history of dated `:TimelineEvent` rows path-anchored via
|
|
1368
|
+
// `(entity)-[:HAS_TIMELINE_EVENT]->(:TimelineEvent)`. The
|
|
1369
|
+
// compiledTruth is what the agent reads when it needs a summary;
|
|
1370
|
+
// the timeline is what it cites and appends to.
|
|
1371
|
+
//
|
|
1372
|
+
// Naming:
|
|
1373
|
+
// :TimelineEvent — distinct from :Event (scheduling plugin's
|
|
1374
|
+
// schema:Event with eventId-unique). Collisions were the reason
|
|
1375
|
+
// to fork; do not blend.
|
|
1376
|
+
// :HAS_TIMELINE_EVENT — distinct from :HAS_EVENT (visitor analytics
|
|
1377
|
+
// Session→PageView/Click/ScrollMilestone).
|
|
1378
|
+
//
|
|
1379
|
+
// MERGE doctrine: writers MUST path-anchor from the entity to defend
|
|
1380
|
+
// cross-account collisions, e.g.
|
|
1381
|
+
// MATCH (e) WHERE elementId(e) = $entityId AND e.accountId = $accountId
|
|
1382
|
+
// MERGE (e)-[:HAS_TIMELINE_EVENT]->(t:TimelineEvent {timelineEventId: $id})
|
|
1383
|
+
// Never label-only MERGE on :TimelineEvent.
|
|
1384
|
+
//
|
|
1385
|
+
// `kind` enum (5 values, no catch-all):
|
|
1386
|
+
// mentioned | met | quoted | attended | transacted
|
|
1387
|
+
// Extractor refuses on ambiguity rather than collapsing to "other".
|
|
1388
|
+
//
|
|
1389
|
+
// `compiledTruth` source:
|
|
1390
|
+
// compiledTruthSource = "rewriter" (default) — compiled-truth-rewriter
|
|
1391
|
+
// library wrote it.
|
|
1392
|
+
// compiledTruthSource = "operator" — operator overrode via the graph UI.
|
|
1393
|
+
// Rewriter refuses to overwrite for 7 days unless forceRewrite=true.
|
|
1394
|
+
// ----------------------------------------------------------
|
|
1395
|
+
|
|
1396
|
+
CREATE CONSTRAINT timeline_event_id_unique IF NOT EXISTS
|
|
1397
|
+
FOR (t:TimelineEvent) REQUIRE t.timelineEventId IS UNIQUE;
|
|
1398
|
+
|
|
1399
|
+
CREATE INDEX timeline_event_account IF NOT EXISTS
|
|
1400
|
+
FOR (t:TimelineEvent) ON (t.accountId);
|
|
1401
|
+
|
|
1402
|
+
CREATE INDEX timeline_event_occurred_at IF NOT EXISTS
|
|
1403
|
+
FOR (t:TimelineEvent) ON (t.occurredAt);
|
|
1404
|
+
|
|
1405
|
+
CREATE INDEX timeline_event_kind IF NOT EXISTS
|
|
1406
|
+
FOR (t:TimelineEvent) ON (t.kind);
|
|
1407
|
+
|
|
1408
|
+
CREATE VECTOR INDEX timeline_event_embedding IF NOT EXISTS
|
|
1409
|
+
FOR (t:TimelineEvent) ON (t.embedding)
|
|
1410
|
+
OPTIONS {
|
|
1411
|
+
indexConfig: {
|
|
1412
|
+
`vector.dimensions`: 768,
|
|
1413
|
+
`vector.similarity_function`: 'cosine'
|
|
1414
|
+
}
|
|
1415
|
+
};
|
|
1416
|
+
|
|
1417
|
+
// `compiledTruthUpdatedAt` indices for stale-detection scans
|
|
1418
|
+
// (e.g. "find :Person with truth older than 7 days that has new events").
|
|
1419
|
+
CREATE INDEX person_compiled_truth_updated_at IF NOT EXISTS
|
|
1420
|
+
FOR (p:Person) ON (p.compiledTruthUpdatedAt);
|
|
1421
|
+
|
|
1422
|
+
CREATE INDEX organization_compiled_truth_updated_at IF NOT EXISTS
|
|
1423
|
+
FOR (o:Organization) ON (o.compiledTruthUpdatedAt);
|
|
1424
|
+
|
|
1425
|
+
CREATE INDEX concept_compiled_truth_updated_at IF NOT EXISTS
|
|
1426
|
+
FOR (c:Concept) ON (c.compiledTruthUpdatedAt);
|
|
@@ -254,7 +254,7 @@ fi
|
|
|
254
254
|
emit_log "gate-blocked sessionId=${SESSION_ID} reason=sentinel-absent"
|
|
255
255
|
|
|
256
256
|
cat >&2 <<'INSTRUCTION'
|
|
257
|
-
The operator signalled session end. Before this session can close, run the
|
|
257
|
+
The operator signalled session end. Before this session can close, run the four-pass retrospective inside this same admin session — do not spawn a new session, do not background anything. Use the tools you already have.
|
|
258
258
|
|
|
259
259
|
Pass 1 — Technical learnings. Walk this session for operator corrections and self-detected mistakes. For each one, delegate one graph write to `database-operator` via the Task tool, persisting a Learning or Correction node per the schema. Count what you write.
|
|
260
260
|
|
|
@@ -262,7 +262,9 @@ Pass 2 — Operator-relationship updates. Walk this session for tonal signals, r
|
|
|
262
262
|
|
|
263
263
|
Pass 3 — Graph completeness sweep. Walk this session for any node, edge, or commitment that was discussed but not written to the graph in-flight. For each missing write, delegate to `database-operator` via the Task tool. Count what you write.
|
|
264
264
|
|
|
265
|
-
|
|
265
|
+
Pass 4 — Typed-edge auto-extraction. Delegate to `database-operator` via the Task tool with a prompt naming the typed-edge pass and the session-start timestamp. The specialist calls `memory-typed-edge-pass` with that timestamp and returns the counters. Capture `nodesProcessed` and the accepted count from its reply.
|
|
266
|
+
|
|
267
|
+
Then call `session-retrospective-mark-complete` with all five counts (learningsCount, preferencesCount, graphWriteCount, typedEdgesCount, nodesProcessed) and reply with one short paragraph in your house voice summarising what you wrote.
|
|
266
268
|
INSTRUCTION
|
|
267
269
|
|
|
268
270
|
exit 2
|
|
@@ -3094,13 +3094,15 @@ eagerTool(server, "action-edit", "Edit a pending action's input and then execute
|
|
|
3094
3094
|
// trigger → gate-blocked → complete → gate-released
|
|
3095
3095
|
// against one operator sessionId. The release decision itself is the
|
|
3096
3096
|
// JSONL grep — never the log line.
|
|
3097
|
-
server.tool("session-retrospective-mark-complete", "Marks the end-of-session retrospective complete. Call this AFTER walking the session for technical learnings, operator-relationship updates,
|
|
3097
|
+
server.tool("session-retrospective-mark-complete", "Marks the end-of-session retrospective complete. Call this AFTER walking the session for technical learnings, operator-relationship updates, missing graph writes, and the typed-edge auto-extraction pass — and persisting each one (Task-tool delegations to database-operator for graph writes and the typed-edge pass, Write to SOUL.md for tonal updates). The counts you pass are the number of items you actually persisted in each pass. The session-end Stop hook gate releases on the presence of this tool_use in the transcript — calling it is the deterministic signal that the retrospective ran.", {
|
|
3098
3098
|
learningsCount: z.number().int().nonnegative().describe("Technical-learning / correction nodes you delegated to database-operator in this retrospective."),
|
|
3099
3099
|
preferencesCount: z.number().int().nonnegative().describe("SOUL.md writes you made for tonal / working-style observations in this retrospective."),
|
|
3100
3100
|
graphWriteCount: z.number().int().nonnegative().describe("Graph nodes/edges/commitments you delegated to database-operator for content that surfaced in the session but was not written in-flight."),
|
|
3101
|
-
|
|
3101
|
+
typedEdgesCount: z.number().int().nonnegative().describe("Typed edges accepted (MERGEd) by the typed-edge auto-extraction pass that database-operator ran in this retrospective. 0 is valid (no prose nodes touched or none yielded edges)."),
|
|
3102
|
+
nodesProcessed: z.number().int().nonnegative().describe("Prose-bearing nodes the typed-edge pass enumerated and scanned in this retrospective. 0 is valid (no prose nodes since the session started)."),
|
|
3103
|
+
}, async ({ learningsCount, preferencesCount, graphWriteCount, typedEdgesCount, nodesProcessed }) => {
|
|
3102
3104
|
const sessionId = process.env.SESSION_ID || "unknown";
|
|
3103
|
-
const line = `complete sessionId=${sessionId} learningsCount=${learningsCount} preferencesCount=${preferencesCount} graphWriteCount=${graphWriteCount}`;
|
|
3105
|
+
const line = `complete sessionId=${sessionId} learningsCount=${learningsCount} preferencesCount=${preferencesCount} graphWriteCount=${graphWriteCount} typedEdgesCount=${typedEdgesCount} nodesProcessed=${nodesProcessed}`;
|
|
3104
3106
|
if (!PLATFORM_PORT) {
|
|
3105
3107
|
// Module-init deliberately tolerates a missing PLATFORM_PORT
|
|
3106
3108
|
// (lines ~62-71) so the MCP server does not loud-fail on plugin-
|
|
@@ -3131,7 +3133,7 @@ server.tool("session-retrospective-mark-complete", "Marks the end-of-session ret
|
|
|
3131
3133
|
return {
|
|
3132
3134
|
content: [{
|
|
3133
3135
|
type: "text",
|
|
3134
|
-
text: `Retrospective marked complete (learnings=${learningsCount} preferences=${preferencesCount} graphWrites=${graphWriteCount}). Session is now free to end.`,
|
|
3136
|
+
text: `Retrospective marked complete (learnings=${learningsCount} preferences=${preferencesCount} graphWrites=${graphWriteCount} typedEdges=${typedEdgesCount} nodesProcessed=${nodesProcessed}). Session is now free to end.`,
|
|
3135
3137
|
}],
|
|
3136
3138
|
};
|
|
3137
3139
|
});
|