| name | task-workflow |
| description | Shared implementation workflow for task-based skills, with profile-check sites wrapped in Jinja conditionals. |
| user-invocable | false |
Context Requirements
This skill is invoked by other skills (e.g., aitask-pick, aitask-explore, aitask-review) after they have selected a task. The calling skill MUST establish the following context before handing off:
| Variable | Type | Description |
|---|
task_file | string | Path to selected task file (e.g., aitasks/t16_implement_auth.md or aitasks/t10/t10_2_add_login.md) |
task_id | string | Task identifier (e.g., 16 or 16_2) |
task_name | string | Filename stem for branches/worktrees (e.g., t16_implement_auth or t16_2_add_login) |
is_child | boolean | Whether this is a child task |
parent_id | string/null | Parent task number if child (e.g., 16), null otherwise |
parent_task_file | string/null | Path to parent task file if child (e.g., aitasks/t16_implement_auth.md), null otherwise |
active_profile | object/null | Loaded execution profile from calling skill (or null if no profile) |
active_profile_filename | string/null | Scanner-returned filename for the profile (e.g., fast.yaml or local/fast.yaml), null if no profile |
previous_status | string | Task status before workflow began (for abort revert, e.g., Ready) |
folded_tasks | array/null | List of task IDs folded into this task (e.g., [106, 129_5]), or null/empty if none. Set by aitask-explore when existing tasks are folded into a new task. |
skill_name | string | Name of the calling skill for feedback tracking (e.g., pick, explore, pr-import) |
feedback_collected | boolean | Guard flag — initialized to false. Set to true after the Satisfaction Feedback Procedure runs. Prevents double execution across workflow paths. |
usage_collected | boolean | Guard flag — initialized to false. Set to true before the unconditional usage bump fires in Satisfaction Feedback Step 0. Set-before-call so a mid-procedure failure does not cause a retry double-bump. |
detected_agent_string | string/null | Agent string (e.g., claudecode/opus4_6). Set by either the verify-path append in planning.md Step 6.1 or by Agent Attribution in Step 7. Consumed by Agent Attribution (fast-path) and by Satisfaction Feedback in Step 9b to skip re-detection. Initialized to null. |
Workflow
Step 3: Task Status Checks
After a task is selected and confirmed, perform these checks before proceeding to Step 4.
Check 1 - Done but unarchived task:
- Read the task file's frontmatter
status field
- If status is
Done:
Check 2 - Orphaned parent task (empty children_to_implement):
Check 3 - Manual-verification task:
- Read the task file's frontmatter
issue_type field
- If
issue_type is manual_verification:
- Execute the Manual Verification Procedure (see
manual-verification.md)
- Skip Steps 6-8; proceed to Step 9 after the procedure returns
- Steps 4 (ownership) and 5 (worktree) still run before dispatch — manual verification is work that should be owned and locked
Check 4 - In-flight gated task, all gates now pass:
- Run
./.aitask-scripts/aitask_gate.sh archive-ready <taskid> and parse:
NO_GATES or BLOCKED:<csv> → skip this check (fall through to normal selection). This is the common case today — no task declares gates yet.
ALL_PASS → the task's substantive work landed in an earlier session and every declared gate now passes, but archival was deferred (it was kept active). This is the next-pick backstop for the deferral in Step 9. Use AskUserQuestion:
- Question: "This task has all gates passing and is ready to archive. Would you like to archive it now?"
- Header: "Archive"
- Options:
- "Yes, archive it" (description: "Skip implementation and archive the now-complete task")
- "No, keep it active" (description: "Leave the task as-is and end the workflow")
- If "Yes, archive it" → skip Steps 4-8, proceed directly to Step 9 (Post-Implementation) for archival
- If "No, keep it active" → end the workflow
Check 5 - In-flight task, resume from first unmet checkpoint:
This makes task-workflow re-entrant: a task left Implementing (crash, session loss, multi-day work) resumes from the first unmet recorded checkpoint instead of restarting at planning.
- Read the task file's frontmatter
status. If it is not Implementing, skip this check (a fresh task plans from scratch).
- Run
./.aitask-scripts/aitask_gate.sh resume-point <taskid> and parse the single-word result:
Note: Check 1, Check 2, and Check 4 should NOT set the task status to "Implementing" — the task is already done (or its work is complete and gated). Skip Step 4 (Assign Task) entirely when archiving via Check 1, Check 2, or Check 4. Check 3 does run Step 4 as normal. Check 5 also runs Step 4 (the in-flight lock must be reclaimed) — it does not skip it; step-skipping happens post-reclaim via Re-entry Routing.
If none of the checks trigger, proceed to Step 4 as normal.
Step 4: Assign Task to User
-
Email resolution (priority order):
-
Check task metadata: Read the assigned_to field from the task file's frontmatter.
-
Check userconfig: Read aitasks/metadata/userconfig.yaml and extract the email: field (if file exists).
-
Mismatch check: If both assigned_to and userconfig email are non-empty and DIFFERENT, use AskUserQuestion:
- Question: "Task is assigned to <assigned_to> but your userconfig email is <userconfig_email>. Which email to use?"
- Header: "Email"
- Options:
- "Keep <assigned_to>" (description: "Continue with the existing assignment")
- "Use <userconfig_email>" (description: "Override with your local email")
- Use the selected email and proceed to the Claim task ownership step below.
-
If assigned_to is non-empty (and matches userconfig, or userconfig is empty): use assigned_to. Display: "Using email from task metadata: <email>". Skip to Claim task ownership.
{# ---------- default_email ---------- #}{% if profile.default_email is defined %}
-
Profile-driven email resolution (profile '{{ profile.name }}', default_email: {{ profile.default_email }}):
{# ---------- default_email value ---------- #}{% if profile.default_email == "userconfig" %}
- Use the userconfig email (from step 2). If userconfig is empty/missing, fall back to reading
aitasks/metadata/emails.txt (first email). Display: "Profile '{{ profile.name }}': using email <email> (from userconfig)". If both are empty, prompt the user via AskUserQuestion as described in step 6 below.
{% elif profile.default_email == "first" %}{# default_email: literal "first" #}
- Read
aitasks/metadata/emails.txt and use the first email address. Display: "Profile '{{ profile.name }}': using email <email>". If emails.txt is empty or missing, prompt the user via AskUserQuestion as described in step 6 below.
{% else %}{# default_email: literal email address #}
- Use
{{ profile.default_email }} directly. Display: "Profile '{{ profile.name }}': using email {{ profile.default_email }}".
{% endif %}{# ---------- end default_email value ---------- #}
- Then skip step 6 and proceed to the Userconfig sync check below.
{% else %}{# default_email: key absent from profile #}
-
Profile check: If the active profile has default_email set:
- If value is
"userconfig": Use the userconfig email (from step 2). If userconfig is empty/missing, fall back to reading aitasks/metadata/emails.txt (first email). Display: "Profile '<name>': using email <email> (from userconfig)". If both are empty, fall through to the AskUserQuestion below.
- If value is
"first": Read aitasks/metadata/emails.txt and use the first email address. Display: "Profile '<name>': using email <email>". If emails.txt is empty or missing, fall through to the AskUserQuestion below.
- If value is a literal email address: Use that email directly. Display: "Profile '<name>': using email <email>"
- Skip the AskUserQuestion below
{% endif %}{# ---------- end default_email ---------- #}
-
Otherwise, ask for email using AskUserQuestion:
- Read stored emails:
cat aitasks/metadata/emails.txt 2>/dev/null | sort -u
- Question: "Enter your email to track who is working on this task (optional):"
- Header: "Email"
- Options:
- List each stored email from emails.txt (if any exist)
- "Enter new email" (description: "Add a new email address")
- "Skip" (description: "Don't assign this task to anyone")
- If "Enter new email" selected:
- Ask user to type their email via
AskUserQuestion with free text (use the "Other" option)
-
Userconfig sync check: After email is resolved, if the final email differs from the userconfig email (or userconfig doesn't exist):
- Use
AskUserQuestion:
- Question: "The selected email (<email>) differs from your userconfig (<userconfig_email>). Update userconfig.yaml?"
- Header: "Userconfig"
- Options:
- "Yes, update userconfig" (description: "Save this email to userconfig.yaml for future use")
- "No, keep current userconfig" (description: "Use this email for now but don't change userconfig")
- If "Yes": Write
email: <email> to aitasks/metadata/userconfig.yaml (create file if needed with comment header # Local user configuration (gitignored, not shared))
- If "No": Proceed without updating
- Skip this check if: the final email matches userconfig, or email was resolved from userconfig itself, or no email was selected ("Skip")
-
Claim task ownership (lock, update status, commit, push):
If email was provided (new or selected):
./.aitask-scripts/aitask_pick_own.sh <task_num> --email "<email>"
If no email (user selected "Skip"):
./.aitask-scripts/aitask_pick_own.sh <task_num>
Parse the script output:
-
OWNED:<task_id> — Success. Proceed to Step 5.
-
FORCE_UNLOCKED:<previous_owner> + OWNED:<task_id> — Force-unlock succeeded. Inform user: "Force-unlocked stale lock held by <previous_owner>." Proceed to Step 5.
-
One of LOCK_RECLAIM:, RECLAIM_CRASH:, or RECLAIM_STATUS: (in addition to OWNED:) — task was already in Implementing and re-locked. When multiple are present, prefer LOCK_RECLAIM > RECLAIM_CRASH > RECLAIM_STATUS. Parse the signal-specific fields and execute the Crash Recovery Procedure (see crash-recovery.md) with signal_type and the parsed fields.
Signal field formats:
LOCK_RECLAIM:<prev_hostname>|<prev_locked_at>|<current_hostname> — multi-PC reclaim (cross-host).
RECLAIM_CRASH:<prev_locked_at>|<prev_hostname>|<prev_pid> — same-host crash (PID anchor is dead). Common case after a tmux/host-shell crash.
RECLAIM_STATUS:<prev_status>|<prev_assigned_to> — anomaly fallback (lock missing or pre-PID-anchor lock).
When the procedure returns:
reclaim → ownership is held here (OWNED: confirms). Continue to the Re-entry Routing gate at the end of Step 4 (it checks resume_point); if no resume applies, proceed to Step 5 normally.
decline → return to the calling skill's task selection. Do NOT proceed. (The procedure has already released the lock and reverted the task to Ready.)
-
LOCK_FAILED:<owner>|<locked_at>|<hostname> — Task is locked by another user/PC. Parse the |-separated fields for lock details. Use AskUserQuestion:
-
LOCK_ERROR:<message> — Lock system error (fetch failure, race exhaustion, etc.). Display the error and suggest running ./.aitask-scripts/aitask_lock_diag.sh for troubleshooting. Use AskUserQuestion:
- Question: "Lock system error: <message>. How to proceed?"
- Header: "Lock error"
- Options:
- "Retry" (description: "Try acquiring the lock again")
- "Continue without lock" (description: "Proceed without locking (risky if multiple users)")
- "Abort" (description: "Stop the workflow")
- If "Retry": Re-run
aitask_pick_own.sh (same command). Parse output again.
- If "Continue without lock": Skip lock acquisition, proceed to Step 5 (task status will be updated but no lock held).
- If "Abort": End the workflow.
-
LOCK_INFRA_MISSING — Lock infrastructure not initialized. Inform user to run ait setup and abort.
Note: The script handles email storage, lock acquisition, task metadata update (status → Implementing, assigned_to), and git add/commit/push internally. If the script fails entirely (non-zero exit without structured output), display the error and abort.
-
Materialize the active-gates tuple (ALWAYS runs — never profile-omitted): With ownership held, derive and persist the task's enforced gate set under the current profile. If active_profile_filename is set, run:
./.aitask-scripts/aitask_gate.sh materialize-active <task_num> --profile aitasks/metadata/profiles/<active_profile_filename>
Parse the single stdout line:
MATERIALIZED:<csv> — active set persisted and committed. MATERIALIZED:(empty) means a fully profile-filtered (or ungated) task — that persisted empty set is exactly what makes a declared-but-unrendered gate invisible to every enforcer. Continue.
MATERIALIZED_UNCOMMITTED:<csv> — the tuple was written and is enforced locally, but the path-scoped git commit failed (e.g. an index lock). Warn: "active-gates tuple written but not committed — other checkouts won't see it until the task data is committed." and continue; a later ./ait git commit of aitasks/ picks it up.
NOOP:unchanged — re-pick under the same profile with unchanged inputs; nothing rewritten (any previously pending commit of the task file was verified/repaired). Continue.
NOOP_UNCOMMITTED:pending-persist — the tuple is unchanged and enforced locally, but the task file still carries changes git refused to commit. Warn as for MATERIALIZED_UNCOMMITTED and continue.
- Nonzero exit — the re-derivation failed (unreadable/invalid profile, compute backend unavailable). The helper clears any previously persisted tuple (its stderr says whether the clear succeeded), but the raw-
gates: fallback is only the task's declared intent — it does NOT include this profile's default_gates, so continuing could silently under-enforce the current profile. ABORT the pick: display "active-gates materialization failed (<output>) — fix the profile / compute backend and re-pick t<task_num>.", then execute the Task Abort Procedure (see task-abort.md). Do NOT proceed to Step 5.
If active_profile_filename is NOT set (a manual/resume invocation without a profile), skip the call. This is the claim-time-snapshot governance model: with no profile in scope there is nothing to re-derive against, so the previously persisted tuple (when present and intact) remains the enforced snapshot, and a task never materialized follows its raw gates: field. (./.aitask-scripts/aitask_gate.sh active-gates-status <task_num> --profile <file> shows the stored tuple + freshness when a profile is in scope.)
This claim-time materialization replaces the former Step-7 gates: backfill: raw gates: stays the task's declared intent, and the persisted active_gates tuple is the enforced set that planning, the Step-9 orchestrator, and the archival guard all read. Re-running on every (re-)pick re-derives the set under the CURRENT profile, so a profile switch can never leave stale enforcement. (Gate CLI verb shapes — decision vs action vs introspection — are documented once in gate-cli.md.)
-
Store previous status for potential abort (remember the previous_status from context)
-
Re-entry Routing gate: After ownership is held (via any success path above — OWNED, FORCE_UNLOCKED + OWNED, or crash-recovery → reclaim), check the resume_point context variable set by Step 3 Check 5. If it is IMPLEMENT or POSTIMPL, follow the Re-entry Routing procedure below instead of proceeding to Step 5 → Step 6. Otherwise (unset, or PLAN), proceed to Step 5 normally. (The routing is gated on resume_point, not on which ownership path was taken — a force-unlock takeover of an in-flight task returns plain OWNED with no reclaim signal, and its resume must not be lost.)
Re-entry Routing
Runs only when resume_point (from Step 3 Check 5) is IMPLEMENT or POSTIMPL and Step 4 left ownership held. It resumes the in-flight task from the first unmet checkpoint instead of restarting at planning. Profile-invariant.
-
Plan-existence guard: Run ./.aitask-scripts/aitask_query_files.sh plan-file <taskid>. If NOT_FOUND (a checkpoint was recorded but no plan was externalized — e.g. a failed externalization), discard the resume: clear resume_point and fall back to the normal flow (Step 5 → Step 6, re-plan). If PLAN_FILE:<path>, read the plan and continue below.
-
Environment setup (Step 5) with reuse: If a worktree for <task_name> already exists — git worktree list --porcelain shows a branch refs/heads/aitask/<task_name> line — reuse it (work in that directory); do NOT recreate the branch/worktree. Otherwise run Step 5 as normal. For current-branch profiles (no worktree), Step 5 is a no-op and you work on the current branch.
-
Route by resume_point:
IMPLEMENT → resume at Step 7's "Follow the approved plan" implementation body. Re-run only the Pre-implementation ownership guard and the Agent Attribution Procedure (both idempotent; attribution re-records the resuming agent), then go straight to implementation. Skip Step 7's post-approval one-time gates:
- Cross-Repo Child Assignment and the risk-mitigation pre-task creation (the post-approval "before" follow-ups) — these are non-idempotent task creators that end the workflow when they fire, so a task that is still a normal
Implementing single task is necessarily past them; re-running would double-create.
- The
plan_approved / risk_evaluated gate re-recordings and the risk-level field write — already done in the original session; re-running only adds redundant commits.
POSTIMPL → resume at Step 9 (Post-Implementation), skipping Steps 6–8 (the code is already committed and review_approved was recorded after the Step 8 commit). Step 9 is safe to re-enter: its merge approval is NON-SKIPPABLE (re-asked), a re-merge of an already-merged branch is a git no-op, and archival just moves and commits the task file. For child tasks, the "verify plan completeness before archival" sub-step backstops the Final Implementation Notes.
Step 5: Environment and Branch Setup
Note: For fully autonomous remote workflows (Claude Code Web), use the aitask-pickrem skill instead — it skips all environment setup and always works on the current branch.
{# ---------- create_worktree ---------- #}{% if profile.create_worktree is defined %}
{# ---------- create_worktree value ---------- #}{% if profile.create_worktree %}- Create a separate branch and worktree for this task. Display: "Profile '{{ profile.name }}': creating worktree". Continue with the If Yes branch below.
{% else %}{# create_worktree: value is false / falsy #}- Work on the current branch in the current directory. Display: "Profile '{{ profile.name }}': working on current branch". Continue with the If No branch below.
{% endif %}{# ---------- end create_worktree value ---------- #}
{% else %}{# create_worktree: key absent from profile #}
If Yes:
- Extract
<task_name> from the filename
- For parent:
t16_implement_channel_settings from t16_implement_channel_settings.md
- For child:
t16_2_add_login from t16_2_add_login.md
{# ---------- base_branch ---------- #}{% if profile.base_branch is defined %}
-
Use base branch {{ profile.base_branch }} for this task. Display: "Profile '{{ profile.name }}': using base branch {{ profile.base_branch }}".
{% else %}{# base_branch: key absent from profile #}
-
Profile check: If the active profile has base_branch set:
- Use the specified branch name. Display: "Profile '<name>': using base branch <branch>"
- Skip the AskUserQuestion below
Otherwise, ask which branch to base the new branch on using AskUserQuestion:
- "Which branch should the new task branch be based on?"
- Options: "main (Recommended)" / "Other branch"
- If "Other branch", ask user to specify the branch name
{% endif %}{# ---------- end base_branch ---------- #}
-
Create worktree directory:
mkdir -p aiwork
-
Create both the branch and worktree in a single command:
git worktree add -b aitask/<task_name> aiwork/<task_name> <base-branch>
Where <base-branch> is main or the user-specified branch.
-
Work in the aiwork/<task_name>/ directory for implementation
If No:
- Work directly on the current branch in the current directory
Step 6: Create Implementation Plan
Full planning workflow: Read planning.md for the complete Step 6 procedure including:
- 6.0: Check for Existing Plan (profile-aware)
- 6.1: Planning (EnterPlanMode, child tasks, complexity assessment)
- Child Task Documentation Requirements
- Save Plan to External File (naming conventions, metadata headers)
- Checkpoint (post-plan action)
After the checkpoint in planning.md:
- If child tasks were created and the child checkpoint returned "Stop here" → collect Satisfaction Feedback Procedure (see
satisfaction-feedback.md) with skill_name from context variables, then END the workflow (do NOT proceed to Step 7/8/9)
- If child tasks were created and the child checkpoint returned "Start first child" → restart with
/aitask-pick <parent>_1 (do NOT proceed to Step 7)
- Otherwise (normal single-task plan) → proceed to Step 7
Step 7: Implement
Pre-implementation ownership guard:
Before starting implementation, verify that ownership/lock was acquired (Step 4 should have done this, but this guard catches edge cases like plan mode deferral):
- Read the task file's frontmatter
status and assigned_to fields
- Resolve the current user's email: use the email from Step 4 if available, otherwise read from
aitasks/metadata/userconfig.yaml
- If status is
Implementing AND assigned_to matches the current user's email: Ownership appears to have been acquired in Step 4 — but verify the lock is held on this host before assuming so. Run ./.aitask-scripts/aitask_lock.sh --check <task_id> and parse the hostname: line from the output. Compare against hostname (the running shell's hostname).
- If the hostname matches or
--check shows no lock at all (single-user / no-remote mode): ownership is confirmed for this host. Proceed normally.
- If the hostname differs (a different machine holds the lock under your email): a multi-PC reclaim has been detected by the guard. Execute the Crash Recovery Procedure (see
crash-recovery.md) with signal_type=LOCK_RECLAIM, parsing prev_hostname from --check output and using the current hostname as current_hostname. If the procedure returns reclaim, run ./.aitask-scripts/aitask_pick_own.sh <task_num> --email "<email>" to refresh the lock to this host before proceeding. If decline, return to the calling skill's task selection. (Same-host crash recovery is moot here: by the time Step 7 fires, Step 4's aitask_pick_own.sh already owned the lock and surfaced any RECLAIM_CRASH: signal.)
- Otherwise (status is not
Implementing, or assigned_to is empty/missing, or assigned_to does not match the current user's email): Ownership was not properly acquired. Display: "Guard: task ownership not confirmed — acquiring ownership now."
Record implementing agent: Execute the Agent Attribution Procedure (see agent-attribution.md) to record which code agent and model is implementing this task.
Repository structure awareness: Before starting implementation, read repo-structure.md
Cross-repo child assignment (post-approval creation): If cross_repo_planned is true (set in planning.md §6.1 — the approved plan is a cross-repo paired design), execute the Cross-Repo Child Assignment Procedure (see cross-repo-child-assignment.md) now. It creates the cross-repo parent first, then assigns all children (local + cross-repo) to their parents with their plans, demotes the local parent to a parent-of-children, and presents its own child checkpoint. When it returns, the workflow has ended (via that checkpoint's "Start first child" / "Stop here") — do NOT continue with the normal single-task implementation below or proceed to Step 8. (This is the post-approval creation gate: planning runs in read-only plan mode, so no tasks were created during Step 6.)
{%- if profile.record_gates is defined and profile.record_gates %}
Record plan-approved gate: Reaching Step 7 means the Step 6 checkpoint approved the plan. Execute the Gate Recording Procedure (see gate-recording.md) with task_id, gate_name=plan_approved, status=pass, fields="type=human".
{%- endif %}
Risk fields (post-approval write): If the approved plan contains a ## Risk section (authored by the Risk Evaluation Procedure during planning), write the two decided levels to the task's frontmatter now:
./.aitask-scripts/aitask_update.sh --batch <task_id> \
--risk-code-health <risk_level_code_health> \
--risk-goal-achievement <risk_level_goal_achievement>
Skip silently if the plan has no ## Risk section (e.g. the task is not risk-gated). This is the post-approval write gate: planning runs in read-only plan mode, so the fields are not written during Step 6.
{%- if profile.record_gates is defined and profile.record_gates and 'risk_evaluated' in rendered_set %}
Record risk-evaluated gate (only when the task will NOT be orchestrator-recorded): When the plan has a ## Risk section, decide whether to self-record risk_evaluated. The Step-9 orchestrator records it for any task whose enforced active set contains risk_evaluated; self-recording here too would double-record. Check first:
./.aitask-scripts/aitask_gate.sh should-self-record <task_id> risk_evaluated
If it exits 0 (the gate is not in the task's active set), execute the Gate Recording Procedure (see gate-recording.md) with task_id, gate_name=risk_evaluated, status=pass, fields="type=machine". If it exits 1 (active), skip — the Step-9 orchestrator records it (no double-record).
{%- endif %}
{%- if 'risk_evaluated' in rendered_set %}
Risk-mitigation "before" creation (post-approval): If the approved plan has a ### Planned mitigations subsection with ≥1 before line (authored during planning by the Risk-Mitigation Follow-up Procedure), execute Part 2 (Step 7 "before" creation) of that procedure now (see risk-mitigation-followup.md). It creates each "before" mitigation as an independent task the original depends on (not a child), read-modify-writes the original's depends: and risk_mitigation_tasks to wire the blocking edge, and back-fills the plan's mitigation links.
If it returns risk_before_created: true, the original is now blocked by an unfinished mitigation and must not be implemented this session. Stop the original here:
- Release the task lock via the Lock Release Procedure (see
lock-release.md).
- Revert the task to
Ready and clear assigned_to (it will show Blocked in ait ls until the mitigation lands):
./.aitask-scripts/aitask_update.sh --batch <task_num> --status Ready --assigned-to ""
- Commit and push the status revert:
./ait git add aitasks/
./ait git commit -m "ait: Revert t<task_num> to Ready (risk mitigation pending)" 2>/dev/null || true
./ait git push
- Display: "Created risk-mitigation 'before' task(s) the original depends on. Task t<task_id> reverted to Ready — implement the mitigation first, then re-pick t<task_id> (its plan will be force re-verified)." Then END the workflow — do NOT proceed to the implementation below or to Step 8.
If it returns risk_before_created: false (no "before" mitigations), continue to implementation normally.
{%- endif %}
Follow the approved plan, working in the directory specified in the plan metadata.
Update the external plan file as you progress:
- Mark steps as completed
- Note any deviations or changes from the original plan
- Record issues encountered during implementation
IMPORTANT: Do NOT commit changes automatically after implementation. Proceed to Step 8 for user review and approval.
Note: When committing implementation changes (in Step 8), the commit message must follow the <issue_type>: <description> (t<task_id>) format. See Step 8 for details.
Step 8: User Review and Approval
⚠️ NON-SKIPPABLE — Auto mode and execution profiles do NOT bypass this review.
The AskUserQuestion below is load-bearing infrastructure, not a routine
confirmation. Auto mode's "minimize interruptions / prefer assumptions for
routine decisions" guidance and execution-profile shortcuts
(skip_task_confirmation, post_plan_action, etc.) target other prompts in
the flow — they do NOT cover this review. The only valid skips are profile
keys explicitly named in this SKILL.md as covering Step 8 review (currently:
none). Skipping this prompt removes the user's last chance to test the change
before it lands in git.
Explicit acceptance required — every iteration. When the user picks
"Need more changes", the loop returns to the top of Step 8: after applying
the requested changes, the AskUserQuestion review prompt MUST be re-issued.
Repeat for every iteration. The ONLY green light to commit is the user
explicitly selecting "Commit changes" with no accompanying notes, requests,
or open concerns. Tacit consent — silence, lack of objection, "looks fine
I guess", a comment that mentions any further change — is NOT acceptance;
keep iterating. There is no upper bound on iterations.
{%- if profile.record_gates is defined and profile.record_gates %}
Procedure-backed gates — run before review (their changes must be reviewed and committed with the code):
Some gates verify work an agent must do (kind: procedure in aitasks/metadata/gates.yaml — e.g. docs_updated). The headless engine defers these; the workflow runs them here, before the change summary, so any files they produce are part of the reviewed diff and land in the Step-8 (t<task_id>) commit.
-
List the task's declared procedure-backed gates that are not terminal-satisfied (their current ledger status is neither pass nor skip):
./.aitask-scripts/aitask_gate.sh procedure-gates <task_id>
-
If the output is empty, skip this step. Otherwise, for each gate <gate> printed:
- Allocate the run and open its
running block:
./.aitask-scripts/aitask_gate.sh begin-procedure <task_id> <gate>
Parse RUN_ID:<run-id> and ATTEMPT:<attempt> from the output.
- Resolve the gate's registry
verifier (an aitask-gate-<name> value) to its SKILL.md in your agent's skill tree and Read-and-follow that skill with arguments <task_id> <attempt> <run-id>. The skill inspects the change, updates the docs confirming with the user, and appends the terminal result (pass / skip / fail) via append --only-if-running <run-id>. (Gate skills currently ship in the Claude tree; per-agent Codex/OpenCode wrappers are tracked in t635_23. Selecting a specific code-agent/model per gate is a planned generalization — see t635_19's follow-up.)
- If the skill records
fail (the user rejected needed doc work), surface it — the gate is unsatisfied and Step-9 archival will be blocked until it is resolved.
This dispatch is generic over kind: procedure gates (docs_updated is the first); a gate already pass/skip is done and is not re-dispatched.
{%- endif %}
After implementation is complete, the user MUST be given the opportunity to review and test changes before any commits are made.
Step 8b: Upstream Defect Follow-up
Entered from Step 8 after the "Commit changes" branch has committed code and plan files. Offers the user a chance to spawn a standalone aitask for an upstream defect surfaced during diagnosis (when the failure was seeded by a separate, pre-existing bug elsewhere — a different script, helper, or module).
Execute the Upstream Defect Follow-up Procedure (see upstream-followup.md) with:
task_file, task_id, is_child, active_profile, parent_id from the current context.
task_slug — filename stem with the t<id>_ prefix stripped (e.g. aitasks/t42_add_login.md → add_login).
When the procedure returns, proceed to Step 8c.
Step 8c: Manual Verification Follow-up
Entered from Step 8b (or directly from Step 8 if 8b was a no-op). At this point code and plan files have already been committed. Offers the user a chance to queue a standalone manual-verification task that will be picked after this task archives.
Execute the Manual Verification Follow-up Procedure (see manual-verification-followup.md) with:
task_file, task_id, is_child, active_profile, parent_id from the current context.
task_slug — filename stem with the t<id>_ prefix stripped (e.g. aitasks/t42_add_login.md → add_login).
{%- if 'risk_evaluated' in rendered_set %}
When the procedure returns, proceed to Step 8d.
Step 8d: Risk-Mitigation "After" Follow-up
Entered from Step 8c. At this point the code and plan files have already been committed. This step applies only when the task was risk-gated; if the approved plan has a ### Planned mitigations subsection with ≥1 after line (authored during planning by the Risk-Mitigation Follow-up Procedure), execute Part 3 (Step 8d "after" creation) of that procedure now (see risk-mitigation-followup.md) with task_id, task_num, plan_file, is_child, parent_id, and active_profile from the current context. If the plan has no such subsection (the common case for non-risk-gated tasks), this step is a no-op.
It creates each "after" mitigation as an independent follow-up task and records it in the original's risk_mitigation_tasks. "After" mitigations block nothing, so the workflow continues normally. When the procedure returns, proceed to Step 9.
{%- else %}
When the procedure returns, proceed to Step 9.
{%- endif %}
Step 9: Post-Implementation
Execute the post-implementation cleanup steps.
If a separate branch was created:
⚠️ NON-SKIPPABLE — Auto mode and execution profiles do NOT bypass this merge approval.
The AskUserQuestion below is a workflow gate, not a routine confirmation. The
following do NOT cover this prompt:
- Execution profiles (no profile key currently bypasses Step 9 merge approval).
- Auto mode / 'work without stopping' system-injected directives.
- Generic user instructions to 'be brief' or 'don't ask'.
The only valid skips are profile keys explicitly named in this SKILL.md as
covering Step 9 merge approval (currently: none) or the user explicitly
authorizing the merge in chat before the prompt fires.
IMPORTANT: Use AskUserQuestion to ask: "Proceed with merge of code changes to main branch?" with options "Yes, proceed with merge" / "No, not yet". Do NOT proceed until the user approves.
{%- if profile.record_gates is defined and profile.record_gates %}
Record merge-approved gate: Once the user approves the merge, execute the Gate Recording Procedure (see gate-recording.md) with task_id, gate_name=merge_approved, status=pass, fields="type=human".
{%- endif %}
-
Check for uncommitted changes:
git status --porcelain
-
Merge branch into main:
git checkout main
git merge aitask/<task_name>
-
Handle merge conflicts: Ask user for guidance if needed.
-
Verify implementation (build / tests / lint):
Do not re-derive which gates this task declares — the gate orchestrator
owns that decision and reports it. Dispatch it once, capturing both its output
and exit status, then branch on the result:
gates_out="$(./ait gates run <task_id> 2>&1)"; gates_rc=$?
-
If gates_rc is nonzero — an infrastructure failure (ait/wrapper
error, task resolution failed, Python unavailable, bad registry path, usage
error). ait gates run exits 0 for every normal gate outcome (a fail/error
is a recorded result, not a process error), so a nonzero exit is never an
ordinary gate failure. STOP and diagnose using gates_out; do NOT fall
through to either branch below (the declared-vs-not decision is only meaningful
on a clean exit).
-
Else if gates_out contains the line No gates declared; nothing to do. —
the task has not opted into the gate system (the common case today). Run the
legacy inline build verification:
- Read
aitasks/metadata/project_config.yaml and check the verify_build field
- If
verify_build is absent, null, or empty (or file doesn't exist): Display "No verify_build configured — skipping build verification." and skip this step.
- If
verify_build is a single command string: Run it.
- If
verify_build is a list of commands: Run each sequentially (stop on first failure).
- If the build fails:
- Analyze the error output and compare against the changes introduced by this task (
git diff against the base)
- If the failure is caused by this task's changes: Go back to the implementation to fix the build errors. After fixing, re-run the build command(s). Repeat until the build passes.
- If the failure is NOT related to this task's changes (pre-existing issue, environment problem, etc.): Log the build failure details in the plan file's "Final Implementation Notes" section under a "Build verification" entry and proceed with the workflow. Do not attempt to fix pre-existing issues.
{%- if profile.record_gates is defined and profile.record_gates %}
- Record build-verified gate: When
verify_build ran, execute the Gate Recording Procedure (see gate-recording.md) with task_id, gate_name=build_verified, status=pass (use fail if the build failed for reasons unrelated to this task and you proceeded), fields="type=machine verifier=<command>". Skip if no verify_build is configured.
{%- endif %}
-
Otherwise — the orchestrator ran the task's declared gates and recorded
each run itself. Read its per-gate report lines ( <gate>: <status> …) and
act per status:
pass / skip — satisfied (skip = not applicable, e.g. no command configured); continue.
fail — an ordinary gate failure: inspect ./ait gate log <task_id> <gate> and diff against the base. If caused by this task, fix and re-run ./ait gates run <task_id>; repeat until it passes. If pre-existing/unrelated, record ./ait gate fail <task_id> <gate> --reason "…" and log it in the plan's Final Implementation Notes.
error ( <gate>: error …) or a malformed-correction line ( ⚠ <gate>: malformed …) — a verifier infrastructure failure (launch failure, timeout, exit 3, or a status that contradicted the exit code), NOT an ordinary gate result. Diagnose the verifier/config (its log, the command, the timeout); do not "fix the code" as if it were a fail, do not record a manual pass, and do not proceed to archival until the verifier itself runs cleanly.
blocked: … — an unlocked gate could not run / none remain runnable. blocked: exhausted … or blocked: upstream … not satisfied means the gate is unsatisfied — surface and diagnose; do not treat it as satisfied or proceed. blocked: pending human signal → route to the human sign-off action (never self-signal).
pending (human gate) — surface to the user; never self-signal.
- Do NOT also run the manual "Record build-verified gate" step in this branch — the orchestrator already appended each gate's run (no double-record).
-
Clean up branch and worktree:
git worktree remove aiwork/<task_name>
rm -rf aiwork/<task_name>
git branch -d aitask/<task_name>
For child tasks — verify plan completeness before archival:
- Read the plan file from
aiplans/p<parent>/<child_plan>
- Verify it contains a "Final Implementation Notes" section with comprehensive details
- If missing or incomplete, add/update it now — the archived plan will serve as the primary reference for subsequent sibling tasks
- Ensure the notes include: actual work done, issues encountered and resolutions, and any information useful for sibling tasks
Run the archive script:
All archival operations (metadata updates, file moves, lock releases, folded task cleanup, git staging, and commit) are handled by a single script call:
For parent tasks:
./.aitask-scripts/aitask_archive.sh <task_num>
For child tasks:
./.aitask-scripts/aitask_archive.sh <parent>_<child>
The script automatically handles:
- Updating task metadata (status → Done, updated_at, completed_at)
- Creating archive directories and moving task/plan files
- For child tasks: removing child from parent's children_to_implement
- For child tasks: archiving parent too if all children are complete
- Releasing task locks (and parent locks if parent was also archived)
- For parent tasks: deleting folded tasks (if any, where status is not Implementing/Done)
- Git staging and committing all changes
Gate guard — pending declared gates (handle BEFORE parsing the success output):
The archive script refuses to archive a task whose declared gates: are not all
pass: it exits non-zero (code 2) and prints a GATE_PENDING:<csv> line (plus a
GATE_BLOCKED line). If you see that, the archival did not happen — do NOT
treat it as success or run the parse step below. (A task with no declared gates,
or all gates passing, exits 0 and archives straight through — the common case
today.) Instead use AskUserQuestion:
-
Question: "Task t<task_id> can't archive yet — pending gate(s): . How would you like to proceed?"
-
Header: "Gates"
-
Options:
- "Resolve now & archive" (description: "Satisfy the pending gate(s) in this session, then archive immediately")
- "Defer — keep in-flight" (description: "Leave the task active and re-enterable; archive on a later pick")
-
If "Resolve now & archive": For each pending gate the user can satisfy now
(run docs/tests, perform the review, etc.), record the pass:
./.aitask-scripts/aitask_gate.sh append <task_id> <gate> pass [k=v ...]
For a procedure-backed gate (kind: procedure — e.g. docs_updated; check
./.aitask-scripts/aitask_gate.sh procedure-gates <task_id>), do not hand-append
a pass — dispatch its skill instead (as in Step 8): begin-procedure <task_id> <gate> → Read-and-follow the gate's aitask-gate-<name> skill in your agent's
skill tree, which records the terminal pass/skip/fail itself.
After each, re-check ./.aitask-scripts/aitask_gate.sh archive-ready <task_id>.
The moment it prints ALL_PASS, re-run the archive script (same command as
above) and continue with the normal success-path parsing below — no re-pick
needed (the archival commit persists the recorded gate runs). If a gate
genuinely cannot be satisfied in this session (e.g. an async reviewer you do
not control), fall through to "Defer".
-
If "Defer — keep in-flight": Do not archive. The task stays active
(Implementing) and re-enterable — its committed code and recorded gate runs
are the resume state, and the task lock is intentionally left held. Inform the
user: "Archival deferred — t<task_id> stays in-flight. Re-pick it with
/aitask-pick <task_id> once the pending gate(s) pass." Skip the
push-after-archival step and proceed directly to Step 9b (Satisfaction
Feedback) — the implementation work was completed.
Parse the script output and handle interactive follow-ups:
The script outputs structured lines. Parse each line and handle accordingly:
ISSUE:<task_num>:<issue_url> — Execute the Issue Update Procedure (see issue-update.md) for the task
RELATED_ISSUE:<task_num>:<issue_url> — A related/merged issue. Execute the Related Issue Update Procedure (see issue-update.md, "Related Issues" section) using --issue-url
PARENT_ISSUE:<task_num>:<issue_url> — Execute the Issue Update Procedure (see issue-update.md) for the parent task
PARENT_RELATED_ISSUE:<task_num>:<issue_url> — A related/merged issue on the parent. Execute the Related Issue Update Procedure (see issue-update.md, "Related Issues" section) using --issue-url
FOLDED_RELATED_ISSUE:<folded_task_num>:<issue_url> — A related issue on a folded task (file deleted). Handle identically to FOLDED_ISSUE: below (same AskUserQuestion, same --issue-url commands, same task_id note)
FOLDED_ISSUE:<folded_task_num>:<issue_url> — The folded task's file has been deleted, so the standard Issue Update Procedure cannot be used (it requires the task file). Instead, handle inline:
- Use
AskUserQuestion:
- Question: "Folded task t<folded_task_num> had a linked issue: <issue_url>. Update/close it?"
- Header: "Issue"
- Options:
- "Close with notes" (description: "Post implementation notes from primary task and close")
- "Comment only" (description: "Post implementation notes but leave open")
- "Close silently" (description: "Close without posting a comment")
- "Skip" (description: "Don't touch the issue")
- If "Close with notes":
./.aitask-scripts/aitask_issue_update.sh --issue-url "<issue_url>" --close <task_id>
- If "Comment only":
./.aitask-scripts/aitask_issue_update.sh --issue-url "<issue_url>" <task_id>
- If "Close silently":
./.aitask-scripts/aitask_issue_update.sh --issue-url "<issue_url>" --close --no-comment <task_id>
- If "Skip": do nothing
- Note: Uses the primary
task_id (not folded_task_num) so the comment references the primary task's commits and plan file
PR:<task_num>:<pr_url> — Execute the PR Close/Decline Procedure (see pr-close-decline.md) for the task
PARENT_PR:<task_num>:<pr_url> — Execute the PR Close/Decline Procedure (see pr-close-decline.md) for the parent task
FOLDED_PR:<folded_task_num>:<pr_url> — The folded task's file has been deleted, so the standard PR Close/Decline Procedure cannot be used. Instead, handle inline:
- Use
AskUserQuestion:
- Question: "Folded task t<folded_task_num> had a linked PR: <pr_url>. Close/decline it?"
- Header: "PR"
- Options:
- "Close with notes" (description: "Post implementation notes from primary task and close/decline")
- "Comment only" (description: "Post implementation notes but leave open")
- "Close silently" (description: "Close/decline without posting a comment")
- "Skip" (description: "Don't touch the PR")
- If "Close with notes":
./.aitask-scripts/aitask_pr_close.sh --pr-url "<pr_url>" --close <task_id>
- If "Comment only":
./.aitask-scripts/aitask_pr_close.sh --pr-url "<pr_url>" <task_id>
- If "Close silently":
./.aitask-scripts/aitask_pr_close.sh --pr-url "<pr_url>" --close --no-comment <task_id>
- If "Skip": do nothing
- Note: Uses the primary
task_id (not folded_task_num) so the comment references the primary task's commits and plan file
FOLDED_WARNING:<task_num>:<status> — Warn the user: "Folded task t has status '' — skipping automatic deletion. Please handle it manually."
PARENT_ARCHIVED:<path> — Inform user: "All child tasks complete! Parent task also archived."
COMMITTED:<hash> — Archival commit was created
Push after archival:
./ait git push
Step 9b: Satisfaction Feedback
Execute the Satisfaction Feedback Procedure (see satisfaction-feedback.md) with skill_name and detected_agent_string from the context variables.
Procedures
The following procedures are in individual files — read on demand when referenced:
- Task Abort Procedure (
task-abort.md) — Lock release, status revert, worktree cleanup. Referenced from Step 6 checkpoint and Step 8.
- Issue Update Procedure (
issue-update.md) — Update/close linked issues during archival. Referenced from Step 9.
- PR Close/Decline Procedure (
pr-close-decline.md) — Close/decline linked pull requests during archival. Referenced from Step 9.
- Contributor Attribution Procedure (
contributor-attribution.md) — Credit PR contributors in commit messages. Referenced from Step 8.
- Code-Agent Commit Attribution Procedure (
code-agent-commit-attribution.md) — Resolve code-agent Co-Authored-By trailer. Referenced from Step 8.
- Plan Externalization Procedure (
plan-externalization.md) — Claude Code only. Copy the approved internal plan file to aiplans/ and parse externalize helper output. Referenced from planning.md (Step 6) and Step 8.
- Model Self-Detection Sub-Procedure (
model-self-detection.md) — Detect the current code agent and model. Referenced from Agent Attribution and Satisfaction Feedback.
- Agent Attribution Procedure (
agent-attribution.md) — Record implementing code agent and model. Referenced from Step 7.
- Satisfaction Feedback Procedure (
satisfaction-feedback.md) — Collect user feedback and update verified model scores. Referenced from Step 9b and standalone skills.
- Lock Release Procedure (
lock-release.md) — Release task locks. Referenced from Task Abort Procedure.
- Crash Recovery Procedure (
crash-recovery.md) — Surveys in-progress work and prompts the user when a reclaim signal is detected (multi-PC, same-host crash via PID anchor, or lock anomaly). Referenced from Step 4 dispatcher and Step 7 ownership guard.
- Manual Verification Procedure (
manual-verification.md) — Interactive checklist runner for issue_type: manual_verification tasks. Referenced from Step 3 (Check 3).
- Auto-Verification Procedure (
auto-verification.md) — Automated verification for manual_verification tasks. Supports two strategies: autonomous (execute inline, document at end) and pre-built (design plan, optionally approve, then execute). Persists to aiplans/p<id>_manual_verification_auto.md. Referenced from manual-verification.md Step 1.5 (whole checklist) and Step 2 (per-item auto verb).
- Manual Verification Follow-up Procedure (
manual-verification-followup.md) — Post-implementation prompt offering to create a standalone manual-verification task, with multi-source candidate discovery. Referenced from Step 8c.
- Upstream Defect Follow-up Procedure (
upstream-followup.md) — Post-implementation prompt offering to spawn a standalone bug aitask for an upstream defect surfaced during diagnosis. Reads the plan file's "Upstream defects identified" subsection. Referenced from Step 8b.
- Remote Drift Check Procedure (
remote-drift-check.md) — Warn before implementation if origin/<base-branch> is ahead of local, with strong emphasis on files the plan touches. Referenced from planning.md Checkpoint.
- Execution Profile Selection Procedure (
execution-profile-selection.md) — Interactive profile scan and selection. Referenced from Step 0a in calling skills.
- Execution Profile Selection Procedure — Auto-Select (
execution-profile-selection-auto.md) — Non-interactive auto-select for remote/web skills. Referenced from Step 1 in aitask-pickrem/aitask-pickweb.
- Batch Task Creation Procedure (
task-creation-batch.md) — Canonical command templates for creating tasks via aitask_create.sh --batch. Referenced from planning.md and multiple skills (explore, review, qa, wrap, pr-import, revert).
{%- if profile.record_gates is defined and profile.record_gates %}
- Gate Recording Procedure (
gate-recording.md) — Record a workflow checkpoint into the task's gate ledger and persist it (path-scoped commit + best-effort push) via aitask_gate_record.sh. Gated behind the record_gates profile key; referenced from Step 7 (plan_approved, risk_evaluated), Step 8 (review_approved), Step 9 (build_verified, merge_approved), and planning.md Checkpoint (plan_approved).
{%- endif %}
Notes
- When working on a child task, always include links to parent and sibling task files for context, plus archived sibling plan files as primary reference for completed siblings
- Archived sibling context priority: When gathering context for a child task, prefer archived plan files (
aiplans/archived/p<parent>/) over archived task files (aitasks/archived/t<parent>/). Plan files contain the full implementation record; task files are just initial proposals. Only use archived task files as fallback when no corresponding plan exists.
- Child tasks are archived to
aitasks/archived/t<parent>/ preserving the directory structure
- Child plans are archived to
aiplans/archived/p<parent>/ preserving the directory structure
- IMPORTANT: When modifying any task file, always update the
updated_at field in frontmatter to the current date/time using format YYYY-MM-DD HH:MM
- Child task naming: Use format
t{parent}_{child}_description.md where both parent and child identifiers are numbers only. Do not insert tasks "in-between" (e.g., no t10_1b between t10_1 and t10_2). If you discover a missing implementation step, add it as the next available number and adjust dependencies accordingly
- When archiving a task with an
issue field, the workflow offers to update/close the linked issue using aitask_issue_update.sh. The SKILL.md workflow is platform-agnostic; the script handles platform specifics (GitHub, GitLab, etc.). It auto-detects commits and includes "Final Implementation Notes" from the archived plan file.
- Folded tasks: When a task has a
folded_tasks frontmatter field (set by aitask-explore or aitask-fold), the listed tasks are deleted during Step 9 archival. Folded tasks have status Folded with a folded_into property pointing to the primary task. They are deleted (not archived) because their full content was incorporated into the primary task's description at creation/fold time.
- Note: Folded tasks are handled by
handle_folded_tasks() in both parent and child archival paths. /aitask-fold and manual folding can add folded_tasks to any task type.
- Symlinks and data worktree: When the project uses a separate
aitask-data branch, aitasks/ and aiplans/ are symlinks to .aitask-data/. See repo-structure.md for the full architecture and rules.
Project Configuration
Project-level settings are stored in aitasks/metadata/project_config.yaml (git-tracked, shared across team). This is separate from execution profiles (workflow behavior) and userconfig.yaml (per-user, gitignored).
| Key | Type | Default | Description | Used in |
|---|
verify_build | string or list | (none — skip) | Shell command(s) to verify the build after implementation | Step 9; build_verified gate |
test_command | string or list | (none — auto-detect) | Shell command(s) for running project tests | aitask-qa Step 4; tests_pass gate |
lint_command | string or list | (none — skip) | Shell command(s) for linting project code | aitask-qa Step 4; lint gate |
attachments_gc_grace | duration (30d/24h/90m/120s/int seconds) | 30d | Grace window before a fully-orphaned attachment (no active or archived task references it) is reclaimed by ait attach gc. Archiving never decrefs, so an archived task's attachments are kept indefinitely; this knob only governs blobs left unreferenced by ait attach rm or task deletion. | ait attach gc |
If the file does not exist or a field is absent, the corresponding feature is skipped.
Execution Profiles
Full reference: See profiles.md for the complete profile schema, available keys, and customization guide.
Profiles are YAML files in aitasks/metadata/profiles/ that pre-answer workflow questions. Default profiles: default (all questions asked) and fast (skip confirmations).