> ## Documentation Index
> Fetch the complete documentation index at: https://warden.buildlab.in/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Install Warden, put it in front of your own PostgreSQL, and watch a change wait for you — in about ten minutes.

You need a PostgreSQL database you can connect to, and about ten minutes.

**1. Install**

```bash theme={null}
brew install abhishek-mallick/tap/warden
warden version
```

Other routes — container image, release archive — are in [Install](https://warden.buildlab.in/install/homebrew.md).

**2. Make a credential for your agent, and one for yourself**

```bash theme={null}
warden keygen                  # an agent key
warden keygen --kind operator  # an operator token, for approving changes
```

Each prints the secret once, with the hash to paste into your configuration. Keep the secrets; the config file should carry only the hashes, so the file is not itself a credential.

**3. Write warden.yaml**

Point Warden at your database with a role that is **not** a superuser. This is the only place the database credential exists — no agent ever sees it.

```yaml warden.yaml theme={null}
version: 1
listen: 127.0.0.1:9470
masking_salt: change-me-to-something-long-and-secret
data_dir: ./warden-state

connections:
  - name: shop
    engine: postgresql
    dsn: postgres://warden_gateway:secret@localhost:5432/yourdb?sslmode=require
    policy: ./policy.yaml
    options:
      schemas: public          # only the schemas an agent has business seeing

agents:
  - id: agent:support
    connection: shop
    owner: platform-team
    key_hash: sha256:…         # from warden keygen
    scope:
      tenant_id: acme          # this agent only ever sees this tenant's records

operators:
  - name: you@example.com
    role: owner
    token_hash: sha256:…       # from warden keygen --kind operator
```

Every field is explained in [the configuration reference](https://warden.buildlab.in/configure/warden-yaml.md).

**4. Write a policy**

What the agent may touch, what needs a human, and what is masked. Replace the table names with yours.

```yaml policy.yaml theme={null}
version: 1
connection: shop
mode: policy

require:
  scoping: enforced            # refuse to start if scoping cannot be enforced
  masking: enforced

approval:
  read: auto                   # reads run on their own
  write: pr_required           # changes wait for a human
  delete: pr_required
  ddl: denied
  unbounded_mutation: pr_required

resources:
  allow:
    - { kind: table, path: [public, orders] }
    - { kind: table, path: [public, customers], columns: [id, tenant_id, email] }

scope:
  enabled: true
  bind:
    orders.tenant_id: "{tenant_id}"
    customers.tenant_id: "{tenant_id}"

limits:
  max_rows: 100
  statement_timeout_ms: 5000
  approval_timeout_ms: 60000

masking:
  customers.email: tokenize
```

Check it before starting:

```bash theme={null}
warden policy lint ./policy.yaml
```

**5. Start the gateway**

```bash theme={null}
warden serve --config ./warden.yaml
```

The first thing it prints is the **capability ladder** — what is enforced on this connection, and by whom:

```
  connection shop (postgresql)
    record scoping               enforced     (governed relations)
    field masking                enforced     (masked inside the statement)
    statement timeout            native       (enforced by the server)
    dry run                      native       (transaction + rollback)
    fence                        native       (xmin, re-checked at commit)
  state: ./warden-state (audit chain, leases, idempotency keys)
```

`native` means PostgreSQL enforces it, `enforced` means Warden does, `unavailable` means nobody does yet — and Warden says so rather than implying otherwise. A connection that cannot meet its policy refuses to start. See [the capability ladder](https://warden.buildlab.in/concepts/capability-ladder.md).

**6. Be the agent**

```bash theme={null}
AK=wk_live_…    # the agent key from step 2

curl -s -X POST http://127.0.0.1:9470/api/v1/gateway/execute \
  -H "Authorization: Bearer $AK" -H 'Content-Type: application/json' \
  -d '{"statement":"SELECT id, tenant_id, email FROM public.customers","reason":"list customers"}'
```

You asked for every customer. You get only the `acme` tenant's, because Warden rewrote the query before PostgreSQL saw it — and the email comes back as a `tok_…` token. Try to defeat it with an alias or a function (`SELECT upper(email) AS shout …`): the value is masked inside the statement, so there is nothing to alias.

**7. Try to change something**

```bash theme={null}
curl -s -X POST http://127.0.0.1:9470/api/v1/gateway/execute \
  -H "Authorization: Bearer $AK" -H 'Content-Type: application/json' \
  -d '{"statement":"UPDATE public.orders SET status = $1 WHERE id = $2",
       "params":["refunded","ord_1"],"reason":"customer asked for a refund",
       "idempotency_key":"demo-1"}'
```

This one waits. Policy sends changes to a human.

**8. Be the human**

Open [http://127.0.0.1:9470/](http://127.0.0.1:9470/) and sign in with your operator token, or use the API:

```bash theme={null}
OK=wc_live_…    # the operator token from step 2
curl -s -H "Authorization: Bearer $OK" http://127.0.0.1:9470/api/v1/pending
```

The pending change carries a **preview**: fidelity `exact`, the records it touches, and the before/after of each field — produced by running the change in a transaction and rolling it back. Approve it:

```bash theme={null}
ID=op_…         # operation_id from the pending list
curl -s -X POST -H "Authorization: Bearer $OK" -H 'Content-Type: application/json' \
  -d '{"reason":"verified with the customer"}' \
  http://127.0.0.1:9470/api/v1/pending/$ID/approve
```

The agent's call returns `committed`, and names who approved it. If the row changed while you were deciding, it returns `stale` instead and nothing is written — see [previews and fences](https://warden.buildlab.in/concepts/previews-and-fences.md).

**9. Check the record**

```bash theme={null}
curl -s -H "Authorization: Bearer $OK" http://127.0.0.1:9470/api/v1/audit/verify
```

Every step — the read, the refused attempt, the approval, the commit — is in a hash-chained log that reports whether it is intact.

## Connect a real agent

`warden-mcp` was installed alongside the gateway. Point any MCP-speaking agent at it:

```json theme={null}
{
  "mcpServers": {
    "warden": {
      "command": "warden-mcp",
      "env": {
        "WARDEN_GATEWAY_URL": "http://127.0.0.1:9470",
        "WARDEN_AGENT_ID": "agent:support",
        "WARDEN_API_KEY": "wk_live_…"
      }
    }
  }
}
```

The agent gets four tools and no database credential. See [the MCP server](https://warden.buildlab.in/offerings/mcp-server.md).

## Next

> [card] [Policy reference](https://warden.buildlab.in/configure/policy.md)
>
> Everything a policy can say, and the traps worth knowing about.

> [card] [Approvals](https://warden.buildlab.in/configure/approvals.md)
>
> Blocking and async, and what an agent should do with each outcome.

> [card] [Debug Mode](https://warden.buildlab.in/operate/debug-mode.md)
>
> Step through your agent's database traffic live.

> [card] [The API](https://warden.buildlab.in/api/data-plane.md)
>
> Four endpoints for agents, and the control plane for operators.

> [!WARNING]
>
> Serving on anything but loopback needs TLS — Warden refuses a non-loopback listener without it unless you say so explicitly. Before anything real, read [TLS and state](https://warden.buildlab.in/configure/tls-and-state.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.