Skip to main content
Watchfire
Templates

Task templates.

Eight starter tasks, ready to copy into .watchfire/tasks/. Adapt the prompt and acceptance criteria; everything else just works. Prefer to fill in a form? Try the playground.

These templates are the library. For the theory behind why a well-shaped task lands cleanly, read Anatomy of a great task. For longer, narrative walkthroughs with full repo context, see /docs/recipes. The formal schema for every field is documented in Projects and Tasks.
Template

Fix a bug

When to use: You have a repro and want a narrow fix — not a rewrite of the surrounding module.

task_id: bug00001
task_number: 1
title: "Fix 500 on POST /api/orders when items[] is empty"
prompt: |
  POST /api/orders returns 500 with an unhandled
  TypeError when the request body has `items: []`.
  Repro: `curl -X POST localhost:3000/api/orders -H 'content-type: application/json' -d '{"items":[]}'`.
  Stack trace points at `services/orders.ts:142`,
  where `items[0].price` is read without a length
  check.

  Return a 400 with body
  `{ "error": "items_required" }` when `items`
  is missing or empty. Mirror the validation
  pattern used in `services/users.ts` for empty
  payloads. Do not change the handler signature
  or the success-path response shape.
acceptance_criteria: |
  - The curl above returns 400 with the documented
    error body, not 500.
  - A new test in `services/orders.test.ts` covers
    the empty-items case and fails on `main`
    without the fix.
  - `npm run test` passes.
  - `npm run lint` passes.
  - No changes outside `services/orders.ts` and
    `services/orders.test.ts`.
status: ready

Pitfall — If you can't write the repro steps, the agent can't either. Reproduce the bug yourself first, then paste the steps verbatim.

Read the full template

When to use: One well-scoped capability you can describe in a single sentence — and a clear list of what it is not.

task_id: feat0002
task_number: 2
title: "Add CSV export button to /analytics"
prompt: |
  Add a CSV export button to the analytics page
  (`app/analytics/page.tsx`). Clicking it
  downloads the currently-filtered rows as
  `analytics-YYYY-MM-DD.csv`. Reuse the existing
  `<Button>` from `components/ui/Button.tsx`
  and place it next to the date-range picker.

  Generate the CSV client-side from the rows
  already in state — do not add a new API route.
  Use the same column order as the on-screen
  table, with the header row matching the visible
  column titles.

  Out of scope:
  - Changing the data-fetching layer.
  - Adding XLSX/PDF export.
  - Restyling the date-range picker.
  - Anything outside `app/analytics/` and a small
    `lib/csv.ts` helper if one is needed.
acceptance_criteria: |
  - A "Download CSV" button is visible on
    `/analytics` next to the date-range picker.
  - Clicking it triggers a download of a valid
    CSV matching the on-screen rows.
  - `npm run test` and `npm run lint` pass.
  - No new runtime dependencies in
    `package.json`.
  - No changes outside `app/analytics/` and (at
    most) one new file under `lib/`.
status: ready

Pitfall — Without an explicit out-of-scope list, the agent will tidy up neighbouring code 'while it's there.' Write the negation list before the prompt.

Read the full template

When to use: A mechanical, behaviour-preserving change: a rename, a move, a signature tightening.

task_id: refac003
task_number: 3
title: "Move parseInput from lib/parse.ts to lib/input/parse.ts"
prompt: |
  Move `parseInput` from `lib/parse.ts` into a
  new file `lib/input/parse.ts`. Update every
  importer in the repo to use the new path. Keep
  the function signature, the function body, and
  the exported name byte-identical — this is a
  mechanical move, not a rewrite.

  If `lib/parse.ts` ends up empty after the move,
  delete it. If it still has other exports, leave
  it alone.

  Do NOT:
  - Reformat any unrelated code.
  - Change the function body.
  - Add or remove tests beyond updating their
    imports.
  - Touch `tsconfig.json` paths.
acceptance_criteria: |
  - `lib/input/parse.ts` exists and exports
    `parseInput` with an identical signature.
  - No file in the repo imports from the old
    `lib/parse.ts` path (verify with grep).
  - `npm run test` passes with no test edits
    other than import paths.
  - `npm run build` and `npm run lint` pass.
  - The diff is a pure move + import rewrites —
    no unrelated changes.
