Skip to content

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:

pnpm install && pnpm --filter gettier build
alias gettier="node $PWD/packages/cli/dist/cli.js"

Once published, npm install -g gettier is the whole install step.

1. Initialise a project

gettier init acme
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.

sensors.yaml
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]
sensors/db-identity.js
// 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:

gettier sensors list
gettier sensors run db.identity
db.identity none    database.identity
{"database":"production"}

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.

.gettier/declaration.json
{
  "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

gettier check
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
echo $?   # 2

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'
echo $?   # 0

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.

.github/workflows/gettier.yml
- 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.md
### 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.

claude mcp add gettier -- gettier mcp
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