
This guide grounds the [Agent as operator](/concepts/agent-as-operator/) page
in a concrete task. By the end, an agent pod in your local enclave will call
the controlplane's audit API and read **its own** records, with the enclave
gateway minting and presenting a 60-second token on its behalf. To also govern
what the agent's model responses contain, see
[Inspect LLM responses and approve tool calls](/guides/inspect-and-approve-tool-calls/).

## Before you start

- A local dev cluster with the controlplane and enclave data plane deployed —
  the path documented in the repository's `deploy/kind/README.md` (`make
  kind-up cilium-install deploy-iam iam-migrate deploy-enclave`, then the
  seeds). This wires the gateway in the shape described below; you do not
  configure it by hand. See [Register an enclave](/guides/register-an-enclave/)
  for what the broker registration step does.
- The `dvx` binary (`make build`) and `kubectl` pointed at the kind cluster.
- An org to work in — the dev default is `dev-org` (created by the deploy
  targets).

## Create the agent

Agents are desired state, authored through the controlplane's operator API —
you never touch Kubernetes, and the CLI never applies a Pod manifest. The
broker on the enclave reconciles each desired agent into a dedicated
namespace, an identity socket, and a Pod.

{{< terminal title="dvx" prompt="$" >}}
$ dvx agent create \
    --controlplane https://cp.example.com \
    --org dev-org \
    --uid agent-a --name agent-a \
    --enclave enc_7f3a... \
    --image curlimages/curl:8.11.1 \
    --command /bin/sh --command -c --command 'sleep 3600'
{{< /terminal >}}

`--enclave` places the agent in a registered enclave; `--uid` names its
namespace and IMDS socket (omit it to get a generated one like
`nostalgic-otter`). Isolation is a property of the ENCLAVE, not the agent:
author the enclave's requested isolation class with
`PUT /api/v1/enclaves/{enclave}/isolation-class` (`node-cvm` runs every agent
in that enclave as a Kata VM pod instead of plain runc containers). Watch the
broker reconcile it:

{{< terminal title="sh" prompt="$" >}}
$ kubectl wait --for=condition=Ready pod -l dvx.io/agent-uid=agent-a -A --timeout=150s
$ kubectl exec -n agent-a agent-a -- curl -s --unix-socket /run/dvx/imds.sock http://imds/api/v1/identity
{{< /terminal >}}

## Grant the auditor role

An agent's authority is an ordinary grant on the `agent:<uid>` principal —
org-scoped only (the grant API validates the agent actually belongs to that
org). The v1 role for agents is **`auditor`**: an org-scoped, own-actor audit
read, and nothing else.

{{< terminal title="dvx" prompt="$" >}}
$ dvx grant create \
    --controlplane https://cp.example.com \
    --org dev-org \
    --principal agent:agent-a \
    --role auditor
{{< /terminal >}}

The role floor is structural, not a convention: a kind condition on every
capability-gated route refuses the agent-operator kind *before* grants are even
loaded, so even an agent mistakenly granted `admin` reaches no manage or read
surface. The own-actor audit read is the entire agent surface.

## The gateway's controlplane-bound wiring

For agent egress toward the operator API to become token injection, the
node-local gateway needs to know which hosts to inject on and how to verify the
controlplane. In the in-repo dev deployment this is already set in
`deploy/enclave/gateway-daemonset.yaml`; the same knobs exist as `dvx serve
gateway` flags:

| DaemonSet env | CLI flag | what it does |
|---|---|---|
| `DVX_CP_API_HOSTS` | `--cp-api-host` | operator-API host suffixes whose agent egress gets a 60s token injected |
| `DVX_AGENT_TOKEN_BROKER_URL` | `--agent-token-broker-url` | broker local mTLS channel the gateway relays the agent's SVID cert to for token mints |
| `DVX_AGENT_TOKEN_BROKER_CA` | `--agent-token-broker-ca` | CA verifying the broker's local serving cert |
| `DVX_AGENT_TOKEN_BROKER_CLIENT_CERT` / `_KEY` | `--agent-token-broker-client-cert` / `-key` | the broker-issued `dvx-gateway-broker-client` identity |
| `DVX_CP_API_CA` | `--cp-api-ca` | CA verifying the controlplane's serving cert on the re-originated hop |

Two pieces of wiring exist but need no per-deployment work: the broker stamps a
fixed east-west egress-redirect rule for the controlplane into each agent's
namespace (the CP is an in-cluster destination, not "world" — without the rule
the connection was dropped by elimination and never mediated), and the broker
stamps the gateway MITM CA into the agent's trust store, so the agent's TLS to
the intercepted host just verifies. The agent itself needs no configuration, no
CA bundle of its own choosing, and no token.

## Allow the operator-API host in policy

The gateway governs every mediated connection — including the CP-bound one —
against destination policy. Add the operator-API host to the org's allow list,
restating any hosts already allowed at that scope: a destination write
**replaces** the scope's whole list (see
[Author and pull policy](/guides/author-and-pull-policy/)).

{{< terminal title="dvx" prompt="$" >}}
$ dvx policy destination \
    --controlplane https://cp.example.com \
    --org dev-org \
    --allow dvx-controlplane.dvx-system.svc.cluster.local \
    --allow example.com --allow example.org --allow example.net
{{< /terminal >}}

(The dev baseline `example.*` entries here are what `make seed-demo-policy`
authors; keep whatever your own scope list holds.)

## The agent reads its own audit trail

Now the payoff. From inside the agent pod, call the controlplane's audit API by
its in-cluster host — **no token, no key, nothing mounted**:

{{< terminal title="sh" prompt="$" >}}
$ kubectl exec -n agent-a agent-a -- \
    curl -s https://dvx-controlplane.dvx-system.svc.cluster.local:8443/api/v1/audit/events
{
  "events": [
    {
      "action": "agenttoken.mint",
      "actor": "agent:agent-a",
      "actor_kind": "agent-operator",
      "outcome": "allow",
      ...
    }
  ]
}
{{< /terminal >}}

What just happened: Cilium redirected the connection into the gateway; the
gateway terminated TLS with a leaf the agent already trusts, relayed the
agent's attested SVID cert (never its key) through the broker's local mTLS
channel, and the controlplane — verifying the cert against the enclave's pinned
agent-identity CA and the agent's containment state — minted a 60-second,
audience-scoped token under its own signing key. The gateway presented the
bearer itself on the re-originated hop and cached the token for at most its
lifetime. The agent's view never changed: a plain HTTPS call to a host it
cannot otherwise reach.

The read is scoped server-side to the caller's own actor, so the response
contains exactly the records attributed to `agent:agent-a` — that own-actor
read is the agent-operator class's entire surface.

## Verify from the operator side

As a human operator, read the same trail and see the agent's actions told
apart by the first-class `actor_kind` field:

{{< terminal title="dvx" prompt="$" >}}
$ dvx audit events --source controlplane \
    --controlplane https://cp.example.com \
    --action agenttoken.mint
{{< /terminal >}}

The gateway side is audited too: each injection flow lands as a
`mitm-agent-token` row in the enclave audit, so both the mint and the
presentation are attributable.

## Containment, not just TTL

Revoking the agent is immediate and layered. `dvx grant delete` removes the
authority outright; containing the agent makes the mint endpoint refuse it and
the gateway drop the cached token — both enforced by mediation, before the
controlplane is consulted. The only residual is the ≤60-second lifetime of a
token already minted, and pod death stops presentation entirely.

{{< terminal title="dvx" prompt="$" >}}
$ dvx grant delete --controlplane https://cp.example.com \
    --org dev-org --id <grant-id>
$ dvx agent delete --controlplane https://cp.example.com \
    --org dev-org --uid agent-a
{{< /terminal >}}
