Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions .agents/scripts/commands/full-loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,85 @@ Claim → Branch Setup → Task Development → Preflight → PR Create → PR R

## Workflow

### Step 0.45: Task Decomposition Check (t1408.2)

Before claiming and starting work, classify the task to determine if it should be decomposed into subtasks. This catches over-scoped tasks before a worker spends hours on something that should be multiple focused PRs.

**When to run:** After resolving the task description (Step 0) and before claiming (Step 0.5). Skip if `--no-decompose` flag is passed or if the task already has subtasks in TODO.md.

**How it works:**

```bash
DECOMPOSE_HELPER="$HOME/.aidevops/agents/scripts/task-decompose-helper.sh"

# Only run if the helper exists (t1408.1 must be merged)
if [[ -x "$DECOMPOSE_HELPER" && -n "$TASK_ID" ]]; then
# Check if subtasks already exist
EXISTING=$(/bin/bash "$DECOMPOSE_HELPER" classify --task-id "$TASK_ID" --repo-path "$(git rev-parse --show-toplevel)" --quiet 2>/dev/null) || EXISTING=""
EXISTING_KIND=$(echo "$EXISTING" | jq -r '.kind // "atomic"' 2>/dev/null || echo "atomic")

if [[ "$EXISTING_KIND" == "composite" ]]; then
EXISTING_SUBS=$(echo "$EXISTING" | jq -r '.existing_subtasks // empty' 2>/dev/null || echo "")
if [[ -n "$EXISTING_SUBS" && "$EXISTING_SUBS" != "[]" ]]; then
# Subtasks already exist — skip decomposition
echo "[t1408.2] Task $TASK_ID already has subtasks — proceeding with implementation"
fi
fi

# If no existing subtasks, classify the task description
if [[ -z "$EXISTING_SUBS" || "$EXISTING_SUBS" == "[]" ]]; then
CLASSIFY=$(/bin/bash "$DECOMPOSE_HELPER" classify --task "$TASK_DESC" --task-id "$TASK_ID" --repo-path "$(git rev-parse --show-toplevel)" --quiet 2>/dev/null) || CLASSIFY=""
TASK_KIND=$(echo "$CLASSIFY" | jq -r '.kind // "atomic"' 2>/dev/null || echo "atomic")
fi
fi
Comment on lines +88 to +109

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The bash example in this section uses 2>/dev/null to suppress errors from classify and jq commands. This practice hides valuable debugging information, such as API failures, malformed JSON, or missing dependencies, which goes against the repository's general rules. The || construct already handles command failures gracefully. Removing 2>/dev/null will make this example script more robust and easier to debug without changing its behavior on success.

DECOMPOSE_HELPER="$HOME/.aidevops/agents/scripts/task-decompose-helper.sh"

# Only run if the helper exists (t1408.1 must be merged)
if [[ -x "$DECOMPOSE_HELPER" && -n "$TASK_ID" ]]; then
  # Check if subtasks already exist
  EXISTING=$(/bin/bash "$DECOMPOSE_HELPER" classify --task-id "$TASK_ID" --repo-path "$(git rev-parse --show-toplevel)" --quiet) || EXISTING=""
  EXISTING_KIND=$(echo "$EXISTING" | jq -r '.kind // "atomic"' || echo "atomic")

  if [[ "$EXISTING_KIND" == "composite" ]]; then
    EXISTING_SUBS=$(echo "$EXISTING" | jq -r '.existing_subtasks // empty' || echo "")
    if [[ -n "$EXISTING_SUBS" && "$EXISTING_SUBS" != "[]" ]]; then
      # Subtasks already exist — skip decomposition
      echo "[t1408.2] Task $TASK_ID already has subtasks — proceeding with implementation"
    fi
  fi

  # If no existing subtasks, classify the task description
  if [[ -z "$EXISTING_SUBS" || "$EXISTING_SUBS" == "[]" ]]; then
    CLASSIFY=$(/bin/bash "$DECOMPOSE_HELPER" classify --task "$TASK_DESC" --task-id "$TASK_ID" --repo-path "$(git rev-parse --show-toplevel)" --quiet) || CLASSIFY=""
    TASK_KIND=$(echo "$CLASSIFY" | jq -r '.kind // "atomic"' || echo "atomic")
  fi
fi
References
  1. Avoid blanket error suppression with 2>/dev/null in shell scripts. The || construct handles command failures, and leaving stderr visible is crucial for debugging issues like authentication failures, syntax errors, or missing dependencies.

```

