Blog · Deployment guide
Agent deployment approval workflow
If an AI agent can trigger a production deploy, the approval should be tied to the exact deploy facts: commit, environment, service, migration, actor, and rollback plan.
The deployment risk
Traditional CI/CD approvals often answer a vague question: “may this pipeline continue?” That is weak for agent-driven work. An agent may summarize the intent correctly but still pass the wrong commit, target the wrong environment, include an unsafe migration, or retry with changed parameters.
A safer workflow asks a person to approve the exact operation the runner will execute. The approval is useful only if the deploy code compares the signed payload with the final deploy inputs and fails closed on any mismatch.
Payload checklist
For a production deploy, include enough detail that the approver can recognize the operation and the runner can prove it is still the same one:
- service — the app, worker, job, or package being deployed;
- environment — production, staging, region, account, tenant, or cluster;
- commit — SHA, tag, image digest, or artifact id;
- actor — agent, pipeline, human requester, or service account;
- migration facts — schema file, command, destructive flag, or “none”;
- rollback — previous version, rollback command, or runbook reference.
GitLab CI example
The job creates a Cosignet approval request and long-polls over an outbound HTTP connection. The runner
proceeds only when the decision is approved.
deploy_production:
stage: deploy
script:
- export DEPLOY_PAYLOAD="{\"service\":\"api\",\"environment\":\"production\",\"commit\":\"$CI_COMMIT_SHA\",\"pipeline\":\"$CI_PIPELINE_URL\"}"
- node scripts/cosignet-deploy-gate.mjs
- ./scripts/deploy-production.sh "$CI_COMMIT_SHA"
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual
The same shape works in GitHub Actions, Buildkite, Jenkins, or a plain deployment script. The important invariant is that the deploy command receives the same commit and environment that were signed.
Gate script sketch
const payload = JSON.parse(process.env.DEPLOY_PAYLOAD);
const created = await fetch('https://cosignet.com/api/confirmations', {
method: 'POST',
headers: {
'content-type': 'application/json',
'X-Api-Key': process.env.COSIGNET_API_KEY
},
body: JSON.stringify({
username: process.env.COSIGNET_APPROVER,
action: `Deploy ${payload.service} to ${payload.environment}`,
payload
})
}).then((r) => r.json());
let decision = { status: 'pending' };
while (decision.status === 'pending') {
decision = await fetch(
`https://cosignet.com/api/confirmations/${created.id}?wait=25`,
{ headers: { 'X-Api-Key': process.env.COSIGNET_API_KEY } }
).then((r) => r.json());
}
if (decision.status !== 'approved') {
throw new Error(`deployment not approved: ${decision.status}`);
}
const finalDeploy = {
service: 'api',
environment: 'production',
commit: process.env.CI_COMMIT_SHA
};
if (
payload.service !== finalDeploy.service ||
payload.environment !== finalDeploy.environment ||
payload.commit !== finalDeploy.commit
) {
throw new Error('signed payload does not match the operation about to run');
}
In production, also compare the final deploy variables with the signed payload immediately before the deploy command runs. If the commit, environment, migration, or service name changed, stop and request a new approval.
Operational rules
- Fail closed: reject, expiry, pending timeout, network error, and malformed response all stop the deploy.
- Do not approve broad labels: avoid “deploy latest” or “run migration”; sign concrete facts.
- Keep secrets out of payloads: include secret names or ids, not raw secret values.
- Log the approval id: store it beside the deployment record so an auditor can verify the decision later.
- Separate deploy from approval: Cosignet signs the decision; your runner still executes and enforces the matching operation.
For AI agents
If an agent proposes the deployment, make the gate a required tool or script step, not a prompt-only convention. The agent may draft the payload, but your deploy wrapper should compute and validate the final payload from CI variables. For direct tool-calling agents, use Cosignet as an MCP approval gate.