Skip to main content

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.

TierActionsResourceConstraint
Staging (auto)deploy.service.releaseacme:env:staging/*none — auto-allow
Production (gated)deploy.service.releaseacme: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:

TestExpectedReason
deploy.service.release on acme:env:staging/service:apiALLOWMatches the staging mandate
deploy.service.release on acme:env:production/service:apiREVIEWProduction requires human review
deploy.config.delete on acme:env:staging/service:apiDENYAction not in scope
deploy.service.release on acme:env:production/* after revokeDENYMandate 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 context check (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.