subagentcoworkers

.com 19 pages

Approval gates

Legal and Finance are the two roles with a hard rule: never act without a human saying yes, on a physical device, in the moment.

Why a gate is just a typed prompt

A gates.toml file is a policy, not code:

# cwc-finance/gates.toml
[gate.spend]
match   = { tool = "*", action = "purchase|subscribe|deploy_paid" }
require = "operator"
surface = "buddy"
hint    = "{{vendor}} ${{amount}}"

When a gated action is attempted, the coworker runtime emits a WirePermissionPrompt tagged with the role asking. Nothing about the gate is hard-coded per role — the same mechanism serves a legal send-for-review, a finance purchase, or an engineering deploy that happens to touch production.

The buddy: a physical approve/deny

The prompt surfaces on coworkers-desktop-buddy — a macOS app paired over BLE to a small hardware device. The operator presses a physical button; the decision round-trips back through the same permission-<id>.json file the desktop app's own Approve/Deny buttons write. A pending gate carries its role, so the device shows who is asking — [legal] awaiting approval: NDA v3 — not just that something is waiting.

Two-phase publish (the proof-of-loop)

Design's publish step is the pattern in miniature: artifact_request_publish asks, the operator decides on the buddy, artifact_finalize_publish only runs on approval. It's additive to the wire protocol — an optional role field both the Rust and Swift sides already tolerate via default/decodeIfPresent — so adding a gate to a new role never breaks an old one.

Related

original sha256:16 27667d8e91ec2293 · verify