# Neo4j (knowledge graph + inter-agent messaging) The Neo4j knowledge graph is shared across all assistants. Read broadly; write to nodes you own (see your team's graph context). ## Writing discipline - **Always MERGE on `id`** to make writes idempotent. Never blind-create. - **Use the canonical ID format:** `{type}_{identifier}_{qualifier}` (e.g., `infra_neo4j_prod`, `proto_mcp_dashboard`, `note_2026-05-20_harper_scotty_prod_handoff`). Lowercase, snake_case. - **Always set timestamps.** `ON CREATE SET n.created_at = datetime()` for new nodes; `SET n.updated_at = datetime()` on every write. - **Check before creating.** A quick `MATCH` against the intended `id` avoids duplicate nodes that diverge over time. ## Parameterized queries - **Never use `{placeholder}` syntax in the Cypher body.** Local models (Qwen3.5-35B) mishandle it. Pass values through `params`, and use `$name` in the query: ```cypher // good MERGE (n:Note {id: $id}) SET n.title = $title, n.updated_at = datetime() ``` ```cypher // bad — do not do this MERGE (n:Note {id: '{id}'}) SET n.title = '{title}' ``` - Literal values in the query body are fine when they are *actually constants* in your code (`'from:harper'`, a node label, a relationship type). The rule is no template interpolation into the query string. ## Reading discipline - **Read your own domain freely**; cross-team reads are useful when you need context — don't be shy. - Use `LIMIT` on exploratory queries. Returning the whole graph kills latency and burns tokens. ### Common syntax pitfalls - **Node ownership is by label, not by a `type` property.** Harper's nodes are `:Prototype` and `:Experiment` (label = ownership). Scotty's are `:Infrastructure` and `:Incident`. There is no `n.type = 'harper'` filter; the label is the filter. The `type` property only appears on `Note` nodes (e.g., `n.type = 'assistant_message'` for messaging) — do not generalize that pattern. - **`MATCH ... OR MATCH ...` is not valid Cypher.** You cannot OR-combine match patterns at the top level. To query alternative structures, use `UNION` or `OPTIONAL MATCH`: ```cypher // UNION — three separate queries, same return columns, results combined MATCH (n:Prototype)-[:DEMONSTRATES]->(t:Technology) RETURN n.id AS id, n.name AS name, t.name AS related, 'demonstrates' AS rel UNION MATCH (n:Prototype)-[:SUPPORTS]->(o:Opportunity) RETURN n.id AS id, n.name AS name, o.name AS related, 'supports' AS rel UNION MATCH (e:Experiment)-[:LED_TO]->(p:Prototype) RETURN e.id AS id, e.title AS name, p.id AS related, 'led_to' AS rel ``` ```cypher // OPTIONAL MATCH — one row per starting node, with nulls where a relationship doesn't exist MATCH (n:Prototype) OPTIONAL MATCH (n)-[:DEMONSTRATES]->(t:Technology) OPTIONAL MATCH (n)-[:SUPPORTS]->(o:Opportunity) RETURN n.id, n.name, collect(DISTINCT t.name) AS technologies, collect(DISTINCT o.name) AS opportunities ``` Use `UNION` when you want results from any of several structures with the same shape. Use `OPTIONAL MATCH` when you want everything attached to the same starting node, with nulls/empty collections when a relationship is missing. ## Error handling If a graph query fails, continue the conversation. Mention the failure briefly. Never expose raw Cypher errors to the user. ## Inter-agent messaging Other assistants may leave you messages as `Note` nodes. See your team's graph context document for the exact inbox-check and send patterns, and the inbox-check prompt if a scheduler is invoking you.