Skip to content

Run the broker locally

packages/envoy/scripts/dev-broker.sh creates a Postgres database of its own, builds the broker and its clients, writes a fake secrets file, which stands in for Secrets Manager, and starts the broker on a free port. It needs Go, and a Postgres server: by default it starts one in Docker through packages/envoy/scripts/dev-postgres.sh (a container named dispatch-pg, reused when it is there; one made from another image than that script’s is refused, with the commands that move its data onto the right one). Without Docker, run Postgres yourself and set DEV_BROKER_POSTGRES_URL to postgres://<user>@<host>:<port>/<database>, for a role that may create databases; the script then creates and drops its database there with psql.

With Docker the command is the script alone; this run used a Postgres of its own:

Terminal window
$ DEV_BROKER_POSTGRES_URL=postgres://postgres@127.0.0.1:47567/postgres packages/envoy/scripts/dev-broker.sh
dev-broker: workdir /tmp/agent-secrets-dev.SGUyqq
dev-broker: created isolated database dev_broker_sguyqq
dev-broker: building broker, agent-secrets, agent-secrets-helper and agent-secrets-devrelay...
dev-broker: starting broker...
2026/10/03 03:20:19 INFO broker listening addr=127.0.0.1:45355
dev-broker: ready.
export AGENT_SECRETS_URL=http://127.0.0.1:45355
export AGENT_SECRETS_UI_TOKEN=dev
export AGENT_SECRETS_APPROVER=ada@example.com
export AGENT_SECRETS_HELPER_SOCK=/tmp/agent-secrets-dev.SGUyqq/helper.sock
export AGENT_SECRETS_OPERATOR_FILE=/tmp/agent-secrets-dev.SGUyqq/operator
export DEV_BROKER_DIR=/tmp/agent-secrets-dev.SGUyqq
export PATH=/tmp/agent-secrets-dev.SGUyqq/bin:$PATH
...

With Docker, the script says dispatch-pg is already running, reusing it (or starts it) before it creates the database.

Leave it running, and paste the exports into a second shell. They point the clients at this broker and name its UI token, the credential with which Dispatch’s server, and here agent-secrets-devrelay in its place, decides for an approver (Concepts). They also keep the helper’s socket and its operator file (the Dispatch login the helper’s machine login names, ada@example.com) in the stack’s workdir, so the helper never touches one already running for your user and nothing lands in your current directory. Check that type agent-secrets names the workdir’s bin/: a tool manager that puts its own directories first on PATH at every prompt runs an installed agent-secrets instead.

The fake secrets file holds two secrets of ada@example.com’s, under the namespace example/agent-secrets/ and on its example key: DEMO_READ_TOKEN, tagged tier=agent, which her own sessions (the kinds of session) get without asking, and DEMO_API_KEY, tagged tier=human, which she approves. A grant lasts until its session ends, or for the --ttl its request names. Every command below runs in that second shell.

Start the helper in the background, with its log in the workdir, and log it in:

Terminal window
agent-secrets-helper serve 2>"$DEV_BROKER_DIR/helper.log" &
agent-secrets machine login

machine login prints a code and waits. In a third shell with the same exports, approve it as Dispatch would:

Terminal window
$ agent-secrets-devrelay machine-approve --code YE9L-5FM5
{"credential_id":"3bc28cd1-d04b-4481-92a8-ff10350fe795","grant_id":null,"state":"approved"}

and machine login exits 0. The helper’s log, $DEV_BROKER_DIR/helper.log, says machine login issued; the helper holds a launcher credential.

Start a shell as a registered session and use the secrets:

Terminal window
$ agent-secrets register --wait 10 --exec -- bash
$ agent-secrets self
enrollment_id: 2b8627f4-9a2a-4c12-85bf-910144bc0b5c
kind: session
operator: ada@example.com
lease_expires_at: 2026-10-03T03:36:40Z
$ agent-secrets DEMO_READ_TOKEN -- printenv DEMO_READ_TOKEN
demo-read-token-value
$ agent-secrets DEMO_API_KEY --reason "Deploy the example service" -- sh -c 'echo "deploying with a ${#DEMO_API_KEY}-character key"'

The last command waits for approval, and names who approves it (waiting for ada@example.com to approve it under Credential requests in their Dispatch Inbox). In the third shell, list what waits on the approver and approve it (approving a request shows the output), and the command runs:

Terminal window
deploying with a 18-character key

A request has two ids: the request id agent-secrets prints, and the record id the approver’s list shows and agent-secrets-devrelay takes. agent-secrets request NAME --json prints both, and agent-secrets-devrelay deny --record <record id> denies a request.

Each grant so far lasts until the session ends (agent-secrets self lists it until this session ends). A request may ask for less with --ttl, and the grant then ends on its own once that has passed, while the session lives on:

