diff --git a/.agents/scripts/commands/define.md b/.agents/scripts/commands/define.md index bc7ae0f8da..c289cca297 100644 --- a/.agents/scripts/commands/define.md +++ b/.agents/scripts/commands/define.md @@ -38,6 +38,34 @@ 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. + +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 2cae8578a0..136a0c8975 100644 --- a/.agents/scripts/commands/new-task.md +++ b/.agents/scripts/commands/new-task.md @@ -183,6 +183,64 @@ 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 Model Tier and Agent Routing Labels + +**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 | +|--------------|----------|--------------|-------| +| 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 +# Apply labels if task_ref exists (issue was created) +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" >/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" >/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 +``` + ### 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..4d4d656764 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,75 @@ 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 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 +~/.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 @@ -213,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`. 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 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 @@ -225,6 +274,56 @@ 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. + +### Model tier selection + +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. + +**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 | 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 | + +**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`: @@ -332,13 +431,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 +438,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..e2289ac834 100644 --- a/.agents/scripts/commands/save-todo.md +++ b/.agents/scripts/commands/save-todo.md @@ -47,6 +47,33 @@ 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 | +|--------|-----|-------| +| 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 +207,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 }