taskops
v0.5.0
Published
Markdown-first task/run graph CLI with EoW closure, delegation, and Obsidian-friendly TaskOps work trees.
Maintainers
Readme
TaskOps CLI
TaskOps is a work-truth protocol, not just a task manager.
It exists to keep human + AI work honest: agents must not silently stop, pretend work is done, or continue executing a wrong plan. Plans, execution logs, blockers, delegation, and closure evidence stay inspectable and versionable in plain markdown.
It separates two truths:
- Task graph — the decomposition truth: objective, task groups, selected snapshots, readiness, and explicit terminal EoW nodes.
- Run graph — the execution truth: what actually happened, including execution, exploration, decomposition, delegation, waiting, verification, failure, and closure.
Why TaskOps exists
Traditional task lists usually blur three different questions:
- What is the right decomposition of the work?
- What is actually runnable right now?
- What happened during execution, including delegation or waiting?
TaskOps keeps those layers separate so an agent or human can make honest decisions instead of pretending every task is a flat checklist item.
Core concepts
work— the top-level objective container (entityType: work).task-groups/— versioned decomposition trees.snapshots/— selected version paths through the task graph.runs/<run-id>/— independent execution graphs.eow— explicit End of Work nodes attached to terminal task/run branches.runRefs— task-side references to run nodes.sourceTaskId/sourceTaskGroupVersionId— run-side references back to the task graph.type: delegate+status: waiting— explicit human/AI/agent delegation points.
Install
npm install -g taskopsThen run:
taskops --helpQuick start: the smallest useful loop
# 1. Create a work tree around one objective
taskops init ./my-work \
--id my-work \
--title "My Work" \
--objective "Ship the first useful version" \
--language en
# 2. Validate and summarize the current graph
taskops validate ./my-work
taskops summary ./my-work
# 3. Inspect machine-readable state
taskops show ./my-work --json
# 4. Classify what can honestly happen next
taskops classify-runnable ./my-work task-design --json
# 5. Advance bounded work
taskops run ./my-work --executor dry-run --max-steps 1 --jsontaskops run dispatches by readiness: runnable tasks execute, needs_decomposition tasks expand the task graph, needs_exploration tasks create exploratory run evidence, and blocked/waiting/delegated work stops instead of being silently skipped.
Example: AI-assisted OAuth refactor
A large refactor should not be trusted to a flat checklist or a disappearing chat transcript. With TaskOps:
- A
workcaptures the objective: “Refactor the OAuth flow safely.” - The task graph decomposes analysis, token validation changes, regression tests, migration notes, and review.
- The runner classifies each task as
runnable,needs_decomposition,needs_exploration, orblocked. - The run graph records what the agent actually did, which tests failed, what was delegated, and why each branch was closed.
- Reviewers inspect
taskops summary,runs/<run-id>/events.jsonl, and EoW nodes before trusting completion.
TaskOps tells agents how the work is actually getting done.
Commands
taskops init <dir> --id <id> --title <title> --objective <objective> [--language en|ko]
taskops validate <path>
taskops summary <path> [--write]
taskops show <path> [--json]
taskops classify-runnable <work-dir> <task-id> [--json]
taskops next <work-dir> [--json]
taskops explain <work-dir> [--json]
taskops close <work-dir> <run-node-id|task-id> [--reason <reason>] [--json]
taskops unblock-check <work-dir> [--dry-run] [--json]
taskops run <work-dir> [--run-id <id>] [--agent <agent-id>] [--executor dry-run|openclaw-agent] [--max-steps <n>] [--until <iso-timestamp>] [--timeout <seconds>] [--json]
taskops decompose <work-dir> --task-group-id <id> --spec <spec.json>
taskops refactor <work-dir> --task-group-id <id> --spec <spec.json> --supersedes <version-id>
taskops vault-init <vault-dir> [--repo-url <url>] [--branch main] [--auto-sync true|false]
taskops git-status <vault-dir>
taskops git-sync <vault-dir> [--message <msg>] [--branch <branch>]
taskops watch-sync <vault-dir> [--message <msg>] [--debounce-ms <ms>] [--branch <branch>]Honest-action commands
These three read-only/guarded commands are the "honest loop" surface. They never lie about progress:
taskops next <work-dir> [--json]— return the one next honest action:execute,decompose,explore,wait,delegation_pending,blocked,done, orno_runnable. The output also includes the target task or run node and a recommended command. It does not mutate state.taskops explain <work-dir> [--json]— explain why the work is or is not done. Reports the closure summary, the next honest action, and the concrete reasons the work is still open (missing terminal EoW, blockers, waiting delegations, runnable/decompose/explore tasks, validation errors). It does not mutate state.taskops close <work-dir> <run-node-id|task-id> [--reason <reason>] [--json]— make EoW closure explicit and guarded. Refuses to close a task that already has an EoW, has open child branches, or is not yetdoneunless--reason manual_verifiedis supplied (in which case the task status is also flipped todoneso closure counts stay honest). Refuses to close a run node that is notdone/cancelledunless an explicit reason (failure,superseded,cancelled,manual_verified) is supplied; refuses delegated/waiting nodes unless one ofmanual_verified|cancelled|supersededis given. On success it writes an EoW file (and acloses_withrun edge for run nodes).
taskops next ./my-work --json
taskops explain ./my-work
taskops close ./my-work task-foo --reason manual_verifiedRun a TaskOps work
taskops run <work-dir> executes runnable tasks against the canonical markdown state. It is the bridge between the passive task graph and an actual run graph: agents (OpenClaw or otherwise) invoke this command rather than mutating files by hand.
# bounded single step using the safe synthetic executor
taskops run ./my-work --executor dry-run --max-steps 1
# real execution against an OpenClaw agent, capped by a deadline
taskops run ./my-work --executor openclaw-agent --agent main --until 2026-05-13T09:00:00+09:00
# machine-readable summary for programmatic callers
taskops run ./my-work --max-steps 3 --jsonThe runner:
- Re-uses an existing active run when there is exactly one, else creates/uses
runs/run-main/. Override with--run-id. - Rechecks blocked tasks that declare
blockedByreferences before selecting the next action. When every blocker is resolved, the runner reopens the task (status: pending) and clearsrunReadiness: blockedunlessunblockRunReadinesssays what readiness to use next. Usetaskops unblock-check <work-dir> --dry-run --jsonto inspect the same transition without mutating files. - Picks the next task deterministically: active snapshot order, then
task.order, thenidlexicographic. Only tasks with statuspending/activeare eligible. Tasks classified asblockedare excluded; tasks classified asrunnable,needs_decomposition, orneeds_explorationare dispatched to the matching runner step. - For
runnabletasks: creates the run node, mutates task status to done, attaches task and run EoW nodes, and writes thecloses_withedge. - For
needs_decompositiontasks: creates atype: decompositionrun node, expands the task graph by writing a child task group and version (dry-run synthesizes a deterministic placeholder;openclaw-agentdelegates authoring to the agent), updates the parent task'schildTaskGroupId, marks the parent done with an EoW reasondecomposed_by_runner, closes the run node with an EoW reasondecomposition_recorded, and extends the active snapshot'sselectedVersionsso the new child task group/version becomes visible to subsequent steps of the same runner invocation. - For
needs_explorationtasks: creates atype: explorationrun node, writes a reflection artifact underruns/<run-id>/artifacts/<run-node-id>.md, marks the parent task done with an EoW reasonexploration_recorded_by_runnerandrunReadiness: needs_decomposition(ready for an informed decomposition pass), and closes the run node with an EoW reasonexploration_recorded. - Pauses immediately when it encounters a
status: waitingtask or run node, or atype: delegaterun node that is not yetdone/cancelled. Delegated run nodes are classified bytypefirst, sotype: delegate+status: waitingreportsdelegation_pendingrather than genericwaiting. - Appends a JSONL event log at
runs/<run-id>/events.jsonlplus human entries inruns/<run-id>/run-log.md. - Holds a
.taskops-runner.lockdirectory under the work root and removes it on exit. A second runner against the same work refuses to start.
Stop conditions
--max-steps and --until are both optional and combine with OR semantics: the runner stops before starting a new step if either limit is reached. When neither is supplied the runner defaults to --max-steps 1 (one bounded step). Every action — execute, decompose, or explore — counts as one step against --max-steps.
| Stop reason | Meaning |
| ---------------------- | -------------------------------------------------------------------------------------------------------- |
| all_closed | The selected work is fully closed: every terminal task is closed by task EoW, every run terminal node is closed by run EoW, and no waiting/delegated/blocked work remains. This is the closure-complete terminal state. |
| no_runnable | No remaining task is actionable (runnable, decomposable, or explorable), but the work is not yet closed (terminal EoW coverage incomplete, or otherwise inconsistent). Treat this as a state to inspect rather than a successful finish. |
| blocked_only | Open tasks remain but they are all classified as blocked; resolve the blockers before continuing. |
| waiting | A task or run node is in status: waiting; resolve or cancel it before continuing. |
| delegation_pending | A type: delegate run node is still pending (not done/cancelled); resolve the delegation first. |
| max_steps | The --max-steps budget is exhausted. |
| deadline_reached | --until has already passed when the next step would start. |
| task_failed | The executor reported a non-zero exit, timeout, or refused to author the expected decomposition/artifact.|
| validation_failed | A mid-run re-parse found errors and the runner refused to act. |
--until accepts any value Date.parse understands. When both --until and --timeout are supplied, the per-task timeout is capped at the remaining time before the deadline.
Executors
--executor dry-run(default) — no external process. Synthesises a successful result and mutates the markdown graph. For decomposition steps it writes a deterministic child task group/version with a single blocked placeholder task asking for human input. For exploration steps it writes a deterministic reflection artifact underruns/<run-id>/artifacts/. Intended for smoke tests, dress rehearsals, and skill reviews. It does not perform real work. Pass--executor openclaw-agentto dispatch a real run.--executor openclaw-agent— spawnsopenclaw agent --agent <agent-id> --message <prompt> --json [--timeout <seconds>]. The prompt is tailored to the picked action — execute, decompose, or explore — and instructs the agent not to recursively invoketaskops run. After the agent returns the runner verifies that the expected artifact (the executed task's outcome, the decomposition version index, or the exploration artifact) was authored before marking the step done. Default--agentismain.
Canonical file layout
<taskops-work>/
index.md # entityType: work
work-log.md
task-groups/
<task-group-id>/
index.md
versions/
<version-id>/
index.md
tasks/
<task-id>.md
eow/
<eow-id>.md
snapshots/
<snapshot-id>.md
runs/
<run-id>/
index.md
nodes/
<run-node-id>.md
<eow-id>.md
edges/
<run-edge-id>.md
derived/
canvases/
views/Run readiness
TaskOps classifies each task before execution:
runnable— send it to a run graph.needs_decomposition— split it into a child task group/version.needs_exploration— run/search/try/debug first to learn enough for honest decomposition.blocked— resolve the dependency before continuing.
Closure and delegation
TaskOps does not treat done as the same thing as closure.
A branch is closed when an explicit EoW node is attached. The summary reports closure state, for example:
Terminal task EoW coverage: 4/4
Waiting delegations: 0
Open blockers: 0
Work completion: completeDelegation is represented in the run graph rather than hidden as a vague blocker:
entityType: runNode
type: delegate
status: waiting
delegateeType: human
delegateeRef: jimmy
request: Confirm the constraints needed before downstream execution.
expectedOutput: A clear decision and any constraints that update the task graph.Obsidian and Git-backed vaults
TaskOps works well with Obsidian because canonical state is plain markdown.
Initialize a vault with automatic Git sync metadata:
taskops vault-init ~/vaults/my-taskops-vault \
--repo-url [email protected]:ORG/my-taskops-vault.git \
--branch main \
--auto-sync true \
--language enThen sync manually or watch for changes:
taskops git-status ~/vaults/my-taskops-vault
taskops git-sync ~/vaults/my-taskops-vault --message "Sync TaskOps vault"
taskops watch-sync ~/vaults/my-taskops-vault --debounce-ms 5000The companion Obsidian plugin can read the same markdown state and export derived canvas views.
Examples and docs
- GitHub: https://github.com/jimmylegendary/taskops
- Canonical example:
examples/taskops-canonical-minimal-v1/ - Core model:
docs/CORE_MODEL.md - Markdown format:
docs/MD_FIRST_FORMAT.md - Run readiness:
docs/RUN_READINESS.md
Development
git clone [email protected]:jimmylegendary/taskops.git
cd taskops
npm install
npm run verify
npm run release:preflightLicense
MIT
