OpenEik.org
Developers

Build on OpenEik. A building is a folder.

Read a building model, check it, keep your app's results beside it and store its versions, with one library that has no dependencies. Fifteen minutes from nothing to a checked model.

In testing The specification, OpenEik 0.4, its schemas and the @openeik/tools library are in testing with early users, and open to the public at a release whose date is not set yet. Everything on this page runs today, with the library installed from openeik.org; npm and the links to the repository come with the public release.

01 · The format

One small JSON record per file, in folders that mean something.

An OpenEik model is a folder of plain files: it works on a disk, in git and in any store. What a compiler can derive (bodies, joins, areas, drawings) is never stored.

one-storey-house/
eik.json                the format and the units
AGENTS.md               the conventions, for agents
project/
  project.json  levels.json  order.json
types/architecture/wall/
  type-wall-exterior-300.json
architecture/level-0/
  wall/wall-south.json
  door/door-entrance.json
  window/window-north.json
architecture/level-1/
  roof/roof-main.json
drawings/views/plan/view-plan-0/view.json
architecture/level-0/wall/wall-south.json{
  "category": "wall",
  "height": 2700,
  "id": "wall-south",
  "levelId": "level-0",
  "name": "South wall",
  "offset": 0,
  "profile": [
    [10000, 0],
    [0, 0]
  ],
  "revision": 1,
  "top": {
    "attach": {
      "face": "underside",
      "hostId": "roof-main"
    },
    "constraint": "unconnected"
  },
  "typeId": "type-wall-exterior-300",
  "width": 300
}

Millimetres, Z up. A wall's level plus its offset is its base, and its two profile points are its centreline. Its layers come from its type, exterior first, on the left of the line's direction. The specification, Parts I to III, fixes the rest.

02 · Quick start

Read it, check it, hash it: three calls.

@openeik/tools reads, checks, hashes, diffs and stores OpenEik models. It is written from the specification alone and has no dependencies. It needs Node.js 20.16 or later, or any modern browser. Its README is the API, with its functions and every diagnostic code. It reads and writes models of 0.2 and of 0.1, and migrates a model of 0.1 to 0.2.

  1. Install

    From openeik.org until the public release. From then on it is on npm, as npm install @openeik/tools.

    shellnpm install https://openeik.org/tools/openeik-tools-0.4.tgz
  2. Get a building, and check it

    Download a sample, unzip it, and check it with the openeik command that comes with the library. In the folder you installed it in, npx @openeik/tools runs that command and fetches nothing. Exit 0: the folder is valid.

    shellcurl -O https://openeik.org/samples/one-storey-house.zip
    unzip one-storey-house.zip
    npx @openeik/tools check one-storey-house
    one-storey-house: 34 files (1 agents, 1 eik, 10 element,
    1 levels, 1 order, 1 project, 10 record, 6 type, 3 view);
    0 errors, 0 warnings; 0 generic elements
  3. Read a folder

    A folder is checked whole as it is read, and refused, with the file and the rule, if it fails.

    read.mjsimport { readFolder } from "@openeik/tools";
    
    const house = readFolder("one-storey-house");
    house.model.elements.length;                 // 10
    house.get("elements", "wall-south").name;    // "South wall"
    house.pathOf("elements", "wall-south");
    // "architecture/level-0/wall/wall-south.json"
  4. Check it

    Every diagnostic has a code and names the rule of the specification it comes from. The codes are the library's own, and a command refused for a rule the validator checks carries the same code (Part XVII, 10.5).

    check.mjsimport { checkFolder } from "@openeik/tools";
    
    const { ok, errors, warnings } = checkFolder("one-storey-house");
    // ok: true, 0 errors, 0 warnings
    // each diagnostic: {severity, code, path, pointer?, message, rule}
  5. Hash it

    The root of its version tree. The same folder gives the same root on any machine, in any store.

    hash.mjsimport { versionTree } from "@openeik/tools";
    
    versionTree("one-storey-house").root;
    // "d59785ace3086ce8bb0cb609e68e2ddf64c7edd665d42bc09a25b70abad5491f"