**If atomic (or helper unavailable):** Proceed to Step 0.5 (claim and implement directly). This is the default path — most tasks are atomic.

**If composite — interactive mode:**

Show the decomposition tree and ask for confirmation:

```bash
DECOMPOSE=$(/bin/bash "$DECOMPOSE_HELPER" decompose --task "$TASK_DESC" --task-id "$TASK_ID" --repo-path "$(git rev-parse --show-toplevel)" --quiet 2>/dev/null)
SUBTASK_COUNT=$(echo "$DECOMPOSE" | jq '.subtasks | length' 2>/dev/null || echo 0)
Comment on lines +119 to +120

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The bash example in this section uses 2>/dev/null to suppress errors. This practice hides valuable debugging information, which goes against the repository's general rules. The || construct can handle command failures gracefully. Removing 2>/dev/null will make this example script more robust and easier to debug. I've also added || DECOMPOSE="" to the first command to make it safer if set -e is active.

Suggested change
DECOMPOSE=$(/bin/bash "$DECOMPOSE_HELPER" decompose --task "$TASK_DESC" --task-id "$TASK_ID" --repo-path "$(git rev-parse --show-toplevel)" --quiet 2>/dev/null)
SUBTASK_COUNT=$(echo "$DECOMPOSE" | jq '.subtasks | length' 2>/dev/null || echo 0)
DECOMPOSE=$(/bin/bash "$DECOMPOSE_HELPER" decompose --task "$TASK_DESC" --task-id "$TASK_ID" --repo-path "$(git rev-parse --show-toplevel)" --quiet) || DECOMPOSE=""
SUBTASK_COUNT=$(echo "$DECOMPOSE" | jq '.subtasks | length' || echo 0)
References
  1. Avoid blanket error suppression with 2>/dev/null in shell scripts. The || construct handles command failures, and leaving stderr visible is crucial for debugging issues like authentication failures, syntax errors, or missing dependencies.

```

Present to the user:

```text
This task appears to be composite (contains 2+ independent concerns).
Suggested decomposition:

1. {subtask_1_description} (~{estimate})
2. {subtask_2_description} (~{estimate}) [depends on: 1]
3. {subtask_3_description} (~{estimate})

Options:
Y - Create subtasks and dispatch them separately (recommended)
n - Implement as a single task anyway
e - Edit the decomposition before creating subtasks
```

If the user confirms (Y):

1. Create child task IDs using `claim-task-id.sh` for each subtask
2. Add child entries to TODO.md with `blocked-by:` edges from the decomposition
3. Create briefs for each child task (inheriting parent context + subtask-specific scope)
4. Label the parent task `status:blocked` with `blocked-by:` refs to children
5. Ask: "Implement the first leaf task now, or queue all for dispatch?"

**If composite — headless mode:**

Auto-decompose without confirmation (the pulse already classified this as composite):

1. Create child tasks, briefs, and TODO entries automatically
2. Label parent as `status:blocked`
3. Exit cleanly with: `DECOMPOSED: task $TASK_ID split into $SUBTASK_COUNT subtasks ($CHILD_IDS). Parent blocked. Children queued for dispatch.`
4. The next pulse cycle dispatches the leaf tasks

**Depth limit:** Controlled by `DECOMPOSE_MAX_DEPTH` env var (default: 3). At depth 3+, tasks are always treated as atomic regardless of classification.

### Step 0.5: Claim Task (t1017)

If the first argument is a task ID (`t\d+`), claim it before starting work. This prevents two agents (or a human and an agent) from working on the same task concurrently.
Expand Down
49 changes: 38 additions & 11 deletions .agents/scripts/commands/new-task.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,25 +110,48 @@ Use `templates/brief-template.md` as the base. Populate from conversation contex
- Claude Code: `$CLAUDE_SESSION_ID` or the conversation ID from the CLI
- If unavailable: use `{app}:unknown-{ISO-date}` and note "session ID not captured"

