From 072e2e4af5f6e07b618387f1d0274e2b4547c08e Mon Sep 17 00:00:00 2001 From: marcusquinn <6428977+marcusquinn@users.noreply.github.com> Date: Sat, 14 Mar 2026 01:40:15 +0000 Subject: [PATCH 1/4] feat: pulse monitoring loop with agent routing labels at task creation Transform pulse from one-shot dispatch to long-running monitoring loop: - Pulse session runs up to 60 minutes, sleeping 60s between cycles - Each cycle checks slots via ps, dispatches to fill open slots - Todo-based drift prevention anchors each monitoring cycle - Eliminates 125s cold-start penalty per backfill iteration Add agent routing labels to all task creation paths: - new-task.md: Step 6.5 evaluates domain and applies GitHub labels - save-todo.md: Step 1b extended with domain tag evaluation - define.md: Step 1 classification includes agent domain mapping - pulse.md: Dispatch checks issue labels for agent routing Simplify pulse-wrapper.sh: - Remove enforce_utilization_invariants() backfill loop (redundant) - LLM session handles continuous slot filling internally - Wrapper watchdog still enforces 60-minute hard ceiling --- .agents/scripts/commands/define.md | 18 ++++ .agents/scripts/commands/new-task.md | 30 +++++++ .agents/scripts/commands/pulse.md | 115 ++++++++++++++++++++------ .agents/scripts/commands/save-todo.md | 21 ++++- .agents/scripts/pulse-wrapper.sh | 72 ++++------------ 5 files changed, 171 insertions(+), 85 deletions(-) diff --git a/.agents/scripts/commands/define.md b/.agents/scripts/commands/define.md index bc7ae0f8da..018105ab4e 100644 --- a/.agents/scripts/commands/define.md +++ b/.agents/scripts/commands/define.md @@ -38,6 +38,24 @@ Parse `$ARGUMENTS` to classify the task. Use keyword signals: | **docs** | document, readme, guide, explain, describe | Accurate, concise, follows existing doc patterns | | **research** | investigate, explore, evaluate, compare, spike | Time-boxed, deliverable is a written recommendation | +Also classify the **agent domain** — this determines which specialist agent handles the task at dispatch time: + +| Domain | Signal Words | Agent | +|--------|-------------|-------| +| **seo** | SEO, keywords, rankings, GSC, schema markup | SEO | +| **content** | blog, article, newsletter, video script, copy | Content | +| **marketing** | campaign, email blast, landing page, FluentCRM | Marketing | +| **accounts** | invoice, receipt, financial, bookkeeping | Accounts | +| **legal** | compliance, terms, privacy policy, GDPR | Legal | +| **research** | market research, competitive analysis, tech spike | Research | +| **sales** | CRM, proposal, outreach, pipeline | Sales | +| **social-media** | social post, scheduling, engagement | Social-Media | +| **video** | video generation, editing, animation | Video | +| **health** | wellness, health content, nutrition | Health | +| *(code)* | implement, fix, refactor, CI, test | Build+ (default — no label needed) | + +Include the domain tag (e.g., `#seo`, `#content`) in the TODO.md entry and as a GitHub label on the issue. Omit for code tasks. + If ambiguous, ask: ```text diff --git a/.agents/scripts/commands/new-task.md b/.agents/scripts/commands/new-task.md index 2cae8578a0..f5278875b4 100644 --- a/.agents/scripts/commands/new-task.md +++ b/.agents/scripts/commands/new-task.md @@ -183,6 +183,36 @@ Format the TODO.md entry using the allocated ID: If the brief is too thin for auto-dispatch, omit the tag and note why. +### Step 6.5: Apply Agent Routing Label + +Evaluate whether the task maps to a specialist agent domain. If it does, add the corresponding tag to the TODO.md entry AND apply the matching GitHub label to the issue (if `task_ref` exists). + +| Domain Signal | TODO Tag | GitHub Label | Agent | +|--------------|----------|--------------|-------| +| SEO audit, keywords, GSC, schema markup, rankings | `#seo` | `seo` | SEO | +| Blog posts, video scripts, newsletters, social copy | `#content` | `content` | Content | +| Email campaigns, FluentCRM, landing pages | `#marketing` | `marketing` | Marketing | +| Invoicing, receipts, financial ops | `#accounts` | `accounts` | Accounts | +| Compliance, terms of service, privacy policy | `#legal` | `legal` | Legal | +| Tech research, competitive analysis, market research | `#research` | `research` | Research | +| CRM pipeline, proposals, outreach | `#sales` | `sales` | Sales | +| Social media scheduling, posting | `#social-media` | `social-media` | Social-Media | +| Video generation, editing, prompts | `#video` | `video` | Video | +| Health and wellness content | `#health` | `health` | Health | +| Code: features, bug fixes, refactors, CI | *(none)* | *(none)* | Build+ (default) | + +**Omit the domain tag for code tasks** — Build+ is the default and needs no label. + +If the task clearly matches a domain, apply the label: + +```bash +# Only if task_ref exists (issue was created) and domain is non-code +if [[ -n "$task_ref" && -n "$domain_label" ]]; then + gh label create "$domain_label" --repo "$(gh repo view --json nameWithOwner -q .nameWithOwner)" 2>/dev/null || true + gh issue edit "${task_ref#GH#}" --repo "$(gh repo view --json nameWithOwner -q .nameWithOwner)" --add-label "$domain_label" 2>/dev/null || true +fi +``` + ### Step 7: Commit and Push Commit both the brief and TODO.md change, passing the commit message via a variable to prevent injection: diff --git a/.agents/scripts/commands/pulse.md b/.agents/scripts/commands/pulse.md index 960ba3b2f1..84eba98928 100644 --- a/.agents/scripts/commands/pulse.md +++ b/.agents/scripts/commands/pulse.md @@ -1,10 +1,10 @@ --- -description: Supervisor pulse — triage GitHub and dispatch workers for highest-value work +description: Supervisor pulse — long-running monitoring loop that triages GitHub and dispatches workers agent: Automate mode: subagent --- -You are the supervisor pulse. You run every 2 minutes via launchd — **there is no human at the terminal.** +You are the supervisor pulse. The wrapper launches you as a long-running session — **there is no human at the terminal.** Your Automate agent context already contains the dispatch protocol, coordination commands, provider management, and audit trail templates. This document tells you WHAT to do with @@ -12,16 +12,17 @@ those tools — the triage logic, priority ordering, and edge-case handling. ## Prime Directive -**Fill all available worker slots with the highest-value work. That's it.** +**Fill all available worker slots with the highest-value work. Keep them filled.** -If you finish without having dispatched workers or merged PRs, you have failed. Your output -is the log of actions you ALREADY TOOK (past tense) — captured to `~/.aidevops/logs/pulse.log`. +Your session runs for up to 60 minutes. Each monitoring cycle is tiny (~3K tokens). You +dispatch, then monitor, then backfill — continuously. Workers finishing mid-session get +their slots refilled immediately, not after a 3-minute restart penalty. **You are the dispatcher, not a worker.** NEVER implement code changes yourself. If something needs coding, dispatch a worker. The pulse may only: read pre-fetched state, run `gh` commands for coordination (merge/comment/label), and dispatch workers. -## The Dispatch Loop (DO THIS FIRST) +## Initial Dispatch (DO THIS FIRST) Read this section, then execute it. Everything below this section is refinement. @@ -40,8 +41,9 @@ AVAILABLE=$((MAX_WORKERS - WORKER_COUNT)) ### 2. Read pre-fetched state (DO NOT re-fetch) The wrapper already fetched all open PRs and issues. The data is in your prompt between -`--- PRE-FETCHED STATE ---` markers. Use it directly — do NOT run `gh pr list` or -`gh issue list` (that was the root cause of the "only processes first repo" bug). +`--- PRE-FETCHED STATE ---` markers or in the state file path provided. Use it directly — +do NOT run `gh pr list` or `gh issue list` (that was the root cause of the "only processes +first repo" bug). ### 3. Merge ready PRs (free — no worker slot needed) @@ -51,7 +53,7 @@ For each PR with green CI + review gate passed + maintainer author: gh pr merge NUMBER --repo SLUG --squash ``` -Check external contributor gate before ANY merge (see Appendix A). +Check external contributor gate before ANY merge (see Pre-merge checks below). ### 4. Dispatch workers for open issues @@ -78,28 +80,74 @@ sleep 2 Repeat until `AVAILABLE` slots are filled or no dispatchable issues remain. -### 5. Record and exit +### 5. Record initial dispatch success ```bash ~/.aidevops/agents/scripts/circuit-breaker-helper.sh record-success ``` -Output a brief summary of what you did (past tense), then exit. +Create todos for what you just did, then proceed to the monitoring loop. + +## Monitoring Loop + +After the initial dispatch, enter a monitoring loop. Each cycle: + +1. **Create a todo batch** for this cycle (drift prevention): + + ```text + - [x] Check active workers (22/24, 2 slots open) + - [x] Dispatch worker for issue #3567 (marcusquinn/aidevops) + - [x] Merge PR #4551 (marcusquinn/aidevops.sh) + - [ ] Monitor cycle N+1 (sleep 60s, check slots) + ``` + + The last todo is always "Monitor cycle N+1" — this anchors the loop. Complete it by + sleeping, checking state, and creating the next batch. + +2. **Sleep 60 seconds**: `sleep 60` + +3. **Check capacity**: + + ```bash + source ~/.aidevops/agents/scripts/pulse-wrapper.sh + MAX_WORKERS=$(cat ~/.aidevops/logs/pulse-max-workers 2>/dev/null || echo 4) + WORKER_COUNT=$(list_active_worker_processes | wc -l | tr -d ' ') + AVAILABLE=$((MAX_WORKERS - WORKER_COUNT)) + ``` + +4. **If slots are open**: check for mergeable PRs (free), then dispatch workers for the + highest-priority open issues. Use the same dedup guards and dispatch commands as the + initial dispatch. Re-fetch issue state with targeted `gh` calls only for repos where + you need to dispatch (not a full re-fetch of all repos). + +5. **If fully staffed**: log it, mark the cycle todo complete, continue to next cycle. + +6. **Exit conditions** — exit the loop when ANY of: + - 55 minutes have elapsed (leave 5 min buffer before the wrapper's 60 min watchdog) + - No runnable work remains AND all slots are filled + - Circuit breaker or stop flag detected + +On exit, run: + +```bash +~/.aidevops/agents/scripts/circuit-breaker-helper.sh record-success +~/.aidevops/agents/scripts/session-miner-pulse.sh 2>&1 || true +``` + +Output a brief summary of total actions taken across all cycles (past tense). --- -**Everything below adds sophistication to the loop above. A pulse that only executes -steps 1-5 is a successful pulse. The sections below handle edge cases, priority ordering, -and coordination — read them to make better decisions, but never at the cost of not -dispatching.** +**Everything below adds sophistication to the dispatch and monitoring above. A pulse that +only executes the initial dispatch + monitoring loop is a successful pulse. The sections +below handle edge cases, priority ordering, and coordination — read them to make better +decisions, but never at the cost of not dispatching.** ## How to Think You are an intelligent supervisor, not a script executor. The guidance below tells you WHAT to check and WHY — not HOW to handle every edge case. Use judgment. -**Speed over thoroughness.** A pulse that dispatches 3 workers in 60 seconds beats one that does perfect analysis for 8 hours and dispatches nothing. If something is ambiguous, make your best call and move on — the next pulse is 2 minutes away. - -**One pass, then exit.** Scan all repos, act on everything, exit. Don't loop or re-analyze. +**Speed over thoroughness.** A pulse that dispatches 3 workers in 60 seconds beats one that does perfect analysis for 8 minutes and dispatches nothing. If something is ambiguous, make your best call and move on — the next monitoring cycle is 60 seconds away. ## Capacity and Priority @@ -225,6 +273,28 @@ Default `MAX_WORKERS_PER_REPO=5`. If a repo already has this many active workers Do NOT treat `auto-dispatch` or `status:available` as hard gates. Build candidates from unassigned, non-blocked issues. Prioritize `priority:critical`, `priority:high`, and `bug` labels. Include `quality-debt` when it's the highest-value available work. +### Agent routing from labels + +Before dispatching, check issue labels for agent routing. This avoids guessing from the title: + +| Label | Dispatch Flag | Agent | +|-------|--------------|-------| +| `seo` | `--agent SEO` | SEO | +| `content` | `--agent Content` | Content | +| `marketing` | `--agent Marketing` | Marketing | +| `accounts` | `--agent Accounts` | Accounts | +| `legal` | `--agent Legal` | Legal | +| `research` | `--agent Research` | Research | +| `sales` | `--agent Sales` | Sales | +| `social-media` | `--agent Social-Media` | Social-Media | +| `video` | `--agent Video` | Video | +| `health` | `--agent Health` | Health | +| *(no domain label)* | *(omit)* | Build+ (default) | + +If no domain label is present and the title/repo context is ambiguous, fetch `body[:200]` with `gh issue view NUMBER --json body --jq '.body[:200]'` for clarification. Default to Build+ when uncertain. + +Also check for bundle-level agent routing overrides: `bundle-helper.sh get agent_routing `. Explicit labels always override bundle defaults. + ### Execution mode Not every issue is `/full-loop`: @@ -332,13 +402,6 @@ Check the latest comment on each repo's quality review issue. Triage findings us Dedup before creating (search existing issues). Max 3 issues per repo per cycle. NEVER close the quality review issue itself. -## Record and Exit - -```bash -~/.aidevops/agents/scripts/circuit-breaker-helper.sh record-success -~/.aidevops/agents/scripts/session-miner-pulse.sh 2>&1 || true -``` - ## Hard Rules 1. NEVER modify or dispatch for closed issues. Check state first. @@ -346,7 +409,7 @@ Dedup before creating (search existing issues). Max 3 issues per repo per cycle. 3. NEVER use `claude` CLI. Always dispatch via `headless-runtime-helper.sh run`. 4. NEVER include private repo names in public issue titles/bodies/comments. 5. NEVER exceed MAX_WORKERS. Count before dispatching. -6. One pass through all repos, act on everything, then exit. Don't loop. +6. Run the monitoring loop — dispatch, sleep 60s, check slots, backfill. Exit after 55 minutes or when no work remains. 7. NEVER create "pulse summary" or "supervisor log" issues. Your output IS the log. 8. NEVER create duplicate issues. Search before creating: `gh issue list --search "tNNN" --state all`. 9. NEVER ask the user anything. You are headless. Decide and act. diff --git a/.agents/scripts/commands/save-todo.md b/.agents/scripts/commands/save-todo.md index 66cb47618d..032ccdaffb 100644 --- a/.agents/scripts/commands/save-todo.md +++ b/.agents/scripts/commands/save-todo.md @@ -47,6 +47,23 @@ Every task MUST be evaluated for these pipeline tags: **`#plan`** — Add when the task needs decomposition into subtasks before implementation (multi-phase, >2h, research/design needed). +**Agent domain tags** — If the task maps to a specialist agent domain, add the corresponding tag. This enables the pulse to route dispatch to the correct agent without guessing from the title: + +| Domain | Tag | Agent | +|--------|-----|-------| +| SEO, keywords, rankings, GSC | `#seo` | SEO | +| Blog posts, newsletters, video scripts | `#content` | Content | +| Email campaigns, landing pages | `#marketing` | Marketing | +| Invoicing, financial ops | `#accounts` | Accounts | +| Compliance, legal docs | `#legal` | Legal | +| Research, analysis, spikes | `#research` | Research | +| CRM, proposals, outreach | `#sales` | Sales | +| Social media management | `#social-media` | Social-Media | +| Video generation/editing | `#video` | Video | +| Health/wellness content | `#health` | Health | + +Omit domain tags for code tasks — Build+ is the default. + **Default to `#auto-dispatch`** — only omit when a specific exclusion applies. This keeps the autonomous pipeline moving. See `workflows/plans.md` "Auto-Dispatch Tagging" for full criteria. ### Step 2: Determine Complexity @@ -180,13 +197,13 @@ AI: This looks like complex work. Creating execution plan. Title: Authentication Overhaul Estimate: ~2w (ai:1w test:0.5w read:0.5w) Phases: 4 identified (OAuth, sessions, migration, testing) - + 1. Confirm and create plan 2. Simplify to TODO.md 3. Add context User: 1 AI: Saved: "Authentication Overhaul" - Plan: todo/PLANS.md - Reference: TODO.md - PRD: todo/tasks/prd-auth-overhaul.md - + Start anytime with: "Let's work on auth overhaul" ``` diff --git a/.agents/scripts/pulse-wrapper.sh b/.agents/scripts/pulse-wrapper.sh index 87b1f60839..af3c8e4a0e 100755 --- a/.agents/scripts/pulse-wrapper.sh +++ b/.agents/scripts/pulse-wrapper.sh @@ -2470,66 +2470,21 @@ check_worker_launch() { } ####################################### -# Enforce utilization invariants post-pulse (t1453) +# Enforce utilization invariants post-pulse (DEPRECATED — t1453) # -# Invariant: keep dispatching pulse cycles until either: -# - active workers >= MAX_WORKERS, OR -# - no runnable work remains in scope +# The LLM pulse session now runs a monitoring loop (sleep 60s, check +# slots, backfill) for up to 60 minutes, making this wrapper-level +# backfill loop redundant. The function is kept as a no-op stub for +# backward compatibility (pulse.md sources this file). # -# Launch validation integration: -# queued issues with no active worker are treated as launch failures, -# which trigger an immediate backfill cycle. +# Previously: re-launched run_pulse() in a loop until active workers +# >= MAX_WORKERS or no runnable work remained. Each iteration paid +# the full LLM cold-start penalty (~125s). The monitoring loop inside +# the LLM session eliminates this overhead — each backfill iteration +# costs ~3K tokens instead of a full session restart. ####################################### enforce_utilization_invariants() { - local attempts=0 - local max_attempts="$PULSE_BACKFILL_MAX_ATTEMPTS" - - while [[ "$attempts" -lt "$max_attempts" ]]; do - if [[ -f "$STOP_FLAG" ]]; then - echo "[pulse-wrapper] Stop flag present during utilization enforcement — exiting" >>"$LOGFILE" - return 0 - fi - - local max_workers active_workers runnable_count queued_without_worker - max_workers=$(get_max_workers_target) - active_workers=$(count_active_workers) - runnable_count=$(count_runnable_candidates) - queued_without_worker=$(count_queued_without_worker) - - [[ "$max_workers" =~ ^[0-9]+$ ]] || max_workers=1 - [[ "$active_workers" =~ ^[0-9]+$ ]] || active_workers=0 - [[ "$runnable_count" =~ ^[0-9]+$ ]] || runnable_count=0 - [[ "$queued_without_worker" =~ ^[0-9]+$ ]] || queued_without_worker=0 - - run_underfill_worker_recycler "$max_workers" "$active_workers" "$runnable_count" "$queued_without_worker" - active_workers=$(count_active_workers) - [[ "$active_workers" =~ ^[0-9]+$ ]] || active_workers=0 - - if [[ "$active_workers" -ge "$max_workers" && "$queued_without_worker" -eq 0 ]]; then - echo "[pulse-wrapper] Utilization invariant satisfied: active workers ${active_workers}/${max_workers}" >>"$LOGFILE" - return 0 - fi - - if [[ "$runnable_count" -eq 0 && "$queued_without_worker" -eq 0 ]]; then - echo "[pulse-wrapper] Utilization invariant satisfied: no runnable work remains (active ${active_workers}/${max_workers})" >>"$LOGFILE" - return 0 - fi - - attempts=$((attempts + 1)) - echo "[pulse-wrapper] Backfill attempt ${attempts}/${max_attempts}: active=${active_workers}/${max_workers}, runnable=${runnable_count}, queued_without_worker=${queued_without_worker}" >>"$LOGFILE" - - # Refresh prompt state before each backfill cycle so pulse sees latest context. - prefetch_state || true - local underfilled_mode=0 - local underfill_pct=0 - if [[ "$active_workers" -lt "$max_workers" ]]; then - underfilled_mode=1 - underfill_pct=$(((max_workers - active_workers) * 100 / max_workers)) - fi - run_pulse "$underfilled_mode" "$underfill_pct" - done - - echo "[pulse-wrapper] Reached backfill attempt cap (${max_attempts}) before utilization invariant converged" >>"$LOGFILE" + echo "[pulse-wrapper] enforce_utilization_invariants is deprecated — LLM session handles continuous slot filling" >>"$LOGFILE" return 0 } @@ -2720,7 +2675,10 @@ main() { fi run_pulse "$initial_underfilled_mode" "$initial_underfill_pct" - enforce_utilization_invariants + # enforce_utilization_invariants removed — the LLM pulse session now runs + # a monitoring loop (sleep 60s, check slots, backfill) for up to 60 minutes, + # making the wrapper's backfill loop redundant. The wrapper's watchdog still + # enforces the hard 60-minute ceiling via PULSE_STALE_THRESHOLD. return 0 } From 1020ec13dec3ef08f1c756822a300a58d97205b9 Mon Sep 17 00:00:00 2001 From: marcusquinn <6428977+marcusquinn@users.noreply.github.com> Date: Sat, 14 Mar 2026 01:50:20 +0000 Subject: [PATCH 2/4] feat: model tier labels at task creation + tier-aware dispatch Add tier:thinking and tier:simple labels to task creation commands: - new-task.md: Step 6.5 evaluates reasoning complexity alongside domain - save-todo.md: Step 1b extended with model tier tag table - define.md: Step 1 classification includes model tier mapping Add model tier selection to pulse dispatch refinements: - Precedence: failure escalation > tier labels > bundle defaults > sonnet - tier:thinking -> opus for architecture/novel design - tier:simple -> haiku for docs/formatting/config - No label -> default round-robin (sonnet-tier) - Includes model-availability-helper.sh resolve for provider fallback --- .agents/scripts/commands/define.md | 10 +++++++++ .agents/scripts/commands/new-task.md | 32 ++++++++++++++++++++++----- .agents/scripts/commands/pulse.md | 21 +++++++++++++++++- .agents/scripts/commands/save-todo.md | 10 +++++++++ 4 files changed, 66 insertions(+), 7 deletions(-) diff --git a/.agents/scripts/commands/define.md b/.agents/scripts/commands/define.md index 018105ab4e..c289cca297 100644 --- a/.agents/scripts/commands/define.md +++ b/.agents/scripts/commands/define.md @@ -56,6 +56,16 @@ Also classify the **agent domain** — this determines which specialist agent ha Include the domain tag (e.g., `#seo`, `#content`) in the TODO.md entry and as a GitHub label on the issue. Omit for code tasks. +Also classify the **model tier** — this determines the intelligence level of the worker dispatched for this task: + +| Tier | Tag | Signals | +|------|-----|---------| +| **thinking** | `tier:thinking` | Architecture decisions, novel design with no existing patterns, complex multi-system trade-offs, security audits requiring deep reasoning | +| **simple** | `tier:simple` | Docs-only changes, simple renames/formatting, config tweaks, label/tag updates | +| *(coding)* | *(none)* | Standard implementation, bug fixes, refactors, tests — **default, no tag needed** | + +Default to no tier tag — most tasks are coding (sonnet). Only tag when the task clearly needs more reasoning power or clearly needs less. + If ambiguous, ask: ```text diff --git a/.agents/scripts/commands/new-task.md b/.agents/scripts/commands/new-task.md index f5278875b4..39fb8ddb19 100644 --- a/.agents/scripts/commands/new-task.md +++ b/.agents/scripts/commands/new-task.md @@ -183,9 +183,19 @@ Format the TODO.md entry using the allocated ID: If the brief is too thin for auto-dispatch, omit the tag and note why. -### Step 6.5: Apply Agent Routing Label +### Step 6.5: Apply Model Tier and Agent Routing Labels -Evaluate whether the task maps to a specialist agent domain. If it does, add the corresponding tag to the TODO.md entry AND apply the matching GitHub label to the issue (if `task_ref` exists). +**Model tier label** — Evaluate the task's reasoning complexity and add a tier tag. This tells the pulse which model intelligence level to use at dispatch time: + +| Tier | TODO Tag | GitHub Label | When to Apply | +|------|----------|--------------|---------------| +| thinking | `tier:thinking` | `tier:thinking` | Architecture decisions, novel design, complex trade-offs, security audits, multi-system reasoning | +| simple | `tier:simple` | `tier:simple` | Docs-only, simple renames, formatting, config changes, label/tag updates | +| *(coding)* | *(none)* | *(none)* | Standard implementation, bug fixes, refactors, tests — **default, no label needed** | + +**Default to no tier label** — most tasks are coding tasks that use sonnet. Only add a tier label when the task clearly needs more reasoning power (thinking) or clearly needs less (simple). When uncertain, omit the label — sonnet handles the vast majority of work well. + +**Agent routing label** — Evaluate whether the task maps to a specialist agent domain. If it does, add the corresponding tag to the TODO.md entry AND apply the matching GitHub label to the issue (if `task_ref` exists). | Domain Signal | TODO Tag | GitHub Label | Agent | |--------------|----------|--------------|-------| @@ -206,10 +216,20 @@ Evaluate whether the task maps to a specialist agent domain. If it does, add the If the task clearly matches a domain, apply the label: ```bash -# Only if task_ref exists (issue was created) and domain is non-code -if [[ -n "$task_ref" && -n "$domain_label" ]]; then - gh label create "$domain_label" --repo "$(gh repo view --json nameWithOwner -q .nameWithOwner)" 2>/dev/null || true - gh issue edit "${task_ref#GH#}" --repo "$(gh repo view --json nameWithOwner -q .nameWithOwner)" --add-label "$domain_label" 2>/dev/null || true +# Apply labels if task_ref exists (issue was created) +REPO_SLUG="$(gh repo view --json nameWithOwner -q .nameWithOwner)" +if [[ -n "$task_ref" ]]; then + ISSUE_NUM="${task_ref#GH#}" + # Apply tier label (only for non-default tiers) + if [[ -n "$tier_label" ]]; then + gh label create "$tier_label" --repo "$REPO_SLUG" 2>/dev/null || true + gh issue edit "$ISSUE_NUM" --repo "$REPO_SLUG" --add-label "$tier_label" 2>/dev/null || true + fi + # Apply domain label (only for non-code domains) + if [[ -n "$domain_label" ]]; then + gh label create "$domain_label" --repo "$REPO_SLUG" 2>/dev/null || true + gh issue edit "$ISSUE_NUM" --repo "$REPO_SLUG" --add-label "$domain_label" 2>/dev/null || true + fi fi ``` diff --git a/.agents/scripts/commands/pulse.md b/.agents/scripts/commands/pulse.md index 84eba98928..0ed8d08873 100644 --- a/.agents/scripts/commands/pulse.md +++ b/.agents/scripts/commands/pulse.md @@ -261,7 +261,7 @@ This is informational, not an auto-kill trigger. Workers doing legitimate resear ### Model escalation -After 2+ failed attempts on the same issue (count kill/failure comments), escalate to `--model anthropic/claude-opus-4-6`. See automate.md "Model escalation" for the tier table. At 3+ failures, also add a summary of what previous workers attempted. +After 2+ failed attempts on the same issue (count kill/failure comments), escalate to `--model anthropic/claude-opus-4-6`. This overrides any `tier:` label on the issue. At 3+ failures, also add a summary of what previous workers attempted. See "Model tier selection" under Dispatch Refinements for the full precedence chain. ## Dispatch Refinements @@ -295,6 +295,25 @@ If no domain label is present and the title/repo context is ambiguous, fetch `bo Also check for bundle-level agent routing overrides: `bundle-helper.sh get agent_routing `. Explicit labels always override bundle defaults. +### Model tier selection + +Before dispatching, determine the appropriate model tier. Check these sources in precedence order: + +1. **Failure escalation** (highest priority): Count kill/failure comments on the issue. After 2+ failed attempts → `--model anthropic/claude-opus-4-6`. This overrides all other tier signals. +2. **Issue labels**: `tier:thinking` → `--model anthropic/claude-opus-4-6`, `tier:simple` → `--model anthropic/claude-haiku-4-5-20251001`. These labels are set at task creation time. +3. **Bundle defaults**: `bundle-helper.sh get model_defaults.implementation `. If the bundle says `opus` for this task type, respect it. +4. **No signal** → omit `--model` (default round-robin, currently sonnet-tier). + +| Label | Model Flag | Use Case | +|-------|-----------|----------| +| `tier:thinking` | `--model anthropic/claude-opus-4-6` | Architecture, novel design, complex trade-offs | +| `tier:simple` | `--model anthropic/claude-haiku-4-5-20251001` | Docs, formatting, config, simple renames | +| *(no tier label)* | *(omit — default round-robin)* | Standard coding — features, bug fixes, refactors | + +Use `model-availability-helper.sh resolve ` to get the best available model for a tier when the primary provider is backed off. For example, if Anthropic is backed off, `resolve opus` returns the cross-provider fallback (o3). + +**Cost justification**: One opus dispatch (~3x sonnet) is cheaper than 3 failed sonnet dispatches. One haiku dispatch (~0.25x sonnet) saves 75% on tasks that don't need sonnet's reasoning. The tier labels make this automatic — no per-dispatch analysis needed. + ### Execution mode Not every issue is `/full-loop`: diff --git a/.agents/scripts/commands/save-todo.md b/.agents/scripts/commands/save-todo.md index 032ccdaffb..e2289ac834 100644 --- a/.agents/scripts/commands/save-todo.md +++ b/.agents/scripts/commands/save-todo.md @@ -47,6 +47,16 @@ Every task MUST be evaluated for these pipeline tags: **`#plan`** — Add when the task needs decomposition into subtasks before implementation (multi-phase, >2h, research/design needed). +**Model tier tags** — Evaluate the task's reasoning complexity: + +| Tier | Tag | When to Apply | +|------|-----|---------------| +| thinking | `tier:thinking` | Architecture, novel design, complex trade-offs, security audits | +| simple | `tier:simple` | Docs-only, simple renames, formatting, config changes | +| *(coding)* | *(none)* | Standard implementation, bug fixes, refactors — **default, no tag needed** | + +Default to no tier tag. Most tasks are coding tasks (sonnet). Only tag exceptions. + **Agent domain tags** — If the task maps to a specialist agent domain, add the corresponding tag. This enables the pulse to route dispatch to the correct agent without guessing from the title: | Domain | Tag | Agent | From a43fc14e33a3e77944fb814d7589dbfaf9e374ee Mon Sep 17 00:00:00 2001 From: marcusquinn <6428977+marcusquinn@users.noreply.github.com> Date: Sat, 14 Mar 2026 01:54:21 +0000 Subject: [PATCH 3/4] fix: warn on label application failure instead of silently swallowing Address CodeRabbit review feedback: label application failures (tier and domain labels) now emit warnings to stderr instead of being silently suppressed. Also validates repo slug resolution before attempting label operations. --- .agents/scripts/commands/new-task.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/.agents/scripts/commands/new-task.md b/.agents/scripts/commands/new-task.md index 39fb8ddb19..136a0c8975 100644 --- a/.agents/scripts/commands/new-task.md +++ b/.agents/scripts/commands/new-task.md @@ -217,18 +217,26 @@ If the task clearly matches a domain, apply the label: ```bash # Apply labels if task_ref exists (issue was created) -REPO_SLUG="$(gh repo view --json nameWithOwner -q .nameWithOwner)" -if [[ -n "$task_ref" ]]; then +REPO_SLUG="$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null || true)" +if [[ -n "$task_ref" && -n "$REPO_SLUG" ]]; then ISSUE_NUM="${task_ref#GH#}" # Apply tier label (only for non-default tiers) if [[ -n "$tier_label" ]]; then - gh label create "$tier_label" --repo "$REPO_SLUG" 2>/dev/null || true - gh issue edit "$ISSUE_NUM" --repo "$REPO_SLUG" --add-label "$tier_label" 2>/dev/null || true + gh label create "$tier_label" --repo "$REPO_SLUG" >/dev/null 2>&1 || true + if ! gh issue edit "$ISSUE_NUM" --repo "$REPO_SLUG" --add-label "$tier_label" >/dev/null 2>&1; then + echo "[new-task] WARN: failed to apply tier label '$tier_label' to ${task_ref}" >&2 + fi fi # Apply domain label (only for non-code domains) if [[ -n "$domain_label" ]]; then - gh label create "$domain_label" --repo "$REPO_SLUG" 2>/dev/null || true - gh issue edit "$ISSUE_NUM" --repo "$REPO_SLUG" --add-label "$domain_label" 2>/dev/null || true + gh label create "$domain_label" --repo "$REPO_SLUG" >/dev/null 2>&1 || true + if ! gh issue edit "$ISSUE_NUM" --repo "$REPO_SLUG" --add-label "$domain_label" >/dev/null 2>&1; then + echo "[new-task] WARN: failed to apply domain label '$domain_label' to ${task_ref}" >&2 + fi + fi +else + if [[ -n "$task_ref" ]]; then + echo "[new-task] WARN: unable to resolve repo slug for label application" >&2 fi fi ``` From 9e2b1a4666df6cf5be0cb0874c6beaf03bf5ab99 Mon Sep 17 00:00:00 2001 From: marcusquinn <6428977+marcusquinn@users.noreply.github.com> Date: Sat, 14 Mar 2026 02:04:11 +0000 Subject: [PATCH 4/4] fix: use tier resolver instead of hardcoded model IDs in dispatch Address CodeRabbit review: replace hardcoded provider/model IDs (anthropic/claude-opus-4-6, anthropic/claude-haiku-4-5-20251001) with model-availability-helper.sh resolve calls. This ensures dispatch respects provider backoff and cross-provider fallback chains. Also mark exit cleanup commands as best-effort/opportunistic since the wrapper's watchdog may hard-kill before they complete. --- .agents/scripts/commands/pulse.md | 34 ++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/.agents/scripts/commands/pulse.md b/.agents/scripts/commands/pulse.md index 0ed8d08873..4d4d656764 100644 --- a/.agents/scripts/commands/pulse.md +++ b/.agents/scripts/commands/pulse.md @@ -127,7 +127,8 @@ After the initial dispatch, enter a monitoring loop. Each cycle: - No runnable work remains AND all slots are filled - Circuit breaker or stop flag detected -On exit, run: +On exit, run these best-effort cleanup commands (the wrapper's watchdog may hard-kill +before these complete — that's fine, they are opportunistic telemetry, not critical state): ```bash ~/.aidevops/agents/scripts/circuit-breaker-helper.sh record-success @@ -261,7 +262,7 @@ This is informational, not an auto-kill trigger. Workers doing legitimate resear ### Model escalation -After 2+ failed attempts on the same issue (count kill/failure comments), escalate to `--model anthropic/claude-opus-4-6`. This overrides any `tier:` label on the issue. At 3+ failures, also add a summary of what previous workers attempted. See "Model tier selection" under Dispatch Refinements for the full precedence chain. +After 2+ failed attempts on the same issue (count kill/failure comments), escalate by resolving the `opus` tier via `model-availability-helper.sh resolve opus` and passing `--model `. This overrides any `tier:` label on the issue. At 3+ failures, also add a summary of what previous workers attempted. See "Model tier selection" under Dispatch Refinements for the full precedence chain. ## Dispatch Refinements @@ -297,21 +298,30 @@ Also check for bundle-level agent routing overrides: `bundle-helper.sh get agent ### Model tier selection -Before dispatching, determine the appropriate model tier. Check these sources in precedence order: +Before dispatching, determine the appropriate model tier. Resolve tier names to concrete model IDs via the availability helper — never hardcode provider/model IDs in dispatch commands. -1. **Failure escalation** (highest priority): Count kill/failure comments on the issue. After 2+ failed attempts → `--model anthropic/claude-opus-4-6`. This overrides all other tier signals. -2. **Issue labels**: `tier:thinking` → `--model anthropic/claude-opus-4-6`, `tier:simple` → `--model anthropic/claude-haiku-4-5-20251001`. These labels are set at task creation time. -3. **Bundle defaults**: `bundle-helper.sh get model_defaults.implementation `. If the bundle says `opus` for this task type, respect it. +**Resolve a tier to a model:** + +```bash +RESOLVED_MODEL=$(~/.aidevops/agents/scripts/model-availability-helper.sh resolve ) +# Then pass: --model "$RESOLVED_MODEL" +``` + +This handles provider backoff and cross-provider fallback automatically (e.g., if Anthropic is backed off, `resolve opus` returns o3). + +**Precedence order:** + +1. **Failure escalation** (highest priority): Count kill/failure comments on the issue. After 2+ failed attempts → resolve `opus` tier. This overrides all other tier signals. +2. **Issue labels**: `tier:thinking` → resolve `opus`, `tier:simple` → resolve `haiku`. These labels are set at task creation time. +3. **Bundle defaults**: `bundle-helper.sh get model_defaults.implementation `. If the bundle says `opus` for this task type, resolve that tier. 4. **No signal** → omit `--model` (default round-robin, currently sonnet-tier). -| Label | Model Flag | Use Case | -|-------|-----------|----------| -| `tier:thinking` | `--model anthropic/claude-opus-4-6` | Architecture, novel design, complex trade-offs | -| `tier:simple` | `--model anthropic/claude-haiku-4-5-20251001` | Docs, formatting, config, simple renames | +| Label | Tier to Resolve | Use Case | +|-------|----------------|----------| +| `tier:thinking` | `opus` | Architecture, novel design, complex trade-offs | +| `tier:simple` | `haiku` | Docs, formatting, config, simple renames | | *(no tier label)* | *(omit — default round-robin)* | Standard coding — features, bug fixes, refactors | -Use `model-availability-helper.sh resolve ` to get the best available model for a tier when the primary provider is backed off. For example, if Anthropic is backed off, `resolve opus` returns the cross-provider fallback (o3). - **Cost justification**: One opus dispatch (~3x sonnet) is cheaper than 3 failed sonnet dispatches. One haiku dispatch (~0.25x sonnet) saves 75% on tasks that don't need sonnet's reasoning. The tier labels make this automatic — no per-dispatch analysis needed. ### Execution mode