Projects & Synthesis
A Project is a scoped corpus: a subset of your documents grouped around a question, theme, or initiative. Projects turn a free-form knowledge graph into a targeted research artifact — and unlock automatic synthesis: cross-document signal mining followed by LLM-generated Article nodes that summarize the most interesting findings in the corpus.
Synthesis runs in two stages. First, the server mines structural signals from the project's documents using Datalog and embedding clustering — no LLM. Then an optional second stage turns the top idea clusters into Article nodes linked via Covers edges.
Concepts
- Project node — an Intent-layer node with
name,description,status, and optionaltarget_completion. - PartOfProject edge — attaches a Document to a Project. Synthesis queries filter documents through this edge.
- Article node + Covers edge — the output of synthesis. Each generated Article covers one entity or idea cluster in the project.
Create a project
Projects are regular Intent-layer nodes. Create one via the /intent/commitment cognitive endpoint with action: "project".
const project = await graph.commit({
action: "project",
label: "Q2 China strategy",
summary: "Track regulatory and geopolitical shifts impacting operations.",
})
// Attach a document to the project
await graph.addLink({
from_uid: documentUid,
to_uid: project.uid,
edge_type: "PartOfProject",
})
Synthesis endpoints
/synthesis/signals/{project_uid}Mine structural signals across the project corpus (no LLM)/synthesis/run/{project_uid}Spawn a background synthesis job that generates Article nodesSignals
GET /synthesis/signals/{project_uid} returns a structured candidate surface in one blocking call. No LLM; Datalog + embedding clustering only.
| Signal | What it surfaces |
|---|---|
| documents | All documents attached to the project via PartOfProject. |
| entity_bridges | Entities that appear in multiple project documents — the connective tissue. |
| claim_hubs | Entities with the most claims attached (attention hubs). |
| ranked_claim_hubs | Claim hubs filtered (≥10 claims, ≥3 docs, no existing Covers article) and scored on breadth + tension + density. |
| clustered_claim_hubs | Idea clusters (union-find over claim embedding similarity) with epistemic counts and dominant entities. |
| theory_support_gaps | Theories / hypotheses with few supports — candidates for deeper investigation. |
| concept_clusters | Concepts co-mentioned across multiple documents. |
| analogy_candidates | Structurally analogous entities across the corpus. |
| dialectical_pairs | Contradictory claims — tension to explore. |
Pass ?signals=clustered_claim_hubs,dialectical_pairs to run a subset, and ?target_types=Person,Organization to filter the entity-anchored signals.
const signals = await graph.signals(project.uid, {
signals: "clustered_claim_hubs,dialectical_pairs",
})
for (const cluster of signals.clustered_claim_hubs.slice(0, 5)) {
console.log(cluster.dominant_entity_label, cluster.score)
}
Run synthesis
POST /synthesis/run/{project_uid} spawns a background job that mines signals, selects top clusters, runs LLM synthesis, and persists candidate Article nodes linked via Covers edges. It returns a job_id immediately; poll with GET /jobs/{id}.
const { job_id } = await graph.runSynthesis(project.uid)
let job = await graph.getJob(job_id)
while (job.status === "processing" || job.status === "pending") {
await new Promise((r) => setTimeout(r, 2000))
job = await graph.getJob(job_id)
}
const result = job.result as { candidates: Array<{ article_uid: string; title: string }> }
for (const c of result.candidates) {
console.log(c.title, c.article_uid)
}
MINDGRAPH_LLM_MODEL_WIKI, falls back to MINDGRAPH_LLM_MODEL). LLM credits are deducted from the calling org's balance.Reading synthesis output
Articles written by synthesis live alongside entity wiki articles. Query them with the normal Wiki endpoints or open the project in the dashboard.
const articles = await graph.listArticles({
article_type: "synthesis",
limit: 20,
})
for (const a of articles.articles) {
console.log(a.label, a.uid)
}