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
README
synced Oct 27, 2026dolly
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, wherevalidatingmeans 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) anddolly-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 (
--mcpordolly 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
| Agent | What gets written |
|---|---|
| Claude Code | .claude/skills/, .claude/commands/dolly/, CLAUDE.md block, .mcp.json |
| Codex CLI | AGENTS.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 CLI | GEMINI.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 else | AGENTS.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 tocontext/steps.mdas entry0003. 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 mcpspeaks JSON-RPC there and--jsonis parsed, so the notice is stderr-only and suppressed entirely formcp,hookandstatusline. - 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
nulland 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:
| Situation | What happens |
|---|---|
| store is current | nothing, silently |
| store is behind, and the upgrade is lossless | applied automatically on the next command, reported on stderr |
| store is behind, and the upgrade moves or rewrites data | every command warns with the exact command; you run dolly migrate |
| store is ahead of your dolly | writes 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:
| Marker | Tool | What the agent is told |
|---|---|---|
.codegraph/ | CodeGraph | codegraph explore "<question>" — symbols' source plus call paths, including dynamic dispatch |
graft/ | graft | graft ask "<task>" — ranked nodes and file:line |
.serena/ | Serena | symbolic 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:
| Need | Command | Contains |
|---|---|---|
| what work exists | dolly board | one line per task |
| orienting, no code yet | dolly context <ref> --brief | spec, criteria, the newest 20 log entries |
| picking up a task, about to write code | dolly context <ref> | the above + full spec + last 3 steps' full context |
| why is this code like this | dolly context <ref> -n 0 | every 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.jsonhooks 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.