Configuration
Deedglass reads one typed configuration for every command. Each setting is declared once, in
internal/config/keys.go; the table below is generated from it, and a test fails if the two drift.
Where values come from
Section titled “Where values come from”The first source that sets a key wins:
- The process environment (
DEEDGLASS_*). .envin the working directory. Dev only: prod ignores it and says so. PlainKEY=VALUElines, optionalexportand matching quotes, no interpolation and no inline comments. OnlyDEEDGLASS_*lines are read, and a real environment variable always wins.deedglass.yamlin the working directory. Keys are the env names without the prefix, in lowercase (listen,log_level). Seedeedglass.example.yaml..deedglass/dev-secrets.json. Dev only; see below.- The default in the table.
deedglass config print shows each effective value next to its source, with every secret shown as
••••••••. The database URL shows everything but its password.
Settings
Section titled “Settings”| Key | deedglass.yaml | Default | Secret | Needed by | What it does | Added |
|---|---|---|---|---|---|---|
DEEDGLASS_ENV |
env |
dev |
no | all | dev or prod. Prod ignores .env and dev secrets and refuses unsafe settings. | S01 |
DEEDGLASS_DATABASE_URL |
database_url |
— | yes | serve, migrate, org, dev | The control-plane Postgres URL for the runtime role. Printed with only the password hidden. Prod needs sslmode=require or verify-full unless the host is loopback or a Unix socket. | S01 |
DEEDGLASS_MIGRATE_DATABASE_URL |
migrate_database_url |
— | yes | migrate | The control-plane Postgres URL for the migrator role (deedglass_migrator), used only by deedglass migrate. Dev falls back to DEEDGLASS_DATABASE_URL; prod requires it or –database-url. | S02 |
DEEDGLASS_LISTEN |
listen |
127.0.0.1:8080 |
no | serve | Address the API, the Gate and the Console listen on (host:port). | S01 |
DEEDGLASS_BASE_URL |
base_url |
http://127.0.0.1:8080 |
no | serve, org login-link | The URL people and agents use to reach this server. Prod needs https. | S01 |
DEEDGLASS_SEAL_KEY_FILE |
seal_key_file |
— | no | serve, keys | Path to the seal key file used to seal deeds. | S01 |
DEEDGLASS_VAULT_KEY |
vault_key (dev only) |
— | yes | serve, vault, connector credentials | A key ring of 1 to 4 comma-separated base64 keys of exactly 32 bytes; the first encrypts connector credentials, the rest only decrypt during a rotation (S16). config print shows only the key ids (kek:…). Dev generates one in .deedglass/. | S01 |
DEEDGLASS_API_KEY_PEPPER |
api_key_pepper (dev only) |
— | yes | serve, org keys, agent keys | Base64 of at least 32 bytes, mixed into API key hashes. Keep it stable: changing it invalidates every API key. Dev generates one in .deedglass/. | S01 |
DEEDGLASS_LOG_LEVEL |
log_level |
info |
no | all | Log level. | S01 |
DEEDGLASS_SIGNUP |
signup |
invite |
no | serve | Who can join: invite (people an admin invites) or closed. | S01 |
DEEDGLASS_RESEND_API_KEY |
resend_api_key (dev only) |
— | yes | serve | Resend API key for sign-in and approval emails (starts with re_). Without it, dev prints emails to the log. | S01 |
DEEDGLASS_MAIL_FROM |
mail_from |
— | no | serve, org create | The From address of sign-in and invite emails, e.g. Deedglass signin@deedglass.com. Required when DEEDGLASS_RESEND_API_KEY is set. | S07 |
DEEDGLASS_ANTHROPIC_API_KEY |
anthropic_api_key (dev only) |
— | yes | serve, insights | Optional Anthropic API key for Insights phrasing (starts with sk-ant-). | S01 |
DEEDGLASS_ANTHROPIC_MODEL |
anthropic_model |
claude-sonnet-5-5 |
no | serve | The Claude model that rewords Insights suggestions when an org turns phrasing on (a model id like claude-sonnet-5-5, no date suffix). | S28 |
DEEDGLASS_TRUSTED_PROXIES |
trusted_proxies |
— | no | serve | Comma-separated CIDRs whose X-Forwarded-For is believed. | S01 |
DEEDGLASS_ALLOW_PRIVATE_NETWORKS |
allow_private_networks |
false |
no | serve | Dev only: lets connectors reach private networks. Refused in prod. | S01 |
DEEDGLASS_EGRESS_ALLOW_CIDRS |
egress_allow_cidrs |
— | no | serve | Comma-separated CIDRs of private networks connectors may reach, in prod too, for example 10.20.0.0/16. Never cloud metadata, reserved, IPv4-embedding or loopback ranges. Deedglass’s own database is reachable only when its exact address is listed (a /32 or /128). | S16 |
DEEDGLASS_METRICS_LISTEN |
metrics_listen |
— | no | serve | Address for a separate listener serving Prometheus metrics at /metrics (host:port). Off when unset; bind it to localhost or a private network. | S03 |
DEEDGLASS_SLOT_LAG_ALERT_MB |
slot_lag_alert_mb |
512 |
no | serve | Alert when a Postgres replication slot holds more WAL than this (1-1048576 MB). | S01 |
DEEDGLASS_DECODER_PAUSE |
decoder_pause |
false |
no | serve | Dev only: decoder runners stop reading after their current poll, so tests can see late evidence. Refused in prod. | S20 |
DEEDGLASS_API_KEY |
api_key (dev only) |
— | yes | CLI, mcp, SDKs | Client key; serve ignores it. An agent or admin API key (dg_live_… or dg_test_…) for client commands. |
S01 |
DEEDGLASS_SERVER |
server |
— | no | CLI, mcp, SDKs | Client key; serve ignores it. The Deedglass server client commands talk to. |
S01 |
DEEDGLASS_INIT_DSN |
init_dsn (dev only) |
— | yes | connector postgres init | Client key; serve ignores it. The admin DSN deedglass connector postgres init connects with to prepare a customer database (a superuser, or a role with CREATEROLE such as the RDS master user). Read once, never stored or printed. Rename it with –admin-dsn-env. |
S17 |
DEEDGLASS_NO_BANNER |
no_banner |
false |
no | CLI | Client key; serve ignores it. Never show the terminal banner or the inline mark (bare deedglass, version, help). Scripts, pipes and CI never see them anyway. |
S05X |
DEEDGLASS_THEME |
theme |
auto |
no | CLI | Client key; serve ignores it. Draw the terminal mark for a light or dark terminal. auto: COLORFGBG decides, else dark. |
S05X |
DEEDGLASS_NO_ANIM |
no_anim |
false |
no | CLI | Client key; serve ignores it. Turn off the Sealed animation at the end of deedglass init; only its final frame shows. |
S05X |
DEEDGLASS_LICENCE_FILE |
licence_file |
— | no | licence, scan –full | Client key; serve ignores it. Where the Pro licence token lives (default .deedglass/licence.json); the key sits next to it as licence.key. Both are 0600. Free commands never read it. |
S48 |
DEEDGLASS_NO_NETWORK |
no_network |
false |
no | licence, scan –full | Client key; serve ignores it. Never call Dodo Payments or deedglass.com. Pro keeps working on the cached licence inside its 14-day offline window. |
S48 |
DEEDGLASS_LICENCE_MODE |
licence_mode |
live |
no | licence, scan –full | Client key; serve ignores it. Which Dodo Payments mode licence keys come from: live, or test for test-mode keys while setting up checkout. |
S48 |
DEEDGLASS_LICENCE_SERVER |
licence_server |
— | no | licence | Client key; serve ignores it. Where licence tokens are signed (default https://deedglass.com); a local site in development. |
S48 |
Client keys are read by the CLI, the MCP commands and the SDKs. serve ignores them.
Dev and prod
Section titled “Dev and prod”DEEDGLASS_ENV is dev unless it is set to prod in the environment or in deedglass.yaml
(prod in .env is refused, because prod ignores .env).
Prod refuses, all problems at once and each with a next step:
- a
DEEDGLASS_BASE_URLthat isn’t https; - a missing
DEEDGLASS_VAULT_KEYorDEEDGLASS_API_KEY_PEPPER; DEEDGLASS_ALLOW_PRIVATE_NETWORKS=true;- a
DEEDGLASS_DATABASE_URLto a remote host withoutsslmode=requireorverify-full(loopback and Unix sockets are fine); - secrets inside
deedglass.yaml; - unknown
DEEDGLASS_*names (dev only warns, with a did-you-mean).
Dev secrets
Section titled “Dev secrets”In dev, a missing vault key or pepper is filled from .deedglass/dev-secrets.json. The first run
creates it with 32 random bytes for each, so deedglass config print works with no setup:
✓ Valid for dev · 2 local secrets generated in .deedglass/ (never used in prod).deedglass/is created with mode 0700 and the file with mode 0600, usingO_EXCL.- Deedglass refuses a
.deedglassthat is a symlink or writable by other users, and a secrets file that is a symlink or readable by other users. - On a read-only filesystem it stops and asks you to set both keys in the environment.
- Dev API keys depend on the dev pepper: deleting the file invalidates them.
- Prod never reads this file.
Names Deedglass ignores or refuses
Section titled “Names Deedglass ignores or refuses”- Tooling names are ignored by the binary:
DEEDGLASS_IT_*(integration scripts andmake attack),DEEDGLASS_DEV_*(local compose;DEEDGLASS_DEV_ATTACK_HOSTSis read only by a-tags attackhooksbuild in dev, for the attack suite’s host answers) andDEEDGLASS_REPO_PUBLIC(the banned-words check). - Any
TXV_*variable is an error in both modes, with its new name:✕ TXV_DATABASE_URL is from the old name. Rename it to DEEDGLASS_DATABASE_URL.
deedglass.yaml
Section titled “deedglass.yaml”- One YAML document, a mapping of
key: value, at most 64 KiB. - No anchors, aliases or merge keys. This keeps a hostile file from expanding in memory.
- Unknown keys are errors, with a did-you-mean.
trusted_proxiesmay be a list; every other value is a single scalar.- A world-readable file gets a warning.
Sections (file-only settings)
Section titled “Sections (file-only settings)”Everything else tunable lives in sections of deedglass.yaml, never in new environment variables
(contract §11). A section is a mapping; a nested mapping makes a dotted path. Unknown settings are
errors with a did-you-mean, and every value has a range. S13 added the gate section:
gate: wait_timeout: 10s # POST /v1/actions waits this long for a gateway action (at most 25s) execute_timeout: 30s # a connector call; past it the outcome is unknown edge_result_timeout: 10m # proxy mode: how long the edge has to report max_args_bytes: 262144 max_result_bytes: 262144 worker: concurrency: 4 max_attempts: 8| Setting | Default | Range | What it does | Added |
|---|---|---|---|---|
gate.wait_timeout |
10s |
0s to 25s | How long POST /v1/actions waits for a gateway action to finish before it answers 202 processing (at most 25s). | S13 |
gate.execute_timeout |
30s |
100ms to 10m0s | How long the Gate lets a connector call run. Past it, the outcome is unknown and the deed says UNVERIFIABLE. | S13 |
gate.edge_result_timeout |
10m |
1s to 24h0m0s | Proxy mode: how long the edge has to report a result before the action is sealed UNVERIFIABLE (edge_lost). | S13 |
gate.max_args_bytes |
262144 |
1024 to 262144 | The largest args object POST /v1/actions accepts, in bytes (at most 256 KiB, the policy input limit). | S13 |
gate.max_result_bytes |
262144 |
1024 to 1048576 | The largest tool result stored on the action, in bytes. Larger results are digested, and only the digest is kept. | S13 |
gate.worker.concurrency |
4 |
1 to 64 | Witness jobs one instance runs at once. | S13 |
gate.worker.max_attempts |
8 |
1 to 100 | Attempts per witness job that can’t run before the Witness decides without it (witness_gave_up, listed as a gap). | S13 |
witness.inline_budget |
1500ms |
100ms to 5s | The longest an L1 read-back may hold the request path. Past it, the worker finishes the read-back and the agent sees processing. | S19 |
witness.readback_timeout |
10s |
100ms to 1m0s | The worker’s limit for one read-back job; past it, the read-back is the gap readback_timeout. | S19 |
witness.max_detail_bytes |
65536 |
4096 to 262144 | The largest witness_detail (as JCS), in bytes. Beyond it, diff rows and evidence are cut and counted. | S19 |
decoder.enabled |
true |
true or false | Run the decoder on this server. A connector opts in with witness.enabled: true. | S20 |
decoder.transport |
peek |
peek | How the decoder reads a slot: peek (SQL peek, then advance). Streaming is planned. | S20 |
decoder.poll_active |
100ms |
10ms to 1s | How often a runner reads its slot while an action waits for L2 evidence. | S20 |
decoder.poll_idle |
1s |
100ms to 10s | How often a runner reads its slot otherwise. It doubles (to 5s) while a read takes more than a quarter of it. | S20 |
decoder.batch_changes |
5000 |
100 to 100000 | Rows one read asks for (upto_nchanges). Postgres returns whole transactions, so a larger one comes in full. | S20 |
decoder.max_images_per_tx |
1000 |
0 to 10000 | Row changes per transaction whose before and after images are kept. Counts and the digest cover every change. | S20 |
decoder.heartbeat_interval |
10s |
1s to 1m0s | How often a runner writes a heartbeat message to the watched database. | S20 |
decoder.heartbeat_deadline |
60s |
5s to 10m0s | With no heartbeat read back within this time, the runner is degraded (stalled). | S20 |
decoder.lease |
15s |
5s to 2m0s | How long an instance holds a connector’s slot without renewing; another instance takes over after it. | S20 |
decoder.reconcile_interval |
60s |
5s to 1h0m0s | How often a runner checks that every ledger row has a frame; one without is recorded as missing. | S20 |
decoder.retention |
168h |
1h0m0s to 8760h0m0s | How long decoded frames (row images included) are kept. Unattributed and forged-marker frames stay 30 days. | S20 |
approvals.default_expires_in |
30m |
1m0s to 168h0m0s | How long an approval waits for a person when neither the rule nor the policy’s defaults say. Timeout means no. | S22 |
approvals.estimate_timeout |
500ms |
50ms to 5s | The longest the connector’s estimate (Postgres: EXPLAIN, never ANALYZE) may take while the approval is prepared. | S22 |
approvals.context_max_bytes |
32768 |
8192 to 65536 | The largest context snapshot a person countersigns, in bytes. Recent actions, then sections, then arguments are cut first. | S22 |
approvals.link_max_ttl |
24h |
5m0s to 24h0m0s | The longest an emailed approval link works (never past the approval’s own expiry). | S22 |
approvals.link_status_window |
15m |
1m0s to 1h0m0s | How long a used link may still follow its action on the approval page, until the deed is sealed. | S22 |
approvals.max_recipients |
20 |
1 to 100 | The most people one approval emails. | S22 |
webhooks.timeout |
10s |
1s to 30s | A webhook delivery succeeds on a 2xx within this time; anything else is retried. | S22 |
webhooks.max_attempts |
8 |
1 to 8 | Retries after a delivery’s first attempt (at 30s, 2m, 10m, 30m, 1h, 3h, 6h, 12h), then it is dead. | S22 |
webhooks.disable_after |
72h |
1h0m0s to 720h0m0s | An endpoint that has failed every delivery for this long is disabled, with a webhook.disabled deed. | S22 |
webhooks.max_endpoints_per_org |
16 |
1 to 100 | The most webhook endpoints one org may have. | S22 |
undo.max_rows |
1000 |
1 to 100000 | The most rows one undo puts back (and at most decoder.max_images_per_tx, whose images it needs). | S23 |
undo.max_age |
168h |
1m0s to 8760h0m0s | How long after an action is sealed it can be undone (while decoder.retention keeps its row images). | S23 |
undo.lock_timeout |
2s |
100ms to 1m0s | How long an undo waits for a row lock before it fails with nothing changed. | S23 |
undo.statement_timeout |
10s |
100ms to 1m0s | The longest any one statement of an undo may run. | S23 |
undo.preview_steps |
20 |
1 to 1000 | Steps a preview lists by default (?steps_limit= asks for up to 1,000). | S23 |
The mcp section (S15)
Section titled “The mcp section (S15)”The MCP gateway’s settings (MCP.md). Unlike gate, upstreams maps names you choose to
their settings, so it is validated on its own; an unknown key is a problem like any other.
| Setting | Default | What it does |
|---|---|---|
mcp.allowed_origins |
[] |
Browser origins (scheme and host) allowed to call /mcp/*, besides the server’s own. Every other Origin gets 403. |
mcp.passthrough |
[] |
Method families forwarded to an upstream and logged, never sealed: resources, prompts, completion. |
mcp.upstreams.<name>.url |
required | The upstream’s Streamable HTTP endpoint. https; http only in dev with allow_private_networks; never credentials in the URL. <name> is 2 to 32 lowercase letters, digits or hyphens, not deedglass, mcp, native or fake. |
mcp.upstreams.<name>.headers_from_env |
{} |
Header name → environment variable name. Values are read once at start, never logged, returned or sealed. Names can’t start with DEEDGLASS_. Dev only: prod refuses it (use an mcp_http connector with its credential in the vault; S18). |
mcp.upstreams.<name>.timeout |
30s |
Each HTTP request to the upstream (100ms to 10m). |
mcp.upstreams.<name>.tools_ttl |
30s |
How long its tools/list is cached (100ms to 10m; a shorter upstream ttlMs wins). |
The connectors section (S40)
Section titled “The connectors section (S40)”File-only. It matters only to the ops build (make ops-build, -tags outboundmail), the one build
that contains the outbound_mail connector. A release build (make build, the Docker image, the
npm and PyPI wrappers) refuses to start when this section is set: This build has no outbound_mail
connector. Build the ops binary with make ops-build.
| Setting | Default | Meaning |
|---|---|---|
connectors.outbound_mail.orgs |
[] |
Org ids (org_…) that may create and use outbound_mail connectors. Never a slug or *. For every other org the type doesn’t exist: create answers 404 connector_type_unknown, and the type is never in GET /v1/connector-types. Empty means no org may use it. |
connectors.outbound_mail.api_base |
https://gmail.googleapis.com |
The Gmail API base. Prod accepts only Gmail’s; dev may point at a fake mailbox (deedglass-ops dev fake-gmail), whose own /token is then used. |
connectors: outbound_mail: orgs: [org_01m45qnz7jvqfzb3fhh50pr6yg]Examples
Section titled “Examples”# Dev, with a local Postgres from deploy/docker-compose.ymlDEEDGLASS_DATABASE_URL=postgres://deedglass_app:local@127.0.0.1:55432/deedglass deedglass config print
# Prod: generate the two keys once and keep the pepper stableexport DEEDGLASS_ENV=prodexport DEEDGLASS_BASE_URL=https://deedglass.example.comexport DEEDGLASS_VAULT_KEY="$(openssl rand -base64 32)"export DEEDGLASS_API_KEY_PEPPER="$(openssl rand -base64 32)"deedglass config print --jsonWhat this doesn’t do
Section titled “What this doesn’t do”- It doesn’t read secrets from a secret manager; the environment is the interface.
- It doesn’t reload at run time. Restart after a change.
- Later sessions add their settings as rows in the table (for example
DEEDGLASS_MIGRATE_DATABASE_URLin S02). A name that isn’t in the table is refused in prod.