### Step 5.5: Decomposition Check (t1408)
### Step 5.5: Classify and Decompose (t1408.2)

After creating the brief, classify the task to check if it should be decomposed:
After creating the brief, classify the task to determine if it should be decomposed into subtasks. This is the earliest point where decomposition can happen — before the task enters the dispatch queue.

```bash
CLASSIFY=$(~/.aidevops/agents/scripts/task-decompose-helper.sh classify "$TASK_TITLE" 2>/dev/null || echo '{"kind":"atomic"}')
TASK_KIND=$(echo "$CLASSIFY" | sed -n 's/.*"kind"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
DECOMPOSE_HELPER="$HOME/.aidevops/agents/scripts/task-decompose-helper.sh"

if [[ -x "$DECOMPOSE_HELPER" ]]; then
CLASSIFY=$(/bin/bash "$DECOMPOSE_HELPER" classify --task "{title}" --quiet 2>/dev/null) || CLASSIFY=""
TASK_KIND=$(echo "$CLASSIFY" | jq -r '.kind // "atomic"' 2>/dev/null || echo "atomic")
Comment on lines +121 to +122

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The bash example in this section uses 2>/dev/null to suppress errors. This practice hides valuable debugging information, which goes against the repository's general rules. The || construct already handles command failures gracefully. Removing 2>/dev/null will make this example script more robust and easier to debug.

Suggested change
CLASSIFY=$(/bin/bash "$DECOMPOSE_HELPER" classify --task "{title}" --quiet 2>/dev/null) || CLASSIFY=""
TASK_KIND=$(echo "$CLASSIFY" | jq -r '.kind // "atomic"' 2>/dev/null || echo "atomic")
CLASSIFY=$(/bin/bash "$DECOMPOSE_HELPER" classify --task "{title}" --quiet) || CLASSIFY=""
TASK_KIND=$(echo "$CLASSIFY" | jq -r '.kind // "atomic"' || echo "atomic")
References
  1. Avoid blanket error suppression with 2>/dev/null in shell scripts. The || construct handles command failures, and leaving stderr visible is crucial for debugging issues like authentication failures, syntax errors, or missing dependencies.

fi
```

If `TASK_KIND` is `composite`, offer to decompose:
**If atomic:** Proceed to Step 6 (add single entry to TODO.md). This is the default.

**If composite:** Present the decomposition to the user:

```text
This task appears to have multiple independent concerns. Would you like to split it into subtasks?
[Y] Yes — decompose into 2-5 subtasks with dependency edges
[n] No — keep as a single task
[e] Edit — show the decomposition and let me adjust
This task appears composite — it contains 2+ independent concerns.
Suggested decomposition:

{task_id}.1: {subtask_1_description} (~{estimate})
{task_id}.2: {subtask_2_description} (~{estimate}) [depends on: .1]
{task_id}.3: {subtask_3_description} (~{estimate})

Options:
1. Create parent + subtasks (recommended for auto-dispatch)
2. Keep as single task (implement all at once)
3. Edit decomposition
```

On confirmation, run `task-decompose-helper.sh decompose "$TASK_TITLE"` and create child task IDs with `claim-task-id.sh` for each subtask. Set `blocked-by:` edges between siblings as indicated by the decomposition output.
If the user chooses option 1:

1. Create the parent task entry in TODO.md (with `status:blocked`)
2. For each subtask, run `claim-task-id.sh` to allocate `{task_id}.N` IDs
3. Create a brief for each subtask (inheriting parent context)
4. Add subtask entries to TODO.md with `blocked-by:` edges
5. The parent entry gets `blocked-by:{task_id}.1,{task_id}.2,...`

Each subtask brief references the parent: `**Parent task:** {task_id} — see [todo/tasks/{task_id}-brief.md]`

**Skip decomposition when:** `--no-decompose` flag is passed, or the helper script is not available (t1408.1 not yet merged).

### Step 6: Add to TODO.md

Expand All @@ -139,6 +162,7 @@ Format the TODO.md entry using the allocated ID:
```

**Auto-dispatch eligibility**: Only add `#auto-dispatch` if the brief has:

