Documentation
Drums is software that maintains itself. It carries a change from the moment it is written until the software is proven healthy again: it detects a failure, links it to the deploy that caused it, reproduces it, repairs it with the coding agent you already use, and verifies the fix in production.
One binary, one command, and Drums starts by watching.
Detect, attribute, reproduce, repair, canary, promote or revert.
Scheduled maintenance described in plain language.
Install
One binary. macOS today.
$ curl -fsSL drums.sh/install | sh
installed drums → /usr/local/bin/drumsWhat it assumes
- A git repository. Drums reads it to work out what your project is and how it ships.
- git on your PATH. Drums itself is a single binary — it does not need Node, Python, or a package manager.
- Whatever your project already uses to run tests and to deploy. Drums drives those; it does not replace them.
macOS only for now, on Apple Silicon and Intel. Linux and Windows are not published yet — curl -fsSL drums.sh/install | sh refuses them by name rather than installing something that will not run.
drums watch prints a one-time notice about the anonymous install heartbeat
before it sends anything; DRUMS_TELEMETRY=off sends nothing at all.Quickstart
Two commands. The second one ends by proving a real event reached the record.
Install
$ curl -fsSL drums.sh/install | shSet up
drums init detects the framework, writes the config, installs the reporting snippet, and then proves the path works by sending a real event and reading it back out of the record. It prints the plan and waits before writing anything.
The last thing it prints is the two things it cannot do for you — because an install that looks finished but reports nothing is worse than one that says what is missing.
$ drums init
drums 0.1.0 · ~/checkout-api
git repository
express — package.json declares `express` as a dependency [observed]
entrypoint server.js
claude — the agent that will write repairs
This will:
+ .drums/config.toml what `drums daemon start` reads
+ drums-report.js the reporting middleware
~ server.js 2 lines appended — mounts the reporter
Proceed? [Y/n]
Verifying the path from your app to the record…
a test event travelled http://127.0.0.1:63921/v1/events → the
record at .drums/record.jsonl [verified]
Set up. Start the loop:
drums watchWatch
Drums detects failures, attributes them to a deploy, reproduces them, and repairs them. It proposes; nothing ships without drums ship.
On a real terminal this is a live view — one card per failure, updated in place, with a provenance chip on every claim. Piped, scripted, run under CI, or with --plain / DRUMS_PLAIN=1, it is the plain line-by-line narration.
$ drums watch --plain
watching ~/checkout-api · ingest :7787 · repairs propose only — nothing ships without `drums ship`.BoundariesIn design
The line between safe and risky is the most important setting in the product. It is also the one nobody will fill in a form for, so Drums proposes it and takes corrections in plain language.
Not shipped yet: there is no drums boundaries command today, and the session below is the intended shape rather than a transcript. What DOES exist is the
autonomy ladder — authority earned per failure class, granted by a human with drums authority promote, and taken away automatically on one rollback.
$ drums boundaries
Based on your reverts, code owners, and migration
history, here is what I would treat as risky:
always ask a person
billing/** owned by @rishi, 2 reverts
lib/auth/** touches sessions
prisma/migrations/** not reversible
infra/** no test coverage
safe to repair alone
app/** components/** lib/format/**
reversible, covered by tests, no prior incidents
Sound right?
$ drums boundaries "also ask before touching anything under app/checkout"
✓ checkout moved to always ask.Always ask, by default
Four classes require a named human no matter what the track record says, and Drums applies them before it has read anything:
Everything else starts in observe-only and moves only as far as you allow. The structured version of
your boundaries exists underneath and is readable with drums boundaries --show (in design — not in the CLI yet) — it is never
the thing you have to write.
The loop
One failure, six stages. For safe, reversible failure classes this completes without waiting for an engineer.
| Stage | What happens |
|---|---|
| detect | A failure is recognised from your telemetry, with the evidence that says so. |
| attribute | The failing stack trace is matched against the deploy that shipped before it and the files it changed. |
| reproduce | A container is built at that exact revision and the captured request is replayed. If it does not fail, Drums says so and stops. |
| repair | Your own coding agent writes the fix, given the reproduction and the acceptance criteria. |
| canary | The repair goes to a small share of traffic and is compared against the baseline. |
| promote / revert | The rollout completes, or it is rolled back. Either way the record says what happened. |
Provenance: how Drums says what it knows
Every claim Drums makes carries how it knows. These five states appear in the CLI, in notifications, in the record, and in the audit export.
| verified | Drums ran something and watched it pass. |
| observed | Telemetry says so. |
| inferred | A model concluded it. |
| approved | An identified human signed off. |
| unresolved | Drums does not know, and says so. |
Autonomy is earned per failure class
Each class of failure — identified by its service and error name — climbs the ladder independently, on its own track record. Drums proposes the promotion and shows the evidence; you decide. Nothing promotes itself.
| observe | Drums watches and says what it would have done. Changes nothing. |
| shadow | Repairs are generated and verified in isolation, never shipped. You compare them against what your team actually did. |
| propose | A verified repair waits for a person. The default, and a success state. |
| act alone | Drums ships, verifies, and reports. You read about it afterwards. |
--repair auto is consent, not a grant: it raises the ceiling to act-alone, and the
class still has to have earned it. Five consecutive repairs that shipped and stayed shipped earns a
promotion proposal; drums authority promote <class> applies it. That command
refuses a class that hasn't earned it — there is deliberately no override, because a flag that granted
autonomy would make the ladder decorative.
Act-alone is the paid capability
The first three rungs are free, forever, on your own machine: detect, attribute, reproduce, repair,
verify, propose. Everything up to and including a verified repair waiting for you at
drums ship needs no key and no account. The one thing that is paid is the last rung —
letting Drums ship a failure class without waiting for you — and
drums authority promote is the only command that asks for a license.
It asks in that order on purpose. A class that hasn't earned act-alone is refused on the evidence, and money is never mentioned; the license question only comes up once your own production record shows five consecutive clean ships. Nothing else in the product reads the license, so an expired or absent key never stops a repair being produced, verified, or proposed — it only withholds the grant.
Verification is offline. A key is an Ed25519 signature checked against a public key
compiled into the binary: no license server, no activation call, no heartbeat. A product that stops
working when our servers do is not one you should deploy in front of production, and a binary that phones
home with a customer identifier would contradict everything on this page about the boundary. The
trade-off is stated plainly: an offline key cannot be revoked mid-term, which is why keys carry an expiry.
drums activate <key> stores one; a key that is malformed or expired grants nothing and
says exactly why.
Demotion is automatic, immediate, and loud. One rollback or failed ship drops the class straight back to propose the moment it is recorded, and the terminal says so. Earning authority takes a human and a streak; losing it takes one mistake and no meeting.
The whole ladder is folded from the append-only record, so a restart rebuilds it and there is no separate state file that could disagree with your audit trail.
When the ladder says ask
A repair that needs a human does not sit in a terminal waiting to be noticed. The job is created and held — nothing is dispatched into your CI and nothing is pushed — and a link is opened that can go into a Linear comment, a Slack digest, or an email. Opening it shows the repository, the failing signature, the commit, and why you are being asked. Two buttons: approve, or reject.
The link is an address, not a credential. Holding it proves nothing: answering requires being signed in
to app.drums.sh with at least the approver role on the account that owns the repair. This is
not ceremony — links travel. A Linear comment is readable by a whole workspace and Slack forwards, so a link
that authorized on its own would be an approval anybody who scrolled up could replay.
The decision is also never a GET. Slack, Linear and most mail clients fetch a URL the moment
it is posted to render a preview card; if following the link approved anything, posting it would approve the
repair before a person ever read it. Rendering is a page, deciding is a form.
One approval per repair, answered once, recorded with the address of the person who answered and the time they did it. A rejection cancels the repair outright. Approvals expire after seven days, so a link found in a channel a month later does nothing.
RoutinesIn design
Maintenance that isn’t a failure — scheduled work, described the way you would describe it to a colleague.
Not shipped yet: there is no drums routine command in the CLI today.
$ drums routine "every monday, upgrade any
dependency with a patch release, run the full
suite, and open one PR with everything that passed"This is the intended shape, not a transcript: routines would run under the same boundaries as everything else, and one that touches an always-ask path would propose rather than act. See the routines guide for the full set of recipes being designed.
Dependency upgrades
Mondays 06:00Patch-release bumps run against your real test suite, landing as one pull request containing everything that passed.
Flaky test hunt
NightlyReruns the suite overnight, finds tests that fail without a code change, and proposes quarantining them with the evidence attached.
Dead feature flags
1st of monthFinds flags that have been at 100% or 0% long enough to be decided, and proposes removing the dead branch.
Audit export
1st of monthEvery automated change in the period — its chain, its approvals, its reversions — collected into one signed document.
Stop and undo
One control, reachable from everywhere, that a frightened engineer can find in three seconds.
drums stop or drums undo command yet
In design. What stops Drums today: ctrl-c or SIGTERM on drums watch tears the
watch down immediately — its worktrees are cleaned up and any in-flight agent process groups are killed.$ drums watch
watching 3 environments · ctrl-c to detach
^C
stopping · cleaning up worktrees · killing in-flight agent process groups
stopped.Reversing a shipped repair with one command — and a bulk drums undo --since — is
In design. Repair and release are themselves Rolling out to design partners; see the
status note in the introduction.
Inspect
Git is the record. Repairs land as real commits and the evidence chain attaches as a git note, so it travels with the repository and survives Drums.
$ git log --oneline -1
a91f22d repair: guard optional promo field
$ git notes --ref=drums show a91f22d
failure POST /api/checkout · 41 errors
caused-by 8f32a1 "add promo code field"
reproduced container 8f32a1 · exit 1 · TypeError
verified 14 specs · 6 endpoints · original 200
canary 10% · 90s · 3.1% → 0.0%
authority act-alone · null-reference classdrums why
Blame, except it knows about production. Mined straight from the record — per-file failure history, newest first, every occurrence carrying its own provenance chip. A line or column is accepted and noted, but matching stays file-level for now: history isn't split out per line yet.
$ drums why lib/cart/total.ts:41
matching is per-file — line 41 is not tracked separately.
2025-07-20 08:32 UTC · TypeError in shop (POST /api/checkout) [observed]
2024-01-01 00:00 UTC · TypeError in shop (POST /api/checkout) [observed]
this file has failed in production twice in 570 daysdrums ps In design
A daemon doing invisible work is why teams refuse production access. drums ps will show what
is running right now and the authority each task is operating under.
$ drums ps
TASK STAGE AUTHORITY ELAPSED
export-pdf reproducing propose-only 1m 12s
dep-upgrade verifying act-alone 6m 40s
2 running · 0 waiting on approval · 0 in canaryIntegrations
Drums is not a coding agent, not a monitoring product, and not a deployment platform. It drives the ones you already run.
Coding agents
Claude Code and Codex write the repair today; OpenCode is next. Drums decides what needs repairing, whether the repair worked, and whether it ships — being agent-neutral is worth more than owning that step. Drums also ships an MCP server, so an agent can call it mid-session: reproduce this, verify this patch, tell me what broke last time someone touched this file.
Telemetry in
Failures are detected from the signals you already collect. Drums consumes telemetry; it does not display it, and it has no graphs, alert rules, or on-call schedule of its own.
Deploys out
Canary and promotion happen through your existing deployment platform. The moment Drums requires its own deploy target, the install stops being five minutes.
Slack and Linear
Where Drums says what it found and asks what it may do. Both are optional — the record is in the
console and in git log either way — and neither can act on your behalf. Connecting Slack does
not give anyone in Slack the power to approve anything.
Connect them in the console under Settings → Slack and Linear. Press Connect, approve the screen on Slack's or Linear's side, and you land back on the same page with the workspace named. Approver or owner, the same bar as answering an approval — deciding where a question is delivered is part of answering it. Disconnect sits in the same row, and it tells Slack to revoke the bot token as well as forgetting it here.
Drums asks Slack for three scopes and nothing else: chat:write to post,
im:write to open a direct message, and channels:read so the console can show you
#alerts instead of C0123456789. It does not ask to read your conversations or
your member directory. On Linear it writes as the app rather than as whoever clicked Connect, so machine
work never lands under a person's name.
What gets announced where
| Event | Slack | Linear |
|---|---|---|
| failure_reproduced | The channel | Comments on the issue that reported it — or opens one, if you named a team |
| approval_needed | A direct message, carrying the approval link | Comments on the known issue |
| repair_shipped | The channel | Comments on the known issue |
| nothing_established | Nothing | Nothing |
| digest | The channel | Nothing |
An approval that cannot be addressed to a person is dropped, not posted to a channel. That is deliberate. An approval is a question for someone: put it in a channel and it becomes everybody's problem, which is the reliable way to make it nobody's — the repair sits there while four people each assume one of the others is looking at it. So when Drums cannot work out who to write to, it records that and stops. A fallback to the channel would silently undo the one decision this table exists to make, on the event where being wrong costs the most.
An approval arrives as a plain link, never a button, and following it approves nothing. Mail and chat clients fetch every URL in a message to build a preview card, so anything a GET request can do is something a preview card can do by accident. The link is an address, not a credential: the page it opens renders nothing at all until the reader is signed in and holds the approver role on that account, and the decision is a separate press from that page. It is also why there is no Slack button and no Slack interactivity endpoint — a Slack user id is not authority, and Drums has no way to turn one into a Drums identity that could stand behind a decision.
Nothing established is routed nowhere on purpose. It is a real outcome and it belongs in the record, but a tool that pings on every non-result is muted within a week, and a muted tool cannot tell you the one thing that mattered. The count still surfaces in the digest, where a number is the right shape for it.
Where it shows up
A CLI. A git remote — git push drums verifies the change and forwards to origin if it holds,
which is the smallest possible install for a team that will not grant production access on day one. A tool
inside your agent. Annotations in the editor gutter on lines with failure history. And git log
as the audit trail.
Deployment modes
One product, three postures. The engine is identical; only where the work runs changes.
| Mode | Where work runs | What leaves |
|---|---|---|
| local | Your own machine | Nothing about your code. The anonymous install heartbeat — eight fields, none of them yours — unless you turn it off |
| team | Drums cloud, with reproductions in your CI | The record and its metadata. A CI runner has no coding-agent CLI logged in, so the repair step reads a credential from your repository secrets — a claude setup-token against the subscription you already pay for, or an API key. Drums cannot read it: the GitHub App requests no secrets permission. |
| private | Your VPC or on-premises | Nothing — Drums ships the control plane as an image. Same heartbeat, same opt-out |
Reproduction is designed to run inside the customer boundary: the container, the replayed request, and the failing document stay put. Drums moves the record, not the material. Enterprise accounts can point Drums at their own Anthropic, OpenAI, or Bedrock agreement so no new AI subprocessor is introduced.
What local mode costs
Nothing, on as many repositories as you like, with no time limit and no account. Local mode runs the whole loop — detect, attribute, reproduce, repair, verify, propose — on your machine, through your own coding-agent CLI and your own model key. We never resell models and never ask for your API key.
The paid boundary is act-alone authority: letting Drums ship a failure class without waiting for you. That is the only thing a license unlocks, and the ladder has to have earned it first — which means you can evaluate Drums against your own incidents indefinitely before the question of paying ever comes up. We publish no price list at this stage; talk to us and we'll size it to what you're running.
Anonymous usage telemetry
One heartbeat that says an install is alive. Eight fields, none of them yours, and one command that turns it off.
The first time drums watch runs on a machine where it would send anything, it prints this
in full — and prints it before the first heartbeat leaves. Once per machine, not once per run.
$ drums watch
────────────────────────────────────────────────────────────
Anonymous usage telemetry is ON. You are seeing this once, on this machine.
Sent when `drums watch` starts, and every 6 hours while it runs:
· a random install id — generated here, stored at
~/.drums/install-id
(delete that file and you become a new install)
· the drums version, and this machine's OS and CPU architecture
· four running totals: failures detected, repairs attempted,
repairs verified, repairs shipped
NEVER sent — absent from the payload, not redacted out of it:
repository names · file paths · branch names · commit shas ·
error messages · stack traces · request bodies · URLs ·
agent output · failure-class names · anything derived from your code
To send nothing at all, either:
export DRUMS_TELEMETRY=off
or add this line to ~/your-repo/.drums/config.toml:
telemetry = "off"
────────────────────────────────────────────────────────────The whole payload
| Field | What it is |
|---|---|
install_id | 128 random bits, generated once and stored in plain text at ~/.drums/install-id. Not derived from your hostname, MAC address, username, or repository path — nothing about the machine can reproduce it, two installs on one machine get different ids, and deleting the file makes you a new install. |
drums_version | The version of the binary. |
os, arch | macos/linux and aarch64/x86_64 — compile-time constants of the binary, not an inspection of your machine. |
failures_detected | A number. Never which, never where. |
repairs_attempted | A number. |
repairs_verified | A number. |
repairs_shipped | A number. |
Sent when drums watch starts and every six hours while it runs. It is a background send
with a five-second timeout: it cannot delay or fail the loop, and a telemetry failure is never narrated
as a product failure. drumsd, the detached daemon, sends nothing at all today.
What is never sent
Absent from the message, not redacted out of it — there is nowhere in it to put any of this:
That list is enforced, not promised. One test in the engine pins the payload's field names to a literal list, so a ninth field cannot appear without someone deliberately editing it; a second feeds real repository paths, branch names, stack traces, error messages and request bodies through the counters and asserts none of them show up in the bytes on the wire.
Turning it off
Either one. The environment variable wins over the file.
$ export DRUMS_TELEMETRY=offtelemetry = "off"The opt-out is enforced where the message is built, not merely where the notice is printed: an
opted-out install generates no install id, starts no background task, and opens no connection. A value
that is neither on nor off — or a .drums/config.toml that cannot be
parsed at all — resolves to off, with a line on stderr saying so. An opt-out we could not
read is never treated as consent.
CLI reference
The whole product, scriptable.
| drums init | The whole setup, one command: detect the framework, write the config, install the reporting snippet, and prove the path from your app to the record by sending a real event and reading it back. Prints the plan and waits before writing anything; --yes skips the prompt. Re-running is a no-op. |
| drums init --wire | Just the reporting snippet, for re-running one part of setup. |
| drums init --verify | Just the round trip, against an already-running Drums. Earns a verified claim about your install, or says unresolved and exits non-zero so a setup script stops. |
| drums open | Open a completed repair in your editor: a fresh worktree at the repair's commit, so your working tree is never touched. --close removes it. |
| drums login | Connect this machine to a Drums account. Prints a URL and a short code; you approve it in a browser, and no token is ever typed or pasted. --no-browser prints the URL instead of opening one. The credential lands in ~/.drums/credentials.toml, mode 0600, outside any repository — never in .drums/, which some teams commit. |
| drums logout | Forget this machine's credential. Local only: the token stays valid until you revoke it in the console, which is what to do if the machine itself is no longer trusted. |
| drums whoami | Which account this machine is signed in to. Prints the account and the console, never the token. |
| drums watch | The loop: detect, attribute, reproduce, repair, verify, propose. Live TUI on a real terminal; --plain (or DRUMS_PLAIN=1) forces the plain line-by-line narration. --propose-pr opens a pull request carrying the evidence. --repair-reported also attempts human-reported issues from Linear or Agentation — propose-only, and whether it resolves the report stays unresolved. |
| dashboard | Served by the daemon itself at http://127.0.0.1:<ingest-port> while drums watch runs. Same origin as the API it reads, so there is nothing to deploy and no port to configure. |
| drums authority | What Drums may do on its own, per failure class. list shows each class's rung, streak and any promotion it has earned the right to propose; promote/demote are the only ways a rung ever changes by hand. Nothing promotes itself. promote is the one command in the product that needs a license — see act-alone is the paid capability. |
| drums activate | Store a license key. Verified offline against a public key compiled into the binary — no license server is contacted, now or later. A malformed or expired key grants nothing, says exactly why, and changes nothing else: the free loop up to a verified, proposed repair never reads it. |
| drums digest | The morning message: what broke, what Drums did, what still needs you. Read-only, built from the record. --to slack posts it. |
| drums repair | One repair, from an instruction file to an outcome file. This is what runs in CI — it reproduces at the attributed revision, repairs, verifies, and writes what it found. It never ships, never opens a pull request, and needs no network: the outcome file is the whole interface. Always writes one, including when nothing was established, because a silent run is indistinguishable from one that never happened. |
| drums ship | Ship a proposed repair, then check that the original failure is gone. |
| drums revert | Roll a shipped repair back to the deploy that preceded it. |
| drums record | The append-only record, read back as history — newest first, with the provenance chip on every claim. |
| drums why | A file's production history: what broke there, when, and whether it was proven. |
| drums daemon | Run the loop as a background service, so it keeps watching when the terminal closes. |
| drums routine | In design Describe scheduled maintenance in a sentence — not in the CLI yet. |
| ctrl-c / SIGTERM | Stops drums watch immediately: worktrees are cleaned up and in-flight agent process groups are killed. |
| DRUMS_TELEMETRY=off | Sends no anonymous usage telemetry at all — no install id is generated, no background task starts, no connection is opened. telemetry = "off" in .drums/config.toml does the same; the variable wins over the file. |