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:
- The secrets store — you hand the org a credential once; it reaches only the agents you grant it to.
- 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.
- Channels — named outbound routes (Slack, email, any webhook) that the org sends through for the agents, with per-agent allow / ask / deny.
- 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.
- 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.
- 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 it — github-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:
- The coordinator plans the goal and assigns the build to the Builder.
- The Builder writes the site in its workspace, creates a repository and
pushes (
gh repo create,git push— authenticated byGITHUB_TOKEN). - It publishes with
wrangler pages deploy(authenticated byCLOUDFLARE_API_TOKEN) and gets a live URL. - 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
- Create a Slack app in your workspace, give its bot the
chat:writescope, and install it. Copy the bot token (xoxb-…). - Invite the bot to the channel you want the org in:
/invite @yourbot. - 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
webhookchannel pointed at a hook that sends through that mailbox. - Every send is yours to sign. Set the outreach channel to
askand 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
webhookchannel — 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:
-
The form POSTs to a tiny Worker (the org builds it) that inserts each signup into a D1 table. Nothing touches your machine.
-
On a schedule, the org checks for new signups — a
## Triggersentry likeevery: 4hseeds the goal; the agent handling it queries the table with its own token. -
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 betaContacts are gated by the
crmcapability — with the defaultask, each entry waits for your approval in the Requests panel; grantallowto 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. -
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 feed —
channel.send,service.register,crm.contact,crm.billing,payment.charge,revenue.book,telemetry.pull,errors.pull, andsecret.setevents 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.