secure-agent-workflow · an open-source software factory
Let agents build. Keep the controls out of reach.
Coding agents work approved issues through to merged pull requests with little supervision. Nothing reaches production without passing checks the agent can't edit, skip or see the credentials for.
Controls live outside the agent
They sit in the sandbox, GitHub permissions and cloud credentials. Instructions and settings files inside the sandbox are not controls, because the agent can rewrite them.
Risk decides who approves
A tier map sorts every changed path. Low-risk changes merge on green checks. Dependencies, auth and workflow changes wait for a human.
Each part is replaceable
v1 runs on Cloudflare and AWS. Every component sits in its own folder behind a stated contract, so a fork can rewrite one without touching the rest.
Where things run, and who holds the keys
The agent runs in a sandbox with no credentials and no direct internet. Everything it sends leaves through a handler that adds short-lived tokens on the way out. Deploy credentials sit behind separate roles: previews and staging in the dev target, where only staging can change infrastructure, and one that only a production release can assume.
What stops what
edits a workflow file…
GitHub rejects the push. The App has no workflows permission.
pushes to main or production…
Rulesets require a PR and bind everyone, admin included. Only you can release.
looks for a credential…
There is none in the sandbox. Tokens are added outside it.
calls an unknown host…
The outbound handler allows listed hosts only.
changes a dependency or auth code…
The tier check fails until you approve the latest commit.
rewrites the tier map or CODEOWNERS…
Tier 3. An agent-authored PR there always fails.
tries to change infrastructure…
It can only write OpenTofu. The plan is posted on the PR, it waits for you, and only the pipeline applies it.
deletes a database or bucket…
A plan that deletes or replaces stored data fails the gate unless you wrote the PR.
hides a command in a build script…
Previews get a Workers-only token. Only staging, after merge, holds the token that changes infrastructure.
From an approved issue to production
You plan and dispatch work. Agents build it. The merge gate decides what needs you. A tier 1 change can go from label to staging without anyone touching it. Production is a release you cut.
Plan
Spec, implementation plan and ADRs. The plan becomes issues, with dependencies marked.
Dispatch
One command labels a wave's issues agent:ready. The label is the only start button.
Queue
Runs each issue once its dependencies merge, within a concurrency cap and a budget.
Build
Works in a fresh sandbox on its own branch, writing code and infrastructure alike. Opens a draft PR and gets a preview deploy.
Check
Tests, scanners, tier check, an infrastructure plan, and an LLM audit that can reject but never approve. You approve what needs you, in GitHub.
Stage
Every merge to main applies infrastructure, then deploys to staging, in the dev target.
Release
A versioned PR from main into production, with a summary of the whole batch. Only you approve it.
Later phasePress play to follow one issue from plan to production. Switch to a tier 2 change to see where you come in.
App source, tests, general docs
All checks green and no blocking audit finding. Zero touches from you.
Dependencies, auth code, migrations, infrastructure, specs, ADRs and agent instructions, and anything the map doesn't name
Your approval on the PR's latest commit. A new push needs a new approval.
.github/, CODEOWNERS, the tier map itself
An agent can't author these at all. You write them and approve them.
Waves keep approvals few. The plan groups issues into waves that you dispatch and review together. Dependency, auth and migration changes go into one or two foundation issues at the start of a wave, so the rest of it stays tier 1. A wave of twenty issues might need three approvals, not twenty.
Blocked
The agent pushes its work, writes a handoff note on the PR, labels it agent:blocked and exits. Your reply on GitHub or Slack starts a fresh sandbox that picks up from the note.
Stopped
agent:stop revokes the agent's token and tears down its sandbox. The orchestrator enforces it, so it works even if the agent ignores instructions.
Out of budget
The outbound handler refuses model calls once a run's token budget is spent, and all runs share a daily spend cap. Provider spend limits sit behind both, outside this system's code.
Putting a new project on it
Protecting a repo is one command. After that, your work is planning, releasing issues and approving the changes that need you.
-
Install the agent's GitHub App once per repo
The agent acts as this App. It can write code, PRs and issues, and nothing else: no workflows, no settings.
-
Run bootstrap one command
bootstrap.sh owner/repoapplies the ruleset, Actions policy, environments and labels, and opens one PR with the gate wiring and a tier map. Safe to re-run; it reads back every setting it writes. -
Merge the bootstrap PR
It runs the gate on itself first. From then on, every PR into main passes the same checks.
-
Connect deploy credentials one command
broker.sh add-repo owner/repocreates the repo's preview and staging roles. You put each environment's tokens in Parameter Store; no deploy secret is stored in GitHub. -
Plan in waves
Spec, plan and ADRs, broken into issues sized for one agent run and grouped into waves, with their dependencies marked.
-
Dispatch a wave, then review only what needs you
/dispatchlabels the wave's issuesagent:ready. Tier 1 PRs merge on their own and reach staging. Tier 2 and 3 PRs arrive with the reason they need you. When staging looks right, you cut a release.
Keeping the gate current
Every repo pins the gate to a commit of this repo, so a change here reaches a project only when that project accepts it.
bootstrap.sh owner/repo --update-gate <sha> moves the pin through a PR. It refuses a commit that isn't on this repo's main.
Swapping a part
| Sandbox | E2B, Docker Sandboxes, gVisor or Kata containers |
| Egress proxy | Envoy or Squid with an allowlist |
| Credential broker | Vault or OpenBao, GCP or Azure federated credentials |
| Infrastructure | Terraform, Pulumi |
| Scanners | GitHub Advanced Security, a commercial scanner |
| Agent CLI | Any CLI that runs headless |
GitHub stays. Whatever you replace, keep the property the design depends on: the agent can't change the control.
Where it stands
The agent's identity, bootstrap, the merge gate, the credential broker and deploys to preview and staging are built and tested on testbeds. Next is the orchestrator, then the first wave of a real project. Production releases are designed and wait until that works.
Built and tested
- Agent GitHub App, with no workflows or admin permission
- Bootstrap: ruleset, Actions policy, environments, labels
- Merge gate: checks and scanners, pinned tools
- Tier check, re-run on every review
- Gate wiring installed and updated by bootstrap
- Specs, ADRs and agent instructions are tier 2
- Templates, wave planning standard and
/dispatch - AWS credential broker: preview and staging roles, state bucket
- Credentials declared per repo, checked before storing and daily
- Preview and staging deploys through the dev roles
- This repo's own PRs go through the pinned gate
Next
- Orchestrator, sandbox, outbound handler and budgets
- Static assets in deploys, and an onboarding guide for a new project
- Infrastructure as OpenTofu: plan on the PR, apply after merge, drift checks
- Red-team runs, with results published
- PR audit: LLM reviewers and an approver summary
- Production releases: versioned, batch-reviewed
- Operator console that works on a phone, and a separate identity for the planning agent