Playbook: Deployment Agent
The scenario
An agent helps ship software: it can run tests, cut releases, and deploy services. Deploying to staging all day is fine. Deploying to production should never happen without a human. This is a tiered-authority problem.
The risk
A single "deploy" permission treats staging and production the same. One confident-but-wrong plan, and the agent pushes an untested build to prod at 2am. The fix is not to forbid the agent from deploying — it is to make the stakes part of the authority decision.
The mandate design
Model the two tiers as two mandates for the same delegate: one that auto-allows low-risk targets, one that requires human review for production.
| Tier | Actions | Resource | Constraint |
|---|---|---|---|
| Staging (auto) | deploy.service.release | acme:env:staging/* | none — auto-allow |
| Production (gated) | deploy.service.release | acme:env:production/* | requires_human_review: true |
Create both mandates
const delegate = {
type: "AGENT" as const,
subject_id: "agent:release-bot",
display_name: "Release Bot",
};
const principal = {
type: "NATURAL_PERSON" as const,
subject_id: "user:platform-lead@acme.com",
display_name: "Platform Lead",
};
// Tier 1 — staging: auto-allow.
await mandaitor.createMandate({
principal,
delegate,
scope: {
actions: ["deploy.service.release"],
resources: ["acme:env:staging/*"],
effect: "ALLOW",
},
constraints: { time: { duration: "P30D" } },
});
// Tier 2 — production: allowed, but only with human review.
await mandaitor.createMandate({
principal,
delegate,
scope: {
actions: ["deploy.service.release"],
resources: ["acme:env:production/*"],
effect: "ALLOW",
},
constraints: {
time: { duration: "P30D" },
context: { requires_human_review: true },
},
});
Enforce it at runtime
The agent calls the same tool for any deploy. Mandaitor picks the mandate that matches the target and returns ALLOW for staging or REVIEW for production — your handler routes accordingly.
async function deployTool(args: { service: string; env: "staging" | "production" }) {
const result = await mandaitor.verify({
delegate_subject_id: "agent:release-bot",
action: "deploy.service.release",
resource: `acme:env:${args.env}/service:${args.service}`,
context: { service: args.service, env: args.env },
});
if (result.decision === "ALLOW") {
const deployment = await ci.deploy(args.service, args.env);
return { deployed: true, deployment, proof: result.proof };
}
if (result.decision === "REVIEW") {
// Production: open an approval and pause. A human approves, then you deploy.
const approval = await approvals.open({ service: args.service, env: args.env });
return { deployed: false, pendingApproval: approval.id };
}
return { deployed: false, reason_codes: result.reason_codes };
}
What should be denied
Confirm in the Sandbox, using the production mandate:
| Test | Expected | Reason |
|---|---|---|
deploy.service.release on acme:env:staging/service:api | ALLOW | Matches the staging mandate |
deploy.service.release on acme:env:production/service:api | REVIEW | Production requires human review |
deploy.config.delete on acme:env:staging/service:api | DENY | Action not in scope |
deploy.service.release on acme:env:production/* after revoke | DENY | Mandate not active |
Evidence & review
Production deploys leave a trail of REVIEW decisions, each paired with the human approval that released it. That pairing — the agent proposed, a named human approved, here is the proof — is exactly what an incident review or a change-management audit needs.
Adapt it
- Freeze windows: add a
contextcheck (e.g.deploy_window: "open") and pass it at verify time so deploys outside the window are denied. - Per-service delegates: issue mandates per service so a release bot for one team cannot deploy another team's services.
- Dual control: require two approvals for production by treating a single approval as insufficient in your approval workflow, keeping Mandaitor as the authority gate in front of it.