Let the agent drive.
Keep the keys.

Graft assumes the operator is an autonomous agent. The safety story is structural, not a promise.

Safe by construction, not by promise.

Four properties hold before you configure anything. Each one is code you can read.

  • 01

    Git is the record

    Every content write lands as a file in your repository. Nothing bypasses history: a change is a reviewable diff before it is anything else.

  • 02

    Destructive waits for you

    Functions marked destructive never execute on the first call. They file a one-shot, input-bound approval and stop until a human decides.

  • 03

    Identity is verified, never minted

    Tokens are checked against OIDC issuers you name. A bad token is a 401 that says so — never a silent downgrade to anonymous.

  • 04

    Your infrastructure, no phone-home

    Your Postgres. Your object store. No telemetry. There is no Graft cloud in the path — nothing extra to trust or harden.

Destructive ops ask twice.

A destructive function's first call never executes. It files an approval bound to the exact input, and the retry only succeeds after a human has decided.

One-shot
consume flips approved → consumed exactly once
Input-bound
approve A, execute B is impossible
Self-decision refused
the approver cannot be the requester

The runtime role cannot self-approve.

Separation of duties lives in Postgres, not in a promise: consuming an approved request rides a SECURITY DEFINER function, while deciding stays an operator-role UPDATE stamped server-side with decided_role. Raw SQL with the runtime credential does not change that.

Runtime credential

serves your app and its agents

  • Serve functions under your access rules
  • File an approval request (INSERT)
  • Consume an approved row — via SECURITY DEFINER only
  • Write its own audit rows
  • Flip pending → approved
  • Decide its own request
  • Touch approvals directly

Operator credential

yours, used by graft approve

  • Decide approvals (approve / deny)
  • Compile, migrate, branch, merge
  • Own the schema and migrations

One command sets it up on your database:graft harden graft_runtime

Every call leaves a row.

Actor, function, input hash, status, duration, correlation id, and the git SHA of the serving code. Hand the correlation id back and the row comes up; rate limits count these same rows, refusals included.

audit_log

actor
agent:mcp · 9f2c…
function
deleteSubmission
git sha
cb3060b
status
403
duration
1.8 ms
correlation
x-graft-correlation-id

The worst case is a diff.

An agent edit is a file edit. It shows up in git status, in your editor, in the Studio's Changes drawer. Undo belongs to git — scoped to the content directory, nothing else.

Mechanisms, documented.

Every claim on this page links to the page that teaches it.

Asked about trust

Can an agent delete my content?

Deletes are destructive functions, so the first call never executes — it files a one-shot, input-bound approval and returns 403 with its id. Only after you run graft approve does the retry succeed, and the deletion is still a commit in your history.

What stops the agent approving itself?

Postgres role separation. The runtime credential can request and consume approvals only through a SECURITY DEFINER function; flipping pending to approved is an operator-role UPDATE stamped server-side with decided_role. Even raw SQL with the runtime credential cannot self-approve.

Does Graft phone home?

No telemetry. Content lives in your repository, operational data in your Postgres, assets in your bucket. There is no Graft cloud in the path.

What happens when a token leaks?

Tokens are verified against your OIDC issuer, carry only the scopes you granted, and expire on the issuer's schedule. Everything the token did is in the audit log keyed by actor and correlation id, so the blast radius is knowable — and revocation happens at your issuer, not ours.

Run it on your keys.

Scaffold locally. Point it at your Postgres. Harden the runtime role before anything external speaks to it.