context-intelligence-gds

$npx mdskill add microsoft/amplifier-bundle-context-intelligence/context-intelligence-gds

The context-intelligence graph is large (millions of nodes across thousands of sessions). Naive Cypher tends to **re-derive, one hop and one round-trip at a time, what a graph algorithm computes in a single server-side pass**. This skill trains the reflex: recognize the *shape* of the question, and when it is a graph-algorithm shape, reach for **GDS or APOC** instead of improvising Cypher.

SKILL.md

.github/skills/context-intelligence-gdsView on GitHub ↗
---
name: context-intelligence-gds
description: >
  Use when about to write naive Cypher for a graph-topology question — pathfinding,
  reachability, centrality/influence, community/clustering, structural similarity,
  or similar event paths/trajectories (with/without time) — reach for Neo4j GDS or
  APOC instead of hand-rolling traversals one hop at a time. WHEN/WHY layer;
  graph-query's "Push Work to the Database" section is the HOW.
license: MIT
metadata:
  version: "1.2.0"
  changelog:
    - "1.2.0: Lifted patterns from a real graph exploration — TF-IDF weighting to defeat glue-tool dominance in similarity; read-only vector-property gds.knn fallback (no bipartite edges); a derived co-occurrence/teaming projection (distinct from the edge-less Agent-node dead end); apoc.map.fromPairs/apoc.coll.sort as a fourth primitive (in-graph frequency map); a read-only .stream/.stats vs .write/.mutate guardrail; and a 'When GDS itself isn't enough' boundary (structure vs intent → escalate to LLM embeddings)."
    - "1.1.0: Added the 'Complex topology searches' section — centrality, topological/structural similarity (nodeSimilarity/KNN/FastRP embeddings), and similar event PATHS / trajectories with-or-without a time dimension (set-similarity vs transition-signature), plus pathfinding and a non-lineage projection recipe. These are the hard searches the skill exists to route off naive Cypher."
    - "1.0.0: Initial — GDS/APOC scenario-selection and advantages, avoid-naive-Cypher decision map, three reusable primitives tuned to this graph."
---

# Context Intelligence — Graph Data Science First

The context-intelligence graph is large (millions of nodes across thousands of
sessions). Naive Cypher tends to **re-derive, one hop and one round-trip at a time,
what a graph algorithm computes in a single server-side pass**. This skill trains the
reflex: recognize the *shape* of the question, and when it is a graph-algorithm shape,
reach for **GDS or APOC** instead of improvising Cypher.

> **This skill is WHEN and WHY. The HOW lives next door.** The graph-query skill owns
> the schema meaning, the traps, and the worked projection queries — its **Section 8,
> "Push Work to the Database (APOC / GDS)"** has the concrete `gds.graph.project →
> algorithm → gds.graph.drop` lifecycle and the `Session`/`FORKED` + agent-rollup
> example. Load `context-intelligence-graph-query` for mechanics; use this skill to
> decide *whether the algorithm is the right tool at all.*

---

## The naive-query smell test

You are probably about to write naive Cypher where GDS/APOC would be better if you catch
yourself doing any of these:

- **Guessing edge types to walk a tree** (`-[:HAS_SUBSESSION|FORKED|HAS_FORK]->`) or
  repeatedly stepping `Session.parent_id` one hop at a time to rebuild a delegation tree.
- **`collect()` then `size([x IN … WHERE …])`** to count how many things are reachable /
  match — reconstructing an algorithm's output by hand.
- **A giant `WHERE node_id IN [ …50 literals… ]`** pasted into the query text (client-side
  set-building shoved into Cypher).
- **`UNWIND range(0, size(list)-2)`** to compute consecutive gaps / pairwise math.
- **Variable-length `-[*1..n]->`** followed by client-side filtering of the paths.
- Pulling many rows to the client to **iterate and aggregate** what the DB could reduce.

Each of these is a tell that the question has a *graph-algorithm shape*.

---

## What the graph-data-science package buys you (the advantages)

