# GeoSpec for agents

The check-and-fix loop an agent runs with GeoSpec, what each verdict demands, and the Markdown endpoints to read.

Canonical page: https://www.geospec.xyz/docs/agents/for-agents

GeoSpec gives a coding agent a verdict about a model rather than a picture. The agent writes a spec, one claim per requirement, runs it on the export and reads `passed`, `failed` or `refused`. The claims come from the requirement; every number in a result comes from the model.

### 1. Run the loop [#1-run-the-loop]

1. Model the part.
2. Export: STEP for feature claims, GLB or STEP for bounds and mass.
3. Write one claim per requirement, at the drawing's tolerance.
4. Run the spec; read each verdict.
5. Fix the model at its root, never the claim. Export and run again.

Never widen a tolerance, drop a claim or remove detail to pass; missing evidence is unsupported, not a pass.

### 2. Read the three verdicts [#2-read-the-three-verdicts]

| Verdict   | What it demands                                                                                                                                                                    |
| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `passed`  | Nothing. Keep the canonical result bytes with the model bytes; model, spec and engine version reproduce them ([Reproduce a result](https://www.geospec.xyz/docs/guides/reproduce-a-result.mdx)).              |
| `failed`  | The model differs from the claim. Read what was measured in the diagnostic's `details`, change the model, re-export.                                                               |
| `refused` | The evidence cannot decide the claim; the code names what is missing. Give the claim that evidence, or claim what this subject answers, and report the requirement as unsupported. |

A claim for a feature the model lacks fails; a claim is refused only when the evidence cannot decide it, negated or not. [Read a refusal](https://www.geospec.xyz/docs/guides/read-a-refusal.mdx) maps each code to a change.

### 3. Match the claim to the evidence [#3-match-the-claim-to-the-evidence]

A triangle mesh (GLB or glTF) answers bounds, volume, surface area, mass with a density, centre of mass, watertightness, components and integrity. Faces, holes, patterns, chamfers, fillets, wall thickness, topology and STEP units need a STEP export; on a mesh they refuse with `GEOSPEC_UNSUPPORTED_EVIDENCE`. The [cheat sheet](https://www.geospec.xyz/docs/reference/cheat-sheet.mdx) has the full table.

```ts
expectGeo(part)
  .toHaveCircularHole({ diameter: 12, axis: 'z' });
```

The drawing calls for a 12 mm bore and the plate carries 10 mm: the claim fails with `GEOSPEC_FEATURE_MISMATCH` and `details.circularHoles` lists the hole it found, 10 mm along z. The fix is a 12 mm bore in the CAD source and a new export; the demo cannot re-export, so set `diameter` to 10 to see the fixed model's answer.

### 4. Run from a shell [#4-run-from-a-shell]

**TypeScript**

```bash
npm install geospec@beta geospec-engine
```

`geospec` is the spec API; `geospec-engine` is the engine, here its native addon ([Install](https://www.geospec.xyz/docs/install.mdx) covers WASM). A failed or refused claim throws `GeoSpecAssertionError`; run the script with Node 24 next to the plate:

```ts title="check.ts"
import { readFile } from 'node:fs/promises';
import { expectGeo } from 'geospec';
import { GeoSpecAssertionError } from 'geospec/assertion-client';
import { createModelLoader } from 'geospec/model';
import { Engine } from 'geospec-engine/node';

const engine = new Engine();
const loadModel = createModelLoader({
  engine,
  readSource: async (source) => new Uint8Array(await readFile(source as string)),
});
const plate = await loadModel({ source: 'plate-with-through-bore.step', format: 'step' });

try {
  expectGeo(plate).toHaveCircularHole({ diameter: 12, axis: 'z' });
  console.log('passed');
} catch (error) {
  if (!(error instanceof GeoSpecAssertionError)) throw error;
  console.log(error.report.status, error.diagnostics[0].code, error.message);
}
await loadModel.dispose();
engine.close();
```

```console
$ node check.ts
failed GEOSPEC_FEATURE_MISMATCH No circular hole matches diameter 12 within 0.02 mm.
```

In a repository, write `*.geospec.ts` files and run `geospec run .` from `geospec`: the exit code is 0 only when every test passed, and a refused claim leaves its test `inconclusive` ([Test parts in CI](https://www.geospec.xyz/docs/guides/test-parts-in-ci.mdx)). Under your own test runner, a failed or refused claim throws `GeoSpecAssertionError`, so the test fails and a refusal never counts as a pass.

**Python**

```bash
pip install geospec-engine
```

The wheel installs the `geospec` module (CPython 3.13 or 3.14) with a pytest plugin; matchers are the registry names in snake case. A failed or refused claim raises `GeoSpecAssertionError`; `evaluate_geo` returns the report without raising. Run the script next to the plate:

```python title="check.py"
from geospec import GeoSpecAssertionError, GeoSpecEngine, expect_geo, load_model

with GeoSpecEngine(model_root="."):
    plate = load_model(file="plate-with-through-bore.step", format="step")
    try:
        expect_geo(plate).to_have_circular_hole(diameter=12, axis="z")
        print("passed")
    except GeoSpecAssertionError as error:
        print(error.result["status"], error.code, error)
```

```console
$ python check.py
failed GEOSPEC_FEATURE_MISMATCH No circular hole matches diameter 12 within 0.02 mm.
```

Under pytest the plugin supplies the engine, so a test file needs no fixtures.

### 5. Read the docs as Markdown [#5-read-the-docs-as-markdown]

* `/llms.txt`: the index, every page linked as Markdown.
* `/llms-full.txt`: every page, concatenated.
* `/docs/<slug>.mdx`: one page, both language faces labelled, each live demo as its spec fence.

### 6. Paste into AGENTS.md [#6-paste-into-agentsmd]

Also served at [`/AGENTS-snippet.md`](/AGENTS-snippet.md):

```md title="AGENTS.md"
## GeoSpec geometry checks

- `npm install --save-dev geospec@beta` (Node 24) or `pip install geospec-engine` (CPython 3.13 or 3.14).
- Export z-up: STEP for feature claims, GLB or STEP for bounds and mass; a millimetre GLB declares `sourceUnit: 'mm'` / `source_unit="mm"`.
- Load `loadModel({ source: 'part.step', format: 'step' })` / `load_model(file="part.step", format="step")`; one claim per requirement at the drawing's tolerance (default 0.02 mm).
- Run `geospec run .` over `*.geospec.ts` files (`describe`, `it` and `expectGeo` from `geospec`, `loadModel` from `geospec/model`; exit 0 only when every test passed; a refused claim leaves its test `inconclusive`; `--json` prints the report) or `pytest` over `test_*.py`; each claim is `passed`, `failed` or `refused`.
- Failed means the model is wrong: fix it and re-export; never widen a tolerance, drop a claim or remove detail to pass.
- Refused means the evidence cannot decide: admit the subject the code names or claim what it answers; report it as unsupported, never passed, even negated.
- Keep the canonical result bytes with the model bytes; model, spec and engine version reproduce them.
- Reference: https://www.geospec.xyz/docs/reference/cheat-sheet.mdx and https://www.geospec.xyz/llms.txt
```