One key starts it. The port stays on your machine.
A fresh instance needs exactly one secret to be useful —
an LLM provider key — and it listens on one port bound to
loopback. Everything else here is the next key and the next
call: where a second key goes so it does not widen what every agent can
reach, every outside service the container can reach once you
give it one, the token you deliberately do not set, and the
handful of routes worth knowing before you learn the console.
Which key goes where — click a location
./.env — the hub, and the one required secret
Sits next to the compose file, copied from .env.example. Section 1 is the only required part: pick a provider and paste one key. This file is read as environment, never mounted into the artifact tree.
What to know
Shape
The blast radius is the point. Bundled agents share the hub
.env. Every key you add there becomes reachable by every
bundled agent you invoke — which is convenient on day one and too broad
by week three. agent-env/<id>.env exists so a
connector key can belong to one agent instead of all of them.
Every outside service this container can reach
One key is required. Everything below it is off until you add a key.
This is the outbound surface the buyer template covers — you pay each
provider directly, and nothing here is reachable just because it was
bundled. Two further blocks sit beyond it, noted under the table.
Service
What it unlocks
Env keys
Ships as
Reasoning — pick exactly one
OpenAI
The default provider. Model and a reasoning-effort knob alongside it.
Two defaults worth knowing before you add any of these.
Notifications ship pointed at a file, not a network —
DAL_COO_P2A_TRANSPORT=file writes to a local outbox, so an
instance with no connectors still has somewhere to tell you things. And
the switches above that read =0 or =1 —
draft-only posting, dry-run code edits, email mutations — ship
commented out in the template, which means the profile default
applies rather than the value you see. If you want one of them
guaranteed, uncomment it and set it yourself. The gate you can rely on
without reading any of that: no key, no connector, and anything leaving
the building still waits on Review.
Payments and live travel booking go further, and are deliberately not
on this page.
Both have real key blocks. Neither is a single credential you paste in:
the payment path sits behind its own live-enable switch layered over
amount ceilings, a currency allowlist, webhook verification, and an
approval check before anything executes — and the travel skill draws its
product boundary at research, leaving ticket purchase and
merchant-of-record outside unless you deliberately turn a live connector
on. Those blocks want real domain knowledge and caution to run, so the
variables are not published here; they are documented in the kit beside
the code that uses them — packages/payment/README.md and
agents/saffron_agent/docs/providers.md.
Travel research, though, needs no new credential. Flight, hotel,
and directions research all ride the search key you already
set — they resolve through the same shared SerpAPI client as
the rest of the kit. So the trip packet is reachable from the connector
you configured for web search, and only live booking is a separate
decision.
Two things about the door
The token you do not set
Leave DAL_COO_API_TOKEN unset. First start writes one
into .dal-docker/ — so the instance has a token you
never chose and never pasted.
If you do set it, it becomes a real requirement:
POST /api/tasks/run-due wants it, and anything on your
host that calls wake has to send it too. The shipped wake scripts
already source .env and add
Authorization: Bearer …, which is why a hand-rolled
cron line is the thing that breaks.
A sudden 401 on a local call usually means a token got set and something calling in does not know.
The port is a decision you own
Compose publishes
${DAL_COO_DOCKER_BIND:-127.0.0.1}:${DAL_COO_DOCKER_PORT:-4040}:4040.
Loopback is the default, not a promise the kit makes for
you: the bind is a variable, so reachable-from-the-LAN is something
you would be choosing.
Worth choosing carefully because this container holds your provider
keys. The compose file makes the same judgement in the one place it
could go wrong — the optional public-facing services are
deliberately given no env_file, with
the reason written in place: a front door that only matches paths
"has no business holding the provider keys in .env."
If you change the host port, use the new one in every URL below.
What you can actually call — grouped by the spine
Prove
Is it alive, and are the keys good
GET/healthLiveness. The same check compose uses.
GET/api/statusReturns JSON — the "it installed" proof.
GET/api/debug-aiOpen this after fixing a key, when you see an AI error.
curl -fsS http://127.0.0.1:4040/api/status
Do
Say something, or queue work
POST/api/messageTalk to the coordinator.
POST/api/taskPut work in the queue instead of a chat turn.
GET/api/review/pendingThe things holding for a person. The honest center of the day.
GET/api/targetsEvery capability you may invoke, bundled and your own.
POST/api/coordinator/invokeCall one by capability_id.
POST/api/operator/work/<job_id>/runRun one catalog job now.
curl -sS http://127.0.0.1:4040/api/review/pending
These are the Docker URLs. Every call above assumes the published
loopback port, so if you changed DAL_COO_DOCKER_PORT, use
yours. Command lists and version tags come from
INSTALL.md; console surfaces from
OPERATOR_GUIDE.md.
One secret in, one port out, and a token you never typed.
That is the whole security posture a Principal needs to hold on day one —
and the reason it stays true is placement, not promises: keys in a file
read as environment, narrowed per agent when they get sensitive, absent
from anything published, and never sitting in the tree an agent writes
into.
.env · .env.example · agent-env/<agent_id>.env · my-agents/<id>/.env · .dal-docker/ (generated token)Next: The disk — where those files live and what is mounted · Your agents — giving one of your own a key← The kit that holds these keys