- At least 2 acceptance criteria beyond "tests pass" and "lint clean"
- A non-empty "How (Approach)" section with file references
- A non-empty "What" section with clear deliverable
Expand Down Expand Up @@ -213,11 +237,14 @@ AI: Brief: todo/tasks/t326-brief.md

## CRITICAL: Supervisor Subtask Creation

When the AI supervisor creates subtasks (e.g., decomposing t005 into t005.1-t005.5), it MUST:
When the AI supervisor creates subtasks (e.g., decomposing t005 into t005.1-t005.5) — whether manually or via `task-decompose-helper.sh` (t1408.2) — it MUST:

1. Create a brief for EACH subtask at `todo/tasks/{subtask_id}-brief.md`
2. Reference the parent task's brief: `**Parent task:** {parent_id} — see [todo/tasks/{parent_id}-brief.md]`
3. Inherit context from the parent but add subtask-specific details
4. Include the session ID of the supervisor session that created the subtask
5. Set `blocked-by:` edges between subtasks based on dependency analysis from the decomposition

When using `task-decompose-helper.sh decompose`, the output includes dependency edges (`depends_on` array) that map to `blocked-by:` references in TODO.md. The decompose output also suggests a `batch_strategy` (depth-first or breadth-first) — use this to inform dispatch ordering in the pulse.

A subtask without a brief is a knowledge loss. The parent task's rich context (from the original conversation) must flow down to every subtask.
75 changes: 72 additions & 3 deletions .agents/scripts/commands/pulse.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ When closing any issue, ALWAYS add a comment first explaining: (1) why you're cl
- **`status:done` label or body says "completed"** → find the PR(s) that delivered the work, comment with links, then close. If no PR exists (pre-existing completed work), explain that in the comment.
- **`status:blocked` but blockers are resolved** (merged PR exists for each `blocked-by:` ref) → remove `status:blocked`, add `status:available`, comment explaining what unblocked it. It's now dispatchable this cycle.
- **Duplicate issues for the same task ID** (multiple open issues whose titles start with the same `tNNN:` prefix) → keep the one referenced by `ref:GH#` in TODO.md; close the others with a comment like "Duplicate of #NNN — closing in favour of the canonical issue." This happens when issue-sync-helper and a manual/agent creation race, or when a task ID is reused after a collision. Check TODO.md's `ref:GH#` to determine which is canonical. If neither is referenced, keep the older one.
- **Too large for one worker session** (multiple independent changes, 5+ checklist items, "audit all", "migrate everything") → create subtask issues, label parent `status:blocked` with `blocked-by:` refs to subtasks
- **Too large for one worker session** (multiple independent changes, 5+ checklist items, "audit all", "migrate everything") → auto-decompose using `task-decompose-helper.sh` (see "Task decomposition before dispatch" below), or manually create subtask issues. Label parent `status:blocked` with `blocked-by:` refs to subtasks
- **`status:queued` or `status:in-progress`** → likely being worked on (possibly on another machine). Check the `updatedAt` timestamp: if the issue was updated within the last 3 hours, skip it. If it's been 3+ hours with no open PR and no recent commits on a related branch, the worker likely died — relabel to `status:available`, unassign, and comment explaining the recovery. It's now dispatchable.
- **`status:available` or no status label** → dispatch a worker (see below)

Expand Down Expand Up @@ -221,16 +221,85 @@ The "Active Workers" section in the pre-fetched state includes a `struggle_ratio
- `STRUGGLE_RATIO_THRESHOLD` — ratio above which to flag (default: 30)
- `STRUGGLE_MIN_ELAPSED_MINUTES` — minimum runtime before flagging (default: 30)

### Task decomposition before dispatch (t1408.2)

Before dispatching a worker for an issue, classify the task to determine if it's too large for a single worker session. This catches over-scoped tasks before they waste a worker slot.

**When to classify:** For each dispatchable issue (after passing the skip checks below), run the classify step. Skip classification for issues that already have subtask issues (check if issues with `tNNN.1`, `tNNN.2` etc. exist in the title search).

**How to classify:**

```bash
# Extract task description from the issue title/body
TASK_DESC="<issue title and first paragraph of body>"

