the problem
agents start each session without memory of the last one. humans forget too, just more slowly. what usually survives is a task list: titles, maybe a status.
a title doesn’t carry why the work exists, what was decided along the way, which options were ruled out, what blocks it, or how anyone will know it is done. without that, a fresh session either re-derives it (expensive), guesses (risky), or asks someone who might not remember.
so i treat project management as a context-management problem: tasks become useful when they are connected to goals, decisions, evidence, history and the coordination between the people and agents doing the work.
the approach
PM CLI is a git-native tracker for humans and coding agents. items live as plain text files inside the repository, next to the code they describe, so they travel with branches and pull requests and need no separate server.
- a small vocabulary. goals and epics, tasks, decisions, plans; each item carries acceptance criteria that describe the outcome.
- typed relationships. parents, blockers and “discovered from” links, so the shape of the work is queryable.
- evidence. files, docs, comments and test commands attached to the item they support.
- history. every change is recorded in an append-only stream with its author.
- context on request. an agent asks for the slice it needs, within a token budget, and is told what was left out.
behaviour moves out of the core into configuration, an SDK and separately published extensions, so you can build what you need from the parts.
a reproducible handoff
a synthetic project with two sessions that share nothing but the repository. session 1 records a goal (“CSV export for monthly reports”), a decision (stream rows instead of buffering), two tasks with acceptance criteria (one blocked by the other), a progress comment and a linked file. session 2 starts cold.
session 2 asks for orientation
pm context --for orientreturns the goal, the decision, the in-progress task, the blocked task with its blocker, and session 1’s last actions, within a 3,000-token budget.it drills into the active task
pm getshows the acceptance criteria still to meet (the 409 for open months), the linked file, who held the claim, and what it omitted with the flag to fetch it.
# session 1 recorded a goal, a decision, two tasks (one blocked) and:pm claim rpt-5epo --start …pm comments rpt-5epo --add "text=Endpoint streams via a cursor (see rpt-gfhy). Remaining: 409 for open months." …pm files rpt-5epo --add "path=src/reports/csv.ts,scope=project" …# session 2: a fresh agent, nothing carried overpm context --for orient --limit 5summary: … in_progress: 1 blocked: 1 …hierarchy[1]: - id: rpt-l1pf title: CSV export for monthly reports … children[3]{id,title,type,status,children_total,children_closed}: rpt-5epo,Streaming CSV endpoint,Task,in_progress,0,0 rpt-gfhy,Stream CSV rows instead of building files in memory,Decision,open,0,0 rpt-yzm4,Download button on the reports page,Task,blocked,0,0activity[3]{id,op,ts,author}: rpt-5epo,release,"2026-10-18T19:03:01.720Z",session-1 rpt-5epo,files_add,"2026-10-18T19:02:56.447Z",session-1 rpt-5epo,comment_add,"2026-10-18T19:02:51.788Z",session-1blockers[1]{id,title,blocked_by,blocked_by_title,blocked_by_status}: rpt-yzm4,Download button on the reports page,rpt-5epo,Streaming CSV endpoint,in_progress … token_budget: 3000 … estimated_tokens: 988 within_budget: truepm get rpt-5epoitem: id: "rpt-5epo" title: "Streaming CSV endpoint" … status: "in_progress" … acceptance_criteria: "GET /reports/:month.csv streams rows with a header line; Open months return 409 with an explanation" parent: "rpt-l1pf" …linked: files[1]{path,scope}: src/reports/csv.ts,projectclaim_state: claimed: false …omission_receipt: has_omissions: true … comments,"--fields comments"
the script creates a throwaway repository, turns telemetry off and prints this transcript. run it yourself with PM CLI installed:
curl -fsSLO https://preu.at/case-study/pm-handoff.sh && bash pm-handoff.shread the script before running it
#!/usr/bin/env bash
# Reproducible PM CLI handoff demo from https://preu.at/work/pm-cli
# Download: https://preu.at/case-study/pm-handoff.sh
#
# Two "sessions" share nothing but the repository: session 1 records a goal, a decision,
# work with acceptance criteria and evidence; session 2 starts cold and asks PM for context.
# Synthetic project, throwaway directory, telemetry off. Requires @unbrained/pm-cli (`pm`).
#
# bash pm-handoff.sh # prints the transcript
# KEEP=1 bash pm-handoff.sh # keeps the demo repo for inspection
set -euo pipefail
dir="$(mktemp -d "${TMPDIR:-/tmp}/pm-handoff.XXXXXX")"
[[ "${KEEP:-}" == 1 ]] || trap 'rm -rf "$dir"' EXIT
cd "$dir"
git init -q
run() { printf '$ %s\n' "$*"; "$@"; printf '\n'; }
id_of() { pm list --json 2>/dev/null | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const d=JSON.parse(s);const it=(d.items||d).find(i=>i.title===process.argv[1]);process.stdout.write(it?it.id:"")})' "$1"; }
echo "# pm $(pm --version) — demo repo: $dir"
pm init rpt --defaults --agent-guidance skip >/dev/null 2>&1
pm config project set telemetry-tracking off >/dev/null 2>&1
echo "# ---- Session 1 ----"
export PM_AUTHOR=session-1
pm create epic "CSV export for monthly reports" \
--description "Finance re-types numbers from the reports page into spreadsheets." \
--ac "Finance can download any closed month as CSV" >/dev/null
epic="$(id_of 'CSV export for monthly reports')"
pm create decision "Stream CSV rows instead of building files in memory" --parent "$epic" \
--description "Large months exceed the 512 MB worker limit when buffered." \
--ac "Export memory stays flat regardless of month size" >/dev/null
decision="$(id_of 'Stream CSV rows instead of building files in memory')"
pm create task "Streaming CSV endpoint" --parent "$epic" \
--ac "GET /reports/:month.csv streams rows with a header line" \
--ac "Open months return 409 with an explanation" >/dev/null
api="$(id_of 'Streaming CSV endpoint')"
pm create task "Download button on the reports page" --parent "$epic" --blocked-by "$api" \
--ac "Button appears only for closed months" >/dev/null
run pm claim "$api" --start
run pm comments "$api" --add "text=Endpoint streams via a cursor (see $decision). Remaining: 409 for open months."
run pm files "$api" --add "path=src/reports/csv.ts,scope=project"
pm release "$api" >/dev/null
echo "# ---- Session 2: a fresh agent, no memory of session 1 ----"
export PM_AUTHOR=session-2
run pm context --for orient --limit 5
run pm get "$api"
download pm-handoff.shunder the hood
three pieces of the public source, pinned to one commit so the links keep pointing at what is described here.
context selection and packing
ranks the items that matter for the request, spends at most a token budget on them, and reports omitted sections with the flag to restore each.
src/sdk/query/context.ts @ 828edf4resumable item transactions
atomic batches of creates, updates and closes behind a journal and a workspace writer lock. Stable ids double as idempotency keys, so an interrupted batch resumes instead of duplicating; compensations run in reverse.
src/sdk/item-transaction.ts @ 828edf4history replay
each change is an append-only patch with hash verification. replaying the chain rebuilds an item, which restore, repair and the health checks use to detect drift.
src/core/history/replay.ts @ 828edf4
what this doesn’t show
measured scope
one synthetic project, one machine, one run. it shows that recorded context is returned to a cold session in a bounded, inspectable form.
- it doesn’t measure whether agents finish work faster or better with it, or how many tokens it saves overall.
- it says nothing about large teams, very large trackers or many agents writing concurrently.
- acceptance criteria describe the intended outcome; they are not proof the outcome was reached. that still takes tests, review and evidence.
- the token figure is PM CLI’s own estimate, not a tokenizer count for a specific model.
try it
ask your agent to set it up for your project and see how it fits your existing workflow, rather than copying someone else’s setup.
npm install -g @unbrained/pm-cliPM CLI on the web
documentation, the package list and hosted ways to reach the same project record.
- website and docspm-cli.unbrained.devguides, reference and every published package.
- pm-webpm-web.unbrained.devbrowse, search and manage pm projects in the browser.
- pm-mcppm-mcp.unbrained.devconnect agents and IDEs over remote MCP and OAuth.
- pm-gptpm-gpt.unbrained.devwork with pm projects in ChatGPT and Codex.
- pm-searchpm-search.unbrained.devfind decisions, tasks and fixes in public repositories’ pm items, with commit-pinned links.