Maintenance agent
An opt-in nightly agent that reads an app's logs, source and check history and either reports nothing, records a finding, or opens a change for a different person to approve — it never deploys on its own.
Once an app works unattended — Stage 5’s nightly pipeline is the tutorial’s version of that sentence — the next question is who watches it while you sleep. The maintenance agent is that watch: it reads the app’s logs, the source that shipped, and recent application check results, then leaves one of three typed answers. A clean night is a result. A finding is a result. A concrete code change is a proposal that a different person must approve before anything rolls out.
The agent never writes main, never approves itself, and never carries your
login. Approval is a human decision on a server-owned proposal.
Nothing here is on until you turn it on, and there are two switches, not one. Both ship off, for every app and every organization — including Dibbla’s own.
- The organization capability. Enabled organization by organization. While
it is off, every maintenance command answers
404withMAINTENANCE_AGENT_NOT_FOUND— that is the rollout, not a mistake on your side. - The app itself. Off by default even inside an enabled organization. You
turn it on per app with
dibbla apps maintenance enable, or with the switch on the app’s Maintenance agent tab. Shipping code does not do it for you.
If you asked for a run and nothing happens, it is almost always one of these two, in that order.
What you need before the first run
You need to be an owner or admin of the organization. Viewers can read history; they cannot enable the agent or start a run.
Confirm the organization has the agent, then look at one app — the tutorial’s
lumen is the demo fixture this guide walks:
$dibbla apps maintenance status lumen --json # settings.enabled is the app switch; a 404 with MAINTENANCE_AGENT_NOT_FOUND is the org switch
The JSON document is the server’s. It names the model, the overnight cadence,
and the latest run if there is one. A 404 with MAINTENANCE_AGENT_NOT_FOUND
(exit 4) means the organization is not switched on yet — ask us to enable it.
That is not something a different command will fix.
The agent shares the organization’s Application Check token pool. A run that stops because the pool is used up is Budget limited, not an agent failure: nothing about the app went wrong, and the next night gets a fresh allowance.
Which model it runs on, and who pays
The agent runs on a model from Dibbla’s model catalog, one per purpose: the nightly watch, the triage of a failed check, and writing a proposed fix. Only catalog models the agent can actually hold a conversation with are offered.
- With Dibbla Care the runs go through Dibbla’s gateway on Dibbla’s own key. Dibbla chooses the models and the inference is included in Care: it is not added to your bill, and in AI Usage the agent’s calls are marked as the agent’s and kept out of your totals. The settings show which model each purpose uses; only Dibbla can change it.
- Without Care the runs use your organization’s own provider key, and an owner or admin picks the model for each purpose from the same catalog.
Turn it on
Enablement is explicit, per app, and needs owner or admin:
$dibbla apps maintenance enable lumen --yes ✓ maintenance enabled for lumen (deployment dep_…)
# history is preserved; new scheduled runs stop $dibbla apps maintenance disable lumen --yes ✓ maintenance disabled for lumen (deployment dep_…)
The console form is the same switch on Apps → lumen → Maintenance agent. Turning it off keeps every past run readable.
Run now
Waiting overnight is no way to check your work. Run now uses the same path as the overnight pass, records that you asked, and returns a typed result:
$dibbla apps maintenance run lumen --follow --json # NDJSON: execution_created, then status changes, then exactly one summary with outcome and exit_code
# reuse the same --idempotency-key to follow the original run; it does not start a second one $dibbla apps maintenance run lumen --async --idempotency-key lumen-first-look
A reused idempotency key is a replay of the same intent. Two clicks, a retry after a timeout, or the same key from a script all land on one execution — the agent does not spend a second budget on a double submit.
Read the result
Every finished run has a chip and a sentence. The mapping is closed: a code the console does not recognise is Unrecognised result, never folded into a green one.
| What you see | What happened | What to do |
|---|---|---|
| Ran, found nothing | A complete look; nothing worth reporting | Nothing. This is a healthy night. |
| Finding recorded | The agent wrote down a finding about the app | Read the summary and fingerprint. Exit 11 from the CLI. |
| A proposal in the change queue | The agent opened a concrete code change | A different owner or admin reviews it. The author cannot approve it. |
| Budget limited | The shared token pool ran out | Wait for the next allowance, or raise the pool. Nothing failed. |
| Skipped | Another run already held this app | Nothing. The other run is the one to read. |
| Agent error | The agent could not finish | Report it. This is not a finding about the app. |
| Unrecognised result | The run finished with a code this screen does not know | Do not treat it as clean. Tell us. |
The CLI prints the same facts as a typed outcome. Exit 0 covers found
nothing, proposed, budget exhausted and deliberate skips; exit 11 is a
recorded finding; transport failures keep the CLI-wide ladder (3 auth, 4
not found, 5 validation, 6 conflict, 7 timeout, 1 unexpected). Full
JSON, NDJSON and codes live in the CLI reference.
History is newest first:
$dibbla apps maintenance runs lumen --limit 10 🔧 maintenance runs for lumen EXECUTION STATUS TERMINAL CODE TOKENS CREATED
A summary, fingerprint and evidence references travel with the run so a known fingerprint can say “seen before” without pretending the night was empty.
Its model calls in AI Usage
The agent reaches its model through Dibbla’s AI gateway, so every call it makes shows up in the app’s AI Usage tab and in the organization’s AI Usage. Each one is labelled Maintenance agent, and the scheduled application checks that use a model are labelled Application check.
Under Dibbla Care, Dibbla pays for these calls, so they are not your usage. They carry Included in Dibbla Care and are left out of the Requests and Tokens totals. A separate line under the totals shows how many calls and tokens Dibbla Care covered for the same filter. Your app’s own calls are labelled and counted exactly as before.
If Care is off and the organization has stored its own provider key, the agent’s calls are signed with that key and go on your provider bill. They are still labelled Maintenance agent, but they count in your totals like any other call.
Review a proposal
When the agent opens a change, it lands in the app’s change queue — the same queue a human deploy uses when the app requires review. The agent is the author. Approval is a different person’s job, enforced by the server, not by the client guessing who may click.
$dibbla apps proposals list lumen --json $dibbla apps proposals show lumen pr_0123456789abcdef0123 --diff --json # one document: the proposal plus the server-generated diff. Eligibility is the decision object the API returned — the CLI does not compute it.
$dibbla apps proposals approve lumen pr_0123456789abcdef0123 --yes # deny and retry are the same shape. The server refuses the author, the wrong role, and a proposal that is not ready.
--diff is the exact server diff between the two immutable revisions. It is
evidence for a reviewer, not a patch you apply by hand. Approving queues the
same deploy path a human change would take; denying leaves the live app
untouched.
Opt-in runbook for the next gradual step
The production proof (2026-09-01 to 2026-09-02, Dibbla’s own checks-canary)
followed this exact sequence. Copy it for the next app; nothing here turns
anything on for anyone else.
-
Confirm the runtime is on for the cluster. Operators: deploy-api, workflow-server and the AI gateway must run builds with the maintenance env wired — see the platform runbook (
terra-dibbla/MAINTENANCE.md). Organizations and apps stay off regardless. -
Subscribe to the events (org admin, Organization settings → Integrations): paste
application.maintenance.*as the event type, choose email and your address, immediate. The first run then reaches you without anyone watching a console. -
Enable the organization (owner/admin):
PUT /api/deploy/orgs/{orgId}/maintenance-agent {"enabled": true, "model": "dibbla/claude-sonnet-5"}A model is required the first time. The response also carries the defaults you inherit:
nightly_cron0 3 * * *, timezoneUTC,cooldown_hours24. -
Name the app — the second switch, and the one that actually opts an app in:
PUT /api/deploy/deployments/{alias}/maintenance-agent {"enabled": true}An app with no settings row is OFF. Enabling the organization enables nothing by itself; that is the whole point of two switches.
-
Watch the first run. Either wait for the nightly — a platform scheduler dispatches it on the org’s
nightly_cron— or start one yourself:POST /api/deploy/deployments/{alias}/maintenance-agent/runs {"mode": "nightly"}Read the result with
GET .../maintenance-agent(settings plus the last run) orGET .../maintenance-agent/runs(the history, with the terminal code, the model, the tokens spent and the evidence each run actually read). If the agent proposed, review and decide as a different owner or admin than the proposal’s author — the agent itself can never approve. -
Disable fast when you must:
PUT /api/deploy/deployments/{alias}/maintenance-agent {"enabled": false}New runs, spend and writes stop at once, an in-flight run is fenced to a typed terminal state, and all history stays readable. Re-enable is the same switch. The organization switch works the same way and covers every app at once.
Every step above leaves every other organization and app untouched.
What the agent can never do
The safety model is the product:
- It never deploys on its own. A proposal waits for a distinct owner or admin. There is no self-approve path.
- It never carries your token. The run uses a short-lived workload identity bound to this app, this execution and this lease. Revoking the lease fences later tool calls even if a leftover token still verifies.
- It never publishes secrets. Findings, summaries and evidence are bounded and redacted. Raw logs, diffs of credentials, and customer data do not belong in a result document — and a capture of this surface hard-fails if they appear.
- It never spends past the shared budget as a policy. A run that hits the ceiling stops as Budget limited. That is the ceiling working.
- At most one proposal per app per day. A second finding the same day is recorded; it does not open a second change.
Have an agent walk it
The same loop a coding assistant can run against your own app, once the organization capability is on:
Verify the maintenance agent on this app, run once, and report the typed outcome.
You are helping an owner verify the Dibbla maintenance agent on the app in
this repo (alias from dibbla.yaml / the deployed app — never invent a host).
1. Confirm login with `dibbla status --json`. If not logged in, stop and say so.
2. `dibbla apps maintenance status <alias> --json`. If the command exits 4
with MAINTENANCE_AGENT_NOT_FOUND, the organization does not have the agent
yet — report that and stop. Do not try a different command.
3. If settings.enabled is false, run `dibbla apps maintenance enable <alias>
--yes` and show the acknowledgement.
4. Start one run with a stable idempotency key and follow it:
`dibbla apps maintenance run <alias> --follow --json --idempotency-key
verify-<date>`. Report the terminal summary's outcome and exit_code
exactly. Exit 0 is found_nothing / proposed / budget_exhausted / skipped;
exit 11 is finding_recorded; do not scrape prose for the result.
5. If the summary carries a proposal_id, show
`dibbla apps proposals show <alias> <proposal_id> --json` and the
server-owned decision object. Do not approve unless I ask, and never as
the same identity that authored the proposal.
6. Repeat the same run command with the same idempotency key and confirm it
replays the original execution instead of starting a second one.
Do not print API tokens, raw logs, or diff hunks that look like credentials. See also
dibbla apps maintenanceanddibbla apps proposals— every command, the exit-code ladder, and the JSON/NDJSON contract.- Application checks — the assertions the agent can read; the two instruments share one token pool.
- Notifications — route
application.maintenance.*events the same way as checks and deploys. - Stage 5 — Give it a schedule — the tutorial stop this guide hangs off. The spine does not require the agent.