Reduce the reach of an injection without pretending it disappears.
Agents City keeps yolo so a committee can work, but narrows what a repository agent can read, write, and authorise. Security is shared across the kernel, credentials, bus, and seat protocol.
Control layers
- 01
Repo cage on macOS
Claude, OpenCode, and Kimi may start under a generated seatbelt profile: writes denied by default, allowed for the repo and required caches, then credential stores sealed last. Codex uses its native workspace-write sandbox to avoid incompatible nesting.
- 02
Credential broker
With CITY_BROKER=1 an agent asks an outside process to push or open a PR. Its token is per window and bound to one repo; the default branch and force pushes are always refused.
- 03
Audit chain
Every served or refused request links the previous hash. Evidence vocabulary separates enforced control, attribution, unknown, ambiguous, and unsupported. Unknown never authorises.
- 04
Path containment
The cage and broker resolve real paths through symlinks and missing leaves, and refuse repositories placed inside sealed stores.
- 05
Road admission
An ordered graph checks address, road, sender, and payload. The first blocker decides and returns a stable reason without leaking the allowlist.
- 06
Pairing primitive
The source tests a short code with a TTL, pending limit, and opaque approval id, but the current beta does not yet wire it into the live road router or CLI. Operational remote onboarding still exchanges public invitations in both directions.
- 07
Untrusted text
Every inbound message is wrapped in a random boundary and chat-role tokens are defanged. This is defence in depth, not a promise that the model obeys.
Cage controls
The cage never wraps the seat. The seat is not yolo and retains the decision boundary against external context.
CITY_CAGE=0turn off the cage; Codex changes to dangerFullAccess.CITY_CAGE_DENY=a:bseal extra paths, for example ~/.npmrc.CITY_CAGE_ALLOW_WRITE=a:badd writable roots for unusual toolchains.CITY_BROKER=1start the opt-in PR and push broker.$CITY_BROKER=1 agents-city seat$python3 plugin/scripts/broker.py call pr --title "Add X" --body "Why"Remote roads
The optional relay authenticates one owner prefix with a credential issued by its operator, accepts only seat-to-seat road envelopes, and may queue offline direct messages for up to 72 hours. road invite neither mints nor shares that credential.
$agents-city road invite product > product.invitation.json$agents-city road connect product research.invitation.jsonWhat it does not solve
Prompt injection still exists; the layers reduce its value and reach.
Outbound networking remains open. An agent may still exfiltrate what it can already see inside its own repository.
Every process runs as your OS user. The cage is not hostile-process isolation.
Linux without sandbox-exec has no seatbelt layer. Use separate users, containers, or a VM for untrusted code.
Keychain credentials may work over IPC even when files are sealed; protect branches and use the broker.
Verify the invariants
The repository turns past sharp edges into cross-cutting tests: sealed secrets, profile ordering, unknown without authority, redacted diagnostics, and the seat outside the cage.
$python3 bin/test-security.py$python3 bin/test-cage.py$python3 bin/test-broker.py$python3 bin/test-pairing.py