| Advantage | Naive Cypher | GDS / APOC |
|---|---|---|
| **One pass, not many round-trips** | N queries + client stitching | project once, compute server-side, return a bounded result |
| **Scales to the whole graph** | degrades / times out on deep or wide traversals | algorithms are built for millions of nodes |
| **Correct, not approximate** | hand-rolled shortest-path / "influence" is easy to get subtly wrong | `gds.shortestPath`, `gds.pageRank`, `gds.betweenness` are the real thing |
| **Less context churn** | large client-side pulls flood the session | the payload never crosses the wire; only the answer does |
| **Stable, reusable shapes** | every question becomes a new bespoke query | a handful of parameterized primitives cover most questions |

---

## Best-fit scenarios → the right tool

Match the *question shape* to the tool. This is the core of the skill.

| The question is really… | Naive trap | Reach for | Why it wins |
|---|---|---|---|
| **Full delegation subtree / all descendants of a session** | one-hop `parent_id` walks, edge-type guessing | `apoc.path.expandConfig(root, {relationshipFilter:'FORKED>', minLevel:1, maxLevel:20})`, or `gds.bfs` | one call returns the whole bounded lineage; respects Trap 1 (lineage is `FORKED`, *not* `HAS_SUBSESSION`) |
| **How are two sessions connected / shortest path** | manual `-[*1..n]->` + filtering | `gds.shortestPath.*` (or `apoc.path.expandConfig`) | correct pathfinding in one pass |
| **Which agent/session is a hub / most influential** | `collect` + `size` degree-counting | `gds.pageRank` / `gds.degree` / `gds.betweenness` over `Session`/`FORKED`, rolled up by the `Session.agent` property | real centrality; see graph-query §8 for the exact projection + agent-rollup (agents are a property to roll up by, **not** a projectable graph) |
| **Clusters / communities of related sessions** | can't express it naively | `gds.louvain` / `gds.wcc` | community structure in one call |
| **Sessions similar by shared tools/skills** | pairwise `collect` intersections | `gds.nodeSimilarity` over a `Session`→`Tool`/`Skill` projection | scalable similarity instead of O(n²) hand-joins |
| **Reach a JSON field inside `Event.data`** | `e.data.working_dir` (raises a type error) | `apoc.convert.fromJsonMap(e.data).working_dir` | correct *and* lean — see graph-query §8's authoritative pattern |
| **Operate over a known set of ids** | 50-id `IN [ … ]` literal | pass `$ids` and `UNWIND $ids AS id` | one reusable shape; for heavy scans, `apoc.periodic.iterate` |
| **Consecutive-pair math (inter-event gaps)** | `UNWIND range(0,size-2)` | `apoc.coll.pairsMin(list)` | expresses the intent directly |

---

## Complex topology searches — the hard ones this skill exists for

These are the searches naive Cypher either **cannot express** or does at O(n²) with
wrong/approximate results. When the question is one of these, do NOT hand-roll it —
project the right graph and run the algorithm.

**1. Centrality / influence** — "which sessions, agents, or tools are structurally most important?"
- `gds.pageRank` · `gds.betweenness` · `gds.degree` · `gds.closeness` · `gds.eigenvector`.
- Project the relevant graph (`Session`/`FORKED` for delegation influence; a tool
  co-occurrence graph for tool influence), stream, roll up by the property you care
  about, `gds.graph.drop`.

**2. Topological / structural similarity** — "which sessions are shaped alike?"
- **By shared neighbors** (same tools/skills/agents used): project a **bipartite**
  `Session → Tool` (or `→Skill`/`→Agent`) graph → `gds.nodeSimilarity` (Jaccard/overlap)
  or `gds.knn`.
- **By structural role** (behave alike even without sharing exact neighbors):
  `gds.fastRP` (or `gds.node2vec`) node embeddings → `gds.knn` for nearest structural
  neighbors. This finds role-equivalent sessions.
- **Weight the vector before you compare it.** Raw tool-usage counts are dominated by glue
  tools (`bash`, `read_file`, `todo`) — un-weighted, *every* session looks similar and the
  result is noise. Down-weight ubiquitous tools with a TF-IDF weighting (the same job
  `fastRP` otherwise buys you), built in-graph with `apoc.map.fromPairs(collect([tool_name, count]))`.
- **Read-only fallback — no bipartite edges needed.** When the store is read-only and you
  can't materialise `Session→Tool` edges, compute the weighted vector as a **node property**
  inside the Cypher projection and run `gds.knn(nodeProperties: ['toolVector'])` directly on
  it — same answer, no derived edges. (General principle: when an algorithm's naive input is
  wrong or unavailable, reshape the input in the projection before reaching for a fancier algorithm.)

