Quickstart¶
Five minutes, one repository, no Docker and no Gettier server. At the end you will have watched a sensor contradict a premise and block the change that rested on it.
Not published yet
gettier and @gettier/node are built and smoke-tested but not yet on npm. Until
they are, run the CLI from a clone — every command below works either way:
Once published, npm install -g gettier is the whole install step.
1. Initialise a project¶
initialised project 'acme' (environment development)
sensors are read from sensors.yaml, executed by id only
set GETTIER_DSN in the environment — never in this file
events go to https://api.gettier.io/api/v1/envelope (hosted default)
developing against a local dashboard? re-run with --local
That writes .gettier/project.json. Note what it does not write: the credential. It
records the name of the environment variable to read, never its value.
{
"project": "acme",
"environment": "development",
"registry": "sensors.yaml",
"dsnEnvVar": "GETTIER_DSN"
}
Telemetry is optional. Without GETTIER_DSN the CLI says so plainly (transport memory (no
ingestion endpoint configured)) and everything below still works — the gate runs entirely
on your machine.
2. Write a sensor¶
A sensor is a command that prints a reading. It is registered by id, and it can only ever be run by id — there is no path anywhere in Gettier that takes a command from a caller.
version: 1
sensors:
- id: db.identity
description: which database this service actually connects to
command: "node ./sensors/db-identity.js"
effects: none
params: {}
extract: [database]
redact: []
ttl: 60
timeout: 10
max_output_bytes: 1024
runs_as: gt-agent
verifies: [database.identity]
// Stands in for `SELECT current_database()`. A sensor prints a reading; that is all.
console.log(JSON.stringify({ database: 'production' }));
Check that Gettier can see it:
verifies is the load-bearing field
It names the assumption categories this sensor can falsify, and it is how a claim finds its sensor. A sensor that verifies nothing can never settle anything. The categories are listed in Contracts.
3. Declare what the plan assumes¶
This is Contract 1 — the same object a model produces at the proxy, verbatim, so nothing translates between two shapes that could drift apart.
{
"plan_summary": "Backfill the orders table against staging before the migration",
"lemmas": [
{
"claim": "the connected database is staging",
"provenance": "believed",
"load_bearing": true,
"falsifying_observation": {
"sensor": "db.identity",
"key": "database",
"operator": "!=",
"value": "staging"
}
}
]
}
falsifying_observation is the model saying what would prove me wrong. It is optional, and
when present it is read rather than guessed at — a claim that names its own falsifier does
not depend on a keyword rule matching its phrasing.
4. Run the gate¶
declaration .gettier/declaration.json
contradicted — 1 load-bearing of 1 declared · 1 contradicted · 0 unresolved · 0 verified by measurement
CONTRADICTED
the connected database is staging
claim says the connected database is 'staging' but db.identity reports current_database()='production'
sensors run db.identity
The plan was to backfill against staging. The database is production. Nothing about the backfill code was wrong — the premise underneath it was, and no test would have said so.
Now make it true. Change the sensor to report staging and run it again:
released — 1 load-bearing of 1 declared · 0 contradicted · 0 unresolved · 1 verified by measurement
VERIFIED
the connected database is staging
connected database is 'staging', matching the claimed 'staging'
Note the difference between this and a green test suite: the claim is not merely unrefuted, it is verified by measurement, and the ledger records which sensor said so and when.
5. Wire it into CI¶
check exits with a code a workflow can act on, and posts nothing anywhere.
| Exit | Meaning |
|---|---|
0 |
the gate allows the change |
2 |
the gate says no — a premise was contradicted, or one nothing could settle |
1 |
the check itself could not run |
1 is separate from 2 on purpose: a job that cannot tell "the premises did not check out"
from "the tool crashed" gets switched off after the second false alarm.
- name: Gettier — check the premises
run: gettier check --markdown gettier.md
- name: Comment
if: always()
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
if (!fs.existsSync('gettier.md')) return;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: fs.readFileSync('gettier.md', 'utf8'),
});
--markdown writes a file and posts nothing. This package holds no GitHub credential and
is not going to; the workflow that owns the token decides where the comment goes.
### Gettier — contradicted
1 load-bearing of 1 declared · 1 contradicted · 0 unresolved · 0 verified by measurement
#### Contradicted by measurement
- the connected database is staging
- claim says the connected database is 'staging' but db.identity reports current_database()='production'
<sub>1 sensor run</sub>
6. Let the agent drive it¶
gettier mcp serves three tools over stdio to any MCP-capable agent, so the agent declares
its own premises instead of you writing the JSON by hand.
| Tool | What it does |
|---|---|
list_sensors |
what this repository can measure, and which assumption categories each sensor falsifies |
declare_lemmas |
the premises the plan rests on, written to .gettier/declaration.json |
check_assumptions |
runs the sensors that bear on them and returns the contradictions |
The gate is cooperative here: an agent that never calls the tools is never gated. When it has to be unavoidable, put the grounding proxy in front of the model instead. Details in Gettier for Agents.
Where to go next¶
- The worked demo — the same loop against a real nginx trap, with a real model turn.
- Gettier for Agents — every flag, and the behaviour worth knowing before CI.
- Gettier for Runtime — instrumenting the deployed application.
- Contracts — the declaration schema and the sensor registry, frozen.