Agent Office v0.2.3 · POC Get started
Menu: Guides

Guides

Agents that open pull requests

Let an agent clone a repo into its vault, push a branch as itself and open a pull request that a human reviews and merges.

An agent with forge.write on a repository works on it the way a colleague would: it clones the repo, commits on its own branch, pushes as its own forge user, and opens a pull request. A human reviews and merges. The agent never merges and can't push to main.

1. Grant the capability#

Add the repos to the agent's personnel file in a policy PR, or pick them in the hire wizard (Can open pull requests on):

agents/ada/personnel.yaml (excerpt)
capabilities:
  - vault.write
  - forge.read: { repos: ["acme-billing"] }                       # clone and read only
  - forge.write: { repos: ["acme-site"], via: pull_request }       # push branches, open PRs

After the merge, the reconciler makes the agent's forge user a read or write collaborator on each repo, and the next run's token gets write:repository.

Protect main first. The boundary that stops an agent from changing main is the forge's branch protection, not the agent's settings. officectl init protects main on the policy, office and handbook repos. For any other repo you grant forge.write on, turn on branch protection for main in Gitea (merges by your approvers team, no direct pushes) before the agent starts. Otherwise a write collaborator can push to it.

2. What the agent may run#

Git is allowed as a whole (Bash(git *)) for agents with vault.write, forge.read or forge.write. Listing git subcommands one by one left agents stuck on harmless calls such as git --version or git rev-parse, and the real limits are elsewhere: the run token's forge permissions and branch protection.

With forge.write, the compiled settings also deny git push * main, git push * HEAD:main and git push origin main. That's a speed bump, not the boundary: a refspec can be spelled many ways (x:main, refs/heads/main). Branch protection is what refuses the push.

Other shell commands follow the agent's shell profile. Without shell, the agent runs only git and Claude Code's built-in read-only commands; anything else is refused.

3. How the agent works on a repo#

The rendered CLAUDE.md tells the agent the workflow:

  1. Clone into work/<repo> inside its vault: git clone <forge.url>/<org>/<repo>.git work/<repo>. The vault's .gitignore ignores work/, and the office's post-run commit of the vault excludes it, so code never ends up in the agent's memory.
  2. Work on a branch named <slug>/<topic>, never on main. Commits are authored as the agent.
  3. Push the branch: git push -u origin <slug>/<topic>. Git authenticates with the run's own forge token, passed as an HTTP header in the environment (never on a command line or in .git/config) and revoked when the run ends.
  4. Call forge_open_pr with the repo, the branch as head, a title and a body (what changed, why, how it was tested, any risk). The office opens the PR as the agent's own forge user, with a short-lived token that is revoked right after. If a PR for that branch is already open, the agent gets that one back. Ledger: forge.pr.opened.
  5. Put the PR's URL in its reply. On a later run, cd work/<repo> and git fetch first.

forge_list_prs lists the open PRs on a repo the agent can read or write, so it can find its own PR or see what is already in review.

4. Review and merge#

The PR is an ordinary pull request in Gitea, authored by the agent's forge user. Review it and merge it there; branch protection decides who may merge. An agent asking for access to a repo it doesn't have can use approval_request for a time-boxed window, but windows don't add forge collaborator access, so lasting repo access goes through a policy PR.

Sandbox notes#

These are handled for you; they matter when you debug a failing clone.

  • TLS to the forge. Sandboxed commands may not read /etc, except the CA trust paths git, curl and node need: on Linux /etc/ssl/certs, /etc/ca-certificates, /usr/share/ca-certificates, /etc/pki/tls/certs and /etc/pki/ca-trust; on macOS /etc/ssl and /private/etc/ssl. On Linux the run also gets SSL_CERT_FILE and GIT_SSL_CAINFO pointing at the host's CA bundle (/etc/ssl/certs/ca-certificates.crt or /etc/pki/tls/certs/ca-bundle.crt, whichever exists).
  • Forge on the same host. When the forge (or the office) resolves to an address of this machine, the Linux sandbox refuses the allowlisted name unless the address and port are allowlisted too. For each run the office adds <ip>:<port> (for example 203.0.113.10:443) for every office or forge URL that resolves to this host.