# Classify — uses haiku-tier LLM call (~$0.001)
CLASSIFY_RESULT=$(/bin/bash ~/.aidevops/agents/scripts/task-decompose-helper.sh classify \
--task "$TASK_DESC" --repo-path "$path" --quiet 2>/dev/null) || CLASSIFY_RESULT=""

# Parse result
TASK_KIND=$(echo "$CLASSIFY_RESULT" | jq -r '.kind // "atomic"' 2>/dev/null || echo "atomic")
Comment on lines +237 to +241

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The bash example in this section uses 2>/dev/null to suppress errors. This practice hides valuable debugging information, which goes against the repository's general rules. The || construct already handles command failures gracefully. Removing 2>/dev/null will make this example script more robust and easier to debug.

Suggested change
CLASSIFY_RESULT=$(/bin/bash ~/.aidevops/agents/scripts/task-decompose-helper.sh classify \
--task "$TASK_DESC" --repo-path "$path" --quiet 2>/dev/null) || CLASSIFY_RESULT=""
# Parse result
TASK_KIND=$(echo "$CLASSIFY_RESULT" | jq -r '.kind // "atomic"' 2>/dev/null || echo "atomic")
CLASSIFY_RESULT=$(/bin/bash ~/.aidevops/agents/scripts/task-decompose-helper.sh classify \
--task "$TASK_DESC" --repo-path "$path" --quiet) || CLASSIFY_RESULT=""
# Parse result
TASK_KIND=$(echo "$CLASSIFY_RESULT" | jq -r '.kind // "atomic"' || echo "atomic")
References
  1. Avoid blanket error suppression with 2>/dev/null in shell scripts. The || construct handles command failures, and leaving stderr visible is crucial for debugging issues like authentication failures, syntax errors, or missing dependencies.

```

**If atomic:** Dispatch the worker directly (unchanged flow — proceed to step 6 below).

**If composite:** Auto-decompose and create child tasks instead of dispatching:

```bash
# Decompose into subtasks
DECOMPOSE_RESULT=$(/bin/bash ~/.aidevops/agents/scripts/task-decompose-helper.sh decompose \
--task "$TASK_DESC" --repo-path "$path" \
--depth 0 --max-depth "${DECOMPOSE_MAX_DEPTH:-3}" --quiet 2>/dev/null) || DECOMPOSE_RESULT=""

SUBTASK_COUNT=$(echo "$DECOMPOSE_RESULT" | jq '.subtasks | length' 2>/dev/null || echo 0)
Comment on lines +250 to +254

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The bash example in this section uses 2>/dev/null to suppress errors. This practice hides valuable debugging information, which goes against the repository's general rules. The || construct already handles command failures gracefully. Removing 2>/dev/null will make this example script more robust and easier to debug.

Suggested change
DECOMPOSE_RESULT=$(/bin/bash ~/.aidevops/agents/scripts/task-decompose-helper.sh decompose \
--task "$TASK_DESC" --repo-path "$path" \
--depth 0 --max-depth "${DECOMPOSE_MAX_DEPTH:-3}" --quiet 2>/dev/null) || DECOMPOSE_RESULT=""
SUBTASK_COUNT=$(echo "$DECOMPOSE_RESULT" | jq '.subtasks | length' 2>/dev/null || echo 0)
DECOMPOSE_RESULT=$(/bin/bash ~/.aidevops/agents/scripts/task-decompose-helper.sh decompose \
--task "$TASK_DESC" --repo-path "$path" \
--depth 0 --max-depth "${DECOMPOSE_MAX_DEPTH:-3}" --quiet) || DECOMPOSE_RESULT=""
SUBTASK_COUNT=$(echo "$DECOMPOSE_RESULT" | jq '.subtasks | length' || echo 0)
References
  1. Avoid blanket error suppression with 2>/dev/null in shell scripts. The || construct handles command failures, and leaving stderr visible is crucial for debugging issues like authentication failures, syntax errors, or missing dependencies.

