cermet.

Permission to push a branch — not access to GitHub. A local daemon decides each agent effect against sentences you wrote, in your tools’ own seams, and writes the receipt.

$ cermet rules allow 'github.push where owner = "you" and name = "your-repo"'

# your agent, later — ordinary git, no wrapper:
$ git push origin main
remote: cermet: carried main@9f8734c to you/your-repo (request req_d374a1b8…)

# and in a repo no sentence admits:
$ git push origin main
remote: cermet: no standing authority for main on you/new-project
remote: cermet:   to allow: cermet rules allow 'github.push where owner = "you" and name in {"your-repo", "new-project"}'

Try the loop

The whole first session, scripted: install, connect, the agent's first deny, the one sentence that widens it, the receipt. Type each hinted command yourself, or just press Enter through it, or ▶ play. A replica: the words are what the shipped binary prints; nothing you type leaves this page.

you — operator ▶ play↺ restart

  
▶ run the demoyou type each command — or just press Enter through it

What it is

Cermet authorizes effects, not traffic. Your agent asks for a typed provider effect — refund this charge, push this branch, deploy this project — and a daemon on your machine decides it against sentences you wrote. Credential gateways inspect the traffic that carries an effect; Cermet represents the effect itself as the authorization object.

Not  "this agent may use the Stripe credential."
Not  "this agent may POST to api.stripe.com."
But  "this agent may refund this charge, up to $50."
     (amount is Stripe's own integer minor unit: 5000 = $50.00)
Local-first. One daemon on your box. Your credentials in its vault, your policy in your repo, your history on your disk. No cloud, no account, no dashboard, no per-agent seat, no telemetry. Your audit history is a SQLite file you own — not a retention tier. MIT-licensed.
Effects-based. The unit of authorization is a typed effect, and the rule reads as the intent: allow stripe.refund where charge = "ch_X" and amount <= 5000. A request that doesn’t parse into a known verb with typed fields does not exist — deny by default; absence is never permission.
Native. Your agent already knows these tools. Every model has read a decade of git and vercel documentation — there is no proprietary tool surface to teach, no SDK to adopt, no proxy environment to configure, no CA certificate to trust. Plain git push stays plain git push; your own vercel CLI runs your deploys; Cermet stands only in the credential-bearing hop. The commands your agent was trained on are the commands it runs.
Receipted. Every effect is decided once, frozen, and recorded: approved fields are executed fields — nothing is filled in later — and the row carries the admitting sentence, the frozen fields, and the reason the agent gave. A summary of a gateway log is a story; this is a record.

Where this sits

secrets manager    — who may possess a credential?
credential broker  — who may use one without possessing it?
agent firewall     — which authenticated requests may leave?
cermet             — which external effects may occur?

Each step down delegates less. The layers above are real and increasingly free; the decision that was missing is the last one.

Three enforcement paths

Every request follows the same model — typed policy, integrity-bound fields, a single-use grant, a recorded result. What varies is the enforcement mechanism, chosen to fit each operation instead of forcing everything through one proxy:

Frozen call — Stripe. A refund is one API effect; there is no local tool worth involving. The broker executes the exact request your agent asked and your sentence approved — amount, currency, charge sealed before the grant exists, credential attached inside the daemon, receipt from the ledger. The agent runs nothing.
run stripe.refund {charge, amount}  →  allow  →  broker POSTs /v1/refunds (pinned API version)  →  receipt
Native seam — git. Git authors everything: objects, protocol, byte-identical commits. The remote points at the broker; plain git push reaches its mirror, the update hook asks your sentences, and on allow a credentialed runner carries mirror → upstream. No wrapper, no token in the repo, one receipt in the push output.
git push  →  hook: allow github.push where owner = "you" …  →  carried
remote: cermet: carried feat/x@9f8734c to you/repo (request req_d374…)
Relay window — Vercel. The native CLI must drive its own multi-step protocol, so the broker opens a ten-minute, single-effect credential window on loopback. The CLI runs with an inert handle; every HTTP hop it makes is checked against the approved shape, then stamped with the real credential and forwarded. A hop outside the shape is refused before the credential touches it — and the window burns.
run vercel.deploy {project, team, target}  →  relay handle  →  vercel deploy --api 127.0.0.1:7133 --token cermet_relay_…
each hop: check shape → stamp credential → forward   outside the shape → refused, burned

The credential never leaves the daemon in any of the three. What varies is only where the work lives: the broker's own frozen request, git's own plumbing, or the native CLI under per-hop supervision.

The subtraction is the product

A Vercel token doesn't authorize a deploy — it authorizes the entire API. The same credential that ships your site can natively run vercel env pull (download your production secrets to disk), delete projects, and deploy anything else the account reaches. Inside the deploy envelope, those requests have nowhere to exist: the credential only materializes inside hops that match what an honest deploy actually needs — nine request shapes, one bound create pinned to a project, team, and target, follow-up reads chained to the deployment it created, ten minutes.

The chain is the provider's; the subtraction is ours. That is the ontology doing work: describe the effect, transcribe its legitimate traffic, and everything else the token could do stops existing.

Nothing about this is provider-specific. The same model narrows any API where a bearer token authorizes everything — including the services inside your own firm. Descriptors are declarations over HTTP: describe your internal services' effects, and an agent navigates your microservices under sentences instead of service tokens.

Typed fields, deterministic decisions

Policies bind typed fields of the requested operation — for a push: repository and branch; for a refund: charge and amount; for a deploy: project and target. Every bound must match or the operation is denied.

git — bounds on names. The push arrives through git's own hook with names and shas; the sentence pins the names.
allow github.push where owner = "you" and name = "your-repo"
                                  ▲                   ▲
                                  │                   │
git push  →  { owner: you,  name: your-repo,  branch: main, new_oid: 9f8734c… }
every name matched → ALLOW — carried, receipt req_d374…
stripe — a bound on magnitude. Integer side-effect fields carry order bounds, not just pins — the sentence caps how large the effect may be.
allow stripe.refund where charge = "ch_3TyX…" and amount <= 5000
                                        ▲                   ▲
                           id pins, like git's names   …plus the bound only money gets
                            ┌─────────────────────┴─────────────────────────────────┐
{ charge: ch_3TyX…, amount: 480 }     { charge: ch_3TyX…, amount: 9000 }
    480 ≤ 5000 → ALLOW                   9000 ⊀ 5000 → no sentence admits it → DENY
the deny is kept losslessly — amount and all — and suggests the sentence that would widen it
vercel — bounds as a resource pin. The relay verb carries three fields (project, target, team) — a sentence may bind any of them, and an unbound field is deliberate breadth, not an oversight; all three freeze at request time, and every HTTP hop the native CLI makes is checked against the frozen pair before the credential touches it.
allow vercel.deploy where project = "cermet-site" and target = "preview"
                                          ▲                        ▲
{ project: cermet-site, target: preview }  ← frozen before the grant exists
then, per hop:  POST /v13/deployments { name: cermet-site … }  ← must equal the frozen pin

The full index is a page: the WHERE index — every field a sentence can bind, per verb, generated from the same descriptors the daemon enforces. Live equivalent on your own box: cermet catalog --all. The rules of thumb: identity fields (ids, names, refs) take = and in {…}; integer side-effect fields (money) additionally take <=, >=, and in; free text can only be pinned byte-for-byte, which is rarely useful — bounds need typed fields; and the enumerate verbs (vercel.list_projects, stripe.search_customers) are typically granted bare — you may enumerate, or you may not. Syntax exists only for bounds a delegating human would say out loud.

The effect is the unit of authority

Cermet does not grant "access to GitHub." It grants github.push for a particular repository. That authority is written as sentences in one reviewable file — the grammar is deterministic; no model interprets the decision:

allow github.fetch where owner = "you" and name = "your-repo"
allow github.push  where owner = "you" and name = "your-repo"
allow stripe.refund where amount <= 5000
allow vercel.deploy where project = "your-site" and target = "preview"

Production deploys, payouts, deletes — anything you didn't write a sentence for — deny, and the denial tells you exactly what rule would widen it. Your call, not the model's. The whole corpus is one file in your repo: an example CERMET.md — edit, cermet doc apply, done; a rebuilt box recovers its authority from git.

Every decision is a row

The audit log is not a text stream to grep later. Requests arrive already parsed by the grammar — provider, verb, typed fields — so every decision lands as a typed, hash-chained row in a SQLite database on your disk: what the agent asked, verbatim; the exact sentence that admitted it, stored word for word; the policy version it was decided under; the reason the agent gave; what executed; how it ended.

ALLOW  stripe.refund amount=480    — allowed by: allow stripe.refund where amount <= 5000
DENY   stripe.refund amount=9000   — no sentence admits it; the ask is kept, not blanked
One vocabulary. The log stores the admitting sentence with the decision, word for word — nothing to translate between your policy file and your history.
Denials are data. What your agents asked for and didn't get is kept losslessly — it's your roadmap, and your early warning.

Every row is written at the decision point and chained to the last; cermet audit-verify checks the chain from genesis. The log records what the broker admitted and executed, under the documented daemon and host trust model.

Reading this as an agent? The machine version of this site is at cermet.dev/agents.md (markdown) or cermet.dev/agents.html (HTML, same content) — guarantees, the authority language by real example, and the practitioner's view. Index at /llms.txt.

Install

Linux amd64 and macOS arm64 — artifacts and checksums live on the GitHub release; the package configures and starts the daemon on install:

curl -fsSLO https://github.com/suarezc/cermet/releases/download/v0.1.0/cermet_0.1.0_amd64.deb
curl -fsSL https://github.com/suarezc/cermet/releases/download/v0.1.0/SHA256SUMS | sha256sum -c --ignore-missing
sudo dpkg -i cermet_0.1.0_amd64.deb

Bare binaries (linux, darwin) on the same release · then the quickstart: prove the plumbing, connect a provider, write your first sentence.

Business model

The tool is free. Your credentials never leave your machine — that boundary is the product, and it is absolute. This build contains no telemetry and no Cermet service. Its one network contact besides the providers you connect: a disableable daily update check against GitHub Releases, carrying no installation identifier (cermet update --daily off).

Status

v0.1.0. Pure Rust, MIT — source at github.com/suarezc/cermet. Linux and macOS daemons. GitHub, Stripe, and Vercel today. This page ships through Cermet itself — the agent that wrote it never saw a token.