Deploy and operate
An agent in production is a binary and a folder. There is no control plane to stand up and nothing to migrate; the operational surface is a process, a directory, and a set of environment variables.
Ship the folder with the binary
Section titled “Ship the folder with the binary”The agent folder is deployment input, exactly like source. Put it in a git repository, copy it to the host, and point the binary at it:
maiden serve /srv/my-agent --port 8080Alongside the folder, maiden writes <dir>/.maiden/state — the durable
thread checkpoints. That directory is your data. It must live on a
persistent volume, and it must survive a redeploy, or every conversation
restarts from nothing. Use --state to put it somewhere else:
maiden serve /srv/my-agent --port 8080 --state /var/lib/maiden/stateSecrets
Section titled “Secrets”maiden reads credentials from the environment and never from the agent folder. Two conventions:
- Provider keys:
OPENAI_API_KEY,ANTHROPIC_API_KEY. - Connection credentials:
${ENV_VAR}inside a connection’sheadersorbase_url, expanded from the environment when the agent loads. - Tool credentials:
MAIDEN_SECRET_<NAME>for a name listed in a tool’ssecrets = [...]. The tool only ever sees the name; the host resolves the value and attaches it. A tool can only name credentials its own policy declared, so an unrelatedMAIDEN_SECRET_*in the environment is not reachable from it — see the security model.
Channel configs follow the same rule — they name an env var (token_env,
signing_secret_env), never a literal. Keep the values in your platform’s
secret store and inject them at process start.
Nothing in the state directory should contain a credential. If you find one there, that is a bug worth reporting.
Exposure
Section titled “Exposure”maiden serve binds 127.0.0.1 by default and has no authentication. That
default is deliberate: the front door is meant to sit behind something that
already knows how to authenticate.
- Behind a proxy — bind locally, and let nginx/Caddy/your gateway terminate TLS and authenticate.
- In a container — set
MAIDEN_BIND=0.0.0.0(the published image does), and publish the port only to your internal network.
Webhook channels are the exception: they must be reachable by the provider, and they authenticate themselves by verifying the provider’s signature on every request. Give them a public path through your proxy; do not put a shared password in front of them, or the provider’s own verification handshake will fail.
Running it as a service
Section titled “Running it as a service”A minimal systemd unit:
[Unit]Description=maiden agentAfter=network-online.target
[Service]ExecStart=/usr/local/bin/maiden serve /srv/my-agent --port 8080 --state /var/lib/maiden/stateEnvironment=MAIDEN_BIND=127.0.0.1EnvironmentFile=/etc/maiden/secrets.envUser=maidenRestart=alwaysRestartSec=2
[Install]WantedBy=multi-user.targetRestart=always is safe. A restart mid-conversation is not a data-loss event —
the next message on that thread resumes from the last checkpoint. This is the
same property that makes deploys uneventful: stop the old process, start the
new one, and threads continue.
Upgrades
Section titled “Upgrades”- Install the new binary.
- Restart the process.
Checkpoints are forward-compatible within a minor version. maiden is pre-1.0, so a minor bump may change the state format — the changelog says so when it does. If you need certainty across an upgrade, back up the state directory first; it is plain JSON.
What to watch
Section titled “What to watch”maiden emits one tracing span per model-call turn. It does not ship a
subscriber — install your own and the spans go wherever you already send
telemetry:
tracing_subscriber::fmt::init(); // or an OTLP layerThe span is maiden.model_call, carrying maiden.session.id,
maiden.turn.index, maiden.tool.count and maiden.model. The most useful
signal in it is turn index pressing max_turns — an agent hitting its
ceiling is looping, not thinking.
Token spend is bounded separately by max_session_tokens, and a subagent’s
spend is charged back to its parent, so a runaway nested agent shows up against
the top-level budget rather than hiding one level down.
For failure, watch the process exit code and stderr as you would any service.
Scheduled and channel-driven agents
Section titled “Scheduled and channel-driven agents”maiden serve also runs the folder’s schedules/ and channels/. Two
consequences worth planning for:
- Run exactly one process per agent folder, or a schedule fires once per process and a Telegram long-poll fights itself over the same offset.
- Schedules have no catch-up. A schedule that would have fired during downtime does not fire on restart; the next occurrence is computed from the time the process starts. If a missed run matters, make the prompt idempotent and reconcile from state rather than relying on the tick.