```

If decomposition succeeds (`SUBTASK_COUNT >= 2`):

1. For each subtask, create a child task using `claim-task-id.sh`:

```bash
for i in $(seq 0 $((SUBTASK_COUNT - 1))); do
SUB_DESC=$(echo "$DECOMPOSE_RESULT" | jq -r ".subtasks[$i].description")
SUB_ESTIMATE=$(echo "$DECOMPOSE_RESULT" | jq -r ".subtasks[$i].estimate // \"~2h\"")
SUB_DEPS=$(echo "$DECOMPOSE_RESULT" | jq -r ".subtasks[$i].depends_on | map(\"blocked-by:${TASK_ID}.\" + tostring) | join(\" \")" 2>/dev/null || echo "")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Suppressing stderr with 2>/dev/null hides valuable debugging information from jq, such as malformed JSON. The || construct already handles command failure gracefully. Please remove 2>/dev/null to align with repository guidelines and improve debuggability.

Suggested change
SUB_DEPS=$(echo "$DECOMPOSE_RESULT" | jq -r ".subtasks[$i].depends_on | map(\"blocked-by:${TASK_ID}.\" + tostring) | join(\" \")" 2>/dev/null || echo "")
SUB_DEPS=$(echo "$DECOMPOSE_RESULT" | jq -r ".subtasks[$i].depends_on | map(\"blocked-by:${TASK_ID}.\" + tostring) | join(\" \")" || echo "")
References
  1. Avoid blanket error suppression with 2>/dev/null in shell scripts. The || construct handles command failures, and leaving stderr visible is crucial for debugging issues like authentication failures, syntax errors, or missing dependencies.


# Claim child task ID
CHILD_OUTPUT=$(/bin/bash ~/.aidevops/agents/scripts/claim-task-id.sh \
--repo-path "$path" --title "${TASK_ID}.${i+1}: $SUB_DESC" --parent "$TASK_ID")
CHILD_ID=$(echo "$CHILD_OUTPUT" | grep '^TASK_ID=' | cut -d= -f2)

# Add to TODO.md (planning-commit-helper handles commit+push)
# Format: - [ ] tNNN.N Description ~Nh blocked-by:tNNN.M ref:GH#NNN
done
```

2. Label the parent issue `status:blocked` with a comment explaining the decomposition
3. Create a brief for each child task from the parent brief + decomposition context
4. The child tasks enter the normal dispatch queue — the next pulse cycle picks up the leaves (tasks with no unresolved `blocked-by:` refs)

**Depth limit:** `DECOMPOSE_MAX_DEPTH` env var (default: 3). Tasks at depth 3+ are always treated as atomic. This prevents infinite decomposition.

**Skip decomposition when:**

- The issue already has subtask issues (titles matching `tNNN.N:`)
- The issue body contains `skip-decompose` or `atomic` markers
- Classification fails (API unavailable) — default to atomic and dispatch directly
- The task is a bug fix, CI fix, or docs update (these are almost always atomic)

**Cost:** ~$0.001-0.005 per classify+decompose call (haiku tier). A single avoided over-scoped worker failure saves $0.50-5.00 in wasted compute.

### Dispatch workers for open issues

For each dispatchable issue:

1. Skip if a worker is already running for it locally (check `ps` output for the issue number)
2. Skip if an open PR already exists for it (check PR list)
3. Skip if the issue has `status:queued`, `status:in-progress`, or `status:in-review` labels — but only if the issue was updated within the last 3 hours. These labels indicate a worker is handling it (possibly on another machine). If the label is stale (3+ hours, no PR, no recent branch activity), the worker likely died — recover the issue: relabel to `status:available`, unassign, and comment explaining the recovery. It becomes dispatchable this cycle.
4. Skip if the issue is assigned and was updated within the last 3 hours — someone is actively working on it. If assigned but stale (3+ hours, no PR), treat as abandoned: unassign and relabel to `status:available`.
5. Read the issue body briefly — if it has `blocked-by:` references, check if those are resolved (merged PR exists). If not, skip it.
6. **Task decomposition check (t1408):** Before dispatching, check if the task should be decomposed. Run `task-decompose-helper.sh has-subtasks <task-id>` — if it already has subtasks, dispatch the leaf subtasks instead. If not, the `/full-loop` worker will classify and decompose if needed (Step 0.8). For tasks that are clearly composite from the issue title (multiple independent features listed), consider pre-decomposing before dispatch to avoid wasting a worker session on classification.
7. Dispatch:
5.5. **Classify and decompose (t1408.2):** Run the task decomposition check described in "Task decomposition before dispatch" above. If the task is composite, create child tasks and skip direct dispatch. If atomic (or classification unavailable), proceed to dispatch.
6. Dispatch:

```bash
# Assign the issue to prevent duplicate work by other runners/humans
Expand Down
68 changes: 68 additions & 0 deletions .agents/tools/ai-assistants/headless-dispatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,74 @@ If this task depends on interfaces/types from sibling tasks, define reasonable s