Terminal window
$ agent-secrets DEMO_READ_TOKEN --ttl 2m -- printenv DEMO_READ_TOKEN
demo-read-token-value
$ agent-secrets self
enrollment_id: 7cf72ec3-d10f-46fd-984f-ddd856b229c2
kind: session
operator: ada@example.com
lease_expires_at: 2026-10-11T01:51:13Z
grant: f81251ab-6552-450c-9eca-12a793a29f4c (request 86237327-1f78-4c79-82a5-4f71174e71c5, expires 2026-10-11T01:38:14Z)

Two minutes later that grant releases nothing (GRANT_NOT_LIVE), self no longer lists it, and the same command asks again. --ttl is a whole number of seconds, 2m or 90s; anything else exits 2 before asking. When the broker or the machine’s helper is from before --ttl, which would grant for longer, agent-secrets ends the grant it got (or cancels the request still waiting), says so, runs nothing and exits 1.

In the third shell, which is outside every session, list your machine logins and the live grants as Dispatch’s pages would, under the helper’s machine login (revoke a session or a grant):

Terminal window
$ agent-secrets machine list
CREDENTIAL_ID HOST APPROVED_BY ISSUED EXPIRES STATE
a0324632-0cc0-4113-bb26-d4123fab9da6 example-host-devbox ada@example.com 2026-10-10T15:01:31Z 2026-10-17T15:01:31Z ok
$ agent-secrets grant list
GRANT_ID SECRETS GRANTED APPROVER SESSION OPERATOR EXPIRES
8fe92a26-b355-4c28-8679-60e8ce98b1d9 DEMO_READ_TOKEN automatic - session/example-host-devbox:2856922:35395441 ada@example.com session-end

machine list shows your own machines’ logins alone; Dispatch’s machine-login page also lists every service’s. agent-secrets grant revoke <grant id> ends a grant there, and agent-secrets machine revoke <credential id> one of your logins. Run in the session’s shell, they are refused IN_SESSION, so the session’s own commands cannot act as you:

Terminal window
$ agent-secrets machine list
agent-secrets machine list: IN_SESSION: pid 2857619 is inside a registered machine session, which acts on itself alone; run machine and grant commands from your own shell

The refusal covers the session’s process tree only: a process the session sends out of it (with ( cmd & ), setsid -f or a tmux server it started) passes, and any process running as your user can stop the helper. Through these commands such a process can list and revoke your own machine logins, never a service’s machine login, and your grants, which include grants you approved on any session. agent-secrets enroll --helper is open to any process of your user, so such a process can also enroll a box and read your agent secrets.

The broker reads the fake secrets file again each time it lists the secrets, describes one or reads a value, so an edit to the file is what a write to Secrets Manager is in production, where a person writes with agent-secrets secret (manage a secret); that CLI calls AWS itself, so it cannot write this file. Add a secret of ada@example.com’s to it, here with jq:

Terminal window
jq '.secrets += [{"name": "example/agent-secrets/demo-new-token",
"kms_key_id": "arn:aws:kms:us-east-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab",
"tags": {"owner": "ada@example.com", "tier": "agent"}, "value": "demo-new-token-value"}]' \
"$DEV_BROKER_DIR/fake-secrets.json" >"$DEV_BROKER_DIR/fake-secrets.json.new"
mv "$DEV_BROKER_DIR/fake-secrets.json.new" "$DEV_BROKER_DIR/fake-secrets.json"

and ask the broker to reread it, as agent-secrets secret does right after each write (Concepts). The same call answered {"name":"DEMO_NEW_TOKEN","served":false,"reason":"absent"} before the edit:

Terminal window
$ curl -s -X POST "$AGENT_SECRETS_URL/v1/secrets/DEMO_NEW_TOKEN/reread"
{"name":"DEMO_NEW_TOKEN","served":true}

The registered session gets it at once:

Terminal window
$ agent-secrets DEMO_NEW_TOKEN -- printenv DEMO_NEW_TOKEN
demo-new-token-value

Writing the file whole, as the mv does, keeps it readable throughout: while it does not parse, each of those calls fails, naming the file.

  1. In the first shell, press Ctrl-C: the script stops the broker, drops its database, and prints the workdir it kept.
  2. In the second shell, leave the registered session (exit), then stop the helper: kill %1.
  3. Remove the workdir, which holds the binaries, the fake secrets and the logs: rm -rf "$DEV_BROKER_DIR".

The broker, its database, its policy and every client are the real ones. Two things stand in for production: agent-secrets-devrelay plays Dispatch’s server (it holds the UI token and names the approver itself), and the fake secrets file (BROKER_FAKE_SECRETS_FILE: each secret’s name, key, tags and value, as JSON) plays Secrets Manager and KMS. The broker reads that file again on every call it makes to it, so editing it is how you write a secret here.