agent-heartbeat — Agentic Task Dispatcher
Poll the EXOcortex board for issues that are both Agentic (category) and Ready (status column), validate they're agent-ready, and dispatch them for autonomous execution.
Quick Reference
/agent-heartbeat # single sweep — check queue, dispatch if work found
/loop 10m /agent-heartbeat # recurring — poll every 10 minutes
/agent-heartbeat --dry-run # show what would be dispatched without executing
Architecture
┌──────────┐ ┌─────────────────┐ ┌──────────────┐ ┌─────────────┐
│ /loop │────▶│ /agent-heartbeat│────▶│ /prep-issue │────▶│ /work-issue │
│ (timer) │ │ (dispatcher) │ │ (validator) │ │ (executor) │
└──────────┘ └─────────────────┘ └──────────────┘ └─────────────┘
│ │
▼ ▼
GitHub Projects Branch + PR
Board mutations Issue comments
(status moves) Status updates
This skill is the dispatcher in the pipeline. It doesn't do the work itself —
it finds work, validates it, and hands it to /work-issue.
Step 0: Parse Arguments
--dry-run— query and report what would be dispatched, but don't execute- No other arguments expected; the skill always queries the full Agentic queue
Step 1: Query the Agentic Queue
Find all project items where Category = Agentic AND Status = Ready.
gh api graphql -f query='
query {
node(id: "PVT_kwHOCQ_h4c4BRFq6") {
... on ProjectV2 {
items(first: 50) {
nodes {
id
fieldValueByName(name: "Status") {
... on ProjectV2ItemFieldSingleSelectValue {
name
optionId
}
}
fieldValues(first: 10) {
nodes {
... on ProjectV2ItemFieldSingleSelectValue {
field { ... on ProjectV2SingleSelectField { name } }
name
optionId
}
}
}
content {
... on Issue {
number
title
state
labels(first: 10) { nodes { name } }
repository { name owner { login } }
}
}
}
}
}
}
}'
Filter criteria (applied client-side from the query results):
- Status field value =
Ready(option ID:da247464) - Category field value =
Agentic(option ID:203ea676) - Issue state =
OPEN
If no items match, report "No agentic work in queue" and exit cleanly.
Step 2: Prioritize the Queue
If multiple items match, pick one to dispatch per tick. Priority order:
- Oldest first — items that have been Ready longest get dispatched first (prevents starvation)
- Tie-break by issue number — lower number wins (deterministic)
Only dispatch one item per heartbeat tick. This keeps execution predictable and avoids parallel work that could conflict. The next tick picks up the next item.
Step 3: Validate Agent-Readiness
Before dispatching, run a lightweight readiness check (subset of /prep-issue
criteria):
Required (all must pass)
- [ ] Issue has a body (not empty)
- [ ] Issue body contains an Objective section (or clearly stated goal)
- [ ] Issue body contains Acceptance Criteria (or testable done conditions)
Recommended (warn if missing, but don't block)
- [ ] Execution instructions or playbook reference
- [ ] Target location / scope boundary
- [ ] Dependencies section
If required criteria fail:
- Move the issue to Blocked column
- Add a comment explaining what's missing:
🤖 Agent-heartbeat: This issue is labeled Agentic + Ready but isn't agent-ready. Missing: [list]. Moving to Blocked. Run `/prep-issue <number>` to fill in the gaps, then move back to Ready. - Continue to next item in queue (if any), or exit
If required criteria pass: Proceed to dispatch.
Step 4: Dispatch
4a: Update board state
Move the issue from Ready → In Progress on the project board:
# Get the item ID from Step 1 results
# Status field ID for Project #3: query dynamically or use known field
gh api graphql -f query='
mutation {
updateProjectV2ItemFieldValue(
input: {
projectId: "PVT_kwHOCQ_h4c4BRFq6"
itemId: "<ITEM_ID>"
fieldId: "<STATUS_FIELD_ID>"
value: { singleSelectOptionId: "118f092d" }
}
) {
projectV2Item { id }
}
}'
Add a status label to the issue:
gh issue edit <NUMBER> --repo <OWNER>/<REPO> --add-label "status: In Progress"
gh issue edit <NUMBER> --repo <OWNER>/<REPO> --remove-label "status: Ready" 2>/dev/null
4b: Add dispatch comment
gh issue comment <NUMBER> --repo <OWNER>/<REPO> --body "$(cat <<'EOF'
🤖 **Agent-heartbeat dispatch**
Picked up from Agentic queue at $(date -u +"%Y-%m-%d %H:%M UTC").
Handing off to /work-issue for autonomous execution.
EOF
)"
4c: Execute via /work-issue
Invoke the work-issue skill with the issue number and repo:
/work-issue <NUMBER> --repo <OWNER>/<REPO>
This is where execution transfers to /work-issue. The heartbeat's job is done
once /work-issue takes over. /work-issue handles:
- Branch creation
- Implementation
- Verification
- Commit + PR
- Completion report as issue comment
- Moving to Review column
Step 5: Handle Outcomes
After /work-issue completes (or fails), the heartbeat records the result:
Success path
/work-issue handles its own completion — PR created, issue moved to Review,
completion comment posted. No additional action needed from heartbeat.
Failure path
If /work-issue encounters a blocker or fails:
-
Move the issue to Blocked column:
# Update project board status to a blocked/stalled state gh issue edit <NUMBER> --repo <OWNER>/<REPO> --add-label "status: Blocked" gh issue edit <NUMBER> --repo <OWNER>/<REPO> --remove-label "status: In Progress" 2>/dev/null -
Post a failure comment:
🤖 Agent-heartbeat: Execution failed or was blocked. Reason: <summary of what went wrong> Moving to Blocked. Needs human review before re-dispatch. -
Do NOT retry automatically. Failed items require human review. The heartbeat will skip Blocked items on subsequent ticks.
Guard Rails
Concurrency
- One item per tick. Never dispatch multiple items in parallel. The next tick handles the next item.
- Skip if already working. Before dispatching, check if any Agentic item is currently In Progress. If so, skip this tick — the previous dispatch is still running.
Scope limits
- Only Agentic + Ready items. Never auto-dispatch items without the Agentic category, even if they're Ready. The Agentic label is the explicit opt-in.
- Only open issues. Skip closed issues even if they're still on the board.
- Cross-repo aware. The board can contain items from any repo. Always pass
the correct
--repo owner/nameto downstream skills.
Safety
- No force pushes.
/work-issuealready has this guard, but reinforce: the heartbeat never instructs force operations. - No issue closure. The heartbeat and
/work-issuenever close issues. Dan reviews and closes manually. - Dry-run default for first use. When the heartbeat is first invoked, suggest
running with
--dry-runto verify the queue query works correctly before enabling live dispatch.
Rate limiting
- Respect GitHub API limits. The GraphQL query in Step 1 is a single call. At 10-minute intervals, this is ~144 calls/day — well within GitHub's 5,000 points/hour limit.
- Backoff on errors. If the GitHub API returns an error, log it and exit
cleanly. The next
/looptick will retry automatically.
Dry-Run Output
When invoked with --dry-run, output:
## 🤖 Agent Heartbeat — Dry Run
### Queue Status
- Agentic + Ready items: <count>
- Agentic + In Progress: <count> (would skip tick if > 0)
- Agentic + Blocked: <count>
### Would Dispatch
- **Issue #<number>**: <title>
- Repo: <owner>/<repo>
- Agent-ready: Yes/No (<missing fields if No>)
- Action: Would move to In Progress → invoke /work-issue
### Skipped
- **Issue #<number>**: <title> — <reason skipped>
Integration with /loop
The intended usage pattern:
/loop 10m /agent-heartbeat
This tells /loop to invoke /agent-heartbeat every 10 minutes. Each tick:
- Queries the board (~1 second)
- If nothing to dispatch, exits immediately
- If work found, validates + dispatches (~seconds for validation, minutes for
/work-issueexecution)
The /loop skill handles the timer. The heartbeat handles the per-tick logic.
Recommended intervals
- 10m — good default for active development sessions
- 30m — lighter touch, good for background operation
- 5m — aggressive, useful when loading up the queue with multiple items
Example Session
Dan: /loop 10m /agent-heartbeat
[tick 1]
🤖 Agent Heartbeat: No agentic work in queue. Sleeping.
[Dan creates an issue, labels it Agentic, moves to Ready via Nerve Center]
[tick 2]
🤖 Agent Heartbeat: Found 1 item in queue.
→ Issue #47: "feat: backlog parser — extract todos from markdown"
→ Agent-ready: ✓ (objective, acceptance criteria, execution instructions)
→ Dispatching to /work-issue...
[/work-issue takes over, creates branch, implements, opens PR]
[tick 3]
🤖 Agent Heartbeat: 1 Agentic item In Progress (Issue #47). Skipping tick.
[/work-issue completes, moves #47 to Review]
[tick 4]
🤖 Agent Heartbeat: No agentic work in queue. Sleeping.
Design Philosophy
The heartbeat turns the exocortex from a pull system (Dan manually invokes
/work-issue) into a push system (issues labeled Agentic + Ready get
auto-dispatched). The human remains in control through:
- Explicit opt-in — only Agentic-labeled items are eligible
- Ready gate — items must be moved to Ready (manually or via Nerve Center)
- Prep gate — the heartbeat validates agent-readiness before dispatch
- Review gate —
/work-issueopens a PR, never merges. Dan reviews. - No auto-retry — failures require human review before re-dispatch
This is the "dark factory" pattern applied conservatively: agents do the work, humans control the queue and review the output.