drumbeat-operations

$npx mdskill add microsoft/amplifier-drumbeat/drumbeat-operations

drumbeat is an automation engine for long-running agent sessions. It runs markdown automations on a schedule, in pinned agent sessions, and emits a durable delivery-intent record. This skill is the how-to for standing one up and keeping it healthy. For the deeper "why", link out to the canonical docs (cited inline) — do not assume this repo's files are on disk, they usually are not when this skill is consumed on its own.

SKILL.md

.github/skills/drumbeat-operationsView on GitHub ↗
---
name: drumbeat-operations
description: >-
  Install, run, supervise, and troubleshoot the drumbeat automation engine and
  its `drumbeat` CLI. Covers the single `uv tool install`, workspace `init`, the
  `doctor` staleness check, running under a supervisor (`service install|status|
  uninstall` — systemd --user on Linux, launchd on macOS), pinned-session
  inspection and rotation (`sessions`, `rotate-session`), safe restart via
  `drain`, the invalid-run `sweep`, the engine API key, and fixes for wedged or
  orphaned session pins, PATH gaps, and a missing provider key. Use when
  installing, starting, supervising, health-checking, restarting, or debugging a
  drumbeat engine — or whenever a `drumbeat <command>` is involved.
metadata:
  project: drumbeat
  canonical_docs: https://github.com/microsoft/amplifier-drumbeat
compatibility: Requires uv and git; Python 3.13 is provisioned by uv. An LLM provider key in the engine's environment.
---

# Operating the drumbeat engine

drumbeat is an automation engine for long-running agent sessions. It runs
markdown automations on a schedule, in pinned agent sessions, and emits a
durable delivery-intent record. This skill is the how-to for standing one up
and keeping it healthy. For the deeper "why", link out to the canonical docs
(cited inline) — do not assume this repo's files are on disk, they usually are
not when this skill is consumed on its own.

**One rule underlies everything here:** a run that looks successful but did
nothing is the enemy. Every command below fails loud with a remedy rather than
degrading silently. When something is off, read the actual message — it names
the fix.

## The command surface

`drumbeat` is one binary with subcommands. **`--workspace <dir>` is required
with no default on every command except `init`** (which *creates* a workspace).
Every `--workspace` command also accepts `--data-dir` (default
`<workspace>/runs`); if you split state from policy, pass it *consistently* to
every command or they read different directories.

| Command | Purpose |
|---|---|
| `drumbeat init <dir> [--force]` | Scaffold a workspace (dirs + default files) |
| `drumbeat serve --workspace <dir> [--port N] [--host 127.0.0.1]` | Run the engine (scheduler + loopback HTTP API), foreground |
| `drumbeat doctor --workspace <dir>` | Is the running engine executing the code on disk? Health snapshot |
| `drumbeat sessions --workspace <dir>` | Which conversation each automation resumes; orphan pins |
| `drumbeat session-health --workspace <dir>` | Per-automation consecutive failures, ceiling/drift detail |
| `drumbeat rotate-session <slug> --workspace <dir> --reason R` | Abandon one automation's pinned conversation |
| `drumbeat drain --workspace <dir> --reason R [--wait\|--status\|--clear] [--pid N]` | Stop starting runs; verify it is safe to kill |
| `drumbeat sweep --workspace <dir> [--since ISO8601Z]` | Find invalid runs (runs with no delivery intent) |
| `drumbeat api-key --workspace <dir> [--show]` | This instance's engine API key |
| `drumbeat service install\|status\|uninstall …` | Run the engine under systemd --user / launchd |

## 1 · Install (one command)

