---
name: concept-synthesis
description: "Walks the knowledge documents already in the graph, clusters them by topic, and proposes Concept nodes that connect related material. Triggers when the owner says 'synthesise my notes', 'find patterns across what I've stored', 'build a concept map', or 'deduplicate my knowledge'. The owner approves each cluster before any write."
---

# Concept synthesis

Invoked from `specialists:librarian`.

This skill turns a pile of saved documents into a usable concept map. It reads what the graph already holds, groups related material, names the groups, and connects them back to their sources. It writes nothing without the owner's approval, and it never auto-merges across groups.

**Default parent.** Every `:Concept` node this skill creates is anchored to a `:Project` (when the scope is a project) or to the account's `:LocalBusiness` (when the scope is a topic phrase or date range that crosses projects). The parent is named at proposal time and shown in the per-cluster row the operator approves — a Concept with no project or LocalBusiness parent is a write defect, never an option.

## When to run

Run when the owner asks to make sense of accumulated knowledge: "synthesise my notes", "find patterns across what I've stored", "build a concept map", "deduplicate my knowledge", "what themes are in my graph". Do not run as a routine maintenance pass. A concept map ages quickly and is most useful when the owner has a specific reason to ask.

## Inputs

The owner names the scope. Three shapes:

1. **A LocalBusiness or Project.** Cluster every `:KnowledgeDocument` linked to that entity.
2. **A topic phrase** ("everything I've saved about pricing"). Cluster the `:KnowledgeDocument` and `:Section` nodes that match the phrase by vector similarity.
3. **Date range.** Cluster every document ingested in the given window.

If the owner names none of these, ask one question: "Which scope: a project, a topic phrase, or a date range?".

## What it produces

For each cluster the skill produces a proposal:

- A name for the cluster (one short noun phrase, owner-domain language).
- A one-sentence description of what the documents in the cluster share.
- The list of source `:KnowledgeDocument` and `:Section` nodes (element IDs and human-readable titles).
- The proposed `:Concept` node properties and the `:RELATES_TO` edges from `:Concept` back to each source.

Present one row per cluster, in a numbered list. The owner picks one of three actions per row: **wire** (write the `:Concept` and the edges), **skip** (drop the proposal), or **rename** (the owner gives a different name; the skill re-proposes).

## Method

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.
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.
4. **Present proposals.** One numbered row per cluster, in the format above.
5. **Wait for owner decisions.** Apply each decision one at a time. Never auto-merge two clusters into one even if their names look similar; if the owner wants two clusters merged, they say so.

## Writes

For each approved cluster:

```
memory-write {
  label: "Concept",
  properties: { name, description, accountId, createdByAgent: "concept-synthesis", createdAt: datetime() },
  parent: { elementId: <project-or-localbusiness-elementId>, edgeType: "PART_OF" },
  relationships: [
    { type: "RELATES_TO", direction: "out", target: <source elementId> }
    ... one per source
  ]
}
```

If an approved cluster matches an existing `:Concept` node by name, ask the owner whether to merge into it or create a new one. Never silently overwrite.

## Output to the operator

Return a short report:

- `<N>` clusters proposed, `<K>` approved, `<S>` skipped, `<R>` renamed.
- For each approved cluster: the cluster name, the count of `:RELATES_TO` edges written, and the new `:Concept` element ID.
- Any source nodes that could not be wired (e.g. because the source was deleted between proposal and approval), with the reason.

## Failure modes

- **Candidate set too large.** Tell the owner the count and ask to narrow the scope. Do not proceed.
- **Embedding missing on a node.** Skip the node, list it in the report under "no embedding". Do not synthesise an embedding inline.
- **Owner approves the same cluster name twice in one run.** Treat the second as a merge request for the first; confirm.
- **`memory-write` failure mid-batch.** Stop. Report which clusters wired before the failure and which did not.

## Owner discipline

The owner approves each cluster, not a batch. Approve-all is not a supported action. The single-row-at-a-time gate is the only path because the operator's intent is to curate the concept layer of the graph, and curation cannot be automated.