**3. Similar event PATHS / trajectories — the sequence question, with or without time**
This is the "find similar paths of events" search. The time dimension decides the tool:
- **WITHOUT time (bag of steps):** reduce each session to its *multiset* of event/tool
  types, then `gds.nodeSimilarity`/`gds.knn` over the `Session → Tool` projection. Order
  is ignored — "used A, B, C" matches "used C, B, A".
- **WITH time (ordered trajectory):** order matters, so build the ordering first. Sort
  each session's events by `started_at`, form consecutive pairs with
  `apoc.coll.pairsMin(ordered_list)`, and materialise a **transition signature** —
  `(:Session)-[:HAS_TRANSITION]->(:TypePair {a,b})`. Then either `gds.nodeSimilarity`
  over that transition projection, or `gds.fastRP` + `gds.knn` to cluster whole
  trajectories. This distinguishes `A→B→C` from `A→C→B`, which set-similarity cannot.
- Extract one session's ordered path with `apoc.path.expandConfig` or a time-ordered
  `MATCH` — never a client-side reconstruction.

**4. Pathfinding / reachability** — shortest, k-shortest, all-reachable.
- `gds.shortestPath.dijkstra` · `gds.allShortestPaths` · Yen's k-shortest ·
  `gds.bfs`/`gds.dfs`; or `apoc.path.expandConfig` for filtered reach.

**Projection recipe (similarity/trajectory graphs are not stored edges — build them):**
```cypher
// bipartite Session→ToolCall for similarity (Cypher projection, GDS 2.x)
// confirm the Session→ToolCall reach against the graph-query skill / live schema first
CALL gds.graph.project.cypher('sess-tool',
  'MATCH (n) WHERE n:Session OR n:ToolCall RETURN id(n) AS id',
  'MATCH (s:Session)-[:HAS_EXECUTION|HAS_PART*1..3]->(tc:ToolCall)
   RETURN id(s) AS source, id(tc) AS target');
CALL gds.nodeSimilarity.stream('sess-tool')
  YIELD node1, node2, similarity
RETURN gds.util.asNode(node1).node_id AS a, gds.util.asNode(node2).node_id AS b, similarity
ORDER BY similarity DESC LIMIT 20;
CALL gds.graph.drop('sess-tool') YIELD graphName;   // always release (in-memory only, no data touched)
```

**Deriving a graph that isn't stored (co-occurrence / teaming).** "Which agents work
together?" has no stored `Agent→Agent` edge — but you can *derive* one from shared group
membership (e.g. agents whose sessions share a parent), then run community/centrality on the
derived graph. This is valid and **different from projecting the real (edge-less) `Agent`
nodes, which is the dead end** graph-query §8 warns about — confirm the grouping edge against
the live schema first:
```cypher
CALL gds.graph.project.cypher('agent-team',
  'MATCH (s:Session) WHERE s.agent IS NOT NULL RETURN id(s) AS id, s.agent AS agent',
  'MATCH (a:Session)<-[:FORKED]-(p)-[:FORKED]->(b:Session)
   WHERE a.agent < b.agent RETURN id(a) AS source, id(b) AS target, count(*) AS weight');
CALL gds.louvain.stream('agent-team') YIELD nodeId, communityId RETURN communityId, count(*) LIMIT 20;
CALL gds.graph.drop('agent-team') YIELD graphName;
```

---

## Four primitives that replace most naive Cypher here

Default to these instead of re-deriving structure:

1. **The lineage walker** — `apoc.path.expandConfig` over `FORKED` (or a `Session`/`FORKED`
   GDS projection for tree analytics). This one primitive replaces the single largest
   class of hand-rolled queries on this graph.
2. **The JSON-lift reflex** — never touch `Event.data` with dot-access; parse in-graph
   with `apoc.convert.fromJsonMap` and return only the scalar (graph-query §8 is authoritative).
3. **The parameterized id-set** — `UNWIND $ids`, never a pasted literal list. Collapses a
   long tail of "unique" queries into one reused shape.
