MindGraphDocs

Build a Deal-Flow Agent

Use MindGraph as the governed organizational brain behind an investment workflow: typed companies and leads, source-backed diligence, versioned score inputs, deterministic rankings, and an audit trail your application can explain.

This guide builds the knowledge and analysis layer, not a replacement CRM. Your product should continue to own its pipeline UI, inbox, tasks, notifications, permissions UX, and external integrations.

The system boundary

Your deal-flow application owns

  • Pipeline, inbox, tasks, reminders, and collaboration UI
  • Email, CRM, data-room, market-data, and cap-table integrations
  • Economic models, portfolio construction, and final IC workflow

MindGraph owns

  • Typed domain objects, relations, identity, and history
  • Documents, extracted claims, evidence, risks, and provenance
  • Governed agent access and deterministic comparative analysis

1. Connect your agent

npm install mindgraph
deal-flow.ts
import { MindGraph } from "mindgraph";

const graph = new MindGraph({
  baseUrl: process.env.MINDGRAPH_URL ?? "https://api.mindgraph.cloud",
  apiKey: process.env.MINDGRAPH_API_KEY!,
});
Note:The Python SDK exposes the same workflow with snake-case methods such as create_domain_object(), update_domain_object(), ingest_document(), and query_domain_structured(). See the SDK guide for setup.

Create separate API keys or agent identities for ingestion, analyst automation, and production reads. Grant each only the spaces and actions it needs. See Governance & Access.

2. Establish the deal-flow contract

Ask MindGraph to draft the built-in investment contract, review the proposal, then activate it. The template defines Company, Contact, FundThesis, and InvestmentLead, plus the relations between them.

const { schema_id } = await graph.proposeOntologySchema({
  template_hint: "investment_dealflow",
  target_use_case: "Govern our venture pipeline and diligence evidence",
  desired_workflows: [
    "Screen every admitted lead against the fund thesis",
    "Compare complete scorecards without hiding missing candidates",
    "Trace every investment claim to its source",
  ],
});

// Poll the draft, review the proposed schema, then activate it.
let schema = await graph.getOntologySchema(schema_id);
while (schema.propose_status === "pending" || schema.propose_status === "running") {
  await new Promise((resolve) => setTimeout(resolve, 1_000));
  schema = await graph.getOntologySchema(schema_id);
}
if (schema.propose_status !== "ready") {
  throw new Error(schema.propose_error ?? "Schema proposal failed");
}

// Present schema.object_types and relation_types for a human review here.
await graph.activateOntologySchema(schema_id);
Note:InvestmentLead.deal_id is the stable identity supplied by your application. It is intentionally separate from the company: the same company may return in another round or process. Never derive it from a name, document wording, or model confidence.
Warning:The proposal may adapt stage and round enum values to your stated workflow. Read the activated schema and use its returned enum values rather than hard-coding values from this example.

3. Create leads from your source application

Create the stable record before ingesting pitch materials. Manual creates are checked against the active schema: required fields, enum values, number types, references, and identity must all be valid.

const company = await graph.createDomainObject({
  schema_id,
  object_type: "Company",
  canonical_name: "Acme Robotics",
  fields: { name: "Acme Robotics" },
});

const lead = await graph.createDomainObject({
  schema_id,
  object_type: "InvestmentLead",
  canonical_name: "Acme Robotics — Series A",
  fields: {
    deal_id: "crm-deal-1042",       // stable ID from your application
    company: company.uid,            // validated Company reference
    stage: "screening",             // use an enum value from the active schema
    round: "series_a",
    received_on: "2026-08-18",
  },
});

await graph.linkDomainObjects({
  from_uid: lead.uid,
  to_uid: company.uid,
  relation_type: "LEAD_FOR",
});

Use INTRODUCED_BY to connect a lead to one or more contacts and MATCHES_THESIS to connect it to the relevant fund thesis. Relations are typed and direction-sensitive, so follow the source and target types in the schema.

4. Update score inputs safely

Authored updates use optimistic concurrency. Read the current version, submit only the changed fields, and record a human-readable reason. Identity fields are immutable; explicit unset_fields removes optional values without treating null as a valid domain value.

const current = await graph.getDomainObject(lead.uid, {
  schema_id,
  object_type: "InvestmentLead",
});

const updated = await graph.updateDomainObject(lead.uid, {
  fields: {
    stage: "diligence",
    team_score: 8,
    market_score: 7.5,
    thesis_fit_score: 9,
  },
  unset_fields: ["risk_score"],
  expected_version: current.version,
  reason: "Partner screening review completed on 2026-08-18",
});

console.log(updated.uid, updated.version, updated.proposal_id);

If another writer changed the object first, the API returns a conflict. Fetch the current object, show the competing version to the user, and retry only after reconciling it. Do not silently overwrite the newer assessment.

5. Attach source-backed diligence

Ingest decks, notes, memos, transcripts, and data-room exports into a project-scoped corpus. Include the external deal ID in the ingestion context so extraction can resolve the pre-created lead rather than inventing a new identity.

