Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
18 changes: 18 additions & 0 deletions .agents/scripts/commands/define.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 30 additions & 0 deletions .agents/scripts/commands/new-task.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Comment on lines +218 to +242

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t silently swallow routing-label failures.

On Line 211 and Line 212, 2>/dev/null || true hides labeling failures completely. If label application fails, tasks silently fall back to default routing and this PR’s specialist-routing behavior is lost.

Proposed hardening
 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
+  repo_slug="$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null || true)"
+  if [[ -n "$repo_slug" ]]; then
+    gh label create "$domain_label" --repo "$repo_slug" >/dev/null 2>&1 || true
+    if ! gh issue edit "${task_ref#GH#}" --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
+  else
+    echo "[new-task] WARN: unable to resolve repo slug for domain label '$domain_label'" >&2
+  fi
 fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/scripts/commands/new-task.md around lines 208 - 214, The script
currently hides failures for gh label create and gh issue edit by redirecting
stderr and OR-ing with true; remove the "2>/dev/null || true" silencing and
instead check the exit status of those commands (the gh label create and gh
issue edit invocations) when task_ref and domain_label are set, and handle
failures explicitly—e.g., capture non-zero exit codes and emit a clear error
message including task_ref and domain_label to stderr and/or exit non-zero so
routing-label failures are visible and do not silently fall back to default
routing.


### Step 7: Commit and Push

Commit both the brief and TODO.md change, passing the commit message via a variable to prevent injection:
Expand Down
115 changes: 89 additions & 26 deletions .agents/scripts/commands/pulse.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
---
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
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.

Expand All @@ -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)

Expand All @@ -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

Expand All @@ -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
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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

Expand Down Expand Up @@ -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 <repo-path>`. Explicit labels always override bundle defaults.

### Execution mode

Not every issue is `/full-loop`:
Expand Down Expand Up @@ -332,21 +402,14 @@ 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.
2. NEVER close an issue without a comment explaining why and linking evidence.
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.
Expand Down
21 changes: 19 additions & 2 deletions .agents/scripts/commands/save-todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
```
72 changes: 15 additions & 57 deletions .agents/scripts/pulse-wrapper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}

Expand Down
Loading