4. **The in-graph frequency map** — `apoc.map.fromPairs(collect([key, count]))` builds a
   per-node weight/vector map server-side (feeds TF-IDF `gds.knn` node properties). When
   collecting a set as a grouping key, canonicalise it first with `apoc.coll.sort` so `[A,B]`
   and `[B,A]` group as one.

---

## When naive Cypher IS the right tool (don't over-reach)

GDS/APOC is not always the answer. A **single plain Cypher statement** already wins for:

- counts and existence checks (`RETURN count(*)`),
- single-session scalar lookups,
- group-bys / distributions over one hop (`GROUP BY tool_name`),
- schema probes (`db.labels()`, `db.relationshipTypes()`).

Reach for the algorithm when the naive alternative means **many round-trips, a large
client-side pull, or reconstructing an algorithm by hand** — not for a plain aggregate.

---

## When GDS itself isn't enough (the upper bound)

GDS answers **structural / behavioral** questions — how a session was executed, who is
central, what clusters by shape. It does **not** answer **intent** questions — what a session
was *for*. Tool-vector similarity clusters the *machinery*, not the *mission*. The tell: if you
cross-tab a GDS cluster against the human's actual objective and the same goal is scattered
across most clusters, stop iterating on GDS. Export the bounded inline text (e.g.
`Prompt.prompt`) and run LLM-embedding + k-means instead — a different tool for a different
question shape. Escalating *to* GDS is right; escalating *past* it when the question is
semantic is the next move, not more algorithms.

---

## Guardrails

- **Probe first:** `RETURN gds.version()` / `CALL gds.list()`; `CALL apoc.help('path')`.
  Both plugins run on the live target (Neo4j Community — GDS works).
- **Always release projections:** `gds.graph.drop('g')` when done.
- **Behind a read-only-enforcing endpoint, use `.stream` / scalar `.stats`.** The `.write`/
  `.mutate` variants can be rejected (HTTP 500) even though they only touch the in-memory
  catalog, not the store.
- **Bound the returned result** even when the compute is server-side — `count` / aggregate
  / `LIMIT`. Pushing work to the DB does not excuse an unbounded `RETURN`.
- **Project the labels that exist** — `Session` (nodes), `FORKED` (relationships). Agents
  are a **property** (`Session.agent`) to roll up by, not a projectable graph.
- **Honor the schema traps** — especially Trap 1 (delegation lineage is `FORKED` +
  `Session.parent_id`, and `HAS_SUBSESSION` is a single hop). The graph-query skill owns
  these; load it before writing the query.

More from microsoft/amplifier-bundle-context-intelligence

SkillDescription
context-intelligence-derived-metrics>
context-intelligence-eval-designUse when designing evaluation scenarios for a context-intelligence tool signal. Derives success criteria from domain-concepts.md and produces evaluation-scenarios.md entries plus DTU profile templates.
context-intelligence-evaluation-methodologyUse when deciding how to measure a context-intelligence tool signal. Covers metric design across quality/efficiency/efficacy axes, artifact-metric avoidance via precursor measurement, A/B and statistical-N discipline, and test-data fidelity.
context-intelligence-graph-query>
context-intelligence-hill-climbingUse when an investigation needs more than one step — track it in todo as a hill climb so progress and dead leads leave an audit trail. Governs HOW you track the climb, not how you query or extract — that stays in graph-query / session-navigation.
context-intelligence-server-data-opsExact step order, wording, and the "session details" block format for the delete flows the server-data-ops agent drives — delete the current session, clean up every session from the current working directory, find-then-delete a session by description, and delete a session someone else created.
context-intelligence-session-navigationUse when extracting session data directly from JSONL files — the fallback when the graph server is unavailable or outside graph-analyst.
context-intelligence-session-reconstructionUse when local Amplifier session files are missing, broken, or incomplete — rebuilds events.jsonl, transcript.jsonl, and metadata.json from the context-intelligence graph server.
context-intelligence-tool-designUse when selecting a detection strategy and implementation primitive for a context-intelligence signal. Classifies signals as deterministic, probabilistic, llm-evaluated, or hybrid, and applies the cheapest-sufficient-capability principle.
transcriptRetrieve the prior user and assistant conversation verbatim from the current Context Intelligence capture, or named sessions. Use when a user asks to replay, quote, review, or act on a transcript.