Acting in the world

How an org ships real services, messages real people, charges customers, and watches its own servers — credentials, publishing, outbound channels, and payments.

Last updated

An autonomous org that can only think isn’t much of an organization. Quorum orgs act: they push code to real repositories, publish services to real hosts, and message real people — on your accounts, with your approval boundaries, and with every outbound action audited.

Six pieces work together:

  1. The secrets store — you hand the org a credential once; it reaches only the agents you grant it to.
  2. Publishing + the service registry — agents deploy to external hosts and the org keeps a live record of what it operates. Nothing is ever served from your machine.
  3. Channels — named outbound routes (Slack, email, any webhook) that the org sends through for the agents, with per-agent allow / ask / deny.
  4. Payments — a payment provider the org charges customers through, so it can turn the product into revenue. The org raises the charge and hands back a hosted checkout link; the agent never touches card details or the key.
  5. Telemetry — an observability backend the org reads its own shipped servers from, so it can watch the product’s health and react. The org pulls the logs; the agent never touches the backend token.
  6. Error tracking — a Sentry account the org reads its own unresolved issues from, so it learns which exceptions are hitting real users, with stack traces. The org pulls the issues; the agent never touches the token.

All of them are driven by output markers — lines an agent puts in its task output, like SEND[email | to=…]: … or CHARGE[…]: …. A marker only acts when it stands at the start of a line in work that passed review: one quoted mid-sentence or inside a code block is prose, not a command; rejected work never fires one (the one exception is REQUEST[...] — asking you for help works even from a failing attempt); and every marker is gated by the agent’s capabilities and audited.

This page walks the whole flow, end to end.

First, the thinking: market analysis and a living roadmap

An org that acts should think on paper first — and the paper needs a home that outlives any one agent. Every org has a shared docs folder (workspace/docs/) that all agents read and write, with a convention the runtime teaches them:

  • MARKET.md — the market and competitor analysis: segments, pricing, the wedge the product will take.
  • ROADMAP.md — the product roadmap: what ships in what order, and why.
  • ARCHITECTURE.md — how what the org builds fits together: the services, the database and other stores behind them, and how they connect.
  • DECISIONS.md — decisions made, with the reasoning, so later work doesn’t re-litigate them.

No machinery, no schema — just Markdown files every agent is pointed at and told to keep current. To make an org start with strategy, say so in the mission (“your first deliverables are MARKET.md and ROADMAP.md, then build against them”) and add a recurring trigger that keeps the plan honest:

### roadmap-review

- every: 7d

Re-read MARKET.md and ROADMAP.md against what actually happened this week —
metrics, leads, shipped work. Update both where reality disagrees, and adjust
the goal board to match.

You read the same files in the dashboard’s file browser; disagree with the roadmap, and a chat message to the org is a steering input like any other.

Step 1 — hand the org its credentials

Everything real runs on a credential: a GitHub token to push code, a Cloudflare API token to publish and manage DNS, a Slack bot token to post. Store each one once, by name:

pbpaste | quorum secret set github-token --grant builder
pbpaste | quorum secret set cloudflare-api-token --grant builder
pbpaste | quorum secret set slack-bot-token

(Pipe the value on stdin — an argument would land in your shell history. quorum secret list shows names and grants, never values.)

A granted secret appears in that agent’s work sessions as an environment variable named after itgithub-token becomes GITHUB_TOKEN, cloudflare-api-token becomes CLOUDFLARE_API_TOKEN — which is exactly where gh, wrangler, and most CLIs already look. The agent is told which variables it holds; the values never appear in its prompt, its memory, the chat, or the logs.

Notice slack-bot-token got no grant. For channels (step 3) that’s the point: quorum itself sends with it — no agent ever needs to hold it.

Grants are per-agent and fail closed. An agent that isn’t granted a secret simply doesn’t have it. You can also grant declaratively in QUORUM.md:

### Builder
- capabilities: { "secret:github-token": "allow", "secret:cloudflare-api-token": "allow" }

An explicit "deny" beats every other grant — useful to fence one agent out of an org-wide credential.