In a browser

Import the same library from openeik.org, as the validator does, and pass the files instead of a path: a Map of relative paths to texts or bytes, from a zip or a file picker. Nothing leaves the page.

browserimport { readFolder }
  from "https://openeik.org/tools/0.4/index.js";

const files = new Map([
  ["eik.json", "…"],
  ["project/project.json", "…"],
  /* … */
]);
const house = readFolder(files);

When a folder is refused

readFolder throws an OpenEikError with code: "folder-refused" and the diagnostics. {lenient: true} reads it anyway, with the diagnostics beside the model.

a wall that names a type the model lacksThe folder does not read as OpenEik: 1 error,
first architecture/level-0/wall/wall-south.json:
typeId names type "type-wall-thick", which is
not in the model [ref-missing, I.7]
03 · For AI agents

Work on a building as on a code base: change a file, check, diff.

The openeik command comes with the library: npx @openeik/tools runs it, as in the quick start. An agent maps the folder, reads only the records it will change, and checks after every change.

  1. Read the conventions, then map

    Every folder has an AGENTS.md with what cannot be read off the files. openeik tree shows the layout with a hash and a count per folder.

    openeik tree house architecture --leaves30a3b0429e69…  architecture/  10 leaves
    272c17993b79    level-0/  9
    a7434dea68fd      door/  1
    026e320443e9        door-entrance
    69b374bd16b0      slab/  1
    21bc55f49410        slab-ground
    3dbdc20cf0a4      wall/  5
    10bd3e595a8e        wall-east
    e70e185ebf63        wall-north
    …
  2. Change a record

    Keep every member you do not know, add 1 to its revision, keep the stored form (sorted keys, two spaces, one member a line). Never write derived data.

    architecture/level-0/wall/wall-south.json-  "height": 2700,
    +  "height": 3000,
    -  "revision": 1,
    +  "revision": 2,
    -  "typeId": "type-wall-exterior-300",
    +  "typeId": "type-wall-thick",
  3. Check

    Done means no errors. Exit 0 is valid, 1 is errors found. --json gives the same as data.

    openeik check househouse: 34 files (1 agents, 1 eik, 10 element, 1 levels,
    1 order, 1 project, 10 record, 6 type, 3 view);
    1 error, 0 warnings; 0 generic elements
      error   architecture/level-0/wall/wall-south.json:
      typeId names type "type-wall-thick", which is not
      in the model [ref-missing, I.7]
  4. Fix, check again, and say what changed

    With the type set back, the check passes. openeik diff reports by record and field.

    openeik diff before housechanged elements wall-south
      (architecture/level-0/wall/wall-south)
      fields: height
    1 record change, 0 kept files

A store's token is never an argument: it is read from OPENEIK_TOKEN. The reference implementation also serves models to agents over the Model Context Protocol; the specification's Part VII fixes what any agent host must offer, whatever the protocol.

04 · The model as code

Change it with commands, merge two lines, compile what it means.

Beyond reading and checking, the library changes a model the way the specification says a host must, merges two lines of work by record and field, writes a model back as a folder, and derives the bodies and quantities of the class-A rules.

Commands

Typed changes, checked in three stages and applied all or nothing (Part V). A stale revision is refused, so two writers never overwrite each other unseen.

commands.mjsimport { readFolder, applyCommands }
  from "@openeik/tools";

const house = readFolder("one-storey-house");
const answer = applyCommands(house.model, [{
  kind: "update", id: "wall-west",
  expectedRevision: 1,
  patch: { name: "West wall (render)" },
}]);
// ok: true, affected: ["wall-west"],
// verify: {filter: {elementIds: ["wall-west"]}, …}
// with expectedRevision: 7 instead:
// "Command 0 (update wall-west): Revision conflict:
//  expected 7, current 1. Read and retry."

Merge

Three-way, against the version both lines started from (Part VI, 5): records matched by id, members merged one by one, the result checked whole.

