@rubytech/create-maxy-code 0.1.153 → 0.1.155
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/platform/plugins/linkedin-import/skills/linkedin-import/SKILL.md +2 -2
- package/payload/platform/plugins/linkedin-import/skills/linkedin-import/references/connections.md +2 -3
- package/payload/platform/plugins/linkedin-import/skills/linkedin-import/references/profile.md +1 -2
- package/payload/platform/plugins/memory/skills/concept-synthesis/SKILL.md +0 -1
- package/payload/platform/plugins/memory/skills/conversation-archive/SKILL.md +0 -2
- package/payload/platform/plugins/memory/skills/conversation-archive-enrich/SKILL.md +9 -14
- package/payload/platform/plugins/memory/skills/document-ingest/SKILL.md +0 -1
- package/payload/platform/templates/specialists/agents/librarian.md +1 -1
package/package.json
CHANGED
|
@@ -67,10 +67,10 @@ When the owner is an external Person (non-operator archive), the anchor is the c
|
|
|
67
67
|
1. Run the owner-confirmation flow, persist `$ownerUserId` / `$ownerPersonId`. The owner identity resolves to a single `ownerNodeId` (elementId of the AdminUser or external Person) used in every write call.
|
|
68
68
|
2. For each file the operator approves, load its reference, parse the CSV into typed `rows[]` matching the reference's row schema.
|
|
69
69
|
3. **Selective-ingest gate.** Before invoking any write tool, check the parsed row count against the reference's `selectiveIngestThreshold`. If the count exceeds the threshold, pause and ask the operator to filter the import along the natural axes named in the reference (for `Connections.csv`: Company, Position, Connected On). Apply the filter to `rows[]` before continuing. Compress on write, never after — a 5,000-row blanket import is a landfill, a 200-row filtered import is signal. See [§Selective-ingest](#selective-ingest-threshold-bulk-archives).
|
|
70
|
-
4. Invoke the deterministic write tool the reference names. For all archive references this is `
|
|
70
|
+
4. Invoke the deterministic write tool the reference names. For all archive references this is `mcp__plugin_memory_memory__memory-archive-write` with `{archiveType, ownerNodeId, rows}` — the Cypher body is fixed server-side per `archiveType`, so the agent supplies parsed rows, never Cypher. The tool batches rows at 500 per transaction internally.
|
|
71
71
|
5. After each file emit `[linkedin-import] file=<name> rows=<n> created=<n> matched=<n> ms=<elapsed>` using the counters returned by the write tool.
|
|
72
72
|
|
|
73
|
-
**Doctrine:** raw Cypher and `cypher-shell` invocations are forbidden in this skill and its references. Writes route through `
|
|
73
|
+
**Doctrine:** raw Cypher and `cypher-shell` invocations are forbidden in this skill and its references. Writes route through `mcp__plugin_memory_memory__memory-archive-write` (bulk archives) or `mcp__plugin_memory_memory__memory-write` / `mcp__plugin_memory_memory__memory-update` (single-node enrichments like `profile.md`). If a CSV needs a write shape no current MCP tool supports, file a task to extend `memory-archive-write` with a new `archiveType` handler — never improvise via Bash. See [librarian's LOUD-FAIL prerogative](../../../../templates/specialists/agents/librarian.md#prerogatives).
|
|
74
74
|
|
|
75
75
|
**LOUD-FAIL on parse errors (structurally enforced).** When LinkedIn parser tools land that follow the `mcp__*__*-import-parse` naming convention, the harness-level `platform/plugins/admin/hooks/archive-ingest-gate.sh` will record an `isError: true` response and block every subsequent tool call this turn until the operator submits the next prompt. The hook also denies edits to `platform/plugins/*/lib/*` and JavaScript test runners (`vitest`, `bun test`, `npm test`, `npx jest`) unconditionally. The skill's "no Bash improvisation" doctrine above is the contract; the hook is the enforcement. See [.docs/hooks.md](../../../../../.docs/hooks.md) for the full gate surface.
|
|
76
76
|
|
package/payload/platform/plugins/linkedin-import/skills/linkedin-import/references/connections.md
CHANGED
|
@@ -52,7 +52,7 @@ Resolved at skill start via the owner-confirmation flow. The owner is either an
|
|
|
52
52
|
|
|
53
53
|
## Write surface
|
|
54
54
|
|
|
55
|
-
This reference invokes a single MCP tool: `
|
|
55
|
+
This reference invokes a single MCP tool: `mcp__plugin_memory_memory__memory-archive-write` with `archiveType: "linkedin-connections"`. The Cypher body — Person upsert by `linkedinUrl`, `CONNECTED_ON_LINKEDIN` edge from owner, optional `:Organization` + `WORKS_FOR` when company is non-empty — lives server-side in [`platform/plugins/memory/mcp/src/tools/memory-archive-write.ts`](../../../../memory/mcp/src/tools/memory-archive-write.ts). The agent does not author or pipe Cypher; it parses CSV rows into the tool's row schema and dispatches one (or more, for filtered re-imports) tool call.
|
|
56
56
|
|
|
57
57
|
### Tool input shape
|
|
58
58
|
|
|
@@ -110,7 +110,6 @@ Rows missing a position but present with a company produce a `WORKS_FOR` edge wi
|
|
|
110
110
|
|
|
111
111
|
## Post-import verification (operator-side, not agent-side)
|
|
112
112
|
|
|
113
|
-
After ingest, the operator can verify counts via the `specialists:librarian` specialist's read tools — `mcp__memory__memory-search` with `labels: ["Person"]` plus a filter, or a direct read query through `mcp__graph__maxy-graph-read_neo4j_cypher`:
|
|
114
113
|
|
|
115
114
|
```cypher
|
|
116
115
|
// Owner → connections count
|
|
@@ -132,4 +131,4 @@ These are **read queries**, not writes. Cypher writes from the agent are forbidd
|
|
|
132
131
|
| Tool error "row connectedOn is not ISO 8601" | Parser left `Connected On` in `"23 Apr 2026"` form | Convert to `YYYY-MM-DD` before passing to the tool |
|
|
133
132
|
| Tool error "ownerNodeId not found" | Owner-confirmation flow not run, or operator typed the wrong id | Re-run owner confirmation; pass the resulting `elementId` as `ownerNodeId` |
|
|
134
133
|
| `WORKS_FOR` count « connection count | Many rows have blank company | Expected — LinkedIn doesn't force connections to list a current employer |
|
|
135
|
-
| Tool not present in `init` frame | `specialists:librarian` spawned without the `
|
|
134
|
+
| Tool not present in `init` frame | `specialists:librarian` spawned without the `mcp__plugin_memory_memory__memory-archive-write` token | Loud-fail per librarian's prerogatives. Do not improvise via Bash. Operator must remediate (re-seed specialist templates) |
|
package/payload/platform/plugins/linkedin-import/skills/linkedin-import/references/profile.md
CHANGED
|
@@ -34,7 +34,7 @@ The owner-confirmation flow at the start of the skill resolves the operator's `:
|
|
|
34
34
|
|
|
35
35
|
## Write surface
|
|
36
36
|
|
|
37
|
-
This reference invokes `
|
|
37
|
+
This reference invokes `mcp__plugin_memory_memory__memory-update` once with the parsed row's properties:
|
|
38
38
|
|
|
39
39
|
```json
|
|
40
40
|
{
|
|
@@ -82,7 +82,6 @@ The parser converts:
|
|
|
82
82
|
|
|
83
83
|
## Post-import verification (operator-side, read-only)
|
|
84
84
|
|
|
85
|
-
A direct read query through `mcp__memory__memory-search` (`labels: ["UserProfile"]`) or `mcp__graph__maxy-graph-read_neo4j_cypher`:
|
|
86
85
|
|
|
87
86
|
```cypher
|
|
88
87
|
MATCH (au:AdminUser {userId: $ownerUserId})-[:HAS_PROFILE]->(up:UserProfile)
|
|
@@ -36,7 +36,6 @@ Present one row per cluster, in a numbered list. The owner picks one of three ac
|
|
|
36
36
|
|
|
37
37
|
## Method
|
|
38
38
|
|
|
39
|
-
1. **Pull the candidate set.** Call `memory-search` for the topic phrase, or `mcp__graph__maxy-graph-read_neo4j_cypher` for a project or date range. Bound the set: never cluster more than 500 nodes in one run. If the set is larger, ask the owner to narrow. Pass `fields: ["name", "summary"]` (or the keys you actually cluster on) — full bodies blow up the token budget without changing clustering, since ranking sees the full text either way. See `platform/plugins/memory/references/graph-primitives.md` § Projecting `memory-search` fields.
|
|
40
39
|
2. **Cluster by embedding similarity.** Use the existing embedding on each node. Group nodes whose pairwise similarity is above the cluster threshold (default 0.78). Clusters of one are surfaced separately as "isolated" rows the owner can wire as standalone concepts or skip.
|
|
41
40
|
3. **Name each cluster.** Compose a one-line name from the highest-weighted terms shared across the cluster's sections. Never use a name that is just a copy of a document title; the name should generalise the cluster.
|
|
42
41
|
4. **Present proposals.** One numbered row per cluster, in the format above.
|
|
@@ -35,7 +35,6 @@ The flow:
|
|
|
35
35
|
|
|
36
36
|
2. **Read the file head** to discover senders before the script runs. Read the first ~50 lines (or use a per-source preview heuristic). Extract distinct sender display names. The operator never sees the script's own counters — surface a one-line histogram (`Sender '<name>' (<count> messages)`) per distinct sender.
|
|
37
37
|
|
|
38
|
-
3. **List candidate `:AdminUser` and existing `:Person`** rows for the senders via `mcp__graph__maxy-graph-read_neo4j_cypher`.
|
|
39
38
|
|
|
40
39
|
4. **Iterate the histogram, one operator question per distinct senderName.** For each sender: `"Sender '<name>' (<count> messages) — pick existing :AdminUser/:Person or block?"`. The operator either picks an existing elementId or names "block" (refuses to map to a node). **Never auto-create a `:Person`** — the operator must confirm a canonical node, mirroring `feedback_archives_are_not_documents.md`'s closed-set discipline.
|
|
41
40
|
|
|
@@ -189,7 +188,6 @@ For an empty-delta re-import (`delta.kind === "empty-delta"`): emit only message
|
|
|
189
188
|
|
|
190
189
|
## Verification (post-write)
|
|
191
190
|
|
|
192
|
-
Run via `mcp__graph__maxy-graph-read_neo4j_cypher`:
|
|
193
191
|
|
|
194
192
|
- `MATCH (a:ConversationArchive { conversationIdentity: $cid }) RETURN elementId(a), a.source, a.lastIngestedMessageAt, a.lastIngestedMessageHash` — `source` matches `--source`; counters agree with the JSON summary.
|
|
195
193
|
- `MATCH (a:ConversationArchive { conversationIdentity: $cid })-[:HAS_SECTION]->(c:Section) RETURN count(c)` — equals `chunks`.
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: conversation-archive-enrich
|
|
3
|
-
description: Source-agnostic Phase 2 for any `:KnowledgeDocument` — walks the `:Section` chunks of one named archive and asks the operator to confirm derived claims row-by-row. Fires only when the operator names a specific archive and asks for insights/enrichment; never auto-fires on Phase 1 completion. Per-row gate (wire / skip / reject) over four kinds — `mention`, `task`, `preference`, `observed-relationship`. Re-runs are idempotent on `(elementId(chunk), kind, contentHash)`, so collapsing duplicates is structural. Invokes the read-only MCP tool `mcp__memory__conversation-archive-derive-insights` to obtain proposals; never writes from the tool's return path. Each `wire` decision runs the proposal's returned `mergeCypher` via `mcp__graph__maxy-graph-write_neo4j_cypher`, or, for `task`, mints the `:Task` via `mcp__work__work-create` first and then runs the REFERENCES merge.
|
|
4
3
|
---
|
|
5
4
|
|
|
6
5
|
# Conversation Archive — chunk-anchored insight derivation
|
|
7
6
|
|
|
8
7
|
**Invoked from `specialists:librarian`** — the admin agent dispatches the librarian when the operator names an existing `:KnowledgeDocument` and asks for enrichment / derived insights; the librarian loads this skill.
|
|
9
8
|
|
|
10
|
-
One skill. Walks one `:KnowledgeDocument`'s chunks in pages. Per chunk, Haiku surfaces high-confidence claims via `
|
|
9
|
+
One skill. Walks one `:KnowledgeDocument`'s chunks in pages. Per chunk, Haiku surfaces high-confidence claims via `mcp__plugin_memory_memory__conversation-archive-derive-insights`; per claim, the operator decides `wire / skip / reject`. The tool is read-only; the only writes happen through this skill's per-row dispatch.
|
|
11
10
|
|
|
12
11
|
## When to invoke
|
|
13
12
|
|
|
@@ -21,7 +20,7 @@ Never auto-fire after a Phase 1 ingest completes — `conversation-archive` (the
|
|
|
21
20
|
|
|
22
21
|
## Resolve the archive
|
|
23
22
|
|
|
24
|
-
Operator phrasing usually names participants or the source file, not an `elementId`. Resolve via `
|
|
23
|
+
Operator phrasing usually names participants or the source file, not an `elementId`. Resolve via `mcp__plugin_memory_memory__memory-search`:
|
|
25
24
|
|
|
26
25
|
```
|
|
27
26
|
memory-search { query: "<operator's phrasing>", labels: ["KnowledgeDocument"], expandHops: 0, fields: ["title"] }
|
|
@@ -34,7 +33,7 @@ If multiple `:KnowledgeDocument` rows match, ask the operator to pick by `title`
|
|
|
34
33
|
Page through the chunks with the read-only MCP tool. Default page size is 5 chunks.
|
|
35
34
|
|
|
36
35
|
```
|
|
37
|
-
|
|
36
|
+
mcp__plugin_memory_memory__conversation-archive-derive-insights {
|
|
38
37
|
archiveElementId: "<elementId>",
|
|
39
38
|
chunkOffset: 0,
|
|
40
39
|
chunkLimit: 5,
|
|
@@ -70,22 +69,19 @@ Dispatch per kind. Provenance (`createdByTool`, `createdBySession`, `createdAt`)
|
|
|
70
69
|
|
|
71
70
|
The proposal carries `disambiguation.needsResolution[0]` naming the entity to resolve.
|
|
72
71
|
|
|
73
|
-
1. Resolve via `
|
|
74
|
-
2. If multiple candidates: ask the operator to pick by `elementId`. If zero candidates: ask the operator `mint via contact-create / reject`. Minting goes through `
|
|
75
|
-
3. Run the `mergeCypher` via `mcp__graph__maxy-graph-write_neo4j_cypher` with `mergeParams` plus `targetElementId=<resolved elementId>`.
|
|
72
|
+
1. Resolve via `mcp__plugin_memory_memory__memory-search { query: "<displayName>", labels: ["Person", "Organization"], expandHops: 0 }`.
|
|
73
|
+
2. If multiple candidates: ask the operator to pick by `elementId`. If zero candidates: ask the operator `mint via contact-create / reject`. Minting goes through `mcp__plugin_contacts_contacts__contact-create` (operator-confirmed canonical Person/Organization — never auto-mint).
|
|
76
74
|
|
|
77
75
|
A re-run against the same `(chunkElementId, target, contentHash)` collapses on the MERGE — no duplicate edges.
|
|
78
76
|
|
|
79
77
|
### `task`
|
|
80
78
|
|
|
81
|
-
1. Mint the `:Task` via `
|
|
82
|
-
2. Run the proposal's `mergeCypher` via `mcp__graph__maxy-graph-write_neo4j_cypher` with `mergeParams` plus `taskElementId=<minted Task elementId>`.
|
|
79
|
+
1. Mint the `:Task` via `mcp__plugin_work_work__work-create` with `title=mergeParams.taskTitle`, optional due hint from `mergeParams.taskDueHint`, `affects=archiveElementId`, `raisedDuringConversationKey=chunkElementId`.
|
|
83
80
|
|
|
84
81
|
The `:Task` itself is the durable artefact; the `REFERENCES` edge to the chunk records the evidence that produced it.
|
|
85
82
|
|
|
86
83
|
### `preference`
|
|
87
84
|
|
|
88
|
-
No disambiguation step — the `:Preference` node is account-scoped and MERGE-keyed on `(accountId, contentHash)`. Run the proposal's `mergeCypher` directly via `mcp__graph__maxy-graph-write_neo4j_cypher` with `mergeParams` (no additional substitutions).
|
|
89
85
|
|
|
90
86
|
The `mergeCypher` MERGEs the `:Preference`, sets category/key/value on first create, and creates the `(:Preference)-[:OBSERVED_IN]->(chunk)` edge that ties the preference to its evidence.
|
|
91
87
|
|
|
@@ -93,7 +89,7 @@ The `mergeCypher` MERGEs the `:Preference`, sets category/key/value on first cre
|
|
|
93
89
|
|
|
94
90
|
Two-entity resolution. The proposal carries `disambiguation.needsResolution` with `subject` and `object`.
|
|
95
91
|
|
|
96
|
-
1. Resolve each via `
|
|
92
|
+
1. Resolve each via `mcp__plugin_memory_memory__memory-search` as in `mention`. Reject the proposal if either entity does not resolve and the operator declines to mint via `contact-create`.
|
|
97
93
|
2. Run the `mergeCypher` with `mergeParams` plus `subjectElementId=<resolved>` and `objectElementId=<resolved>`.
|
|
98
94
|
|
|
99
95
|
The merged `:RELATED_TO` edge carries `operatorConfirmed: true` so downstream queries can filter speculative relationships out.
|
|
@@ -102,11 +98,11 @@ The merged `:RELATED_TO` edge carries `operatorConfirmed: true` so downstream qu
|
|
|
102
98
|
|
|
103
99
|
`skip` — the operator wants to move on without writing. Note locally and continue. Skips are not persisted; the proposal re-surfaces on the next run.
|
|
104
100
|
|
|
105
|
-
`reject` — the operator deems the claim wrong. Call `
|
|
101
|
+
`reject` — the operator deems the claim wrong. Call `mcp__plugin_memory_memory__conversation-archive-enrich-rejection` with `mode: "record"` and the proposal's `chunkElementId`, `kind`, `contentHash`, `archiveElementId`. The tool appends a line to the account's `data/accounts/<accountId>/state/conversation-archive-enrich-rejections.jsonl` sidecar; on every subsequent walk the `conversation-archive-derive-insights` tool filters proposals matching any recorded `(chunkElementId, kind, contentHash)` tuple. Duplicate `record` calls for the same key are silent no-ops.
|
|
106
102
|
|
|
107
103
|
## Wire undoes prior rejection
|
|
108
104
|
|
|
109
|
-
After a successful `wire` (any kind), call `
|
|
105
|
+
After a successful `wire` (any kind), call `mcp__plugin_memory_memory__conversation-archive-enrich-rejection` with `mode: "undo"` and the same `(chunkElementId, kind, contentHash)` triple. Operators who change their minds and wire a once-rejected claim should not have to manage rejection state by hand — the wire-then-undo pairing keeps the JSONL aligned with the live graph. Calling `undo` for a key that was never rejected is a silent no-op (`beforeCount=0, afterCount=0`).
|
|
110
106
|
|
|
111
107
|
## Observability
|
|
112
108
|
|
|
@@ -140,7 +136,6 @@ And one end-of-walk summary:
|
|
|
140
136
|
|
|
141
137
|
## Verification (post-enrich)
|
|
142
138
|
|
|
143
|
-
Run via `mcp__graph__maxy-graph-read_neo4j_cypher`:
|
|
144
139
|
|
|
145
140
|
- All wired writes carry `createdByTool='conversation-archive-derive-insights'`. Count per kind:
|
|
146
141
|
```
|
|
@@ -187,7 +187,6 @@ After `memory-ingest` returns the new KnowledgeDocument's `documentNodeId`, this
|
|
|
187
187
|
**Entity sources.** The dispatch brief's "key entities to connect" list. Brief shape: prose names of Persons, Organizations, Services, Tasks, Events, KnowledgeDocuments, BrandingData that the document describes or references. Example: *"Person nodes for Joel Smalley, Adam Mackay, Dan McLeod; LocalBusiness / Organization nodes for Real Agent / Real Agency; Any existing Task nodes related to Real Agent Lettings."* Extract every named entity from the brief before any `memory-write`.
|
|
188
188
|
|
|
189
189
|
**Resolution.** For each named entity:
|
|
190
|
-
1. Resolve the elementId via `memory-search` (preferred — handles fuzzy name matching against existing graph nodes) or a single-shot `MATCH (n:<Label> { <identifying-prop>: $value, accountId: $accountId }) RETURN elementId(n)` via `mcp__graph__maxy-graph-read_neo4j_cypher` for exact-match lookups. For `memory-search` calls here, pass `fields: ["name"]` (or whatever single key you need) — you only want the elementId match, not the full bio/notes/sourceUrl payload. See `platform/plugins/memory/references/graph-primitives.md` § Projecting `memory-search` fields.
|
|
191
190
|
2. If the entity resolves to exactly one node, write the edge from KD to that node (see edge-type table below).
|
|
192
191
|
3. If the entity resolves to zero nodes, append it to the orphan list (do NOT create a placeholder Person/Organization — that's classifier-emitted `documentEdges` territory, not brief-wiring).
|
|
193
192
|
4. If the entity resolves to multiple nodes (ambiguity — two Persons named "Joel"), pick the one whose context best fits the brief and document (e.g. last-mentioned-recently, or matching account). Surface the choice in the operator narrative so it can be corrected.
|
|
@@ -3,7 +3,7 @@ name: librarian
|
|
|
3
3
|
description: "Foreground ingester for the memory graph. Owns external-archive and document ingestion — PDFs, web pages, plain text, conversation transcripts (WhatsApp / Telegram / Signal / iMessage / Slack / LinkedIn DMs / Zoom / meeting minutes), LinkedIn Basic Data Exports, and post-unzip trees the admin agent forwards. Loads `document-ingest`, `conversation-archive`, `conversation-archive-enrich`, and `linkedin-import` skills. Returns to the admin agent with a structured outcome line; never interacts with the operator directly except through the dispatching agent."
|
|
4
4
|
summary: "Owns foreground ingest of documents, conversation transcripts, and external archives into the memory graph."
|
|
5
5
|
model: claude-sonnet-4-5
|
|
6
|
-
tools:
|
|
6
|
+
tools: mcp__plugin_memory_memory__memory-ingest-extract, mcp__plugin_memory_memory__memory-ingest, mcp__plugin_memory_memory__memory-ingest-web, mcp__plugin_memory_memory__memory-archive-write, mcp__plugin_memory_memory__memory-write, mcp__plugin_memory_memory__memory-update, mcp__plugin_memory_memory__memory-search, mcp__plugin_memory_memory__conversation-archive-derive-insights, mcp__plugin_memory_memory__conversation-archive-enrich-rejection, mcp__plugin_contacts_contacts__contact-create, mcp__plugin_work_work__work-create, Read, Bash
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Librarian
|