const ingestion = await graph.ingestDocument({
  title: "Acme Robotics — partner meeting notes",
  content: meetingNotes,
  content_type: "meeting_notes",
  source_uri: "crm://deals/crm-deal-1042/notes/partner-meeting",
  project_uid: "project_acme_diligence",
  ontology_schema_id: schema_id,
  layers: ["reality", "epistemic", "intent", "action", "ontology"],
  context: "InvestmentLead deal_id=crm-deal-1042; Company=Acme Robotics",
});

let job = await graph.getJob(ingestion.job_id);
while (job.status === "pending" || job.status === "processing") {
  await new Promise((resolve) => setTimeout(resolve, 1_000));
  job = await graph.getJob(ingestion.job_id);
}
if (job.status !== "completed") throw new Error("Diligence ingestion failed");

const { items } = await graph.listOntologyProposals({
  schema_id,
  extract_job_id: ingestion.job_id,
  status: "pending",
});
// Render items in your authenticated analyst review surface. Populate this only
// with IDs explicitly selected there; an empty list approves nothing.
const selectedProposalIds: string[] = [];
for (const proposalId of selectedProposalIds) {
  await graph.approveOntologyProposal(proposalId);
}
Note:Ontology fields are not a substitute for evidence. Keep scored inputs on the lead, while claims, risks, decisions, and quotations stay linked to their source chunks through provenance. Use getDomainObjectContext() when the agent needs both.

6. Keep financial history as time series

Do not flatten changing metrics such as ARR, burn, or headcount into an ever-growing object. Store dense measurements as Series nodes associated with the company or lead.

const { series: arr } = await graph.createSeries({
  entity_uid: company.uid,
  name: "annual-recurring-revenue",
  description: "Company-reported ARR",
  unit: "USD",
  temporality: "period",
  period_unit: "month",
  default_source_uid: ingestion.document_uid,
});

await graph.appendSeries({
  series_uid: arr.uid,
  points: [{
    t: 1785542400000000, // UTC microseconds
    value: 4_200_000,
    period_label: "2026-08",
    source_uid: ingestion.document_uid,
  }],
});

const latestArr = await graph.latestSeries(arr.uid, {
  project_uid: "project_acme_diligence",
});

7. Rank admitted leads deterministically

The weighted scorecard reads authored numeric fields, normalizes each criterion over the admitted candidate set, applies explicit directions and weights, and returns component calculations. Model confidence and retrieval similarity never enter the score.

const comparison = await graph.queryDomainStructured({
  schema_id,
  select: "InvestmentLead",
  where: [{ field: "stage", op: "in", value: ["screening", "diligence"] }],
  aggregate: {
    op: "weighted_scorecard",
    criteria: [
      { field: "team_score", weight: 3, direction: "higher_is_better" },
      { field: "market_score", weight: 2, direction: "higher_is_better" },
      { field: "thesis_fit_score", weight: 3, direction: "higher_is_better" },
      { field: "risk_score", weight: 2, direction: "lower_is_better" },
    ],
    missing: "exclude_candidate",
  },
});

const scorecard = comparison.aggregate as {
  ranking: Array<{
    object_uid: string;
    score: number;
    rank: number;
    tied: boolean;
    tie_group_size: number;
    components: unknown[];
  }>;
  ties: Array<{ rank: number; score: number; object_uids: string[] }>;
  excluded_candidates: Array<{ object_uid: string; missing_fields: string[] }>;
};

Show excluded_candidates next to the ranking so incomplete leads do not disappear. Preserve equal scores as ties: each ranked item includes rank, tied, and tie_group_size, and the aggregate includes explicit tie groups.

8. Put it into an agent loop

  1. Receive a deal event from the external CRM or inbox.
  2. Create or resolve the stable Company and InvestmentLead.
  3. Ingest new materials into the deal's scoped project and wait for completion.
  4. Present uncertain extracted objects and relations for human approval.
  5. Retrieve the lead with cognitive context and provenance for analysis.
  6. Write reviewed score inputs with the current version and an audit reason.
  7. Run the deterministic scorecard over an explicit admitted candidate set.
  8. Return rankings, missing data, ties, evidence, and risks to your product UI.
Warning:Treat the agent's recommendation as decision support. Final investment authority, portfolio constraints, legal checks, and money movement belong in separately governed workflows with explicit human approval.

Production checklist

  • Keep a durable external deal_id → MindGraph uid mapping.
  • Read schema enums and required fields at startup; fail closed on contract drift.
  • Retry version conflicts only after reconciling the competing update.
  • Poll every asynchronous job to a terminal state and surface failures.
  • Use project scope for deal-specific retrieval and least-privilege agent access.
  • Never turn extraction confidence into an investment score.
  • Show provenance, missing score inputs, tie groups, and candidate coverage.
  • Record risk and final IC decisions separately from deterministic ranking.

Continue with the Operational Ontology guide for schema details, the Projects & Living Briefs guide for bounded diligence corpora, and the API Reference for endpoint-level contracts.