status: ready

Pitfall — Resist the urge to fold in a 'tiny cleanup' — keep the diff mechanical so review stays cheap.

Read the full template

When to use: Backfilling coverage on one file with zero or near-zero tests, against a clear bar.

task_id: test0004
task_number: 4
title: "Add vitest unit tests for lib/parser.ts"
prompt: |
  `lib/parser.ts` exports four functions
  (`parseInput`, `parseHeader`, `parseRow`,
  `parseFooter`) and currently has no tests.
  Add a colocated suite at `lib/parser.test.ts`
  using vitest 1.x — the existing runner
  configured in `vitest.config.ts`.

  Use `lib/format.test.ts` as the structural
  template: same import order, same describe/it
  nesting, same assertion style (`expect(...)`,
  not snapshots).

  For each exported function, cover:
  - One happy-path case with realistic input.
  - The empty-input case.
  - The malformed-input case (assert the thrown
    error type and message).
  Do not refactor `lib/parser.ts` itself.
acceptance_criteria: |
  - `lib/parser.test.ts` exists with at least 12
    test cases (4 functions × 3 cases each).
  - `npm run test -- parser` passes.
  - `npm run test` (full suite) passes.
  - `npm run lint` passes.
  - No new dependencies; no changes to
    `lib/parser.ts`.
status: ready

Pitfall — 'Add tests' without naming the cases produces shallow happy-path coverage. List the specific cases that must be covered.

Read the full template

When to use: Bumping a library across a major version, with breaking changes the agent has to read about and handle.

