Skip to main content
Glama
README.md
<p align="center">
  <img width="1024" alt="estigIA — AI harness. Light. Local. Agentic." src="assets/branding/estigia-banner.png" />
</p>

<p align="center">
  <a href="https://github.com/asanabrial/estigia/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/asanabrial/estigia/actions/workflows/ci.yml/badge.svg" /></a>
  <a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-blue.svg" /></a>
  <a href="https://www.rust-lang.org"><img alt="Rust 1.97+" src="https://img.shields.io/badge/rust-1.97%2B-orange.svg" /></a>
  <a href="#the-tools"><img alt="MCP: 37 tools" src="https://img.shields.io/badge/MCP-37%20tools-purple.svg" /></a>
  <a href="#install"><img alt="Linux, macOS, Windows" src="https://img.shields.io/badge/platforms-Linux%20%7C%20macOS%20%7C%20Windows-lightgrey.svg" /></a>
  <a href="https://glama.ai/mcp/servers/asanabrial/estigia"><img alt="Glama MCP server" src="https://glama.ai/mcp/servers/asanabrial/estigia/badges/score.svg" /></a>
</p>

---

# estig**IA** — workflow authority for AI coding agents

Your agent says it claimed the issue, that the review passed, that the branch is current. Estigia
turns those sentences into things that were checked: the claim lives in a local store
(`~/.estigia/state.db`), every later write is measured against it, every step that cannot be undone
re-verifies it, and a review verdict is bound to exact bytes, so a push invalidates it.

