Task Lifecycle Policy
P: Canonical lifecycle for creating, executing, and closing tasks under the Scrumban model (
src/core/board_os/). R: Creating a task, transitioning status, or aligning task scripts and templates. S: Reading existing task content unrelated to lifecycle change. N: docs-system.md, agent-workflow.md, _templates/task-detail.md
Nav: Governance Index | Docs Index
SSOT and Mirrors
docs/tasks/TASK-###-slug.md— canonical detail file. One per active or completed task. File frontmatter and body are the source of truth.- the
taskstable in.coding-os/coding-os.db(schema:src/core/thinking_os/database.pymigrations) — derived mirror. Mtime-incremental sync from the detail files; never edited by hand. cos board/cos board --web— live view rendered from the DB.
There is no flat docs/tasks.md index. Status is read from frontmatter, not from a top-level checkbox file.
Board↔git coherence
Because the detail files are the durable, version-controlled truth and the DB is gitignored (.coding-os/), a DB task row whose .md is uncommitted — untracked or modified — is drift: the board knows about work a fresh clone would not. src/core/board_os/git_coherence.py (detect_board_git_drift) is the single detector behind three surfaces:
cos doctor— the on-demandboard.git_trackedWARN check.- the nightly
board_coherencemaintenance task — files one idempotentauto-git-driftboard task while drift persists, so no-hook personas see it without invoking the doctor. - the CI gate — fails on drift in the checked-out tree.
Materialization churn (status transitions, work-log appends, committed <sha> commit links) re-writes .md files as a side-effect; this is expected and is deliberately excluded from the per-turn uncommitted-work nag (session-end.sh). It is reconciled in batches, not auto-committed per operation.
Auto-commit (autonomy-gated). When the project's git autonomy permits unattended writes (COS_GIT_AUTONOMY ∈ {local, local_autonomous, autonomous}), the nightly board_coherence task commits the drift itself — staging exactly the drifted docs/tasks/*.md paths (explicit pathspec, never the whole directory) in one idempotent chore(board): … commit. The tasks-only scope is load-bearing: _post_commit_body.sh appends a committed <sha> work-log line only when a commit also carries non-task files, so a tasks-only commit no-ops that hook and the tree converges clean in a single pass. missing rows (a DB task row whose .md no longer exists) cannot be fixed by a commit and do not block it: the committable untracked/modified set is committed regardless, and the drift task is still filed for the missing rows alone — otherwise one orphaned DB row silently pins hundreds of files dirty forever. When autonomy does not permit the commit, the task only files the drift task and leaves the commit to a human or agent.
Status States
icebox → in_progress → testing → complete, plus blocked reachable from any state (and emergency / archive for the incident and retirement lanes).
Transitions go through cos task-* (or the equivalent MCP tool). The CLI writes the detail-file frontmatter and the DB row atomically; manual edits to one side without the other create drift that the doctor will flag.
Required Artifacts
For any task that is in_progress, testing, blocked, or complete:
- A primary detail file at
docs/tasks/TASK-###-slug.mdauthored fromsrc/templates/task-detail.md. - A non-empty Work Log section once execution starts (transition gates check this).
- An Outcome line at the top, plus Read First, Acceptance (Given/When/Then), and Rollback sections.
- Optional companion reference docs only when the primary file would exceed the size cap (warn ≥1.5 k tokens, block ≥3 k — Rule 14).
Backlog entries that are not yet started may live in cos board (icebox status) without a detail file. Detail file becomes required at the moment of first transition out of icebox.
Execution Rules
- Pull from icebox requires the
readylabel. Withworkflow_policy.require_ready_label(default on),icebox → in_progressis blocked until the task is marked pullable —cos task-ready TASK-NNN(or create with--ready). Theemergencyfast lane is exempt. This separates "groomed idea" from "raw idea": the content DoR gate proves the task is well-formed, thereadylabel proves it was deliberately scheduled. Marking ready also surfaces that DoR check at authoring time —cos_task_readyreuses the same validator theicebox → in_progressgate uses: by default it warns and still sets the label, returning any gaps underdata.dor; withCOS_READY_DOR=strictit refuses the label until the DoR is met (override:COS_DOR_OVERRIDE=1+ a ≥15-charCOS_OVERRIDE_REASON), so a task can no longer be silently labeled ready while incomplete. - Dependency-aware readiness.
depends_onis load-bearing, not documentation. Withworkflow_policy.require_deps_complete(default on),icebox → in_progressis blocked while any task independs_onis not yetcomplete— finish the prerequisites or override withforce=True(cos task-move --force). The error is retryable (categorytransient— the MCP envelope's retryable-by-default class): re-issue the pull once the upstream tasks complete and it succeeds with no edit. Theemergencyfast lane (emergency → in_progress) is exempt, so a fire never waits on a backlog item.cos_task_pickmirrors this — areadyicebox card with any incomplete dependency is omitted from candidates, so the picker only ever surfaces work that is runnable now. - Completion cascade. When a task transitions to
complete, every dependent it unblocks is reconsidered automatically (cos_task_movereturns the result underdata.cascade). A dependent whose dependencies are now all complete and whose body passes the Definition-of-Ready check is auto-labeledready(movingblocked → iceboxfirst if needed) and listed undercascade.readied. A dependent that is unblocked but DoR-incomplete is surfaced undercascade.needs_authoring(not silently hidden — author it, then it's pullable). A dependent still waiting on another open dependency — or on one that wasarchived/cancelled (terminal-failed) — is left where it is undercascade.still_blockedwith a reason, so it never hangs invisibly. The cascade is fire-and-forget: it can never turn a successful completion into a failure. - Atomic claim-next (
cos_task_claim_next). For safe autonomous multi-agent runs, this tool selects and claims the highest-priority runnable-now task in one atomic step — reusingcos_task_pick's dependency-filtered, priority-ordered candidate list, then an atomic→ in_progressmove under the sameBEGIN IMMEDIATE+ compare-and-set the regular transition uses. N sessions racing on the same runnable set each receive a distinct task (data.claimed= the claimed card) ordata.claimed = null— no two sessions ever claim the same task, a loser whose compare-and-set missed walks to the next candidate, and a session already at its per-session WIP cap stops and returnsnull. It never raises and never stalls. - Concurrent sessions don't block each other. With
workflow_policy.per_session_wip(default on), thein_progresscap is counted peragent_session— each session keeps its own focus limit of 1 while two Claude tabs or a Claude + Codex pair run in parallel.testing/emergencycaps stay board-global. Anin_progresstask whose owning session goes inactive and is idle pastreclaim_idle_hours(default 24h) is returned toicebox+readybycos task-reclaim(also run opportunistically bycos daily), so a crashed session never strands work. - Move the task to
in_progressbefore any substantial Write/Edit.enforce-task-startblocks task-less code edits only in formal modes (formal/propose-formal/gov-required); turns classifiedquery/adhoc/choreare exempt, as is a validCLEAR 1gate. The exemption is deliberate (a Q&A turn must not pay refactor-grade enforcement) — which is why the retroactive-work protocol below exists for work that slipped past it. - Retroactive work protocol. Work discovered already done without a card (an exempt-mode session, an emergency fix) still gets the full lifecycle, compressed: create the card, then
cos task-start→cos task-move --to testing→cos task-donein the same sitting, with the verification evidence in the Work Log. NEVER file a card whose Work Log already claims completion and leave it inicebox— that is a zombie card:cos_task_reconcilelists such cards aszombie_icebox, and the board flags themstaleuntil they are either lifecycled throughcompleteor the claim is retracted. - Complexity Gate (AGENTS.md § Core Loop) runs in Classify before Orient. The gate's Dimension Map and Read List inform the rest of the loop and are recorded in the task's Notes when non-trivial.
- Keep Work Log up to date as you go — one bullet per meaningful checkpoint. The CLI's
cos work-log TASK-NNN "note"is preferred so the DB cache (work_log_last_5) stays fresh. - Use
Given / When / Thenfor Acceptance criteria. - Move to
completeonly after Verification Matrix tests for the changed surface pass. Untested error paths fail the gate. - A missing task file fails the DoD gate CLOSED. When
to_status == completeand the task's.mdis absent on disk, the transition is BLOCKED (task file not found — cannot verify DoD) instead of silently skipping the verify-freshness / work-log / Read-First checks. A complete-transition must never be cheaper than one with a present file, so a DB row with no file can no longer close unverified (TASK-532). - Acceptance completeness is re-checked at
complete, graduated by kind. The DoD gate (evaluate_dod) re-reads the task body so the close is never cheaper than the pull: a kind whose DoR requires a Given/When/Then Acceptance (feature / bug / refactor / test / security) is BLOCKed withDOD_ACCEPTANCE_MISSINGwhen that section is absent or malformed atcomplete; kinds that opt out of Acceptance in DoR (docs / chore / spike) only WARN. This closes the DoR-rich / DoD-shallow asymmetry — a task can no longer reachcompletewith the acceptance criteria that are its definition-of-done stripped, or after entering via a path that skipped DoR (emergency lane, force). The severity is derived from the same DoR config that decides whether the kind needs Acceptance, so there is no separate kind list to drift; disable per kind withdefinition_of_done.<kind>.require_acceptance_met: false. - Direct
in_progress → completeis blocked by default. Withworkflow_policy.block_in_progress_to_complete(default on), the shortcut is rejected — routein_progress → testing → completeso the Verification Matrix runs. The state-machine edge stays legal (force=True/cos task-move --forceoverrides for genuine trivial work, and is audited). When the policy knob is off,workflow.transitionfalls back to the legacy soft warning instead of blocking. - Aging blockers surface, never auto-escalate. A card dwelling in
blockedpastworkflow_policy.blocked_sla_hours(default 72h ≈ the "Day 3: surface" escalation rung) is flaggedstaleoncos board/cos dailywith a blocked-specific reason — an observability signal only, so a stuck blocker becomes visible without ever being silently moved to theemergencylane. Set0to disable the flag. - Create-then-park surfaces at turn-end. A card left in
iceboxby the session that created it — carrying neither thereadylabel (a deliberate pull-queue) nor aparked/keeplabel (deliberate backlog) — is a silent create-then-drift: it is invisible tocos_task_pick/cos_task_claim_next, so nothing will ever pull it. Thewarn-abandoned-taskStop hook flags it at turn-end — observability only, never a block — attributing the card to its author via thetask_status_historycreatedrow (so a parked card whoseagent_sessionis NULL is still caught). Resolve it by starting it,cos task-ready-ing it, or labeling itparked/keepto record the deferral as deliberate intent. - Search the repo and the graph before creating any new file, pattern, or rule. Reuse beats reinvention.
Primary Task File Contract
Required sections in this order (template lives at src/templates/task-detail.md):
## Outcome— single sentence describing the externally visible result.## Read First— annotated list of files and refs the executor should load before editing.## Acceptance— numbered list, Given/When/Then format.## Work Log— append-only bullets, newest at the bottom. On a body edit,cos_task_editswaps in the fresh on-disk Work Log in place, so a concurrentcos_work_log_appendthat lands between the editor's fetch and its save is never overwritten (the drawer's body is a snapshot).## Rollback— one-paragraph plan for backing the change out.
Optional sections:
## Notes— working notes, findings, dimension maps for COMPLICATED+ tasks.## Dependencies— only when the task is gated by other tasks; mirrored in thedepends_onfrontmatter.
Frontmatter Fields
| Field | Required | Notes |
|---|---|---|
id |
yes | matches the file name slug |
title |
yes | one line, no trailing punctuation |
swimlane |
yes | agent-defined; common: infra, frontend, docs, research |
kind |
yes | one of feature · bug · chore · spike · docs · refactor · test · security |
status |
yes | icebox · in_progress · testing · blocked · complete · emergency · archive |
priority |
yes | P0 · P1 · P2 · P3 |
appetite |
yes | shape-up budget, e.g. 30m · 2h · 1d · 2w |
epic, labels |
no | grouping helpers |
depends_on, blocked_by |
no | task IDs |
references |
no | doc paths the task points at |
external_ref |
no | optional forge issue/PR link (e.g. github#42), set via cos task-link TASK-NNN <issue>; forge auto-detected from git remote, metadata only — never the canonical id (adr-task-id-allocator-seam.md) |
created, started, completed |
auto | written by the CLI on transitions |
agent_session |
auto | fingerprint of the agent that started the task |
Task ID Scheme
The id is allocated by _next_task_id. Two schemes, set in .coding-os/scrumban-config.yaml:
task_id_scheme |
Format | Use |
|---|---|---|
sequential (default) |
TASK-NNN |
Single-owner projects — readable, sortable, zero config. |
namespaced |
TASK-<NS>-NNN |
Multi-contributor projects — a per-contributor NS keeps each person's counter independent, so two un-synced contributors never compute the same id (the OSS fork/PR collision). |
Under namespaced, NS comes from task_id_prefix (2–8 chars, uppercase, letter-first — e.g. KO) when set; otherwise it is derived stably from git config user.email. The counter is max(ids with this NS) + 1, so KO-… and JD-… sequences never cross. Every task-id-aware site (parser, frontmatter validation, .task-current, commit-linking, git log --grep) matches both TASK-NNN and TASK-<NS>-NNN via one backward-compatible regex, so a project can switch schemes without breaking existing ids. Rationale + the options considered (incl. a future GitHub-issue allocator): adr-task-id-collision-resistance.md.
CLI and MCP Surface
| Action | CLI | MCP |
|---|---|---|
| View board | cos board / cos board --web |
cos_task_board |
| Create | cos task-create --title "…" --swimlane … --kind … |
cos_task_create |
| Mark ready | cos task-ready TASK-NNN |
cos_task_ready |
| Start | cos task-start TASK-NNN |
cos_task_move → in_progress |
| Reclaim zombies | cos task-reclaim |
cos_task_reclaim |
| Move to testing | cos task-move TASK-NNN --to testing |
cos_task_move → testing |
| Complete | cos task-done TASK-NNN |
cos_task_move → complete |
| Block | cos task-move TASK-NNN --to blocked |
cos_task_move → blocked |
| Append work log | cos work-log TASK-NNN "note" |
cos_work_log_append |
| Daily standup | cos daily |
cos_task_daily |
| WIP check | cos wip |
cos_task_wip_check |
| Pick next | cos task-pick |
cos_task_pick |
The make task-* wrappers
cos task-* is the canonical task interface. src/templates/_base/Makefile.base keeps a few make task-* targets purely as muscle-memory wrappers — each shells straight out to cos:
| Wrapper | Delegates to |
|---|---|
make task-start TASK=098 |
cos task-start TASK-098 |
make task-done TASK=098 |
cos task-done TASK-098 |
make task-block TASK=098 REASON="…" |
cos task-block TASK-098 --reason "…" |
make task-create |
prints the cos task-create usage (it needs --swimlane / --kind) |
The legacy file-based task system — the task-*.sh scripts and the flat docs/tasks.md index — has been removed. make task-* provides nothing that cos task-* does not; new code and docs should always call cos task-* directly.
Script Output Convention
| Prefix | Meaning | Stream |
|---|---|---|
OK: |
success | stdout |
ERROR: |
fatal | stderr, exit 1 |
WARN: |
non-fatal | stderr |
INFO: |
progress / context | stdout |
Outcome Tracking
After every transition into complete or blocked, the workflow records an outcome row to coding-os.db for the Learning Loop:
| Field | Values | Source |
|---|---|---|
| outcome | success · rework · partial · blocked |
agent assessment |
| derived_outcome | same enum | verify ledger (.last-verify.json) when a fresh same-HEAD verdict exists; else copies outcome |
| derived_provenance | ledger · self_report |
which source filled derived_outcome — the learning loop prefers ledger (a signal the agent cannot self-report) |
| duration_min | integer | session elapsed time |
| model | active model id | dispatcher |
| skills_used | JSON array | skill enforcement hook |
Outcomes feed pattern extraction every N completions. See agent-workflow.md § Memory & Learning.