Skip to content
Conduit

skill

dolly

like git, but for agents

nick-delirium/dollycommunitylistedunreviewed

Set up

Skills are installed by placing their folder where your agent loads skills.

The download comes from github.com/nick-delirium/dolly itself, not from our server, so what you get is exactly what its author published.

Install instructions come from the README

Skills do not use an MCP config. Follow the steps in the README below.

README

synced Oct 27, 2026

dolly

Task memory for coding agents, as markdown in your repo.

dolly gives an agent a task board, a step log of what it did and why, and versioned specs — all plain files, committed with your code. So the next session picks up where the last one stopped instead of starting blind, and your teammates' agents read your history the same way yours read theirs.

Three things it does:

  • Remembers. Every major step is logged with a timestamp, the GitHub handle who did it, the files it touched, and a full handoff note: decisions, options rejected, gotchas. With hooks installed this happens automatically.
  • Plans. A planning interview that refuses to finish while any section is _TBD_ or any question unanswered, then derives the spec from the answers.
  • Tracks. todo → planning → working → validating → done, where validating means the agent is done and a human must verify. Agents never mark work done.

Installs into Claude Code (plugin, skills, slash commands, hooks, optional MCP), pi (skills and an extension giving the same auto-inject and auto-logging hooks), opencode (skills, slash commands, and a plugin giving the same context injection and auto-logging), ZCode (skills, slash commands and a hook-carrying plugin), and five other agents. Zero runtime dependencies.

.dolly/
  config.json
  tasks/
    b4tk7s2m-oauth-login/
      task.md          # meta + short spec + criteria + one line per event   ← what everyone skims
      context/
        spec.md        # current full spec on top, every superseded version below it
        steps.md       # full context of every step, append-only
        plan.md        # planning interview record (only when the task was planned)

Three files per task, not thirty. A task's whole history is two diffs in a PR.

Install

Not on npm yet — install from the repo. Any of these gives you a dolly on your PATH; pick by whether you want to hack on it.

Straight from GitHub (no clone, builds itself on install):

npm install -g github:nick-delirium/dolly

From a clone — same result, but you have the source:

git clone https://github.com/nick-delirium/dolly.git
cd dolly
npm install          # builds via the `prepare` script
npm install -g .     # or `npm link` if you want edits to take effect live

Working on dolly itself — npm link symlinks the global dolly at your checkout, so a rebuild is picked up immediately:

git clone https://github.com/nick-delirium/dolly.git && cd dolly
npm install && npm link
npm test             # 87 tests

Undo with npm unlink -g dolly. Requires Node ≥ 18 and nothing else — there are no runtime dependencies, and TypeScript is the only devDependency.

Then, in whatever project you want task memory for:

cd your-project
dolly init

dolly init opens a short setup screen: where task memory lives, which of the coding agents on your machine to wire, whether to register the MCP server and the hooks, and your handle. Every prompt opens on dolly's default, so pressing enter through it is the whole setup.

