Task Archiving Protocol
This skill encapsulates the complete protocol for archiving docs/TASK.md to docs/tasks/
and docs/PLAN.md to docs/plans/ (in lockstep with TASK.md).
When to Archive
Archive docs/TASK.md ONLY when:
- Starting a NEW task AND
docs/TASK.mdexists with DIFFERENT content - Completing a task (Orchestrator Completion stage)
DO NOT archive when:
- Refining/clarifying the CURRENT task (overwrite instead)
docs/TASK.mddoes not exist
[!IMPORTANT] PLAN.md rotates in lockstep with TASK.md. Whenever TASK.md is archived for a NEW task, the old
docs/PLAN.md(if present) is archived too — see "PLAN.md Archiving (Lockstep)" below. On task refinement, PLAN.md is overwritten in place, never archived.docs/ARCHITECTURE.mdis a LIVING document and is never touched by this skill.
Decision Logic: New vs Refinement
IF user request implies a NEW SEPARATE feature/refactor:
→ Archive existing TASK.md, then create new
IF user request is a clarification/refinement of CURRENT task:
→ Overwrite TASK.md, do NOT archive
Indicators of NEW task:
- Different feature/component mentioned
- "Create new task for...", "Start working on..."
- Completed previous task
Indicators of REFINEMENT:
- "Clarify requirement X", "Add detail to..."
- Same feature context as current TASK.md
Protocol Steps
Step 1: Check Condition
IF NOT exists("docs/TASK.md"):
SKIP archiving → Create new TASK.md
Step 2: Extract Metadata
Read from current docs/TASK.md:
- Task ID from "0. Meta Information" section
- Slug from "0. Meta Information" section
If Meta Information is missing or malformed:
- Use slug from task title (H1 header)
- Generate ID via tool if available, otherwise use
000or increment last known ID manually.
Step 3: Generate Filename
The ID comes from the DOCUMENT, not from the directory. Step 2 already read it from the Meta block; Step 3 only confirms it is free and turns it into a filename.
Option A: Use Tool (Preferred)
python3 .agent/tools/task_id_tool.py "<slug-from-meta>" --proposed-id "<id-from-meta>" --no-correction
result = generate_task_archive_filename(
slug=slug_from_meta,
proposed_id=task_id_from_meta, # MANDATORY whenever Step 2 found an ID
allow_correction=False, # a cited ID is never renumbered silently
)
proposed_idis mandatory whenever Step 2 produced an ID. Omit it and the tool auto-generatesmax(existing)+1, and that scan counts the task's own planner sub-task files (task-NNN-SubID-slug.md). A task with sub-tasks and no parent archive is then handedNNN+1— a number contradicting its Meta block, itsdocs/plans/plan-NNN-*.md, its sub-tasks and every commit citing it (ARC-1).- Omit
proposed_idonly when Step 2 found no ID. That is the sole case where the tool may choose, and the sole case where Step 4 writes an ID back. - Read
result["status"]:"generated"→ continue."conflict"→ STOP, report both paths, the operator decides."error"→ STOP. - A populated sub-task namespace is not a conflict — with
--proposed-idthe tool checks parent archives only.
Option B: Manual Generation (Fallback)
- Filename =
task-<ID-from-Meta>-<slug-from-Meta>.md. The ID is copied, never invented. - Conflict check, parents only:
ls docs/tasks/task-<ID>-*.md— hits shapedtask-<ID>-<digits>-*are SUB-TASKS, expected, not conflicts. Any other hit, or anydocs/plans/plan-<ID>-*.md, is a real conflict → STOP. - Only if Step 2 found no ID:
NNN = max(ID over docs/tasks/ and docs/plans/) + 1, sub-tasks included in that maximum — a populated namespace reserves its parent's number.
Step 4: Verify the ID — do NOT renumber
The Meta-block ID is the identity; the filename follows it. This step asserts, it does not assign.
ASSERT id_in_filename == id_in_meta_block
IF they differ: STOP. Do not move the file. Report both values.
Do not edit docs/TASK.md here. By archiving time the ID is cited outside the Meta row:
sub-task files, the plan archive, commit messages, CHANGELOG.md, ledger records, and the
document's own H1. Rewriting one row silently falsifies the rest.
Writing an ID back is legitimate in exactly two cases:
- Meta carries no ID — nothing is being overwritten.
- The ID was never published — no sub-tasks, no plan archive, no commit or ledger row cites
it, confirmed by the operator. Then it is a rename of a private draft, and it is a
whole-document edit: Meta row, H1,
Archive nameand every in-body citation move together.
Step 5: Archive (Move File)
Collision guard first. mv overwrites, and Step 3's conflict check sees parent archives
only — a destination shaped like a sub-task (task-096-01-x.md) is invisible to it.
test -e docs/tasks/{filename} && echo "STOP: target exists" || mv docs/TASK.md docs/tasks/{filename}
[!IMPORTANT] The
mvis SAFE TO AUTO-RUN. Do NOT wait for user approval.
Step 5.5: Rebase the moved document's links (MANDATORY)
docs/tasks/ is one directory deeper than docs/. Every relative link in the document was
written against docs/ and now denotes a different path, or nothing (ARC-2).
python3 .agent/tools/rebase_links.py docs/tasks/{filename} --from docs --to docs/tasks
- Do not hand-edit links. A rule like "replace
../with../../" fixes zero of the forms this corpus actually contains — measured. The tool computes what each link denoted from the old directory and re-expresses that same denotation from the new one. docs/PLAN.mdis a mutable slot, not an identity. A link to it inside this task means this task's plan, which Step 7 is about to archive. Pass the pairing so the link is written to the archive name rather than to a path that dies seconds later:--slot docs/PLAN.md=docs/plans/plan-{used_id}-{slug}.md- Pass that slot ONLY when
docs/PLAN.mdexists (ARC-4). A task that reached analysis but not planning has no plan to archive, so Step 7.1 will skip it and the mapped file is never created. Mapping it anyway authors a citation to a path that will not exist and reports success.archive_protocol.py:363-366implements the condition;TestPlanSlotIsConditionalpins it. Without the slot the same link is reported asUNMAPPED_SLOTand left alone, which is correct. - Exit
0clean,3needs review,1a link regressed,2could not run. A3lists links that were left alone — broken before the move, or resolving only by accident. Never "fix" those by guessing; report them.
Step 6: Validate
Verify:
-
docs/TASK.mddoes NOT exist -
docs/tasks/{filename}exists - Every link the document denoted before the move still resolves. File-arrived is not
enough: the move can leave a present file full of dead citations.
rebase_links.pyexits non-zero when a link it rewrote fails to resolve.
If validation fails:
- Check if mv command returned error
- If
docs/TASK.mdstill exists: retry mv or notify user - DO NOT create new TASK.md until validation passes
PLAN.md Archiving (Lockstep)
docs/PLAN.md has no Meta block or identity of its own, and there is always exactly
one PLAN per TASK. Therefore PLAN.md is never archived independently — it rotates
in lockstep with TASK.md, reusing the same ID and slug TASK.md was just archived
under.
Result:
docs/tasks/task-NNN-slug.md↔docs/plans/plan-NNN-slug.mdalways pair up.
[!IMPORTANT]
docs/tasks/is shared — it also holds planner sub-task files (task-NNN-SubID-slug.md). PLAN.md therefore archives to a separatedocs/plans/directory, never todocs/tasks/.
Step 7: Archive PLAN.md (Lockstep)
Run this only after Step 6 validation passed (TASK.md successfully archived for a NEW task). Execute sub-steps 7.1–7.7 in order.
7.1 — Condition check. No PLAN.md means the task never reached planning:
IF NOT exists("docs/PLAN.md"):
SKIP plan archiving → DONE
7.2 — Refinement guard. Step 7 is normally only reached on the NEW-task path; this
guard is stated explicitly so re-planning the SAME task overwrites docs/PLAN.md:
IF Step 1 decision was REFINEMENT (not a NEW task):
DO NOT archive PLAN.md — Planner overwrites it in place → DONE
7.3 — Ensure destination (idempotent, SAFE TO AUTO-RUN):
mkdir -p docs/plans
7.4 — Derive filename (NO new ID generation):
plan_filename = "plan-{used_id}-{slug}.md"
{used_id} and {slug} are REUSED VERBATIM from the TASK.md archive just completed — the
used_id returned by generate_task_archive_filename in Step 3, which Step 4 already asserted
equal to the Meta-block ID.
Under this protocol the two values cannot differ (ARC-10). Step 3 runs with correction OFF, so
status: "corrected" is unreachable and a collision returns "conflict" → STOP. Step 4 then
asserts id_in_filename == id_in_meta_block and stops on a mismatch, before Step 7 is ever
entered. The one path where a corrected ID survives is the automated mirror called as
archive_protocol.archive_task(allow_renumber=True); there TASK and PLAN both take the corrected
ID and stay paired.
7.5 — Collision guard:
IF exists("docs/plans/{plan_filename}"):
STOP. Do NOT overwrite. Report to user:
"Plan archive collision: docs/plans/{plan_filename} already exists."
7.6 — Archive (move):
mv docs/PLAN.md docs/plans/{plan_filename}
7.6.5 — Rebase the plan's links (mirrors Step 5.5):
python3 .agent/tools/rebase_links.py docs/plans/{plan_filename} \
--from docs --to docs/plans --slot-must-exist \
--slot docs/TASK.md=docs/tasks/task-{used_id}-{slug}.md
The slot is not optional here. By this point docs/TASK.md is already gone — Step 5 moved it,
and Step 7 runs only after Step 6 passed. An existence-based rule would see the plan's
[docs/TASK.md](TASK.md) as broken both before and after, leave it dead forever, and report
nothing. The slot map carries the identity without touching the filesystem.
--slot-must-exist is required here and forbidden at Step 5.5 (ARC-6). Here the TASK
archive was created in Step 5, so a mistyped {slug} names a file that is already absent and the
tool exits 1. At Step 5.5 the plan archive does not exist yet, so the same assertion would fail the
protocol's own happy path. Without the flag a one-character slug typo rewrote the citation and
returned exit 0 with "ok": true, and Step 7.7's assertion passed on a dead link.
7.7 — Validate:
ASSERT NOT exists("docs/PLAN.md")
ASSERT exists("docs/plans/{plan_filename}")
ASSERT every link denoted before the move still resolves # rebase_links exit code
IF validation fails: retry mv once, else notify user.
Edge Cases
| Case | Behavior |
|---|---|
docs/PLAN.md absent |
Skip silently (7.1). Not an error — many tasks reach analysis but not planning. |
| Task refinement (same task) | Step 7.2 returns early. PLAN.md is overwritten in place by the Planner. |
docs/plans/ missing |
mkdir -p in 7.3 creates it. |
Corrected used_id |
Unreachable under this protocol: Step 3 runs correction OFF and Step 4 stops on a mismatch. Only archive_task(allow_renumber=True) reaches it, and there 7.4 keeps TASK and PLAN paired. |
| Orphan PLAN.md (PLAN.md exists, no TASK.md) | Step 1 skipped archiving (no TASK.md) → Step 7 is never reached. The orphan PLAN.md is left in place. Warn the user it may be stale. PLAN.md has no independent ID, so it cannot be safely archived alone — this is a deliberate limitation. |
Safe Commands (AUTO-RUN)
See
skill-safe-commandsfor the authoritative list of commands safe for auto-execution.
Key commands for this skill:
mv docs/TASK.md docs/tasks/...— archiving TASK.mdmv docs/PLAN.md docs/plans/...— archiving PLAN.md (lockstep)mkdir -p docs/plans— ensure PLAN archive destination existsls,cat— validation
Safety Boundaries
This skill performs file mutations (mv, mkdir). The following boundaries apply:
- Move, never delete. Archiving uses
mvonly —docs/TASK.md/docs/PLAN.mdcontent is relocated, never destroyed. - No overwrite. Step 5 and Step 7.5 enforce collision guards: if the target archive filename already exists, STOP and report — never overwrite an existing archive.
- Lockstep integrity. PLAN.md is archived only after TASK.md archiving is validated (Step 6). A failed TASK archive aborts the PLAN archive.
- Living documents untouched.
docs/ARCHITECTURE.mdis never moved or archived. - Validate before proceeding. Each
mvis followed by an existence assertion (Steps 6, 7.7); on failure, retry once then notify the user — do not continue blindly.
Integration
Required by Agents
- Analyst (
02_analyst_prompt.md): Before creating new TASK.md - Orchestrator (
01_orchestrator.md): At Completion stage
Example Flow
Trigger: User says "Create new task for implementing login feature."
- Agent loads
skill-archive-task. - Checks
docs/TASK.mdexists? → YES (containsTask {OLD_ID}: {Old Feature}). - Decision: NEW task (different feature) → Archive.
- Step 2 — read the Meta block: Task ID =
{OLD_ID}, Slug ={old-slug}. The ID comes from the document, never from the directory listing. - Step 3 — confirm the ID is free and turn it into a filename.
--proposed-idis mandatory here, because Step 2 produced an ID:python3 .agent/tools/task_id_tool.py "{old-slug}" --proposed-id "{OLD_ID}" --no-correctionstatus: "conflict"→ STOP; the operator decides. Sub-task files matchingtask-{OLD_ID}-<digits>-*are not a conflict. - Step 4 — assert
id_in_filename == {OLD_ID}. This step never assigns. - Step 5 — collision guard, then move:
test -e docs/tasks/task-{OLD_ID}-{old-slug}.md \ && echo "STOP: target exists" \ || mv docs/TASK.md docs/tasks/task-{OLD_ID}-{old-slug}.md - Step 5.5 — rebase the moved document's links.
docs/tasks/is one level deeper, so every relative link now denotes something else.docs/PLAN.mdis a slot, so pass the pairing Step 7 is about to create — only becausedocs/PLAN.mdexists in this example:
With nopython3 .agent/tools/rebase_links.py docs/tasks/task-{OLD_ID}-{old-slug}.md \ --from docs --to docs/tasks \ --slot docs/PLAN.md=docs/plans/plan-{OLD_ID}-{old-slug}.mddocs/PLAN.md, drop the--slotline entirely (ARC-4). Do NOT add--slot-must-existhere: the plan archive is created in step 10, so this slot is a forward reference. Exit3lists links left alone deliberately — report them, never guess a target. - Step 6 — validate:
docs/TASK.mdgone ✓, archive present ✓, links still resolve ✓. - Step 7 — PLAN lockstep.
docs/PLAN.mdexists? → YES.mkdir -p docs/plans, reuse{OLD_ID}+{old-slug}from the TASK archive above.- Collision guard, then
mv docs/PLAN.md docs/plans/plan-{OLD_ID}-{old-slug}.md. - Step 7.6.5 — rebase, with
docs/TASK.mdmapped to the archive it just became:
The slot map is resolved before any filesystem probe, which is why it still works here —python3 .agent/tools/rebase_links.py docs/plans/plan-{OLD_ID}-{old-slug}.md \ --from docs --to docs/plans --slot-must-exist \ --slot docs/TASK.md=docs/tasks/task-{OLD_ID}-{old-slug}.mddocs/TASK.mdwas moved away in step 7 above.--slot-must-existbelongs here and not in step 8: the TASK archive already exists, so a mistyped{old-slug}exits 1 (ARC-6). - Validate:
docs/PLAN.mddoes NOT exist ✓.
- Create new
docs/TASK.mdfor the login feature with ID{NEW_ID}.