The engine installs from a git URL with [uv](https://docs.astral.sh/uv/), which
also provisions Python 3.13. `amplifier-agent` — which ships the engine library
every turn imports — is a dependency, so this single install co-installs it into
the same tool venv. It is **not on PyPI**; there is nothing separate to install
and nothing to put on PATH.

```bash
uv tool install git+https://github.com/microsoft/amplifier-drumbeat
drumbeat --help
```

`uv tool upgrade drumbeat` takes drumbeat and its pinned agent library in one
step. To install the library on its own (a dev checkout, or a venv whose
dependencies were never installed), the full git URL is the mechanism — a bare
`uv tool install amplifier-agent` will not resolve:

```bash
uv tool install "amplifier-agent @ git+https://github.com/microsoft/amplifier-agent@v1#subdirectory=packages/python"
```

A running engine keeps executing the code it started from until restarted:
reinstall or upgrade under a live engine and `drumbeat doctor` reports `STALE`.

## 2 · Provide a provider key (verify it by hand once)

Export the key in the environment the engine starts in:

```bash
export ANTHROPIC_API_KEY=sk-ant-...      # or your provider's equivalent
```

**The failure this prevents:** a run that looks successful while the engine had
no brain. It cannot happen quietly here — a missing credential fails the turn
with the library's own typed error, reported verbatim with its remedy, and a
reply that is itself a provider-unavailability statement fails the run too. Both
land as `failed: true` in `result.json` and one line in `failures.log`.

Prove the key end to end once, the way the engine will: `drumbeat serve`, trigger
one run (§3), and read that run's `result.json`. **Under a supervisor the key
must be in the *unit's* environment** (an `EnvironmentFile=`), not just your
shell's.

## 3 · Scaffold a workspace and run it

```bash
drumbeat init ~/myspace                       # dirs + default files + one example
drumbeat serve --workspace ~/myspace --port 9100
```

`init` refuses rather than overwrite (naming what exists); `--force` overwrites
only the scaffold files. `serve` refuses to start, naming the fix, if the
`amplifier-agent` engine library cannot be imported — an engine that cannot run
a turn never reports itself healthy. Give each engine its own port; there is one engine per
workspace.

`prompts/auto-notify.md` is not decoration: it is the exact text a `notify:
auto` automation sends to ask "is any of this worth telling the user about?"
The engine ships **no built-in copy** — empty or delete it and such an
automation aborts loudly by name. `init` writes it for you.

Trigger a run and read the artifact without waiting on the schedule:

```bash
KEY=$(drumbeat api-key --workspace ~/myspace --show)
curl -s -X POST -H "X-API-Key: $KEY" localhost:9100/api/automations/<slug>/run
curl -s        -H "X-API-Key: $KEY" localhost:9100/api/automations/<slug>/runs
```

Every mutating request needs that key, including from loopback.

## 4 · doctor — the health check to learn first

```bash
drumbeat doctor --workspace ~/myspace
```

What healthy looks like: `status: FRESH` (running process matches disk),
`agent turns in flight: 0` between runs, `agent command:` naming the agent
library's version and the interpreter that imports it,
`draining: no`, `orphan pins: 0`. Two notes are expected on a fresh workspace
and mean nothing is broken: `workspace git: not a git checkout` (your policy has
no archive yet) and a `CONTAINMENT WARNING` that the data dir sits inside the
workspace (fine until you `git init` it — split with `--data-dir`).

`FRESH` → `STALE` the moment you edit engine code under a running process. That
is the difference between "I fixed that" and "I fixed that **and restarted**."

## 5 · Run it as a supervised service

`serve` is the foreground engine; leaving it running is a separate job for the
platform supervisor. These verbs generate the correct unit/plist, install it,
start it, and **verify a real turn** before reporting success — an install that
enabled a unit which never bound its port would otherwise look like it worked.

```bash
drumbeat service install --workspace ~/myspace --port 9100   # systemd --user (Linux) / launchd (macOS)
drumbeat service status                                       # unit state + probe /api/health
drumbeat service uninstall                                    # stop, disable, remove, verify gone
```

`--skip-turn-verify` exists but is not recommended: `/api/health` can pass on a
unit whose every scheduled run fails (missing provider key, or a tool not on the
unit's PATH). Let the install prove a real turn. Remember the provider key must
live in the unit's `EnvironmentFile=`.

## 6 · Restart safely (never `pkill -f`)

When `doctor` says `STALE`, the running process is on old code. Restart with the
drain procedure so an in-flight turn is never severed:

```bash
drumbeat drain --workspace ~/myspace --reason "picking up engine edits" --wait
# … wait for DRAINED, then kill by the EXPLICIT pid doctor/drain reported, then:
drumbeat serve --workspace ~/myspace --port 9100    # or: drumbeat service … / systemctl --user restart
drumbeat drain --workspace ~/myspace --clear        # resume scheduling
```

`--reason` is required to set a drain (a drained scheduler that cannot say why
is indistinguishable from a broken one). `drumbeat drain --status [--pid N]`
reports without changing anything. **Never `pkill -f` / `killall`** — signal
only the pid you identified. (Under a supervisor, prefer `systemctl --user
restart` / `launchctl kickstart -k` over a manual kill.)

## 7 · Session pins: inspect, rotate, unstick

Each automation resumes **one pinned conversation** across runs — that is what
makes "since your last check" mean anything. Pins live in
`<data-dir>/session_pins.json`, written by the engine; **nothing writes to your
automation file, ever.**

```bash
drumbeat sessions --workspace ~/myspace                                 # what each slug resumes + orphans
drumbeat rotate-session <slug> --workspace ~/myspace --reason "context bloat"
```

`rotate-session` takes a **required reason**, clears the pin, writes a durable
line to `<data-dir>/session_rotations.jsonl`, and the next run starts fresh. It
never deletes the old session's storage.

**Orphan pins** are the named cost of keying the store by slug. Renaming an
automation (`git mv teams-check.md teams-check-v2.md`) starts the new slug cold
and strands the old pin as an orphan (reported by `doctor` and `sessions`). To
retire the stranded entry, `rotate-session` the *old* slug. The engine also
rotates a pin **automatically**, with a logged reason, on three zero-judgment
signals: a provider context-ceiling hit, a rewrite of the automation's step
**prompts**, and a change of **provider module** in `agent_config:` (changing
only the model does not rotate).

If `rotate-session` prints `no pinned session to clear` (and exits non-zero) but
you expected one, the usual cause is a `--data-dir` that does not match the
running engine's. Confirm with `drumbeat sessions` using the same `--data-dir`.
An `AMPLIFIER_AGENT_WORKSPACE` export in your shell also re-buckets session ids
under a different slug — `doctor` flags it as `ambient workspace override SET`;
unset it before pin surgery.

## 8 · sweep — catch the runs that stayed silent

Every run must emit a delivery intent; a run with none is an **invalid run**,
and the engine enforces that against itself. Sweep for them:

```bash
drumbeat sweep --workspace ~/myspace                         # all recorded runs
drumbeat sweep --workspace ~/myspace --since 2026-08-09T16:00:00Z
```

Non-zero exit means findings exist. `--since` must be ISO-8601 UTC.

## 9 · Is it alive? Reads that need no key

```bash
drumbeat doctor --workspace ~/myspace                    # process healthy + running disk code?
ls   ~/myspace/runs/                                      # one directory per automation
tail ~/myspace/runs/engine-events.jsonl                   # the durable outbox — the whole product
cat  ~/myspace/runs/failures.log                          # every failed run, one line each
curl -s localhost:9100/api/health                         # HTTP face up?
```

The payoff line is the first `delivery_intent` whose `verdict` is not
`deliver`: the run that stayed quiet **and wrote down why**.

## Troubleshooting quick map

| Symptom | Cause → fix |
|---|---|
| Every run fails naming an unreachable provider | No provider key in the engine's environment. The error carries the library's own remedy verbatim. Export the key (or set the unit's `EnvironmentFile=`); verify per §2 |
| `serve` refuses to start naming `amplifier-agent` | The agent library does not import in the interpreter turns run under. Reinstall per §1; `doctor` shows `agent command: MISSING` with the install hint |
| `doctor` says `STALE` | Process on old code. Restart with the drain procedure (§6) — never `pkill -f` |
| `doctor`/`sessions` show `orphan pins: N` | An automation was renamed. `rotate-session` the old slug to retire it (§7) |
| A tool the automation `requires:` is "not found" at run time | It is not on the constructed turn PATH. See PATH rules in [DRUMPACKS.md](https://github.com/microsoft/amplifier-drumbeat/blob/main/docs/DRUMPACKS.md); read the live base from `GET /api/capabilities` (`packs.path_base_pinned`) |
| `rotate-session` clears nothing but a pin exists | `--data-dir` mismatch, or `AMPLIFIER_AGENT_WORKSPACE` set. Reconcile per §7 |
| A run "succeeded" but delivered nothing expected | Check `notify:` policy and its sentinels, then `drumbeat sweep` for invalid runs (§8) |

## Canonical docs (link, don't duplicate)

- Running for real (WSL, macOS, one-serve-per-workspace, clean shutdown): [docs/PLATFORMS.md](https://github.com/microsoft/amplifier-drumbeat/blob/main/docs/PLATFORMS.md)
- The engine, turn model, session lifecycle, delivery seam: [docs/ARCHITECTURE.md](https://github.com/microsoft/amplifier-drumbeat/blob/main/docs/ARCHITECTURE.md)
- Making an automation actually good over time: [docs/TUNING.md](https://github.com/microsoft/amplifier-drumbeat/blob/main/docs/TUNING.md)
- Authoring automations / drumpacks: the sibling `drumbeat-automation-authoring` and `drumbeat-drumpack-authoring` skills

More from microsoft/amplifier-drumbeat