Two answers are worth knowing before you run it:

  • task memory — .dolly/ in the repo (commit it; that's how teammates and their agents share the memory), or ~/.dolly/projects/ (private to you, nothing added to the repo). Switch either way later; the store moves with your tasks in it.
  • agent instructions — written into this project (CLAUDE.md, .claude/, …) or into your user config (~/.claude/), so every project you open gets them.

dolly setup reopens the same screen with your current settings filled in.

Scripts and CI have no terminal, so the wizard never runs there: pass --yes for the defaults, or spell it out — dolly init --store local --agents claude,cursor --no-mcp. A bare dolly init with no terminal stops and tells you which flags it wanted rather than guessing.

Claude Code (plugin)

The plugin gives you the hooks (auto-logging, session context) without editing .claude/ yourself:

claude plugin marketplace add nick-delirium/dolly
claude plugin install dolly@dolly

The plugin drives the CLI, so install that too (see above) — bin/dolly-hook.mjs falls back to a dolly on PATH and exits 0 rather than breaking your session if it can't find one.

You get:

  • skills dolly (memory, step logging) and dolly-planning (interview) — loaded automatically when relevant
  • slash commands /dolly:board /dolly:resume /dolly:step /dolly:memo /dolly:plan /dolly:spec /dolly:validate /dolly:adopt
  • MCP server — 15 tools, for agents that cannot or should not shell out. Opt-in (--mcp or dolly config set install.mcp true): every agent dolly supports has a shell, and the CLI is the same surface without tool schemas in every prompt
  • SessionStart hook — spec, criteria and recent events injected into every new session automatically
  • Stop hook — auto-logs a step for each finished turn (see Automatic logging), and nudges when a task goes quiet

dolly install claude registers the same hooks in .claude/settings.json if you'd rather not use the plugin (--no-hooks to skip).

Prefer files over a plugin? dolly install claude writes .claude/skills/, .claude/commands/dolly/, a CLAUDE.md block, the hooks in .claude/settings.json, and .mcp.json with --mcp. With the plugin enabled too, the plugin's hooks step aside for the settings ones; a --global install on top of the plugin writes only the CLAUDE.md block.

Local or global

Instructions are written into the project by default, so they land in the repo alongside .dolly/ and your teammates get them on clone. Change the default per project:

dolly config set install.scope global    # write to ~/.claude/, ~/.claude.json instead
dolly config set install.mcp true        # also register the MCP server (off by default)
dolly install claude --local             # or override the setting for one run

Other agents

dolly install --list                 # what's detected here
dolly install cursor codex gemini    # or name them
AgentWhat gets written
Claude Code.claude/skills/, .claude/commands/dolly/, CLAUDE.md block, .mcp.json
Codex CLIAGENTS.md block, ~/.codex/config.toml MCP entry
Cursor.cursor/rules/dolly.mdc (always-apply), .cursor/mcp.json
Windsurf.windsurf/rules/dolly.md
GitHub Copilot.github/copilot-instructions.md block
Gemini CLIGEMINI.md block, .gemini/settings.json MCP entry
opencode.opencode/skills/, .opencode/commands/dolly-*.md, .opencode/plugins/dolly.js, AGENTS.md block, opencode.json MCP entry
ZCode.zcode/skills/, .zcode/commands/dolly-*.md, .zcode/config.json MCP entry, AGENTS.md block, plus a plugin scaffold at ~/.zcode/marketplaces/dolly/ carrying the session-start injection and per-turn auto-log hooks — zcode ignores workspace-level hooks, so they ride in a plugin (one manual enable: Settings → Plugin Management → Discover → "+" → add that folder as a local marketplace → Get "dolly")
pi~/.pi/agent/skills/ (or .pi/skills/ local), AGENTS.md block (~/.pi/agent/AGENTS.md global), mcp.json, and ~/.pi/agent/extensions/dolly.ts — a global-only extension that injects task context at session start and auto-logs a step per finished turn
anything elseAGENTS.md block

All writes are idempotent, delimited by <!-- dolly:instructions --> markers. Re-run anytime — dolly setup to pick from a list, or dolly install for the non-interactive form. --dry-run to preview, --global for user-level instead of project-level (targets with no user-level form say so and are skipped), --mcp to register the MCP server. A JSON config that does not parse (comments, trailing comma) is left untouched with a note — never rewritten. Files dolly used to ship and no longer does are removed on the next install.

Two ways in

Small, understood work

dolly new "Fix token expiry off-by-one" --short "Expiry check uses < instead of <=."
# prints: k7x2mq9v fix-token-expiry-off-by-one → todo

dolly status token-expiry working      # by slug, by hash, or by fuzzy title — all the same task
# … agent works …
dolly step token-expiry -m "Fixed comparison, added regression test." --auto-files --detail-file notes.md
dolly status token-expiry validating --note "run the auth suite"

A feature — planning mode

dolly plan start "Replay filters" --brief "users want to filter replays by country and browser"
dolly plan check 2          # ← the agent's interview agenda; exits 1 while gaps remain
unfilled sections (8):
  Problem — What hurts today? Who feels it? Evidence (bug, metric, quote).
  Goal — One paragraph: what is true after this ships.
  Scope — In: things this change touches. Out: things it explicitly does not.
  Success Criteria — Checkable statements. Each must be verifiable by a human or a test.
  Changes — Files, modules, schemas, configs, migrations. Guesses fine — mark them.
  Risks — What can break, what is uncertain, what needs a fallback.
  Test Plan — Unit / integration / manual. Name the cases, not the framework.
  Open Questions — Anything blocking. Check the box once answered, log answer in Q&A.

The agent reads the codebase, asks you the rest in one batch, and records what you say:

dolly plan qa "$ID" -q "Country from GeoIP or profile?" -a "GeoIP at session start, already stored."
dolly plan set 2 "Success Criteria" --text "- [ ] country filter returns only matching sessions
- [ ] combined filters AND together
- [ ] p95 under 300ms on 1M sessions"
dolly plan check 2          # → plan complete
dolly plan finalize "$ID"    # → spec + criteria generated, status → todo

finalize derives the full spec from the plan into context/spec.md, puts a short summary in task.md, and moves the task to todo. plan.md stays forever as the interview record. Blocked while any section is _TBD_ or any Open Question unchecked — --force to override, then log the unknown as a Risk.

The step log

The core idea: two tiers per step.

dolly step current -m "Wired the OAuth callback route." \
  --auto-files \
  --detail-file /tmp/notes.md
  • -m → task.md. One to three lines. What humans and teammates skim.
  • --detail-file → appended to context/steps.md as entry 0003. Decisions and why, options rejected and why, gotchas, snippets, what to do next. Written for an agent with zero context.

--auto-files reads changed files from git (--files a.ts,b.ts when the tree is noisy).

Result in task.md:

## Log

- `2026-08-07 10:22Z` @nick-delirium: Wired the OAuth callback route.
  files: `src/auth/callback.ts`, `src/auth/index.ts` · full: `steps.md#0003`
- `2026-08-07 11:04Z` @nick-delirium: spec → v2. security review demanded PKCE
  previous version kept in `spec.md`
- `2026-08-07 11:40Z` @nick-delirium: status working → validating. run the auth suite

One line per event, flat and chronological — spec changes and status moves land in the same stream as steps, so the whole history reads top to bottom. Append-only: a wrong step is corrected by a new step, never by rewriting.

context/spec.md works the same way — current spec at the top, each superseded version below it with the reason it was replaced:

**current: v2** · updated 2026-08-07T11:04:00Z by @nick-delirium

<current spec>

## Superseded versions

## v1 — 2026-08-07T08:00:00Z · @nick-delirium

> superseded by v2: security review demanded PKCE

<the v1 spec, verbatim>

Adopting a conversation in progress

You rarely remember to start a task before you start working. dolly reindex reads this project's transcript — Claude Code's own, or the per-turn mirror the opencode plugin writes under ~/.local/share/opencode/dolly/ (opencode keeps its real sessions in SQLite with no stable format, so the plugin mirrors each finished turn out as JSONL) — and turns what already happened into a task:

dolly reindex            # digest — read it first
dolly reindex --apply    # import: one step per turn

The digest is the mechanical record the agent cannot reconstruct from a context window that may have been compacted:

# dolly reindex — session faa33f88

- title (from Claude Code): Build Dolly agent tool for memory and task planning
- span: 2026-08-07T07:45:47Z → 2026-08-07T08:41:20Z
- human turns: 4 (+1 interrupted/duplicate)
- tools used: Bash 48, Write 44, Edit 39, WebFetch 2, ToolSearch 1, WebSearch 1
- already imported: 0 turn(s)

## Segment 3 · 2026-08-07T08:19:38Z
turn `8434189b-8891-4597-b521-efb6c4c883fe`

### Request (verbatim)
> make global and local installment a setting, change it to local by default. …

### Files touched (10)
- `src/core/types.ts`
…

What it extracts: every human request verbatim (injected reminders, hook context and interrupted duplicates stripped), files written or edited per turn, commands run, tool counts, subagent turn counts, and the assistant's closing message. What it never touches: thinking blocks, tool results, subagent file writes, temp paths.

Each imported step records source: session <id> · turn <uuid>, so re-running is a no-op for turns already present. Flags: --into <ref> to attach to an existing task, --all-turns to keep clarification turns as their own steps, -n N for the last N segments, --session <id> / --file <path.jsonl> to pick a different conversation, --list to see them all.

The import is deliberately mechanical. The spec it writes is the raw requests stitched together, and it says so. The agent — which has the conversation in context — is instructed to immediately replace it:

dolly spec "$ID" --short "<2-5 lines>" --file spec.md --reason "reindexed from session faa33f88"

Upgrading between versions

Releases are tagged vX.Y.Z, with package.json bumped in the same commit. dolly reads its own version from package.json at runtime, so there is one source of truth rather than a number copied into several files.

Being told you are behind

A human at a terminal gets one line on stderr:

dolly 0.2.0 is available (you have 0.1.0)
  run: dolly update
  silence this: dolly config set updateCheck false

dolly update does the right thing for how this copy was installed — a linked checkout is pulled (--ff-only, refusing on uncommitted changes) and rebuilt; an npm-installed copy is reinstalled with npm install -g github:nick-delirium/dolly. --check compares versions without touching anything, --dry-run prints the plan.

It is deliberately timid, because dolly runs on every SessionStart, every Stop and every MCP tool call:

  • Never in-band. The check reads a cache in ~/.dolly/update.json. A cache older than 24h is refreshed by a detached background process, and the result is used on the next run. Nothing is ever waited on.
  • Never on stdout. dolly mcp speaks JSON-RPC there and --json is parsed, so the notice is stderr-only and suppressed entirely for mcp, hook and statusline.
  • Never to an agent. Agents act on text they read — shown an upgrade command, one may stop mid-task to run it, or repeat it in a step summary as if it were project state. The notice appears only for a human at a TTY, outside Claude Code.
  • Silent when it cannot tell. A failed lookup caches null and says nothing, so an offline machine is not nagged and does not retry on every command.

Off switches: dolly config set updateCheck false (writes the gitignored local.json, so it is per-person), or CI, NO_UPDATE_NOTIFIER, DOLLY_NO_UPDATE_CHECK in the environment.

This is convenience, not safety — the schema guard below is what actually protects a shared store.

Schema versions

The store carries a schema version in config.json, and dolly compares it to the version the binary understands. Three outcomes:

SituationWhat happens
store is currentnothing, silently
store is behind, and the upgrade is losslessapplied automatically on the next command, reported on stderr
store is behind, and the upgrade moves or rewrites dataevery command warns with the exact command; you run dolly migrate
store is ahead of your dollywrites are refused, reads still work with a warning

So after git pull and a rebuild, you usually do nothing.

Why "lossless" is the dividing line. Adding local.json, extending dolly's own .gitignore — those cannot lose anything, so asking permission is friction. Moving a directory or parsing existing prose can go wrong in ways you cannot undo, and migrate is the one place a surprise is unacceptable. Those never run unattended.

Why a newer store refuses writes. .dolly/ is shared and committed. If a teammate upgrades dolly and you don't, your older binary would happily write the old layout into their store and the two would drift with no error. Refusing is the only safe answer:

$ dolly step 1 -m "..."
dolly: this store is at schema version 7, but this dolly understands 6.
It was written by a newer dolly — upgrade dolly before writing to it.

dolly migrate refuses to downgrade for the same reason. Reads are always allowed, so you can still see the board while you upgrade.

dolly migrate --dry-run   # the chain, hop by hop, plus the per-task detail
dolly migrate             # apply, then stamp the version

Migrations are an ordered chain, each idempotent and each detected by its own fingerprint rather than trusting the stamp — so a store migrated by hand, or one predating the stamp entirely, converges to the right state instead of re-running work. --dry-run shows both the chain and the per-task detail, and writes nothing.

Re-deriving imported steps

dolly migrate                      # upgrade an older .dolly/ layout in place
dolly reindex --apply --rebuild    # re-derive imported steps from the transcript

migrate is idempotent and has a --dry-run. It merges pre-0.2 context/steps/NNNN.md files into steps.md, folds spec.vN.md appendices into spec.md, and repoints dead links in the short log. Un-migrated stores stay readable in the meantime — the readers fall back to the old layout.

Big repos: one repo, many tasks

The failure mode in a large codebase is an agent treating one task as a greenfield project — reinventing conventions, re-deriving the architecture, and undoing a decision another task made deliberately. Three things push against that, and they are injected automatically rather than left for the agent to remember.

The project brief

.dolly/project.md holds what is true about the codebase, independent of any task:

dolly project                                       # read it
dolly project set "Invariants" --text "<what you learned>"

Sections: Overview, Architecture, Conventions, Invariants, Glossary. Unfilled ones are reported so an agent knows what is still unanswered, and are never injected — a _TBD_ heading is worse than silence.

It is not a second CLAUDE.md. CLAUDE.md tells an agent how to behave; the brief records what is true about the code. Instructions versus findings. The rule agents are given: a fact useful to a task that does not exist yet belongs in the brief; a fact about what this task did belongs in a step.

Related tasks, by the files they touched

dolly already records the files every step touched, which makes it the only thing in the toolchain that can answer who else has been in this code, and what did they conclude?

dolly related "$ID"                          # tasks sharing files with this task
dolly related --files src/auth/token.ts      # before you edit something
tasks sharing code with 2 file(s)

- **2rhqyc8c Rate limit the API** (done) — shares `src/auth/token.ts`
  last: Bucket per token; reused the token parser rather than re-parsing.

The index is derived from steps.md on every call and never stored, so it cannot go stale. dolly context <ref> includes it automatically, alongside the brief and the full list of files the task has touched.

Opening a task also checks for one that may already cover it — word overlap against existing titles, plus tasks whose notes and specs already cover most of the new title's words, printed as a warning, never a block:

dolly: 1 existing task(s) may already cover this — check before duplicating
  0004 Add replay country filter (working) · shares: replay, filter

Recall: search what was written

Decisions live in prose — step notes, superseded specs, planning answers, memos. dolly recall searches all of it, ranked, and every hit is a path:line an agent can open directly:

dolly recall token refresh                   # words, ranked (BM25, hand-rolled)
dolly recall "per-store lock"                # a phrase ranks its exact wording higher
dolly recall --kind decision mysql           # only Decisions / Dead ends / Why sections and planning answers
dolly recall lock --status done --since 30d  # filters: --kind --status --since --task -n
dolly recall oauth --all-projects            # every store in ~/.dolly/projects/index.json
recall "lock" — 25 matches, top 8

rqdpztdy Auto-log targets this session's task; lock store writes (validating)
  step 0001 › Auto-log targeting + store lock · .dolly/tasks/rqdpztdy-…/context/steps.md:21
    - core/lock.ts: `.local/write.lock` O_EXCL with pid; dead pid or >120s → cleared; …

Nothing is indexed or cached: each query reads the markdown as it is, so a line number cannot be stale and the store stays the only source of truth. A query word that nothing contains is corrected by edit distance against the store's own vocabulary, and the output says so. Text output is capped at ~3KB unless you pass -n; --json returns up to 100 hits.

dolly why <file> answers "why is this code like this" for one file: the steps that recorded it, the commits that touched it (mapped to tasks through a Dolly-Task: <id> trailer or a task id in the message), and prose that names it. dolly show and dolly context list the tasks that mention this one's id — backlinks nobody had to write.

Code maps: detected, not rebuilt

dolly does not index code, and a half-built third indexer would only be wrong more often. It detects one and tells the agent to reach for it before grep:

MarkerToolWhat the agent is told
.codegraph/CodeGraphcodegraph explore "<question>" — symbols' source plus call paths, including dynamic dispatch
graft/graftgraft ask "<task>" — ranked nodes and file:line
.serena/Serenasymbolic lookup without reading whole files

Shown at session start and in dolly project. If a big repo has none, the agent is told to say so and suggest one.

What a new session is handed

The SessionStart hook injects an index of about 2KB — paid on every session, so only what changes the first move: the store and its task counts, the project brief's Overview (the other sections are named, one dolly project away), any code map, the last three finished tasks by title, and ONE task — this conversation's if it has written to one, else the most recent open task with the command that attaches it (dolly status <ref> working). Its spec and last log line come along only when it is this conversation's task or was touched in the last week. With no open task, it names the commands to run before opening one: dolly board and dolly related --files ….

Rehydrating — read in tiers

Neither "the short chain" nor "the full context" is right on its own. They answer different questions and differ in cost by an order of magnitude:

NeedCommandContains
what work existsdolly boardone line per task
orienting, no code yetdolly context <ref> --briefspec, criteria, the newest 20 log entries
picking up a task, about to write codedolly context <ref>the above + full spec + last 3 steps' full context
why is this code like thisdolly context <ref> -n 0every step in full

The default is the middle one, and it is the answer to "which is better": the short log tells you what happened, full step context tells you why — and you need why before you change anything. Reading every step in full is almost always waste, since older steps were superseded by newer ones.

Nothing in it is there twice: a finalized plan is already the spec (it is shown only while the task is planning), task.md's criteria replace the copy a planned spec embeds, the log drops the files: trailers listed once above, and an auto-logged step keeps what the agent said and touched — not its tool trace. The session-start text is an index, deliberately not the record, and points at dolly context.

Agents without skill support get the whole usage guide from dolly guide (dolly guide planning for the planning interview) — the instruction block every session loads is kept to the essentials and points there.

Automatic logging

Yes, it wires itself up on install. With the plugin (or dolly install claude), the Stop hook fires after every finished turn and appends a mechanical step for it, derived from the transcript: what the agent reported, its work chain, the files it touched. The log has no holes even when the agent forgets.

pi gets the same behavior through its extension, by a different route: pi's agent_end event (once per prompt — turn_end fires per LLM round) already carries the finished work, so the extension reads it in memory and pipes it to dolly hook stop --from-stdin — no transcript file involved. Auto-inject works the same way via before_agent_start. The gating and dedup rules below apply identically.

opencode gets it through its generated plugin (.opencode/plugins/dolly.js): on session.idle — opencode's turn-end event — the plugin pulls the finished turn from the SDK, mirrors it to ~/.local/share/opencode/dolly/<escaped-cwd>/<session>.jsonl, and pipes the same stdin payload dolly's Stop hook consumes. Context injection rides experimental.chat.system.transform (fires every LLM call, so context is never staler than the last turn) and experimental.session.compacting re-injects it across compaction. The same gating and dedup rules apply.

It stays out of the way of real work:

  • it only ever writes to a task this conversation already wrote to — any dolly write attaches the conversation (dolly status <ref> working, a step, a new task). A session that never touched dolly logs nothing, instead of landing on whichever task was touched last
  • a turn the agent logged itself is skipped — dolly compares the turn's start time (or, for harnesses that send none, the step count at the previous turn's end) against the task
  • only fires while a task is working (reindex.autoLogOnlyWhenWorking)
  • deduped by turn id, and every write holds the store lock, so nothing is logged twice — not even with the plugin and settings.json hooks both installed (the plugin's hooks step aside for the settings ones)
dolly config set reindex.autoLog false                 # off
dolly config set reindex.autoLogOnlyWhenWorking false  # log in any status

Agent-written steps are still much better, because auto-entries lift the agent's last message verbatim. The instructions tell the agent to keep logging real steps and treat auto-entries as a floor. /dolly:step is the manual checkpoint.

Logs record what the agent did, not what you asked

A log of user requests is useless for continuity — the request is already in the spec. What the next session needs is what its predecessor concluded.

So a step summary is derived, in order, from: the last thing the agent told the user (that is its own summary), then its first message, then a description synthesised from the work chain, and only as a last resort the request. Full step context is ordered the same way:

## What the agent said it did      ← its last message whole, earlier ones a line each
## Work chain                      ← Read src/a.ts ×3 · Edit src/b.ts · Bash: npm test (first 25)
## Files touched
## Tools: Bash 12, Edit 3
## Request that opened the turn     ← verbatim, but demoted

dolly context shows an auto-logged step without its work chain and tool counts — the full record stays in context/steps.md.

The work chain is the ordered trace of what actually ran, repeats collapsed (Read src/a.ts ×3). Scratch files outside the project appear as Write (outside the project); writes to .dolly/ itself are dropped as circular bookkeeping.

Thinking blocks

Off by default. dolly reindex --include-thinking (or reindex.includeThinking) captures them into a clearly labelled ## Reasoning (raw, opt-in) section.

The honest trade-off: reasoning is verbose, frequently contradicted later in the same turn, and the conclusions that survive already appear in the visible messages and the work chain. It buys rationale that never made it into a reply — real, but low density per token. Try it on one task before enabling it globally.

Reopening the conversation

Every step records the Claude Code session it happened in — CLAUDE_CODE_SESSION_ID, which is exactly the transcript's filename — so a task knows its own conversation history:

dolly continue b4tk7s2m           # claude --resume <latest session for that task>
dolly continue oauth login --fork # fuzzy title works too; resume as a new branch
dolly continue b4tk7s2m --print   # just print the command

From a terminal it execs claude directly. From inside Claude Code — where spawning an interactive TUI out of a tool call would be wrong — it prints the command and says why. Sessions appear in dolly show <ref> and in the frontmatter as sessions: [...].

Status board

todo → planning → working → validating → done

validating means the agent is finished and a human must verify. Agents are instructed to stop there and never set done themselves.

dolly board
dolly board --status working
dolly board --json           # for scripts
dolly · /repo/.dolly

○ TODO  (1)
  xb925jmt  Replay filters            @nick-delirium   2h ago   0✎  spec v2
◐ WORKING  (1)
  b4tk7s2m  OAuth login               @nick-delirium   5m ago   3✎  #auth
◑ VALIDATING  (1)
  6mq9hmg4  Rate-limit the search API @alice           1d ago   7✎

Closing the loop: dolly review

Agents stop at validating, so the queue only drains when a human looks. dolly review walks it in a terminal, oldest hand-over first. Each task shows its short spec, the note the agent left when it handed over, the files it touched and its criteria; you tick criteria (only the checkbox character changes, and the log records who ticked what), mark it done, send it back to working with a note the agent will read, or skip it.

dolly review                 # the whole validating queue
dolly review "$ID"           # one task

It refuses to run without a terminal or inside an agent — that refusal is the point. The store lock is taken per action and the task is re-read under it, so a session logging steps elsewhere never waits on someone thinking. - [ ] _TBD_ placeholders are not criteria. The board ends with a reminder whenever tasks are waiting.

The daily memo

dolly memo                    # digest of today: task events, conversations, commits
dolly memo --date 2026-08-20  # backfill any day
dolly memo --save --file notes.md   # save agent-written prose as the day's memo

dolly memo gathers everything that happened on a day — every task's log lines, the conversations (Claude Code transcripts and the opencode mirror), and the repo's commits — and prints a mechanical digest. You or your agent write it up as a few sentences and save it with --save; the result lands in .dolly/memo/YYYY-MM-DD.md, committed with the repo, so the project's own history answers "what happened Tuesday?".

memo.auto = true makes the session-start hook note it when today has no memo yet — a nudge, never an auto-write.

Compaction: rollups and digests

dolly context carries the last three step bodies; on a long task everything older falls out of view, and with it whatever those steps decided. Compaction keeps it in view — on request, never on a timer, and without dolly writing a word of prose (it never needs a model):

dolly compact "$ID"                              # prints the old bodies + rules + the exact save command
dolly compact "$ID" --through 0026 --save --file rollup.md
dolly compact --tasks a,b,c --title "Install targets"            # several tasks → one digest
dolly compact --tasks a,b,c --title "Install targets" --save --file digest.md
dolly digest [slug]                              # list digests, or show one

Rollup. The agent writes one note covering steps 0001–0026; --save appends it to steps.md as a rollup entry (its own step number) and moves the covered bodies, byte for byte, to context/steps.compacted.md. dolly context then shows the rollup followed by the last three steps. Nothing is deleted or edited: dolly recall, dolly related and reindex still read the moved bodies, and step numbers are never reused. --through is pinned when the material is printed, so a step logged in between stays live. Compacting again folds the previous rollup into the new one. The context suggests it once ten or more bodies are out of view.

Digest. For an area several tasks built together, the agent writes one note at .dolly/digests/<slug>.md. Each task gets digest: <slug> in its frontmatter and a log line; finished ones fold under the digest on the board, and dolly context / dolly related point at it. Saving a digest again keeps the previous prose under "Previous versions".

Compaction is a schema v7 feature: a v6 dolly would number steps from steps.md alone and drop the digest: key, so it is refused writes by the version gate.

No housekeeping

There is none. Tasks are never archived, flagged stale, pruned or touched based on time — the only thing that moves a task or changes its status is an explicit dolly status typed by a human or run by an agent on request. A quiet task stays exactly where it is, visible on the board forever. (dolly migrate flattens any archive/YYYY-MM/ left behind by older versions back into tasks/.)

Sharing and attribution

Each step and status change is stamped with your handle, resolved in this order:

DOLLY_USER  →  .dolly/local.json  →  gh api user (cached)  →  git user.email / user.name  →  $USER

.dolly/ holds two config files and the split matters: config.json is shared policy (statuses, plan sections) and is committed; local.json is per-person and gitignored. Identity belongs in local.json — dolly config set user writes there. A user in the shared config.json is deliberately ignored, because a committed handle stamps every teammate's steps with one name and destroys the attribution the store exists to provide. dolly migrate moves a stale one out and reports it.

Because it's all files in the repo, two people working the same feature see each other's steps on git pull, and a step log shows up in PR diffs as a readable narrative of how the change happened. collaborators in the frontmatter accumulates everyone who touched the task.

Where the store lives

In order: DOLLY_DIR → nearest .dolly/ walking up from cwd → a store this project is linked to → <repo-root>/.dolly → ~/.dolly/projects/<name>-<hash> when you're not in a repo. Non-repo directories get a global store so dolly still works outside git.

~/.dolly/projects/index.json is a registry of every project dolly has seen:

{
  "/Users/you/work/api": { "path": "/Users/you/work/api", "local": false,
                           "store": "/Users/you/.dolly/projects/api-1a2b3c4d",
                           "created": "2026-08-10T09:44:45Z" },
  "/Users/you/proj/web": { "path": "/Users/you/proj/web", "local": true,
                           "store": "/Users/you/proj/web/.dolly",
                           "created": "2026-08-02T11:20:03Z" }
}

local is recorded either way, so "in the repo on purpose" is a fact rather than the absence of one — otherwise a repo you set up deliberately and a repo dolly has never seen look identical. A project registers itself the first time anything writes to its store, so a clone of a teammate's repo appears without you running setup. Reads never write it.

It is descriptive, not authoritative: resolution consults it only when no .dolly/ was found on disk. A directory cannot be wrong about existing; an entry can be stale in every direction — store deleted, project moved, dotfiles half-synced onto a new machine. So when the two disagree — you went private, then pulled a branch where a teammate committed .dolly/ — dolly uses the repo's store, because that is the one the whole team can see, and prints what it is ignoring instead of choosing in silence.

dolly projects lists the registry: every project, whether its store is in the repo, task count and last activity. --prune forgets entries whose store is gone. dolly whoami answers the same question for the project you are standing in.

Commands

init [--yes] [--store local|global] [--agents a,b] [--local|--global]
     [--mcp|--no-mcp] [--no-hooks] [--no-agents] [--dry-run]
setup                                      reopen the setup screen
projects [--json] [--prune]                every project dolly knows
board | list | ls [--status s] [--mine] [--tag t] [--json]
show [ref] [--full] [--json]
context [ref] [-n N] [--brief] [--json]
current                                    alias for: context current
project [show | init | set "<Section>" --text t]
related [ref] [--files a,b] [--json]
recall <words…> [--kind k,…] [--status s] [--since 30d|date] [--task ref] [-n N] [--all-projects] [--json]
why <file> [--json]                        tasks, commits and prose behind one file
continue | resume [ref] [--fork] [--print] [--session id] [--json]
update [--check] [--dry-run] [--force]     self-update in place (clone: pull+rebuild; npm: reinstall)
memo [--date d] [--json] [--save --file f|-]  today's digest → .dolly/memo/YYYY-MM-DD.md

new | add "<title>" [--short t] [--full t | --file f] [--status s] [--tag x] [--criteria c]
step <ref> -m "<summary>" [--files a,b | --auto-files]
           [--detail t | --detail-file f] [--status s]
spec <ref> [--short t | --short-file f] [--full t | --file f] [--criteria c] [--reason why]
status | move <ref> <status> [--note t]
review [ref] [--status s]                  humans only: walk the validating queue
retitle | rename <ref> "<new title>"       renames the task and its directory
compact [ref] [--keep N] [--through NNNN] [--save --file f|-]
compact --tasks a,b,c --title t [--save --file f|-]
digest [slug] [--json]

plan start "<title>" [--brief t | --brief-file f]
plan show <ref>
plan set <ref> "<Section>" (--text t | --file f)
plan qa <ref> -q "<question>" -a "<answer>"
plan check <ref> [--json]
plan finalize <ref> [--short t] [--full t | --file f] [--force] [--status s]

reindex | adopt [--list] [--session id] [--file f.jsonl] [--apply] [--into ref]
        [--all-turns] [-n N] [--rebuild] [--title t] [--status s]
        [--include-thinking] [--json]
migrate [--dry-run]
config [get <key> | set <key> <value>]
defaults                                   the default config
whoami
version
install [agent…] [--agents a,b] [--list] [--local|--global] [--mcp|--no-mcp] [--no-hooks] [--dry-run]
mcp                                        MCP stdio server
hook session-start [--raw]                 context injection (--raw: plain text, not the Claude envelope)
hook stop [--from-stdin]                   auto-log (--from-stdin: the turn as JSON, for pi/opencode/zcode)
statusline                                 one line for a statusline

<ref> is an id (a random 8-character hash like b4tk7s2m, or a legacy sequential number), a slug, a unique substring, or a fuzzy title match — dolly continue oauth login finds the OAuth task (commands that take only a ref accept several words; an omitted ref means current). An exact title, or a single literal match against fuzzy ones, wins outright. One clear match selects directly; several open a type-to-filter picker in a TTY (a numbered list when scripted). current / @ is the active task: most recently touched in working, else validating, else planning.

Every command that produces data takes --json. Every text or file flag accepts - to read stdin — stdin is never read unless you ask. Unknown flags are an error, not silently ignored.

MCP tools

dolly mcp speaks MCP over stdio, no dependencies. Registered by dolly init / dolly install when asked (--mcp, or install.mcp in config) for agents that support it.

dolly_board · dolly_context · dolly_task_show · dolly_task_new · dolly_step_add · dolly_spec_update · dolly_status_set · dolly_plan (start / check / set / qa / finalize) · dolly_project · dolly_related · dolly_recall · dolly_compact · dolly_reindex

Agent instructions

Skills, slash commands and the instruction blocks written into CLAUDE.md / AGENTS.md / Cursor rules are all written in a compressed register — articles and filler dropped, every command, path, flag and gate kept exact. The block every session loads is held to ~1.5KB of essentials; the full guide is the dolly skill (loaded when relevant) or dolly guide. Sizes are asserted in tests/footprint.test.mjs, so they cannot creep back.

Contributing

git clone https://github.com/nick-delirium/dolly.git
cd dolly
npm install     # installs TypeScript, then `prepare` builds dist/
npm link        # puts `dolly` on your PATH, pointing at this checkout
npm test        # 87 tests

npm install first is not optional: npm link runs the prepare script, and on a fresh clone with no node_modules that fails with tsc: command not found.

After editing src/, rebuild — the global dolly resolves to dist/cli.js, not to your sources:

npm run build   # or `npm run dev` to watch, or just `npm test` (it builds first)

After a git pull

npm install     # that's it

The link itself survives a pull: it points at the checkout directory, not at a snapshot. But dist/ is gitignored, so a pull never updates the compiled output — your dolly keeps running the previous build until you rebuild. npm install covers both cases, since prepare recompiles and you can't tell from a pull whether devDependencies changed. npm run build alone is enough when they haven't.

This one bites silently: the symptom is a fix that "doesn't work" because you're still running a stale dist/.

Re-run npm link only if package.json's bin changed. npm materialises one symlink per bin entry at link time, so a newly added or renamed binary won't be on your PATH until you link again.

Undo the link with npm unlink -g dolly.

Cutting a release

npm version patch          # or minor/major — bumps package.json and tags vX.Y.Z
# bump .claude-plugin/plugin.json to match (a test enforces this)
npm test && git push --follow-tags

The update check reads the newest vX.Y.Z tag on the remote, so a release that is committed but not tagged is invisible to everyone else.

The clone ships dolly's own .claude/, .mcp.json, CLAUDE.md and .dolly/, so once dolly is on your PATH a Claude Code session in the checkout gets the skills, slash commands, MCP server and hooks automatically — and dolly board shows the project's real task history. dolly is developed with dolly.

Requirements: Node ≥ 18. Zero runtime dependencies; TypeScript is the only devDependency. TypeScript → ESM.

Conventions worth knowing before a PR are in the project brief: dolly project (or read .dolly/project.md). The invariants section is the one to read first — the block markers in context/*.md are parsed, so changing their shape needs a migration in src/migrate.ts.

Prior art

OpenSpec for spec-driven change folders and the archive-on-done idea. graft for git-tracked agent context and multi-agent install. CodeGraph for CLI/MCP parity. caveman for the compressed instruction register and plugin layout.

MIT.

↑↓ navigate openesc close