Or grant mid-mission, when the org asks. Agents that hit a wall raise a request — you’ll see it in the dashboard’s Requests panel:

🙋 builder requests [credentials]: a Cloudflare API token to publish the landing page

Answer it with a SECRET[...] line:

Here you go.
SECRET[cloudflare-api-token]: <paste the token>

The value goes straight into the store, granted to the asking agent; the agent’s memory learns only that CLOUDFLARE_API_TOKEN is now available, and the task that was blocked re-opens and retries with the credential in hand.

Step 2 — a build agent that ships

With two tokens granted, one role definition covers the whole build-and-publish pipeline:

### Builder

- reports to: ceo
- capabilities: { "secret:github-token": "allow", "secret:cloudflare-api-token": "allow" }

You are the builder. Keep every project in a GitHub repository and publish to
Cloudflare (Pages, Workers) — never serve anything from this machine. Register
everything you publish so the org knows what's live.

Now a mission like “Launch a landing page and collect signups” plays out like this, with no further setup:

  1. The coordinator plans the goal and assigns the build to the Builder.
  2. The Builder writes the site in its workspace, creates a repository and pushes (gh repo create, git push — authenticated by GITHUB_TOKEN).
  3. It publishes with wrangler pages deploy (authenticated by CLOUDFLARE_API_TOKEN) and gets a live URL.
  4. It registers what it shipped (next step) and the reviewer scores the work like any other task.

The org’s machine stays what it is — a workspace. Everything public lives on external hosts, reached by URL.

Step 3 — the service registry: know what’s live

When an agent publishes or updates a service, it records it by putting one line in its task output:

SERVE[Landing Page | repo=https://github.com/acme/landing]: https://landing.acme.dev Signup page for the beta

That lands on the org’s service registry and shows up in the dashboard’s Live Services panel: name, URL, owner, status. Re-registering the same name updates it — which is also how an agent flags trouble:

SERVE[Landing Page | status=down]: https://landing.acme.dev
SERVE[Old Prototype | status=retired]: https://proto.acme.dev

For uptime, the org needs no extra machinery — a scheduled headless check (see Triggers & scheduling) does it for free:

RUN[every 15m]: curl -sf https://landing.acme.dev || echo "landing page is DOWN"

A quiet run costs nothing; output wakes the org to investigate.

Step 4 — channels: the org’s voice

Channels are named outbound routes declared in QUORUM.md, one org-level key:

- channels: { "slack": { "type": "slack", "channel": "#general", "secret": "slack-bot-token" }, "email": { "type": "resend", "from": "hello@acme.dev", "secret": "resend-api-key" } }

An agent sends by putting one line in its task output:

SEND[slack]: We just shipped the landing page 🎉 — https://landing.acme.dev
SEND[email | to=alice@example.com subject="Welcome to the beta"]: Thanks for signing up! Here's your invite link…

Quorum performs the send, not the agent. The runtime reads the channel’s secret from the store at send time and makes the call itself — so an agent can message the world without ever holding a token, and even a locked-down or lightweight agent can announce, notify, and reply. Every send is written to the audit log, message text included.

Slack in three minutes

  1. Create a Slack app in your workspace, give its bot the chat:write scope, and install it. Copy the bot token (xoxb-…).
  2. Invite the bot to the channel you want the org in: /invite @yourbot.
  3. Store the token and declare the channel:
pbpaste | quorum secret set slack-bot-token
- channels: { "slack": { "type": "slack", "channel": "#org-updates", "secret": "slack-bot-token" } }

That’s it — agents can now SEND[slack]: …, and to=#another-channel overrides the default room per message.

Email, out of the box

The resend type sends transactional email with nothing but an API key and a verified domain:

- channels: { "email": { "type": "resend", "from": "hello@acme.dev", "secret": "resend-api-key" } }

Store the key (quorum secret set resend-api-key), verify the domain (DNS records the org can even set itself with its Cloudflare token), and SEND[email | to=… subject="…"]: … works. This is for transactional mail — welcome replies, receipts, product notifications.

Prefer your own plumbing? The webhook type POSTs { text, to, subject, from } as JSON to a URL you control, with the channel’s secret as a bearer token — point it at a Cloudflare Email Worker on your domain or any sender’s hook. Swapping vendors later is a one-line change; the channel name, permissions, and audit trail stay put.

A mailbox the org actually owns — two-way, one key

The agentmail type is the most autonomous email option: a real inbox the org both sends from and receives at, through a single API key — no domain verification, no send-only-vs-full-access key dance, no separate inbound routing to wire.

- channels: { "support": { "type": "agentmail", "inbox": "acme@agentmail.to", "secret": "agentmail-api-key" } }

Sending is the usual SEND[support | to=… subject="…"]: …. What’s different is inbound: because the org can’t be called from the outside, the runtime polls the inbox on a cadence and hands each new email to the org as work — so support@ questions, signup confirmations, and account verifications become directives an agent picks up and replies to on the same channel. It’s the email equivalent of the service registry: the org gets a verifiable address it can use to act in the world, and stays aware of replies by polling. Set the channel to ask if you want outbound support replies to wait for your approval.

Cold outreach — founder-signed, from a real mailbox

Cold outreach is a different animal from transactional mail, and the org should treat it that way:

  • Never through a transactional provider. Unsolicited email violates those services’ terms and burns your domain’s deliverability. Cold mail goes out from a real mailbox — a Workspace/Gmail account wired as an MCP server (see the brain), or a webhook channel pointed at a hook that sends through that mailbox.
  • Every send is yours to sign. Set the outreach channel to ask and each email waits in the Requests panel, exact text shown, before it leaves. The runtime also bounds sends per task, so a single turn can never blast a list.
  • Make the org do it properly. The persona is the policy: research each prospect, record them in the CRM with why they fit, write short personal notes — never a template blast — include an easy opt-out, and log replies back onto the contact.

The pipeline is the same machinery as everything else: web research → CONTACT into the CRM → a drafted SEND per prospect → your approval → replies tracked. Autonomy does the legwork; you keep the send button.

Sourcing prospects — the outbound database

Web research finds a handful of prospects; to turn an ideal-customer profile into a real list at scale, give the org a prospecting provider. Declare it once, like payments — the runtime holds the key and does the search, so no agent ever touches it:

- prospecting: { "provider": "apollo", "secret": "apollo-api-key" }

Then an agent sources people with a PROSPECT[...] marker — the filters are the ICP:

PROSPECT[titles="Head of Ops,VP Operations" locations="Sweden,Norway" size=11,200 industries="Logistics,SaaS" limit=25]: mid-market ops leaders who'd use our tool

The org searches Apollo (the default; the largest B2B contact database — hunter and People Data Labs slot behind the same seam) and the matches — name, title, company, email — arrive in the agent’s memory next cycle. From there it’s the pipeline you already have: record each as a CONTACT lead with why they fit, write a personal note, SEND it from a real mailbox, and SCHEDULE a follow-up. Because it pulls real people’s data for outreach, prospect defaults to ask — each search waits for your approval until you trust a growth role with allow. Keep volume low, always offer an opt-out, and the CRM stages (lead → active → won → lost) track the whole funnel.

Deliverability, at volume. A single mailbox is right for low-volume, genuinely personal outreach. If you scale to automated sequences, put a dedicated cold-email sender (with inbox rotation and warmup) behind a webhook channel — the sourcing, CRM, and reply loop above don’t change.

Approval mode — you sign the outbound mail

Sending to the outside world is a real-world action, so it takes the same allow / ask / deny policy as everything else, per agent and per channel:

- defaults: { "capabilities": { "channel:email": "ask" } }

### Head of Marketing
- capabilities: { "channel:email": "allow" }   # marketing sends freely

With ask, the message doesn’t go out — it appears in the dashboard’s Requests panel, exact text shown, waiting for you:

✋ builder wants to send on “email” — to alice@example.com — “Welcome to the beta”: Thanks for signing up! …

Approve and quorum sends it as written. Deny and it’s dropped, audited. deny on a channel hides it from that agent entirely. A sensible posture for a new org: ask org-wide on email, allow on an internal Slack channel.

Step 5 — full products, not just pages

A real product needs more than static hosting, and the org’s one Cloudflare token already covers the whole stack — every piece provisioned by the agent itself with wrangler, no extra accounts:

need the org uses one command away
app / API runtime Workers or Pages wrangler deploy
database D1 (SQLite at the edge) wrangler d1 create
message broker / async jobs Queues wrangler queues create
file & object storage R2 wrangler r2 bucket create
config / cache KV wrangler kv namespace create
production cron Workers cron triggers a line of config

Agents don’t re-derive the wiring each time: quorum hands every code-capable agent the exact provisioning ceremony — create the resource once with wrangler, wire its binding into the service’s wrangler.toml, redeploy — down to the binding block each store needs. So “build a product with a database and background jobs” is just a task: the builder scaffolds the Worker, creates the D1 schema, wires a Queue for the async work, deploys, registers the result with SERVE[...], and writes the shape of it — the service, its database, and how they connect — into ARCHITECTURE.md so the org stays aware of the state it now operates. If your product truly outgrows this stack, any other vendor with a CLI is one secret away — the pattern doesn’t change.

A repo of record, and staging before production

Give the org a GitHub token and the product stops living in a sandbox folder. Every code-capable agent is told to keep the source in a real repository — created once with gh repo create, committed and pushed as it works, changed on branches through pull requests — so the code outlives any single agent and every colleague shares one checkout. The repo is what CI/CD deploys from: the org adds a GitHub Actions workflow that runs the official Cloudflare Wrangler action, and sets the Cloudflare token as a repository secret from its own token (gh secret set CLOUDFLARE_API_TOKEN) — no dashboard steps.

And it ships to two environments, never one. Cloudflare Workers has this natively — a [env.staging] and [env.production] block in wrangler.toml, each with its own worker name, domain, and its own D1/KV/R2 bindings, so staging can never read or corrupt production data. The pipeline deploys to staging on every push to main and to production only on a tagged release; the org verifies staging first, then promotes the same build. Both land on the service registry as distinct entries — the env column shows which is staging and which is production:

SERVE[acme-staging | env=staging]: https://acme-staging.workers.dev the staging deploy
SERVE[acme | env=production repo=<repo>]: https://acme.dev the live product

Analytics belongs in from day one. Store a PostHog key and tell the org to use it:

pbpaste | quorum secret set posthog-api-key --grant builder

The builder embeds PostHog in what it ships (the public project key lives in the product’s own code); an agent granted posthog-api-key can also read the numbers — funnels, activation, retention — and a recurring trigger like “pull this week’s metrics and decide one action from them” turns analytics into steering, not decoration.

Correctness is a number, not a claim

Plenty of products don’t just do something — they produce an answer. They reconcile, detect, extract, match, score, forecast, classify. For those, being right is the product, and “I ran it once and it looked fine” is how wrong answers reach paying customers.

So every code-capable agent is told to prove it instead: collect real, labelled fixtures — actual inputs with the answer you expect, the ugly ones included — score the logic against them, and run that score in CI so a change that regresses it never reaches production. Correctness gets reported as a number, and a fixture is added for every real case that surprised the product, so the suite gets harder over time rather than staying green by staying easy.

Plumbing that either works or doesn’t — a deploy, a redirect, a form post — needs no fixtures; that gets verified by running it end to end.

Step 6 — the lead loop: a landing page that fills your CRM

The landing page’s contact form must not depend on your machine — the org’s home is never exposed to the internet. So the loop is store at the edge, collect on a schedule:

  1. The form POSTs to a tiny Worker (the org builds it) that inserts each signup into a D1 table. Nothing touches your machine.

  2. On a schedule, the org checks for new signups — a ## Triggers entry like every: 4h seeds the goal; the agent handling it queries the table with its own token.

  3. Each signup becomes a CRM lead. The agent records it with one line in its task output:

    CONTACT[alice@example.com | name="Alice Smith" company=Acme]: signed up via the landing page, interested in the beta

    Contacts are gated by the crm capability — with the default ask, each entry waits for your approval in the Requests panel; grant allow to a growth role and leads flow in on their own. Re-recording the same email updates the stage (stage=active, stage=won) instead of duplicating.

  4. Each lead gets a welcome reply on the email channel — held for your approval if the channel is ask.

Prefer zero code for the form itself? Formspree (or any hosted form backend) works: point the form at it and you get email notifications with no Worker at all. The trade-off is that the loop then ends in your inbox — the self-built form keeps signups flowing into the org’s own CRM and reply queue, which is what makes the funnel autonomous. Formspree first, self-built when the org is ready to own it, is a perfectly good sequence.

When they reply: the inbox

A welcome email invites a response — and the org can hear it. An inbox trigger turns replies into work: an Email Worker on the org’s domain stores inbound mail at the edge (the build agent can deploy it), and the runtime polls it on a cadence, seeding each genuinely new message as a goal. The reply lands threaded to its CRM contact, the conversation continues on the email channel, and you only see what the channel’s ask gate holds for approval.

## Triggers

### Support inbox

- url: https://mail.example.dev/messages
- secret: mail-inbox-token
- allow: ["*"]
- owner: Support Lead

Read each new message; reply on the "email" channel, and update the sender's
CRM entry to match what they told us.

Conversational inbound is fenced fail-closed: only senders matching the allow list are ingested at all, and the message text reaches the agent as quoted data — never as instructions. The full reference lives in Triggers.

Step 7 — charging customers: wiring in payments

A product that can’t take money isn’t a business. Declare a payment provider once, with an org-level payments key — the runtime holds the merchant key so no agent ever does:

- payments: { "provider": "stripe", "secret": "stripe-secret-key", "mode": "test", "currency": "usd" }

Store the key once (quorum secret set stripe-secret-key). Declaring payments lights up two ways to get paid.

The default: self-serve billing, built into the product

For a SaaS, payments belong inside the product the org ships, not bolted on around it. Declaring payments tells the org’s builders to wire a pricing page, the provider’s hosted checkout and subscriptions, and a customer portal into the product itself — so customers subscribe and manage their plan on their own. The provider’s hosted checkout is a real, PCI-compliant page; no card form ever touches your machine.

One thing to hold in mind, because it shapes the whole design: the org is not reachable from the internet. Stripe can’t call it. So the subscription webhook is part of the product’s deployment — a small endpoint on the product’s own host (a Cloudflare Worker, say) that writes payment events into the product’s store — and the provider’s secret key lives in that deployed service (wrangler secret put), not on your machine. The org itself never receives a webhook.

You don’t set a return address. After checkout the payer has to land somewhere, and the obvious instinct is to pin that URL in QUORUM.md. Don’t — you’re writing that file before the org has chosen, priced, and registered a domain, so anything you write is a guess that resolves to nothing. Leave successUrl out and the payer is returned to the org’s own live production service, read from the URL on its SERVE[... env=production] registry entry: the real domain, learned the moment the org shipped it. Staging deploys are never used, and a neutral provider page covers the window before anything is live. Pin successUrl only when you already own the domain and want a specific page.

And what to charge

Left alone, an org prices off the nearest competitor’s tag — which quietly imports that competitor’s value proposition along with its number. The usual result is a thin tool wearing an incumbent’s price, and it doesn’t sell.

Declaring payments also tells the org to derive the price instead. For one named, realistic customer: how much does this save them per month, in hours or money, with a source for every figure? Charge a fraction of that — a tenth is a defensible starting point. If that fraction is too small to be a business, the honest answers are a lower price or a deeper product, never a bigger claim. Any free tier has to be genuinely useful on its own, so the paid tier earns its money on volume, depth, or automation rather than by locking away basic function.

The derivation goes in the org’s shared PRICING.md, alongside MARKET.md and ROADMAP.md — a price you can’t derive is one you can’t defend to a customer, and one you can’t revise when the evidence moves.

The option: org-initiated charges

For a one-off or a sales-led deal, an agent can bill a specific customer directly with one line in its task output:

CHARGE[alice@example.com | amount=49 currency=usd]: Pro plan — first month

Add an interval to bill on a repeat — the checkout becomes a recurring subscription instead of a one-off:

CHARGE[alice@example.com | amount=12 currency=usd interval=month]: Pro plan — monthly

The runtime raises the charge and gets back a hosted checkout link; because the charge is raised after the task finishes, the link comes back into the agent’s memory, and a later cycle delivers it with a SEND[...]. Money is the highest-stakes thing an org does, so charge defaults to ask — every charge waits for your approval, showing who, how much, and what for. Trust it with allow on a finance role when you’re ready:

### Head of Finance
- capabilities: { "charge": "allow" }   # bills without a per-charge sign-off

Knowing what you earned: revenue is a pull

Since nothing external can call the org, it can’t be told about a payment — it has to go and look. Revenue is a pull the org owns: a scheduled job (its own code and the granted key) queries the provider’s API — or the product’s own store — for payments since the last check, and books each one:

REVENUE[ch_3Mx… | amount=49 currency=usd customer=alice@example.com kind=subscription]: Pro plan

Each line lands on the org’s revenue ledger (logs/revenue/), the money-in counterpart to the cost ledger, and is idempotent by the provider’s charge id — so the pull can run as often as it likes and never double-counts. And the customer email reconciles that person onto the CRM: their contact is marked an active paying customer, stamped with the provider’s customer id, subscription, and last-payment date. When a subscription cancels, the same pull records the churn with CONTACT[<email> | stage=lost]: canceled — so the CRM always reflects who’s paying and who left.

You get this out of the box, and it’s automatic: with payments declared and the key stored, the runtime polls the configured provider itself (hourly), books each payment (gross, keyed by the provider’s id), and reconciles the payer onto the CRM — no trigger, no code. This built-in sync is the source of truth for that provider.

Because of that, don’t build a second pull of the same provider. A hand-rolled Stripe re-poll double-counts: if it keys a payment by a different id than the built-in sync used (a PaymentIntent id vs a charge id, say), the ledger can’t tell they’re the same money. The runtime carries known aliases (a charge’s payment_intent) to catch that automatically, but the rule stands — reserve the REVENUE[...] marker for revenue the runtime can’t see on its own: the product’s own store, an offline/manual payment, or a provider that isn’t the configured one. A useful trigger reviews what already came in, rather than re-polling:

### revenue-review

- every: 24h

The runtime already books Stripe automatically. Review what came in since
yesterday: welcome new paying customers, follow up on any churned subscription
(CONTACT[<email> | stage=lost]), and note the trend. Only book a REVENUE line for
money the runtime can't see — a manual payment or the product's own store.

Start everything in test mode — a test key (sk_test_…) rehearses checkout, approvals, and the revenue pull with no real money; swap in the live key when you’re ready.

Stripe today, provider-neutral by design. provider: "stripe" gives the richest API and hosted checkout. If you’d rather a Merchant of Record own global sales tax and VAT for you, the provider field is the only thing that changes — paddle and lemonsqueezy slot behind the same markers.

Step 8 — reading your servers: telemetry

An org that ships a product needs to know how it’s doing in production. One org-level telemetry key wires in the observability backend the org reads its own servers from — the mirror image of payments: the product emits telemetry outward, and the org pulls it back (nothing on the internet can reach the org to tell it).

- telemetry: { "provider": "cloudflare", "account": "<account-id>", "secret": "cloudflare-analytics-token", "window": "1h" }

Store a read-scoped token once (quorum secret set cloudflare-analytics-token). An agent asks for telemetry by putting a line in its task output:

LOGS[* | window=1h]: post-deploy health check

The runtime performs the pull, reading the token from the secrets store — so the agent never holds it. A digest — requests, errors, and error rate per service — lands in the agent’s memory, and a later cycle acts on it: open a task, page the owner, roll back. Name one service in the bracket, or use * for the whole account. Every pull is audited (telemetry.pull).

Cloudflare is the zero-new-vendor default: a product already on Workers reports invocation metrics automatically, so there’s nothing new to stand up. The pull returns the metrics view (traffic and errors per Worker); raw log lines ride Logpush to a destination and are a config swap away, as are the grafana / axiom backends behind the same marker. Because the org should be able to read what it ships, declaring telemetry also steers code-capable agents to instrument the product — structured logs plus request/error metrics, ideally via OpenTelemetry so the backend stays swappable.

Gated by the logs capability, which defaults to allow: a pull is read-only — no state change, no money. (This is the org watching the servers it ships — separate from Quorum’s own anonymous usage analytics.)

Step 9 — reading your errors: Sentry

Telemetry tells the org how many errors; error tracking tells it which ones. One org-level errors key wires in a Sentry account the org reads its own unresolved issues from — the same shape as telemetry, but for grouped exceptions with stack traces, event and user counts, and a permalink straight to the issue.

- errors: { "provider": "sentry", "org": "<org-slug>", "project": "api", "secret": "sentry-token", "window": "24h" }

Store a read-scoped Sentry auth token once (quorum secret set sentry-token). An agent asks for issues by putting a line in its task output:

ERRORS[api | window=24h]: post-deploy check — anything new spiking?

The runtime performs the pull, reading the token from the secrets store — so the agent never holds it. A digest — the worst unresolved issues, each with its event and user counts and a link — lands in the agent’s memory, and a later cycle acts on it: open a fix task, page the owner, roll back the deploy that introduced it. Name one project in the bracket, or use * for every project the token can see. Every pull is audited (errors.pull).

Because issues, like telemetry, are emitted outward by the product the org ships, declaring errors also steers code-capable agents to add the Sentry SDK to what they build — initialized with the client DSN (stored as the sentry-dsn secret) and tagged with a release per deploy, so each issue points back at the build that introduced it.

Gated by the errors capability, which defaults to allow: a pull is read-only — no state change, no money.

Where there’s no API: the browser

Everything above runs on APIs and CLIs. But part of acting in the world has neither: signup flows, supplier portals, dashboards behind a login, forms. One provider key gives the org a real browser for exactly that work:

- defaults: { "provider": { "type": "claude", "browser": true } }

A granted agent can navigate, click, type, fill forms, read pages, and take screenshots — headless by default, in a persistent per-agent profile under the org home, so a login it completes today is still there next week and your own browser is never touched. It’s gated per agent as the browser capability, and agents are taught the ground rules in-prompt: when a site needs an account, a credential, or a human-only step (CAPTCHA, 2FA), they raise a REQUEST instead of improvising — and you can flip "headless": false to finish a login by hand in the window the agent opened. Configuration and fencing (allowedOrigins / blockedOrigins) are covered in the brain.

Start with one CEO and a mission

None of the above needs a pre-staffed org. The intended shape is a solo CEO and a mission — the org does the rest:

# Copper Finch

- budget: 50
- defaults: { "capabilities": { "secret:github-token": "allow", "secret:cloudflare-api-token": "allow", "crm": "allow", "channel:email": "ask" } }
- channels: { "email": { "type": "resend", "from": "hello@copperfinch.example", "secret": "resend-api-key" } }

## Mission

Conceive, build, and launch one small SaaS product end to end — landing page,
signups into the CRM, a welcome reply to each, PostHog on the product. Hire
the team you need. Ask the founder for any credential you lack.

## Agents

### CEO

You are the CEO and, at the start, the only employee…

Boot it with no setup beyond the mission — no tokens seeded. The CEO hires a builder when building starts; the builder hits the need for a GitHub token and asks you (REQUEST[credentials]); you answer with SECRET[github-token]: … and the blocked work resumes on its own. Channels do the same: a send on a channel whose secret was never provided files a single request telling you exactly what to paste. Because the grants live in defaults, every agent the org hires later inherits them — the org grows, the rules hold.

What you’ll see while it runs

  • Live Services — every registered service, its URL, owner, and status.
  • Requests — credential asks, held sends, CRM entries, and charges awaiting your sign-off, each one answer away.
  • CRM — the lead list filling up as the funnel runs.
  • Audit feedchannel.send, service.register, crm.contact, crm.billing, payment.charge, revenue.book, telemetry.pull, errors.pull, and secret.set events as they happen (names, amounts, and messages — never token or key values).

The result is an org that can build a product, put it on the internet, instrument it, tell people about it, and turn the people who answer into a pipeline — while you hold the keys, set the approval lines, and read the full trail.