DRT LOOM

Association Industrial AI — AI² Reference implementation of the Digital Reasoning Thread

No system
holds the join.

A factory’s real questions are answered across ERP, PLM, MES and CAD at once. Extracts of those systems carry commercial and personal data and are almost never publishable; the public corpora that do exist contain one system each. DRT Loom generates the enterprise instead — every domain woven into a single model from one integer seed, with every relationship known by construction.

git clone https://github.com/association-industrial-ai/drt-loom
Explore the knowledge graph

The weave draft

Weaving is notated as a grid: which threads a pass goes over, and which it goes under. So is this generator. Six domains are the warp, eight phases are the weft, and the eleven crossings below are PIPELINE — in the exact order the generator draws from its random stream. Where a pass ties two systems together, a line joins them. Most passes draw from one system only: what holds the six together is not the passes but the brackets above them, which are the dependencies the generator closes before it draws anything at all.

Pull a thread out. The enterprise is rebuilt without it, and the numbers below are what the generator actually produced for that selection.

The DRT Loom generation pipeline as a weave draft Six domain threads run down the page as columns; eight generation phases run across it as rows, in the order they execute. Brackets above the columns show what each domain needs before it can mean anything: PLM needs ERP, MES and CAD and Documents each need PLM, Logistics needs ERP. Eleven marked crossings show which domain contributes to which phase, numbered in generation order. Where a phase draws from more than one domain, a horizontal link joins them: parties ties ERP to MES, catalog ties ERP to PLM, engineering ties PLM to CAD. An open marker on the Logistics thread at the operations row is where its shipments are written, inside ERP’s pass rather than in one of its own.
The brackets above the columns are what each domain needs in order to mean anything — CAD is assemblies of PLM’s parts, and PLM’s parts are bought and sold through ERP. Logistics has no phase of its own: its shipments are emitted inside ERP’s operations pass, which is why it is tied there and marked with an open crossing rather than a numbered one.
entities
typed relations
cross a domain boundary
documents
answerable questions

Questions follow the domains. Each benchmark question declares the domains its reasoning path crosses, and one that crosses a domain you did not select is not emitted — an unanswerable question measures nothing.

Why the join is the hard part

Four objects. One number.

Ask the generated company what is the status of order 4711? and there is no single answer, because four different business objects carry that number. Retrieval on the string 4711 returns all of them and cannot tell you which is which — and they are all links in one causal chain.

  1. PUR-4711

    Purchase order

    60 × bearing housing 30-1177 from Nordwerk Guss GmbH, €12,870.

    erp/purchase_orders.json

  2. ECO-4711

    Engineering change order

    Bearing housing 30-1177, revision B → C. Bearing-seat tolerance tightened after field returns showed fretting.

    plm/engineering_changes.json

  3. PRO-4711

    Production order

    12 × KDU-3-B-45-20-F, released, planned 2026-08-10 to 2026-08-30, eight routing steps.

    mes/production_orders.json

  4. SO-4711

    Sales order

    Nordhavn Marine A/S, €173,430, requested for 2026-09-12, in production.

    erp/sales_orders.json

And they are the same thread

Nordwerk Guss GmbH supplies PUR-4711 delivers part 30-1177 consumed by PRO-4711 fulfils SO-4711 ordered by Nordhavn Marine A/S

Five hops across three systems. ERP holds the first and the last, PLM holds the part, MES holds the production order. No system holds the path, and no document in the corpus states it — but the generator knows it, because it built it. That is what makes an exact answer available to score against.

Output

One seed, one enterprise.

Records, documents, a knowledge graph, benchmark questions and ground truth all derive from the same model, so they cannot disagree with each other. Generation takes about 30 ms, with no network, no API keys and no runtime dependencies.

Everything a company produces, in one directory

data/generated/alpine-drive-systems/
├── config.yaml      the exact configuration this was built from
├── generation.json  seed, domains, counts, cross-domain edges
├── dataset.json     entities and typed relations
├── gold.json        only the questions these domains can answer
├── documents/       when the documents domain is selected
├── nx/              when CAD is selected
└── graph/           after `npm run graph`

The resolved configuration is written next to the output, so a company can always be rebuilt from its own artifacts — byte for byte.

Every relation is typed and carries its provenance

// dataset.json → relations[]
{
  "source": "PART-10-1798",
  "target": "SUP-001",
  "relation": "approved_supplier",
  "confidence": "EXTRACTED",
  "sourceFile": "erp/approved_vendor_list.json"
}

EXTRACTED, INFERRED or AMBIGUOUS. Where CAD carries no part number the link stays AMBIGUOUS rather than being silently resolved, so the uncertainty is in the graph instead of hidden behind it.

Each question carries the answer it must produce

{
  "id": "Q-MH-01",
  "category": "multi_hop",
  "question": "Nordwerk Guss GmbH has told us bearing housing 30-1177 will slip by
                three weeks. Which customer deliveries due before the end of
                November are at risk, and what is the total value exposed?",
  "expectedIds": ["SO-4711", "SO-4716", "SO-4720", ],
  "expectedValues": { "ordersAtRisk": 16, "customersAffected": 11,
                      "exposureEur": 2739771.54 }
}

Eighteen questions across six categories: disambiguation, multi-hop, aggregation, absence, lookup and narrative. The narrative questions are answerable only from prose, and a knowledge graph contributes nothing to them. That is deliberate — if one architecture wins every category, the benchmark is measuring its own construction rather than the systems under test.

Verification

Ground truth is derived, not annotated.

No LLM invents an entity, a relationship or an answer. Every gold answer is computed from the finished environment by a reference oracle that shares no code with anything being evaluated, and no machine-checkable field is copied from a scripted constant.

npm run verify
Generator, gold format, citation handling and scorer are mechanically compatible on the reference seed.
npm run verify:seeds
Six seeds rebuilt from scratch: every answer re-derived and compared to gold, cross-question invariants checked, then rebuilt again to confirm the output is byte-identical.
npm run verify:domains
Ten domain configurations held to the same standard as the full one, including that relations still cross domain boundaries — one enterprise, not several datasets sharing a directory.
npm run verify:config
Configuration round-trips unchanged, dependency closure handles chains and cycles, and every kind of bad input is rejected with a message naming the actual mistake.

What this does not prove. That the answers are objectively correct. A wrong answer scored against itself still returns 1.0. The verification proves self-consistency, determinism and shape — read KNOWN-ISSUES.md before publishing any number measured here.

Extending it

A domain is code, not a key in a YAML file.

Configuration selects meaning; it does not define it. Adding scada: true to config.yaml is a configuration error naming the typo, not a new domain. Registering a module is what makes a domain real.

The contract

export interface DomainModule {
  id: DomainId;
  label: string;
  dependencies?: readonly DomainId[];
  contributes: readonly NodeType[];

  // Keyed by phase, not a single generate(), because
  // domains genuinely interleave in the random stream.
  generate?: Partial<Record<Phase, (ctx) => void>>;

  // Optional. A domain with nothing to assert omits it
  // rather than implementing an empty method.
  validate?(ctx, problems: string[]): void;
}

Registered today

DomainNeedsContributes

Dependencies are closed automatically and the addition is reported, never applied silently. erp and plm are core: an enterprise with no parts and no orders is not a smaller enterprise, it is an incoherent one.

The contract, a worked PLC Engineering example, and the checklist

Build one.

npm run generate -- --name "Alpine Drive Systems" --domains erp,plm,cad --size small

Or run it with no options and it asks — company name, seed, size, and which optional domains to include. Node 20 or newer. Nothing else.