MindGraphDocs

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 optional target_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

GET/synthesis/signals/{project_uid}Mine structural signals across the project corpus (no LLM)
POST/synthesis/run/{project_uid}Spawn a background synthesis job that generates Article nodes

Signals

GET /synthesis/signals/{project_uid} returns a structured candidate surface in one blocking call. No LLM; Datalog + embedding clustering only.

SignalWhat it surfaces
documentsAll documents attached to the project via PartOfProject.
entity_bridgesEntities that appear in multiple project documents — the connective tissue.
claim_hubsEntities with the most claims attached (attention hubs).
ranked_claim_hubsClaim hubs filtered (≥10 claims, ≥3 docs, no existing Covers article) and scored on breadth + tension + density.
clustered_claim_hubsIdea clusters (union-find over claim embedding similarity) with epistemic counts and dominant entities.
theory_support_gapsTheories / hypotheses with few supports — candidates for deeper investigation.
concept_clustersConcepts co-mentioned across multiple documents.
analogy_candidatesStructurally analogous entities across the corpus.
dialectical_pairsContradictory 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)
}
Note:Synthesis runs on the configured wiki model (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)
}