drums
Talk to us
Documentation

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.

Status. Drums is in active development with design partners. The reproduce loop — detect, attribute, rebuild, replay — ships today; repair and release are rolling out to design partners first. Nothing on this page is a claim of general availability.
Get started

Install

One binary. macOS today.

terminal
$ curl -fsSL drums.sh/install | sh
installed drums → /usr/local/bin/drums

What 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.

Installing changes nothing. Drums starts in observe-only and says so — see the quickstart below. The first drums watch prints a one-time notice about the anonymous install heartbeat before it sends anything; DRUMS_TELEMETRY=off sends nothing at all.
Get started

Quickstart

Two commands. The second one ends by proving a real event reached the record.

1

Install

terminal
$ curl -fsSL drums.sh/install | sh
2

Set 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.

~/your-repo
$ 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 watch
3

Watch

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.

~/your-repo
$ drums watch --plain
watching ~/checkout-api · ingest :7787 · repairs propose only — nothing ships without `drums ship`.
Get started

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.

planned interface — `drums boundaries` is not in the CLI today
$ 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:

Customer dataBillingPermissionsInfrastructure

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.

How it works

The loop

One failure, six stages. For safe, reversible failure classes this completes without waiting for an engineer.

StageWhat happens
detectA failure is recognised from your telemetry, with the evidence that says so.
attributeThe failing stack trace is matched against the deploy that shipped before it and the files it changed.
reproduceA container is built at that exact revision and the captured request is replayed. If it does not fail, Drums says so and stops.
repairYour own coding agent writes the fix, given the reproduction and the acceptance criteria.
canaryThe repair goes to a small share of traffic and is compared against the baseline.
promote / revertThe 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.

verifiedDrums ran something and watched it pass.
observedTelemetry says so.
inferredA model concluded it.
approvedAn identified human signed off.
unresolvedDrums does not know, and says so.
A repair ships on its own only when every claim supporting it is verified or observed. Anything resting on an inferred claim goes to a person. Attribution is inferred — which is why reproduction exists.

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.

observeDrums watches and says what it would have done. Changes nothing.
shadowRepairs are generated and verified in isolation, never shipped. You compare them against what your team actually did.
proposeA verified repair waits for a person. The default, and a success state.
act aloneDrums 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.

How it works

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.

planned interface — not yet shipped
$ 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.

Open the routines guide

How it works

Stop and undo

One control, reachable from everywhere, that a frightened engineer can find in three seconds.

There is no single 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.
~/your-repo
$ 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.

How it works

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.

~/your-repo
$ 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 class

drums 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.

~/your-repo
$ 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 days

drums 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.

~/your-repo
$ 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 canary
Ecosystem

Integrations

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.

Claude CodeCodexOpenCode — nextMCP server

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.

OpenTelemetryPostHogSentry

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.

SlackLinear

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

EventSlackLinear
failure_reproducedThe channelComments on the issue that reported it — or opens one, if you named a team
approval_neededA direct message, carrying the approval linkComments on the known issue
repair_shippedThe channelComments on the known issue
nothing_establishedNothingNothing
digestThe channelNothing

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.

Choosing which Slack channel is not in the console yet In design. Until it is, a connected workspace receives the direct messages and nothing else — the channel column above is routing that already exists in code, not a picker you can fill in today. The console says so on the row rather than reporting a workspace as connected and leaving you to work out why nothing arrives.

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.

Ecosystem

Deployment modes

One product, three postures. The engine is identical; only where the work runs changes.

ModeWhere work runsWhat leaves
localYour own machineNothing about your code. The anonymous install heartbeat — eight fields, none of them yours — unless you turn it off
teamDrums cloud, with reproductions in your CIThe 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.
privateYour VPC or on-premisesNothing — 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.

Ecosystem

Anonymous usage telemetry

One heartbeat that says an install is alive. Eight fields, none of them yours, and one command that turns it off.

Not the same thing as Telemetry in. That is your monitoring data flowing into Drums so it can detect failures. This is a small usage signal flowing out, so we know how many installs are actually running.

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.

~/your-repo
$ 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

FieldWhat it is
install_id128 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_versionThe version of the binary.
os, archmacos/linux and aarch64/x86_64 — compile-time constants of the binary, not an inspection of your machine.
failures_detectedA number. Never which, never where.
repairs_attemptedA number.
repairs_verifiedA number.
repairs_shippedA 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:

Repository namesFile pathsBranch namesCommit shasError messagesStack tracesRequest bodiesURLsAgent outputFailure-class names

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.

terminal
$ export DRUMS_TELEMETRY=off
~/your-repo/.drums/config.toml
telemetry = "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.

Ecosystem

CLI reference

The whole product, scriptable.

drums initThe 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 --wireJust the reporting snippet, for re-running one part of setup.
drums init --verifyJust 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 openOpen 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 loginConnect 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 logoutForget 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 whoamiWhich account this machine is signed in to. Prints the account and the console, never the token.
drums watchThe 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.
dashboardServed 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 authorityWhat 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 activateStore 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 digestThe morning message: what broke, what Drums did, what still needs you. Read-only, built from the record. --to slack posts it.
drums repairOne 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 shipShip a proposed repair, then check that the original failure is gone.
drums revertRoll a shipped repair back to the deploy that preceded it.
drums recordThe append-only record, read back as history — newest first, with the provenance chip on every claim.
drums whyA file's production history: what broke there, when, and whether it was proven.
drums daemonRun the loop as a background service, so it keeps watching when the terminal closes.
drums routineIn design Describe scheduled maintenance in a sentence — not in the CLI yet.
ctrl-c / SIGTERMStops drums watch immediately: worktrees are cleaned up and in-flight agent process groups are killed.
DRUMS_TELEMETRY=offSends 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.