task_id: depbump5
task_number: 5
title: "Upgrade pino from 8.x to 9.x"
prompt: |
  Upgrade `pino` and `pino-pretty` from 8.x to
  the latest 9.x release in `package.json` and
  `package-lock.json`. Read the pino 9.0 release
  notes (https://github.com/pinojs/pino/releases)
  and handle the documented breaking changes —
  in particular the changes around
  `transport` options and the removal of legacy
  level-by-string helpers.

  Where the codebase uses removed APIs (search
  for `pino.levels` and `pino.symbols`), update
  the call sites to the 9.x equivalents. Do not
  add a compatibility shim.

  Out of scope:
  - Upgrading any other dependency.
  - Reorganising log structure or fields.
  - Switching transports.
acceptance_criteria: |
  - `pino` and `pino-pretty` resolve to 9.x in
    `package.json` and `package-lock.json`.
  - No usage of removed pino 8.x APIs remains
    (grep clean).
  - `npm run test` passes with no warnings about
    deprecated pino APIs.
  - `npm run lint` passes with no new warnings.
  - The application boots locally with the same
    log output shape as before
    (`npm run dev` succeeds).
status: ready

Pitfall — Don't bundle two upgrades into one task. The agent needs to read one changelog at a time or the failure mode is unreadable.

Read the full template

When to use: A new docs page or section with a clear shape, length budget, and zero code changes.

task_id: docs0006
task_number: 6
title: "Add docs page: /docs/concepts/sandboxing"
prompt: |
  Add a new docs page at
  `content/docs/concepts/sandboxing.mdx` that
  explains how Watchfire isolates each agent with
  `sandbox-exec` on macOS. Use
  `content/docs/concepts/worktrees.mdx` as the
  structural template — same frontmatter shape,
  same heading depth, same voice.

  Sections, in order:
  1. _Why a sandbox?_ — one paragraph on what
     could go wrong without one.
  2. _What the policy allows_ — bullet list:
     read most of the filesystem, write to
     project/temp dirs, full network.
  3. _What the policy blocks_ — bullet list:
     `~/.ssh`, `~/.aws`, `~/.gnupg`,
     `~/Desktop`, `~/Documents`, `~/Downloads`,
     `.env`, `.git/hooks`.
  4. _Turning it off_ — short, with a clear
     warning.

  Target length: 600–900 words. No new
  illustrations. No new dependencies. Register
  the page in `content/docs/concepts/meta.json`
  in the correct position.
acceptance_criteria: |
  - `content/docs/concepts/sandboxing.mdx` exists
    with the four sections above, in order.
  - The page is registered in
    `content/docs/concepts/meta.json`.
  - Word count is between 600 and 900 (rough
    `wc -w` is fine).
  - `npm run build` passes; the page renders at
    `/docs/concepts/sandboxing`.
  - `npm run lint` passes.
  - No code changes outside `content/docs/`.
status: ready

Pitfall — Without a length bound and an outline, agents drift into a 3,000-word essay. Cap the word count and list the sections.

Read the full template

When to use: You need a written diagnosis before deciding on a fix — the task ships a Markdown report, not a diff.

task_id: invest07
task_number: 7
title: "Diagnose intermittent 500s on POST /api/orders since deploy abc123"
prompt: |
  Since commit `abc123` we see intermittent 500s
  on POST /api/orders, roughly 1 in 30 requests.
  The Sentry trace points at
  `services/orders.ts:142`. Reproduce locally if
  you can, identify the root cause, and write
  your findings to `INVESTIGATION.md` at the
  repo root.

  This is a diagnosis task, not a fix task. Do
  NOT change any code outside of
  `INVESTIGATION.md`. The follow-up fix will be
  a separate task once we agree on the proposed
  approach.

  `INVESTIGATION.md` must include sections:
  - _Symptom_ — what users see.
  - _Reproduction_ — exact commands that
    reproduce locally (or "could not reproduce"
    with what you tried).
  - _Root cause_ — the actual mechanism, with
    file:line references.
  - _Proposed fix_ — the smallest plausible
    change, as a bulleted spec for a future task.
  - _Open questions_ — anything you couldn't
    resolve without product input.
acceptance_criteria: |
  - `INVESTIGATION.md` exists at the repo root
    with the five sections above.
  - The Root cause section names at least one
    specific `file:line` location.
  - No code changes outside `INVESTIGATION.md`
    (verify with `git diff --stat`).
  - The file is valid Markdown (renders cleanly
    in GitHub preview).
  - `npm run lint` passes (no incidental
    formatting changes).
status: ready

Pitfall — If you don't say 'diagnosis only,' the agent will try to fix it. Make 'no code changes' a literal acceptance criterion.

Read the full template

When to use: Something got slower and you want a measured before/after, not a vibes-based fix.

task_id: perf0008
task_number: 8
title: "Investigate and fix /api/search p95 regression since v1.4.0"
prompt: |
  Since v1.4.0, `GET /api/search?q=...` p95
  latency has roughly doubled (from ~80ms to
  ~160ms on the staging dataset). Find the
  regression and fix it.

  Workflow:
  1. Establish a baseline. Run
     `npm run bench:search` (existing script,
     see `bench/search.ts`) on `main` and
     record p50/p95/p99 in your scratch notes.
  2. Form a hypothesis from the v1.3.0..v1.4.0
     git log and reading the touched files.
  3. Implement the smallest plausible fix.
  4. Re-run `npm run bench:search` and record
     the new numbers.
  5. If p95 is not back under 100ms on the same
     dataset, throw the change away and try a
     different hypothesis. Do not merge a
     regression-flavoured improvement.

  Ship the diff only if the benchmark passes.
acceptance_criteria: |
  - `npm run bench:search` reports p95 under
    100ms on the staging dataset after the
    change.
  - The PR description (paste it into the task's
    final commit message) includes before/after
    numbers for p50, p95, p99.
  - `npm run test` and `npm run lint` pass.
  - No changes to the public response shape of
    `/api/search`.
  - No new runtime dependencies.
status: ready

Pitfall — Without a numeric pass/fail bar, the agent declares 'feels faster' and merges a regression. Pin the threshold.

Read the full template

How to use these.

  1. Copy the YAML for the template you want.
  2. Save it as .watchfire/tasks/NNNN.yaml, replacing NNNN with the next unused number in that directory.
  3. Edit the prompt and acceptance_criteria to match your repo. Keep status: ready (already set in every template).
  4. The daemon picks it up automatically, or run watchfire run NNNN to start it on demand.

For the full schema of every field — including the bookkeeping ones the daemon writes for you — see Projects and Tasks.

Want a template for something not in this list? Open a discussion — we’ll add it.