Documentation

Executable deployment-actions

AuditTrail is a control plane, not an execution host. It proposes, versions, approves, and governs agent operations — and for the subset of actions it can safely carry out, it performs them. For everything else it is honest: the action is recorded, approved, and handed off to your runner.

This page describes which deployment actions AuditTrail performs itself, how, and the guardrails around each.

Execution runs on YOUR runners.

AuditTrail never runs your agents in our cloud. The only things the control plane can do on its own are: rewrite / throttle / disable traffic that flows through its gateway proxy, and send a kill frame to your own connected runner daemon. Anything that touches your infrastructure (scaling, redeploys) stays a recorded approval plus a hand-off.

The 3-tier safety model is unchanged

Executable actions do not weaken the approval gate:

TierActionsApproval
1alert, tag_trace, create_incidentautonomous-safe (auto-executed on propose in autonomous mode)
2throttle, switch_model, disable_flagsupervised approval always required
3kill_run, rollback_deploy, scale_downexplicit approval always required

The executor only ever acts on an action that already reached status = "approved". It never approves anything. Autonomous mode still auto-executes Tier 1 only.

The performance matrix

ActionWhat AuditTrail does
kill_runSends a kill frame to your connected runner daemon for the target dispatch. The dispatch must be owned by you; an unknown or cross-tenant dispatch, or no connected daemon, records a clear failure (never a silent success).
throttleWrites an active runtime control that caps the per-minute rate for gateway-routed traffic. (gateway-routed only)
switch_modelWrites an active runtime control that rewrites the upstream model id in the gateway forward path. (gateway-routed only)
disable_flagWrites an active runtime control that short-circuits gateway-routed traffic for the target with a clear 4xx. (gateway-routed only)
scale_downExternal handoff. Approve here, perform it on your runner, record via mark-executed.
rollback_deployExternal handoff. Approve here, perform it on your runner, record via mark-executed.
Tier 1Recorded in the audit trail (and fanned out via your webhooks).

GET /api/v1/deployments/actions/capabilities returns this matrix so the /deployments UI can badge each action AuditTrail performs vs External handoff, with the gateway-only caveat where it applies.

How the executor runs

The executor is a single pass on the existing online-eval worker loop — no new always-on poller (this instance runs SQLite, and a tight poll would burn the write budget). Each pass:

  1. Selects every action with status = "approved" and executed_at IS NULL.
  2. Performs the ones it can, records the result in outcome, stamps executed_at, and commits per action.

Idempotency survives a restart

Idempotency is DB-persisted, not in-memory. The tag-deploy cron restarts the API container regularly; an in-process "already done" set would be wiped and the action would run twice. Instead, a row is eligible for execution only while status = "approved" AND executed_at IS NULL. The first pass stamps executed_at and commits, so a restart mid-loop can never re-perform an action.

A successful perform also flips status to executed. A refusal or failure leaves status = "approved" but stamps executed_at and sets outcome.execution_failed = true, so the operator sees an honest result and the executor doesn't retry it forever.

switch_model refuses loudly

switch_model is gateway-routed only. A swap is enforceable only when the action carries a replacement model (outcome.to_model) — the gateway then rewrites body["model"] before forwarding upstream.

If no to_model is supplied, the swap is not gateway-routable, so the executor refuses loudly: it writes no runtime control, records a failure with a human-readable reason, and never marks the action successful. Re-propose with outcome.to_model set, or perform the swap on your own runner and record it with mark-executed.

kill_run targets only your own dispatch

kill_run resolves its target from outcome.dispatch_id (or target_ref) and verifies the dispatch is owned by the action's user before sending the kill frame. A dispatch that doesn't exist for you — or belongs to another tenant — yields a recorded failure (404-style, never leaks existence). The daemon tracks live subprocesses by dispatch id and, on the kill frame, sends terminate(), waits a short grace period, then escalates to kill().

Runtime controls

A performed Tier-2 action writes a runtime_controls row scoped to your user. The gateway consults your active controls on every request:

  • disable_flag matching the model → the call is short-circuited (403).
  • throttle matching the model whose per-minute ceiling is exceeded → 429.
  • switch_model matching the model → the upstream model is rewritten.

A control owned by another user can never affect your traffic. The throttle counter is in-process (best-effort, single-replica); the control itself lives in the DB row, so a restart just resets the rate window — the safe direction for an availability control.

REST surface

MethodPathPurpose
GET/api/v1/deployments/actions/capabilitiesPer action_type: performs vs handoff + gateway-only caveat
GET/api/v1/deployments/actionsList your actions
POST/api/v1/deployments/actionsPropose
POST/api/v1/deployments/actions/{id}/approveApprove
POST/api/v1/deployments/actions/{id}/rejectReject
POST/api/v1/deployments/actions/{id}/mark-executedRecord an external-handoff result

WebSocket events

The executor broadcasts to the action owner's live feed:

  • deployment.action.executed — a performed action.
  • deployment.action.failed — a refused / failed / handoff action.