Concepts
Everything in Writ is built from three objects. Understand these and the rest of the product is just operations on them.
What to protect first
Start with the five categories where one bad agent action is expensive:
- Money movement —
payments.refund,billing.charge,payouts.create - Identity and permissions —
iam.grant_admin,users.promote,tokens.issue - Customer data changes —
users.delete,records.update,crm.modify - External communications —
email.send,sms.send,webhooks.fire - Infrastructure changes —
aws.terminate,dns.update,deploy.promote
One bad call in any of these categories is expensive and hard to undo. Start here, expand by verb.
Writ sits on top of your existing infrastructure — Okta, AWS IAM, Stripe, your API gateway. It does not replace them. It governs the actions your agents take through them.
Principal
A principal is who is acting, and on whose behalf: a human sponsor, an
agent, a task, and a purpose — bound together. It is not a shared service
account. svc-support-admin can do anything; a principal can only do the
thing its sponsor approved, for the purpose stated.
Two credentials represent the two halves:
The API key says which tenant is asking. The sponsor token says a human
approved this. The sponsor token is shown once at tenant creation and never
recoverable — if it leaks, the tenant’s human side is compromised. It can
also come from the WRIT_SPONSOR_TOKEN environment variable.
Decision
A decision is Writ’s answer at the moment of the write: ALLOW, DENY, or
STEP_UP. It is made by POST /v1/check, which matches the request against
the tenant’s policy and any live grants on five fields — sponsor, agent,
verb, target, purpose. All five must match. Anything else is DENY.
- ALLOW mints a 90-second auth token bound to the exact write: this
verb, this target, this purpose, this policy version. The agent performs
the write, then verifies the token against what it actually did with
POST /v1/tokens/verify. The grant is single-use semantics: it cannot be reused for a different write. - DENY means the write must not run. A receipt is still written — the refusal itself is auditable.
- STEP_UP means a human sponsor must approve. The agent stops and tells
the user. The sponsor mints a one-time grant (dashboard or
POST /v1/grantswith the sponsor token), and the agent callsPOST /v1/checkagain. STEP_UP also pages the tenant’s account email with the receipt id and a deep link to the portal grants page — at most one notification per write per hour. Opt out of email withnotify_step_up_email = falseon the tenant row, or route STEP_UPs to a generic JSON or Slack webhook withstep_up_webhook_urlandstep_up_webhook_type.
Receipt
A receipt is the audit record of a decision — ALLOW, DENY, STEP_UP, REVOKE,
all of them. Receipts are hash-chained (prevHash / chainHash,
HMAC-SHA256 keyed by the deploy secret), so altering a stored receipt breaks
the chain detectably. They are tenant-isolated: one tenant’s receipts
are never visible to another.
The receipt is the product’s answer to the auditor. Instead of “send us the CloudTrail,” the compliance owner opens the receipt: who, what, which purpose, which policy version, when. See Audit log for streaming, SIEM ingestion, and chain verification.
Policy modes
Each verb in a tenant has a mode, set with ./writ policy --set <verb> <mode> or the dashboard policy editor:
Start strict (deny or step_up) on consequential verbs, relax as the
agent earns trust. Policy changes take effect immediately. The policy is
layered — a principal’s override beats the environment override, which
beats the tenant default. See Policies.
Grants
A grant is a sponsor’s one-time approval for a specific write: sponsor,
agent, verb, target, purpose, 90-second lifetime. Minted in the dashboard or
with ./writ grant --sponsor-token .... The agent’s next POST /v1/check
with matching fields returns ALLOW. Grants are how STEP_UP resolves — the
human stays in the loop without becoming the bottleneck for every write.
Kill switch
A sponsor can revoke a principal at any time (POST /v1/revoke with the
sponsor token, or the dashboard). Every later check for that sponsor and
agent is DENY — even under an allow policy, even with a live grant. The
revocation itself writes a receipt. POST /v1/reinstate undoes it.
Use it when an agent misbehaves, a credential leaks, or a purpose drifts. Revocation is per-tenant: one tenant’s sponsor can never touch another tenant’s principals.
Tenants and teams
One tenant = one isolation boundary: its own keys, policy, receipts, and
chain. A tenant starts with one member (whoever created it). Members are
added with invite codes (./writ invite), each getting their own API key;
the sponsor token stays with the tenant creator. Members share the tenant’s
policy and receipts.