Back to the catalog

grasp

GRASP cryptographic causation for Gemini CLI: signed, tamper-evident, third-party-verifiable records of what the agent decided, believed, an

Open source Open in the app JSON README (API)

About

GRASP cryptographic causation for Gemini CLI: signed, tamper-evident, third-party-verifiable records of what the agent decided, believed, and claimed.

Details

Kind
Plugins
Topic
Finance & crypto
Publisher
codetonight-sa
Origin
gemini
Category
ferramentas
Version
0.2.0
Stars
2
Last push
2026-08-18T01:03:11Z
Repository state
ativo
Language
Python
License
AGPL-3.0
Added
2026-08-30 14:13:39
Updated
2026-08-30 14:13:39
Origin id
codetonight-sa/grasp

README

# GRASP — Governed Reasoning And Signable Provenance

[![CI](https://github.com/CodeTonight-SA/grasp/actions/workflows/ci.yml/badge.svg)](https://github.com/CodeTonight-SA/grasp/actions/workflows/ci.yml)

**Try it in your browser:** <https://grasp-web-chi.vercel.app>

**Reference implementation of cryptographic causation for AI systems.**

When an AI makes a decision that matters, three questions decide whether anyone
can trust the record of it: *what did it decide*, *what did it believe when it
decided*, and *can every claim it made be checked against its source*? GRASP
answers all three with tamper-evident, replayable, externally-anchorable
records — **what an AI decided** (a signed decision chain), **what it believed
when it decided** (a signed memory chain), and **every outward claim it makes**
(a deterministic citation-provenance floor) — bound together and falsifiable by
construction, so a *skeptic* (a regulator, opposing counsel, an auditor) can
independently refute or confirm them. The verifier is the math plus an external
party, never the AI. Don't trust it — witness it.

## Where GRASP sits (the open-core family)

GRASP is the **open proof layer** of a wider stack. The position is open-core:
*open-source the tools, licence the engine.*

| Piece | What it is | Licence |
|---|---|---|
| **GRASP** (this repo) | The proof layer — the reference implementation of cryptographic causation (signed decision chain, signed memory chain, deterministic citation floor). | **AGPL-3.0-only** — open |
| **HAPPI** | The open protocol the stack speaks — a specification, not a black box. GRASP's `cite.verify` is pinned byte-compatible to the HAPPI `cite.verify` verb (happi/1.3). | Open standard ([happi.md](https://happi.md)) |
| **GRIP + HAL** | The reasoning + provenance engine (GRIP) and the LLM-agnostic multi-provider substrate (HAL). Not required to produce *or* verify a GRASP record. | Licensed / private |

The load-bearing consequence for anyone relying on a record: **GRASP records are
produced and re-verified by this open package alone** — no GRIP runtime, no HAL,
no network, no account (see *Verify a receipt* below). The engine is licensed;
the tool that lets a skeptic check its work is not. Deeds, not words.

## What is in the box

| Module | What it does |
|---|---|
| `grasp.idr` | Signed Intent Decision Records (IDRs): flat-JSON envelopes, HMAC-SHA256 / Ed25519 / ML-DSA-65 / dual-hybrid over a canonical body digest, predecessor hash-chaining, content addressing that excludes volatile metadata, optional output-hash binding, JSONL persistence with POSIX locking. |
| `grasp.idr_forest` | The forest that organises IDRs into a provenance graph **rooted at exogenous anchors only** (CI runs, human commits, cross-provider verdicts, pre-registered hypotheses), with an RFC-6962 Merkle root, `O(log N)` inclusion proofs, tamper-detecting verification, and deterministic replay. |
| `grasp.merkle` | The RFC-6962 (Certificate Transparency) Merkle primitive: domain-separated leaf/node hashing, inclusion proofs, verification. |
| `grasp.context_chain` / `grasp.context_head` | The signed memory/belief chain: append-only `context-delta` records with an atomic HEAD pointer, two-axis verification (per-node signatures + content-addressed blob presence), and a signed cross-reference (`records_idr`) into the decision chain. |
| `grasp.prove_it` | The deterministic citation floor: every claim carries a verbatim quote; the engine verifies each quote exists in its cited source (exact → whitespace/typographic-flexible → not found), records exact character offsets, and renders a self-contained HTML artifact where every citation is clickable and a fabricated one renders **red**. |
| `grasp.cite_verify` | The protocol twin of that floor — the same ladder as the `cite.verify` verb of the [HAPPI](https://happi.md) protocol (happi/1.3), pinned byte-compatible by a cross-implementation agreement test. |
| `grasp.provenance` | The composition: one prove-it run writes an IDR leaf into the decision chain **and** a cross-referencing node into the memory chain — fail-open, so recording problems never block the artifact. |
| `grasp.legal_receipt` | A signed filing gate built on the floor: a legal deliverable is SAFE TO FILE only when every quote is provably present in its cited source; any fabricated citation makes the CLI exit 1. **Never file a red.** |

## The three legs, and why they compose

1. **Decision record** — `grasp.idr` + `grasp.idr_forest`: signed envelopes
   (what/why/how/when), predecessor-chained, organised into a forest whose
   roots must be *exogenous* — something the AI does not control. A record set
   that only confirms itself is theatre; exogenous rooting is what lets a
   skeptic independently check the chain.
2. **Belief record** — `grasp.context_chain`: an append-only signed chain of
   the evolving mental model, so the record carries what the system believed
   *at decision time*, not just its output.
3. **Claim record** — `grasp.prove_it` / `grasp.cite_verify`: deterministic
   verbatim-quote provenance. A hallucinated quote resolves to `not_found` and
   renders red — it cannot earn a pass, because the check is arithmetic string
   matching, not judgement.

They compositionally close: `grasp.provenance.record_proveit_provenance` writes
one prove-it run into **both** chains, and the memory node cites the decision
leaf by content address *inside its signed body* — flip a byte of the citation
and chain verification returns `BROKEN`. The conformance tests anchor this.

**Scope honesty:** the citation floor proves a quote is **verbatim in the
supplied source** — not that the source is authentic, and not that the quote
*supports* the claim. Support-checking is a caller-side layer (the L2 seam on
`Citation.support`), and it must be fail-open: the deterministic floor is the
guarantee; anything above it is recall.

## Signing, honestly stated

Records are hash-chained and Merkle-rooted; sealed **HMAC-SHA256 by default**
over a locally held key (`GRASP_SIGNING_KEY` env var, or a key file created on
first use under `~/.grasp/keys/` with 0600 permissions — the key never enters a
record; only signatures and a short key fingerprint do). Asymmetric signing is
now first-class: **Ed25519**, post-quantum **ML-DSA-65** (FIPS 204), and the
dual `ed25519+ml-dsa-65` hybrid, selected via `GRASP_SIGNING_SCHEME` — the
default is `ed25519` when the optional `cryptography` package is installed
(`pip install grasp-provenance[pqc]`), else HMAC-SHA256. `grasp keygen`
generates keypairs and prints the public key + fingerprint for publication; the
verifier resolves verification keys from `GRASP_*_PUB`, `GRASP_VERIFY_KEYS`, or
`<home>/keys/<scheme>.pub`, and marks schemes it cannot check as `DEGRADED` —
monotone toward safe, never upgraded to `VERIFIED`. The private seed never
enters a record.

## What GRASP proves — and what it doesn't

The full trust-boundary document lives at
[`docs/THREAT-MODEL.md`](docs/THREAT-MODEL.md): the six adversaries the design
considers, which attacks the arithmetic defeats (outsider tampering,
truncation-after-anchor, damaged ledgers, scheme downgrade), which it only
*bounds* (the key holder under symmetric default; time against the Bitcoin
clock rather than an eIDAS-qualified authority), and the ordered hardening
roadmap that closes the gaps. We publish it deliberately — a verifier you
cannot threat-model is marketing.

## Anchored in the real world

The approach this package implements runs in production pilots whose Merkle
roots are anchored into the **public Bitcoin blockchain**, independently
checkable on any explorer:

- Pilot decision chain — committed via an OpenTimestamps proof to block
  **956992**: <https://mempool.space/block/956992>
- Post-quantum finance pilot (Ed25519 + ML-DSA-65) — dual-signed and anchored
  via the same OpenTimestamps path; its anchor block details are shared in
  evaluation materials on request rather than listed here.

Check it yourself — that is the point. To be precise about what this means:
those anchors witness *pilot deployments* of the approach; this package does
not anchor anything to Bitcoin on install. Anchoring is a deployment step you
opt into: `grasp anchor` stamps the current Merkle root via OpenTimestamps
and writes a continuity receipt of the exact leaf set it commits to — so a
later `grasp verify` can prove no anchored record was truncated or rewritten.
It refuses to anchor a chain that does not verify.

### Checking your own anchor

Stamping a root and *confirming it reached a block* are different questions,
and the second is the one that matters. Answering it the orthodox way needs a
Bitcoin node — and a node is not free. Even a pruned one performs a full
initial block download (758 GB as of 2026-08-01; `prune=` caps what is
*retained*, not what is downloaded). More to the point, your node convinces
only you: whoever you are proving something to checks the anchor against
*their* node either way.

So `grasp verify --anchor` works in tiers, and always says which one answered
and what it rests on:

| `verified_by` | What happened | What you are trusting |
|---|---|---|
| `bitcoin-node` | A reachable node confirmed it | Nothing — it validated the chain itself |
| `multi-source-header` | Two or more independent block-header sources returned the **identical** header | That those operators would have to collude |
| *(none)* | **Not confirmed** — a verdict is never manufactured | — |

```console
$ grasp verify --anchor
...
anchor_check:
  confirmed:    true
  block:        957120
  block_hash:   0000000000000000000059a3e7682ab2d862cb6d0988afe8d4e890c93a07c21b
  verified_by:  multi-source-header
  trust:        2 independent block-header sources (blockstream.info,
                mempool.space) returned the identical header, and they would
                have to collude to forge it. This is NOT a local full node.
```

The lighter tier is sound rather than a shortcut, because the cryptography
still happens on your machine: `ots --no-bitcoin verify` binds your digest to
the proof and **computes** the merkle root up from it through the proof's own
operations. Only the "does block N really carry that root?" lookup is
outsourced — a forged proof fails locally, before any lookup happens. Sources
that disagree are refused outright rather than out-voted, and a root the real
block does not carry is reported as a disproof that takes `ok` away.

Set `GRASP_BITCOIN_NODE` to an RPC URL to use the node tier instead; no
code change is needed. A pruned node serves it fine, because the client only
asks for `getblockcount`, `getblockhash` and `getblockheader`, and
`getblockheader` reads the block index rather than block data — which pruning
never discards.

The anchor check is **opt-in** for a reason: plain `grasp verify` is offline
and stays that way. `--anchor` is the only part that touches the network.

## Install and run the conformance tests

```bash
git clone https://github.com/CodeTonight-SA/grasp
cd grasp
python3 -m venv .venv && .venv/bin/pip install -e . pytest
.venv/bin/python -m pytest tests -q
```

The tests are hermetic (a throwaway `GRASP_HOME`, a fixed test signing key) and
run green in isolation — a fresh virtualenv with nothing else on the path. They
are deliberately mutation-sensitive: mutate the verifier to always-pass and the
fabricated-quote tests fail; skip signature verification and the tamper tests
fail.

Requires Python ≥ 3.10 on a POSIX system (file locking uses `fcntl`).

## Use it from any MCP host

GRASP ships an MCP server (`grasp-mcp`, standard library only), and this
repository is simultaneously a **Claude Code plugin + one-plugin marketplace**
(`.claude-plugin/`), a **Gemini CLI extension** (`gemini-extension.json`), and
an **Antigravity plugin**. One prerequisite for every local host:

```bash
pipx install "git+https://github.com/CodeTonight-SA/grasp"   # puts grasp-mcp on PATH
```

| Host | Install | Guide |
|---|---|---|
| Claude Code | `claude plugin marketplace add CodeTonight-SA/grasp` then `claude plugin install grasp@CodeTonight-SA/grasp` | [docs/install/claude-code.md](docs/install/claude-code.md) |
| Gemini CLI | `gemini extensions install https://github.com/CodeTonight-SA/grasp` | [docs/install/gemini-cli.md](docs/install/gemini-cli.md) |
| Antigravity (`agy`) | `agy plugin install https://github.com/CodeTonight-SA/grasp` | [docs/install/antigravity.md](docs/install/antigravity.md) |
| Claude Desktop | one `mcpServers` entry in `claude_desktop_config.json` | [docs/install/claude-desktop.md](docs/install/claude-desktop.md) |
| OpenAI Codex CLI | `[mcp_servers.grasp]` in `~/.codex/config.toml` | [docs/install/codex.md](docs/install/codex.md) |
| xAI Grok Build | `grok mcp add grasp grasp-mcp` | [docs/install/grok-build.md](docs/install/grok-build.md) |
| Claude for Work / Cowork | remote-only — self-hosted bridge required | [docs/install/claude-for-work.md](docs/install/claude-for-work.md) |
| ChatGPT (Developer mode) | remote-only — self-hosted bridge required | [docs/install/chatgpt.md](docs/install/chatgpt.md) |

Any other MCP host registers the same server with one settings entry:

```json
{ "mcpServers": { "grasp": { "command": "grasp-mcp" } } }
```

The behaviour contract travels with the install (`GEMINI.md` for Gemini CLI;
the `grasp-provenance` skill for the Claude Code plugin): call
`grasp_record_decision` before consequential actions, `grasp_record_belief`
at checkpoints, and `grasp_prove_claim` before asserting any sourced
quotation (a fabricated quote returns `not_found` — it cannot pass). Ask the
model to run `grasp_verify` at any time: every signature, the chain linkage,
and the Merkle root re-check offline, and the verdict comes back exactly as
the arithmetic found it (`verified` / `degraded` / `broken`).

**How to use it — just ask.** You are already running GRASP the moment the
server is registered; there is nothing to invoke by hand. Ask the model in plain
words and it reaches for the right verb — and every result comes back as one
portable, glanceable card (box-drawing, no colour, offline-verifiable), the same
shape in any harness:

- **Record a decision** — before a consequential step: *"record this decision"*
  → `grasp_record_decision` writes a signed IDR (what / why / how).
- **Record a belief** — at a checkpoint: *"checkpoint what we believe"* →
  `grasp_record_belief` snapshots the mental model into the signed memory chain.
- **Prove a claim** — after any sourced assertion: *"prove that quote"* →
  `grasp_prove_claim` verifies the quote is verbatim in its source; a fabricated
  one returns `not_found` and cannot pass.
- **Verify integrity** — any time: *"verify the chain"* → `grasp_verify`
  re-checks every signature, the linkage, and the Merkle root offline.

Prefer your own terminal? The same records verify with the Python package or the
`tools/grasp-verify-receipt` script — no server, no network (see *Verify a
receipt* below). Deeds, not words — *facta, non verba*.

Records land in `~/.grasp/` (`idr.jsonl`, `context.jsonl`) — or wherever
`GRASP_HOME` points — and re-verify with this package alone, no server and no
network.

## Quickstart

Verify a claim's citation, then record the run into both signed chains:

```python
from grasp.prove_it import render
from grasp.provenance import record_proveit_provenance

spec = {
    "title": "Limitation analysis",
    "response": "The claim is time-barred [[cite:c1]].",
    "sources": [{"id": "act", "label": "Limitation Act",
                 "text": "An action shall not be brought after six years."}],
    "citations": [{"id": "c1", "claim": "Time-barred after six years.",
                   "source_id": "act", "quote": "not be brought after six years"}],
}

html, prov = render(spec)          # deterministic verification + HTML artifact
print(prov["grounding_rate"])      # 1.0 — the quote is really there

rec = record_proveit_provenance(spec, prov)   # IDR leaf + memory-chain node
print(rec["idr_addr"])             # sha256:… — the signed decision record
```

Gate a legal deliverable on its citations (exit 1 on any fabricated quote):

```bash
grasp-legal-receipt spec.json --deliverable memo.md --out receipt.json
```

Prove one decision is committed by a single Merkle root without revealing the
others:

```python
from grasp.idr_forest import forest_inclusion_proof, verify_forest_inclusion

out = forest_inclusion_proof(forest, node_id)
assert verify_forest_inclusion(out["content_addr"], out["proof"], out["forest_root"])
```

## Activate a deployment

`grasp activate` walks three acts and closes on the chain's birth certificate:

1. **Tier** — `public` (records may be published), `private` (zero-egress by
   construction: egress-capable backends are refused outright and the storage
   self-check runs under an in-process socket blocker), or `combination`
   (records stay private; only content hashes go to public witnesses).
2. **Storage** — a live-probed picker over the six built-in backends (local,
   bitcoin-ots, s3, sepolia, ipfs, website). Every probe is a real check with
   a one-line remedy when a runtime dependency is missing — never a greyed
   "coming soon".
3. **Terms + access** — activation refuses until the install's license/terms
   files are accepted; acceptance is a signed record bound to each file's
   sha256, so changed terms honestly demand re-acceptance. Private and
   combination modes collect a signed visibility allowlist and report whether
   a PII-redaction seam is wired (`GRASP_REDACTION_CMD`).

```text
╭─ GRASP ● activated — chain born ───────────────────────────╮
│ id         precog-1784069403-…                             │
│ mode       private                                         │
│ acl        true                                            │
│ backends   local                                           │
│ count      1                                               │
╰─ facta, non verba ─────────────────────────────────────────╯
```

The activation itself is the deployment's first signed decision record — the
zero-telemetry claim ships with its own falsifier (`egress_guard()`), not an
adjective.

## Per-response prove-it footer

A response whose claims are bound to sources (`[[cite:ID]]`) can close on a
compact provenance card — the moat proving the model's own claims, every
turn. Three modes (`salient` default, `always`, `off`); the fineprint rows
are plain URLs, so modern terminals link them with zero escape codes, and
`grasp open <id>` is the fallback:

```text
╭─ GRASP ✓ prove-it — this response ─────────────────────────╮
│ model      ◆ claude-fable-5                                │
│ verified   true                                            │
│ claims     2 — ✓2 ≈0 ✗0                                    │
│ grounding  ██████████ 1.00                                 │
╰─ facta, non verba ─────────────────────────────────────────╯
┆ inspect  ~/.grasp/prove-it/41e3e9c8bc90.html
┆ or run   grasp open 41e3e9c8bc90
```

The inspect row is a plain path, never a `file://` URI — terminals that
auto-link plain paths and https URLs do not link `file://`, so the URI
form is the one guaranteed-dead click surface.

A fabricated quote cannot pass: it renders ✗ and flips the card's glyph.

## WITNESS — one gesture

`grasp witness` fuses the three things an answer worth keeping deserves:
the rendered prove-it artifact you can open, the deterministic check over
every `[[cite:ID]]`-bound claim, and the sealed record anyone can verify
without trusting us. It composes the footer, the IDR leaf, and the
memory-chain node this package already ships — no new cryptography, no new
spec.

```bash
grasp witness --input spec.json --model claude-fable-5            # see + prove + seal
grasp witness --input spec.json --model claude-fable-5 --no-seal  # stop at the floor
grasp witness --input spec.json --model claude-fable-5 --anchor   # then stamp the root (network; opt-in)
```

Real output (a two-citation spec, both quotes present verbatim):

```text
╭─ GRASP ✓ WITNESS ──────────────────────────────────────────╮
│ model      ◆ claude-fable-5                                │
│ verified   true                                            │
│ claims     2 bound — ✓2 ≈0 ✗0                              │
│ grounding  ██████████ 1.00                                 │
│ decision   idr a17619c11470 · memory precog-1786655048-8e… │
│ anchor     sealed · not yet covered by an anchored root    │
╰─ facta, non verba ─────────────────────────────────────────╯
┆ inspect  ~/.grasp/prove-it/85118dd7eef4.html
┆ or run   grasp open 85118dd7eef4
```

Read the card the way a skeptic would, because every row is a handle:

- **claims** counts BOUND claims only, and says so — the tally measures
  citation discipline, not correctness, and assertions the author never
  bound to a source are simply not measured. A fabricated quote renders
  ✗, flips the glyph, and the command **exits non-zero**.
- **decision** names the sealed IDR leaf and the memory-chain node — the
  tamper-evident record of what was decided and what was believed.
  Sealing is additive and fail-open: if recording fails, the row says
  `unrecorded (degraded)` instead of showing a clean face.
- **anchor** references an anchored Merkle root ONLY when the newest
  continuity receipt actually covers this leaf, by arithmetic on the
  receipt's own committed leaf set — and shows the root digest a verifier
  can re-check, never a bare date. Until the next `grasp anchor` run, a
  fresh seal honestly reads `not yet covered`. Sealed and anchored are
  different strengths, and the card never blurs them.

Why this exists, in one sentence: an embedded watermark is a maker's mark
on the coin — it signals that an AI touched the artifact; WITNESS is the
flight recorder and its maintenance log — the sealed, replayable record of
what was decided, believed and claimed underneath the artifact. The two
answer different questions and compose (the EU AI Act's Article 50 marking
serves transparency of content; record-keeping and explanation duties of
the Article 12/86 kind concern accountability of process). A witness says
**sealed**, **tamper-evident**, **complement-to-watermarks**. It does not
say signed, non-repudiable, compliant, or true — a ✓ means the quote
exists verbatim in the named source and the record has not been altered
since sealing, and nothing more.

## Provider honesty — the floor that refuses to lie

When a provider's salient claims fail the deterministic floor, GRASP blocks
the send, fails over down your provider ladder (nothing unproven is ever
emitted), and records a signed event in the PRIVATE honesty ledger.
`grasp honesty` renders the scoreboard; `grasp attest` re-proves the
deployment's own configuration guarantees and exits non-zero if any fails:

```text
╭─ GRASP ● provider honesty — floor-hold scoreboard ─────────╮
│ ●          ██████████ 1.00  gemini-3.1-pro  ✓1 ✗0          │
│ ✗          ░░░░░░░░░░ 0.00  grok-4  ✓0 ✗1                  │
╰─ facta, non verba ─────────────────────────────────────────╯
```

The public "flagged providers" view exists but ships OFF by construction —
it activates only behind an enterprise switch AND a legal acknowledgement
file, and a test pins that default.

## Shipped here vs. deployment concerns

Shipped in this package: the three legs, their composition, RFC-6962 Merkle
commitment + inclusion proofs, deterministic replay, the signed legal filing
gate, and the conformance tests. Deployment concerns intentionally **not**
bundled: external timestamping/Bitcoin anchoring (a deployment step, as above),
asymmetric/post-quantum key custody (an integration path), and any L2
LLM-based support checking (caller-side, fail-open by design).

## Verify a receipt (without trusting us)

Every GRASP receipt can be re-checked with one command and nothing but Python's
standard library — no GRIP runtime, no network, no account:

```
python3 tools/grasp-verify-receipt SPEC.json RECEIPT.json --root .
```

It re-hashes the deliverable and every pinned source, re-reads each citation
quote at its recorded offsets (verbatim for `verified`; whitespace-only
tolerance for `fuzzy`; `not_found` always fails), and recomputes the tally. Any
tampered byte, shifted offset, or missing quote exits 1, loudly. The script is
~200 readable lines — audit it in one sitting, or write your own from the spec:
the JavaScript and Python verifiers byte-agree on shared vectors, so you never
have to trust a single implementation. Bitcoin anchors verify through the
upstream OpenTimestamps client (`pip install opentimestamps-client && ots
verify <proof>.ots`) — not our code at all.

## TMIF (declaration layer)

GRASP's claims are also published as a signed
[TMIF Claimant document](docs/tmif.md) —
[`draft-laurie-tmif-01`](https://datatracker.ietf.org/doc/draft-laurie-tmif/),
*A Standard for Claiming Transparency and Falsifiability*. To be precise
about the relationship: GRASP is **not** an implementation of TMIF. GRASP
is the engine that produces tamper-evident records; TMIF is a declaration
format above it, and GRASP makes a natural reference Claimant because its
artifacts (signed chain, in-browser verifier, Bitcoin anchor) are exactly
what TMIF directs evaluators to verify. Where TMIF Claimants self-assert
transparency levels, GRASP under-claims by policy — see
[`docs/tmif.md`](docs/tmif.md) for the signed document, the verifying key,
and a copy-paste verification snippet.

## Licence

**AGPL-3.0-only** (see `LICENSE`). A provenance floor earns trust only if the
verifier itself is inspectable — anyone relying on a GRASP record must be able
to read, run, and modify the exact code that produced and verifies it, including
when it is offered as a network service. Copyleft keeps that loop closed.

**Commercial licences.** If the AGPL's network-use obligation does not fit your
deployment, a commercial licence is available — contact licensing@codetonight.co.za.

Copyright (C) 2024-2026 Lourens Cornelius Scheepers / CodeTonight (Pty) Ltd.

Copyright (C) 2026 [CodeTonight SA](https://github.com/CodeTonight-SA).

More