merge.mjsimport { mergeModels } from "@openeik/tools";

const result = mergeModels(base, ours, theirs);
// ours renamed wall-south, theirs raised wall-east:
// ok: true, merged: wall-south from ours,
//   wall-east from theirs
// both renamed wall-south:
// conflicts: [{key: "elements/wall-south",
//   kind: "both changed", path: "/name"}]

Compile

Bodies, quantities and members' lines from the class-A rules of Part IV. Nothing derived is written back.

compile.mjsimport { compileModel } from "@openeik/tools";

const { quantities } = compileModel(house.model);
quantities["wall-south"].grossArea.value;
// 27.0359… m², under the roof's slope
quantities["roof-main"].slopedArea.value;
// 95.4706… m²
quantities["roof-main"].ridge.value;
// 10.6 m

Write it back

writeFolder writes a model as its folder: only the files whose bytes change, the files of records that went removed, and nothing else touched, so git sees exactly what changed. Read a folder and write it back unchanged, and every file comes out byte for byte as it was.

after the rename abovewriteFolder("one-storey-house", answer.model);
// {written: ["architecture/level-0/wall/wall-west.json"],
//  removed: [], unchanged: 32}

From the command line

The same, for people, scripts and agents. --out writes a folder, or one file when the path ends in .json.

shellopeneik apply house batch.json --out house
openeik merge base ours theirs --out merged
openeik compile house --quantities
openeik write model.json house
openeik read house --out model.json
05 · Apps

Keep your results beside the building, tied to what they were computed from.

An app writes into a top-level folder of its own (analysis/<model>/, schedule/ …) and never into the building's records. A reader keeps the files it does not know byte for byte, through export, save, restore and merge. Each result set carries a manifest that says exactly what it was computed from, so anyone can tell later whether it is still current.

results.mjsimport { readFolder, recordInputs, inputsDigest,
  intentInputs, runId, resultManifest, resultStatus }
  from "@openeik/tools";

const house = readFolder("one-storey-house");
const select = { categories: ["wall", "slab"],
                 lists: ["levels", "types"] };
const inputs = inputsDigest(recordInputs(house, select));
// {count: 13, digest: "a35239ab…"}
const model = inputsDigest(
  intentInputs(house, "analysis/daylight")).digest;
const engine = { kernel: "my-app 0.1.0" };

const manifest = resultManifest({
  app: { name: "my-app", version: "0.1.0" },
  kind: "daylight",
  version: null, // a folder with no version control
  inputs, model, engine,
  made: { at: new Date().toISOString(),
          by: "jane@example.com" },
});
runId({ inputs, model, engine }); // "r-4910fce015dc"

// Later: are the results still current?
resultStatus(manifest, { inputs: inputsDigest(
  recordInputs(readFolder("one-storey-house"), select)),
  model });
// {current: true, inputs: true, model: true}

What a result says about itself

inputs
One hash over every record the app read, and their count. The same on disk, in git and in any store.
model
The same over the app's own intent files, leaving out its results and proposals.
version
The version that was read: a commit in a store, a git commit, or null for a plain folder.
upstream
On an alternative line, the main line's version it started from.

The result manifest (openeik-result/0.1) and the proposal file are part of OpenEik since 0.1 (Part VI, 8). The Apps page shows the whole pattern: results in the tree, stale results, alternative lines and proposals.

06 · Versions

Store versions anywhere that speaks the API, or nowhere at all.

A version is the same folder stored as content-addressed objects: each record is named by the hash of its canonical JSON, each folder is a tree, each version a commit. A store is optional: git or a plain disk works too.

versions.mjsimport { connectStore, pushVersion, readVersion }
  from "@openeik/tools";

const store = connectStore({
  url: "https://api.example.org",
  repo: "acme/house",
  token: process.env.OPENEIK_TOKEN,
});
const { version } = await pushVersion(store, "one-storey-house",
  { author: "Jane Doe", name: "Ground floor" });
const back = await readVersion(store, version); // or "main"