One binary, under whatever agent you already use; it does not run your model. Its only detached
processes are a reviewer CLI and the declared checks ([which](docs/what-it-writes.md#which-processes-estigia-launches)).

> *El río del juramento. Lo que un agente promete, queda ligado a unos bytes exactos.*

**Leteo** is the river of forgetting. **Estigia** is the river of the oath — the gods swore by its
waters, and perjury cost nine years outside Olympus. Two rivers of the same underworld, opposite jobs:
one makes you forget, the other holds you to it.

## What it feels like

Mostly, nothing: a run that has claimed nothing works as before. When it refuses, it says what it
read and what to do about it:

```
$ git push origin HEAD
estigia: git push: gh issue view failed (1): none of the git remotes ... (read-failed)
  [world-action] the tracker could not be read; write nothing and retry the read
error: failed to push some refs
```

*The tracker could not be read* is a different answer from *you may not push*, and Estigia will not
turn the first into the second. That distinction is most of the design.

## FLOW — Focused, Lean, Outcome-driven, Witnessed

**FLOW is Estigia's own methodology: how a run takes a piece of work from an issue to a merge.**

- **Focused:** small work stays small. Understood work is done directly, not planned, and exploration is in proportion.
- **Lean:** one writer, the smallest topology that works, and review only as heavy as the risk.
- **Outcome-driven:** done means the issue's acceptance criteria hold, with evidence, merged and verified.
- **Witnessed:** deterministic evidence first, then review by a context that did not write the change, and the merge at the reviewed head.

It rests on two pillars: *the claimed issue is the record of the work*, and *the publication is the
review candidate*. Four principles follow from them: small work stays small; review is sized by risk;
a review lineage gets a bounded number of corrections (`Corrections per lineage`, default 3); and the
heavyweight two-judge review (Judgment Day) runs only when asked for.

You start it with `flow` (analyse and build, choosing the next issue from the tracker), `flow dev
[issue]` (build) or `flow triage` (analyse and file at most one evidenced issue). A run then goes
through five steps, which may merge or repeat:

```mermaid
flowchart LR
    understand["Understand"] --> route["Route"] --> execute["Execute"] --> verify["Verify"] --> deliver["Deliver"]
    verify -->|"finding"| execute
```

| Step | What happens |
|---|---|
| **Understand** | Read only; explore in proportion; at most one product question and one challenge to a premise. |
| **Route** | Do it directly when it is understood; plan briefly only for real uncertainty; delegate only for a stated reason. |
| **Execute** | Bounded changes, Conventional Commits, test-first wherever a deterministic test applies. |
| **Verify** | Deterministic evidence first, then the review: none, one or four sealed lenses by risk tier, by a context that did not write the change. |
| **Deliver** | `deliver` records the verdict, waits for CI, merges at the reviewed head and closes with evidence — or hands the decision back. |

Work that spans sessions or divides into units with their own acceptance keeps an ordered checklist
on the claimed issue (`task`), never in a file. FLOW is how a run works; the workflow below is what
Estigia enforces while it does.

## The workflow

The workflow is the part a run cannot talk its way around: the states an issue moves through and the
checks at each boundary. FLOW runs inside it.

```mermaid
flowchart TD
    analysis(["analysis"]) --> ready(["ready"])
    ready --> claim["claim"]
    claim --> inprogress(["in-progress"])
    inprogress -.-> blocked(["blocked"])
    blocked -.-> inprogress
    inprogress --> publish["publish"]
    publish --> review(["review by risk tier"])
    review --> verdict["verdict on exact bytes"]
    verdict -->|"push: republish"| publish
    verdict --> deliver["deliver"]
    deliver -->|"approved"| release["release"]
    release --> done(["done"])
    deliver -.->|"needs a person"| person["hand-back"]
    person -.-> deliver

    classDef state fill:none,stroke:gray,stroke-width:2px
    classDef action fill:none,stroke:gray,stroke-dasharray:4 3
    class analysis,ready,inprogress,blocked,review,done state
    class claim,publish,verdict,deliver,person,release action
```

Rounded nodes are states, square nodes are actions; dotted edges are the exceptions. What the gate
holds at each boundary:

- **One state at a time**, in the local store; labels are a projection, written and read back.
  `blocked` names an exact exit condition and who discharges it.
- **A claim before the first write.** The earliest live claim wins, and the gate re-verifies it on
  every write it sees and at every irreversible step.
- **A verdict bound to exact bytes** — epoch, head, base and digest. Any push invalidates it:
  republish and ask again. A body says `Refs #<n>`, never a closing keyword.
- **Release last**, after the evidence is posted ([details](docs/what-it-writes.md#a-run-id-release-and-prune)).

The contract an agent reads is [`skill/SKILL.md`](skill/SKILL.md#flow); the GitHub half is
[`delivery.md`](skill/bindings/github/delivery.md); review cost per tier is in
[`review-cost.md`](docs/review-cost.md).

## Install

Pick one route. Each is copy-paste.

Linux, macOS:

```sh
curl -fsSL https://raw.githubusercontent.com/asanabrial/estigia/main/scripts/install.sh | sh
```

Windows (PowerShell):

```powershell
irm https://raw.githubusercontent.com/asanabrial/estigia/main/scripts/install.ps1 | iex
```

npm (installs and sets up in one step):

```sh
npx -y @asanabrial/estigia setup --all
```

Cargo (needs Rust):

```sh
cargo install estigia
```

Then register it in your agents, and check:

```sh
estigia setup --all      # or: estigia tui
estigia status
estigia doctor
```

Linux archives need glibc 2.39+ (not Debian 12, Ubuntu 22.04, RHEL 9); there, use `cargo install estigia`.
The installers check the archive against its published checksum and refuse a mismatch. The binary
lands in `~/.local/bin/estigia` (`%LOCALAPPDATA%\estigia\bin\estigia.exe` on Windows);
`ESTIGIA_INSTALL_DIR` moves it and `ESTIGIA_VERSION` takes a tag other than the latest. The npm
package carries no binary: it fetches the same archive, and it is what the
[MCP registry](https://registry.modelcontextprotocol.io) entry starts (`mcp-name: io.github.asanabrial/estigia`). A source build has no installer record, so the
commands that write into your agents ask for `--allow-source-build` ([why](docs/lifecycle.md));
`cargo install --git https://github.com/asanabrial/estigia` takes main. The [`Dockerfile`](Dockerfile)
builds a server-only image; none is published.

## Set it up in your agents

Installing the binary changes nothing on its own.

1. `estigia tui` opens the setup screen; `s` shows what it will write. Without it:
   `estigia setup claude-code` for one agent, `estigia setup --all` for all; `--dry-run` on either
   writes nothing.
2. Optionally route a role to a model, the spelling the screen ends by printing:

   ```sh
   estigia config set "Model routing" "developer=inherit" --agent claude-code
   ```

3. In a repository where any agent may write, `estigia guard` installs a `pre-push` hook that refuses a
   push no live claim authorises, whoever typed it.

`setup` installs the skill, a directive in the agent's instruction file and the hooks, which go into
every agent with an event able to refuse — five of them, in a gate that reads five dialects. What else it
writes is in [what it writes](docs/what-it-writes.md#what-setup-installs-besides-the-skill-and-what-the-hosts-permission-mode-does-not-change).

## Verify

1. `estigia status`: what is installed, where, and whether each agent is told (`tools on`) and stopped
   (`gate on`).
2. `estigia doctor`: skill, gh, remote, guard, prune, permission and the rest, read-only.
3. `estigia sessions`, `feed`, `where`, `needs-person` and `changes` (uncommitted work) are read-only too.

## Uninstall

Uninstall removes what Estigia created and nothing else: not another tool's block, not your notes, not
a file of its own you have since edited, which it keeps and names. Run in this order:

```sh
estigia uninstall --all --dry-run   # changes nothing; says what the next line will do
estigia uninstall --all             # or: estigia uninstall claude-code
estigia guard --uninstall           # in each repository with the push guard
estigia config forget               # in each checkout
cargo uninstall estigia             # source build only; otherwise delete the binary
rm -rf ~/.estigia                   # optional, read the warning first
```

1. The dry run is safe to repeat; `estigia uninstall claude-code` leaves the other agents alone.
2. `uninstall --all` takes Estigia out of every agent.
3. `guard --uninstall` removes only a hook Estigia wrote, and must go before the binary, which the
   hook calls.
4. `config forget` removes `<checkout>/.git/estigia/`, the repository settings every uninstall
   survives; it says which file it removed.
5. Remove the binary by the route you installed with. Installer script or npm: delete
   `~/.local/bin/estigia` (or `$ESTIGIA_INSTALL_DIR/estigia`); on Windows delete
   `%LOCALAPPDATA%\estigia\bin\estigia.exe` (or under `%ESTIGIA_INSTALL_DIR%`) and take that directory
   out of the user `PATH`. A source build: `cargo uninstall estigia`.

**Before `rm -rf ~/.estigia`:** `state.db` is the workflow store, one copy on this machine: claims,
states, receipts, findings, verdicts, spend, tasks, the decision history, the session registry and the
mailbox. Beside it: run pointers; `jobs/`; `snapshots/` (up to three copies of the store);
`lifecycle/` (the binary's provenance records); `workspaces/` and `roots/`; the screen's language; your
checkouts list. The last `uninstall --all` takes the store and the run state and keeps the rest
([per entry](docs/what-it-writes.md#what-estigia-holds-and-what-each-uninstall-step-leaves)). Delete it
only when you no longer want the history of what this machine's gate decided.

What `REPLACE` and `OVERWRITE` mean is in [`docs/what-it-writes.md`](docs/what-it-writes.md).

## A harness, not a document

The contract under `skill/` is text an agent may ignore, so Estigia holds the tools: a `PreToolUse`
gate, a `Stop` hook, a `pre-push` hook under git, and thirty-seven operations with schemas below. The local store is the
authority; the tracker keeps a projection that never decides. The diagram, the loop plugins and the
gate's rules are in [what it writes](docs/what-it-writes.md#the-gate-the-hooks-and-the-loop-plugins).

## The tools

Thirty-seven operations, table-driven. Thirty-one map to operations the binding must provide;
`send_message`, `ask`, `wait`, `inbox` and `list_sessions` are answered by the local store and `deliver` composes
the rest with the merge gate, outside the binding. `tools/list` carries each schema and a
`readOnlyHint`. The seven delivery tools (`claim`, `publish_review`,
`collect_review_input`, `deliver`, `release`, `transition`, `review_status`) carry `_meta["anthropic/alwaysLoad"]`; every tool takes
`compact` ([cost](docs/review-cost.md#what-the-tool-menu-itself-costs)):

```
read   verify_claim
read   list_state
read   review_status
read   review_read
read   tasks
read   list_sessions
WRITE  claim
WRITE  handoff
WRITE  heartbeat
WRITE  transition
WRITE  comment
WRITE  blocked_until
WRITE  task
WRITE  send_message
WRITE  ask
WRITE  wait
WRITE  inbox
WRITE  release
WRITE  ensure_states
WRITE  start_branch
WRITE  judge_workspaces
WRITE  assist
WRITE  pr_review
WRITE  draft_pr
WRITE  ready_pr
WRITE  review_stages
WRITE  review_thread
WRITE  create
WRITE  publish_review
WRITE  republish_review
WRITE  handoff_review
WRITE  record_review_finding
WRITE  record_disposition
WRITE  collect_review_input
WRITE  escalate_review
WRITE  record_spend
WRITE  deliver
```

`where`, `feed`, `status` and `flush` are commands of `estigia` rather than tools; asking for one of them, or for a
name the surface dropped, answers what replaces it. `review_read` also reads the review tier (`view: tier`) and the
delivery target (`view: target`); `collect_review_input` also starts a reviewer (`launch`) and runs the declared checks
(`checks`); `deliver` stops after the verdict or the CI release with `until`.

Thirty-seven tools. The contract names seventeen operations every binding must map; the tools absorbed some
under a surviving name ([why](docs/github-binding.md#why-the-transport-is-in-this-process-and-why-the-server-is-hand-written)).
Arguments are in each tool's schema.

## Configuration

Thirty-two typed settings, in [`docs/configuration.md`](docs/configuration.md), grouped by theme and shown
in one order by `estigia config list`, the setup screen and the reference alike. Each row says whose
answer it is: this agent's, this repository's, every checkout's or this machine's.

## The ratchet

A message may name a command only when running it discharges the block; a refusal carries a typed code
and a runnable resolution ([classes](docs/refusal-classes.md#the-ratchet-and-what-it-was-started-from)):

```
  run: estigia setup --all   # or one of: agents, claude-code, codex, opencode, pi, cursor, zcode, deepseek-harness
```

## What this instrument does not measure

[`docs/honesty.md`](docs/honesty.md) names what this crate does **not** check, each with the
measurement that found it; [`docs/README.md`](docs/README.md) maps the rest of the set.

## Layout

```
src/harness/   the gate, the run pointer, the transport, the lifecycle protocol, doctor
src/setup/     agent adapters, companions, dry-run, exact inverse
src/config/    typed model, the settings table as data
src/cli/       dispatch, and the only place a refusal becomes an exit code
skill/         SKILL.md, bindings/, policies/, protocols/, references/, assets/, agents/ — not compiled
docs/          the reference the sections above point at, one question each
scripts/       the two installers a release is fetched with, and the Windows source reinstall
```

[`AGENTS.md`](AGENTS.md) — which `CLAUDE.md` is a link to — is how an agent works here.

## Exit codes

Four, and none of them means "something went wrong":

| Code | Meaning |
|---|---|
| `0` | the command did what it said |
| `1` | the command refused; the refusal says whether anything **had already been written** |
| `2` | the world may have changed and this process cannot tell |
| `3` | the invocation could not be read: nothing was attempted, and **nothing was decided** |

Why there are four: [`docs/refusal-classes.md`](docs/refusal-classes.md#the-exit-codes-and-why-there-are-four).

## Licence

MIT. Files derived from another MIT-licensed project carry its notice in [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md).