When the pulse or `/full-loop` dispatches a worker for a decomposed subtask, prepend the lineage block to the worker's prompt. The lineage context is generated by `task-decompose-helper.sh format-lineage` and costs zero API calls (pure formatting).

## Pre-Dispatch Task Decomposition (t1408.2)

Before dispatching a worker, the pipeline classifies tasks as **atomic** (execute directly) or **composite** (split into subtasks). This catches "task too big for one worker" failures that previously required human judgment.

### How It Works

```text
Task description
classify(task, lineage)
├── atomic → dispatch worker directly (unchanged)
└── composite → decompose(task, lineage)
[2-5 subtasks with dependency edges]
├── Interactive: show tree, ask confirmation
│ └── create child TODOs + briefs → dispatch leaves
└── Pulse: auto-proceed (depth limit: 3)
└── create child TODOs + briefs → dispatch leaves
```

### Integration Points

| Entry point | Mode | Behaviour |
|-------------|------|-----------|
| `/full-loop` (Step 0.45) | Interactive | Show decomposition tree, ask Y/n/edit |
| `/full-loop` (headless) | Headless | Auto-decompose, exit with DECOMPOSED message |
| `/pulse` (Step 3) | Headless | Auto-classify before dispatch, create children |
| `/new-task` (Step 5.5) | Interactive | Classify at creation, offer decomposition |
| `/mission` (orchestrator) | Headless | Classify features before dispatch |

### Helper Script

`task-decompose-helper.sh` provides three subcommands:

```bash
# Classify: atomic or composite? (~$0.001, haiku tier)
task-decompose-helper.sh classify --task "Build auth with login and OAuth"
# → {"kind": "composite", "confidence": 0.9, "reasoning": "..."}

# Decompose: split into subtasks with dependency edges
task-decompose-helper.sh decompose --task "Build auth with login and OAuth"
# → {"subtasks": [{"description": "...", "depends_on": [], "estimate": "~2h"}], ...}

# Lineage: show ancestor/sibling context for a subtask
task-decompose-helper.sh lineage --task-id t1408.2 --repo-path ~/Git/myproject
# → formatted hierarchy with sibling tasks
```

### Configuration

| Variable | Default | Description |
|----------|---------|-------------|
| `DECOMPOSE_MAX_DEPTH` | `3` | Maximum decomposition depth (parent → child → grandchild) |
| `DECOMPOSE_MODEL` | `haiku` | LLM model tier for classify/decompose calls |
| `DECOMPOSE_ENABLED` | `true` | Enable/disable decomposition globally |

### Design Principles

- **"When in doubt, atomic."** Over-decomposition creates more overhead (more tasks, more PRs, more merge conflicts) than under-decomposition. A slightly-too-large task that one worker handles is better than 5 tiny tasks that need coordination.
- **Minimum subtasks.** Decompose into 2-5 subtasks, never pad. Each subtask must represent real, distinct work.
- **Reuse existing infrastructure.** Child tasks use `claim-task-id.sh` for IDs, `blocked-by:` for dependencies, standard briefs. No new state management — TODO.md is the database.
- **Skip already-decomposed tasks.** If a task already has subtasks in TODO.md, don't re-decompose.

## Worker Efficiency Protocol

Workers are injected with an efficiency protocol via the supervisor dispatch prompt. This protocol maximises output per token by requiring structured internal task management.
Expand Down
Loading