Only what the store lacks is uploaded. A ref moves by compare-and-swap, so two people saving at once never overwrite each other: the second is told the ref moved, with where it is now.

An app that writes into a building it does not own writes its own line, an alternative (ref: "alternatives/my-app-…"), and proposes changes for review, never straight onto main.

The store protocol is the one EikHub speaks (API v0). EikHub is one commercial service that stores OpenEik models; nothing in the standard requires it.

07 · Reference

What to read, for what you are building.

An app that reads

Parts I to III of the specification (foundations, records, the folder) and the schemas. A reader of the core needs nothing more.

An app that writes

Add Part V (commands) and the guide Implementing OpenEik as an app's native format: ids, the stored form, revisions, what never to store.

An app with results

The guide Reading and saving an OpenEik model: where a model lives, versions, result manifests, proposed changes and sign-in.

An agent host

Part VII, the agent interface: the map, tiered reads, the filter, budgets, honesty fields and the write loop, whatever the protocol.

A version store

Part VI, history: objects, trees, commits and refs, the diff, and the three-way merge by record and field.

An exporter

Part VIII, bridges: IFC and other formats, and the fidelity report an exporter gives.

In 0.2: electrical installations. Part XIII covers devices and boards, circuits, feed chains, control, cable carriers and modelled cables. The library checks a model of 0.2 by its rules; a model of 0.1, where it was a proposal, only when asked: npx @openeik/tools check --proposal electrical <folder>. Its schemas are in /schemas/0.4/, and its 87 conformance cases are in the suite's zip.

In 0.2: drawings. Part XIV covers sheets, issued drawings as SVG whose lines name their elements, the read set and freshness, the register, issues, reviews and sign-offs. The library checks a model of 0.2 by its rules; a model of 0.1 only when asked: npx @openeik/tools check --proposal drawings <folder>. npx @openeik/tools drawing status <folder> and impact tell which issued drawings a change makes stale, diff compares two revisions element by element, and verify checks sign-offs offline. Its schemas are in /schemas/0.4/, and its 79 conformance cases are in the suite's zip.

The files, on openeik.org

The specificationOpenEik 0.4, as pages, and as text for agents: llms.txt and llms-full.txt
The schemaseach at its $id, or all in one zip: openeik-schemas-0.4.zip
The extension registrythe prefixes apps register for what they add to a model, and the names OpenEik reserves; how to register one
The samplescomplete buildings, each a zip of its folder
The conformance suitecases for readers, validators, command processors and drawing validators, each a small model with what they must do with it, in one zip
The libraryfor npm, as in the quick start, and as ES modules under /tools/0.4/; its README is the API, with its functions and every diagnostic code
OpenEik 0.1kept as it was: its schemas, its conformance suite and its library

Conformance

An implementation conforms in one or more classes: reader, validator, writer, compiler, command processor, version store, agent host or bridge, and from 0.2 drawing validator, drawing issuer, host or authoring tool. A claim names the version and the classes, for example "OpenEik 0.4.0 reader and validator". @openeik/tools reads, writes and validates models, merges them, applies the core commands and compiles the class-A rules; the limits below say where it stops short of a class.

Put the OpenEik compatible badge, from the home page, on your site when your tool reads and writes folders that pass openeik check. The conformance suite, cases for readers, validators, command processors and drawing validators, is one zip; Conformance says what each class must do, and where the implementations stand.

Licences

The specification and the guidesCC-BY-4.0, with a royalty-free patent commitment (OWFa 1.0)
The schemas, the examples and the conformance suiteCC0-1.0: copy them into any implementation, open or closed
The tools and packagesApache-2.0
The Unicode data in @openeik/toolsUnicode License v3, in the package's NOTICE

OpenEik is non-profit and vendor-neutral. Proposals and decisions are made in the open, through RFCs, once the repository is public.

08 · Limits

Where it stops, for now.

Where OpenEik stands domain by domain, and what comes next: the roadmap. The apps that already work with it: Apps.