Dispatch workflow scripts for the Saffron agent workspace.
Owner: Saffron (OpenClaw agent) — itsmiso-ai account
Purpose: Version-controlled scripts and workflow documentation for the Dispatch integration layer.
This repository is intended to be safe to make public: it should contain only workflow code, runbooks, and docs. Runtime state, credentials, OpenClaw configuration, memory, cron state, and local workspace artifacts are excluded.
This repo tracks Dispatch workflow files from the Saffron agent workspace:
- Python scripts for heartbeat grooming, lane judging, backlog syncing
- Bash/shell utility scripts
- Workflow documentation and runbooks
- Sanitized cron prompt templates for Dispatch-owned automation
- Public-safe heartbeat contract reference at
docs/heartbeat.md
The following are intentionally excluded and must never be committed:
- HEARTBEAT.md — PVC-backed runtime copy; version the public-safe reference
copy at
docs/heartbeat.mdinstead - cron/jobs.json — Runtime state, managed by
openclaw cron - .state/* — Runtime queue state and watch lists
- pr_fix_queue.json — Legacy local queue state; active PR-fix state lives in Dispatch
- Any file containing tokens, secrets, or credentials
- Any OpenClaw agent config, session, or memory files
- home-ops or dispatch app code
Cron prompts may be versioned only as sanitized templates under
cron-prompts/. Do not commit schedules, delivery targets, runtime state,
channel IDs, token values, local operator-only prompts, or the raw
cron/jobs.json file.
The Dispatch application lives separately at misospace/dispatch. This repo contains only the agent-side workflow scripts that interact with Dispatch as a consumer.
| Script | Purpose |
|---|---|
issue_lane_judge.py |
Classify issues into normal/escalated/backlog lanes |
pr_fix_queue.py |
Compatibility CLI for Dispatch-backed PR review-fix queue management |
dispatch_worker_preflight.py |
Deterministic Normal/Escalated worker preflight: PR-fix, active work, lane verification, queue selection, optional claim |
worker_result_guard.py |
Validate Normal/Escalated worker final text against the terminal worker contract |
heartbeat.py |
Run deterministic heartbeat plumbing: Dispatch PR follow-up sync, scheduled sync, reconciliation, worker queue visibility, and Dispatch run reporting |
backlog_groomer.py |
Deterministic backlog candidate collector for Saffron-owned agent grooming |
project_backlog_sync.py |
Compatibility wrapper for Dispatch scheduled sync (POST /api/sync/scheduled); no GitHub Projects access |
project_groom.py |
Dispatch v0.3 grooming: scheduled sync, status reconciliation, lane classification, and worker queue visibility |
wishlist_read_board.py |
Compatibility reader for Dispatch normal queue; does not query GitHub Projects |
wishlist_read_gpt_audit_board.py |
Compatibility reader for Dispatch escalated queue; does not query GitHub Projects |
dispatch_reporter.py |
Report agent runs to Dispatch using only DISPATCH_URL/DISPATCH_AGENT_TOKEN |
dispatch_work_update.py |
Update Dispatch checkpoints and issue status from worker sessions |
research_before_task.py |
Research GitHub issues before implementing |
sync_summary.py |
Compact Dispatch sync summary helper |
cron-prompts/ contains public-safe source templates for Dispatch workflow
cron prompts. These templates intentionally use placeholders such as
{{WORKFLOW_DIR}}, {{DISPATCH_NORMAL_AGENT}}, and
{{BLOCKED_MERGE_REPOS}} instead of live runtime values.
The templates are documentation and review artifacts. Runtime cron jobs are
still managed by openclaw cron; cron/jobs.json remains excluded because it
contains schedules, delivery targets, model overrides, state, and other
environment-specific data.
Worker cron prompts no longer reference GitHub Project boards. Instead, they consume work from Dispatch queue APIs and Dispatch-owned lifecycle state:
- Normal lane:
GET /api/agents/{agentName}/queue?lane=normal - Escalated lane:
GET /api/agents/{agentName}/queue?lane=escalated
Workers claim work via POST /api/issues/claim and update lifecycle status via Dispatch status/lease/checkpoint APIs. GitHub Projects are fully deprecated and must not be queried or mutated by active workflow scripts.
Normal/Escalated worker selection starts with deterministic local preflight, not model judgment:
DISPATCH_AGENT_NAME=saffron-normal python3 scripts/dispatch_worker_preflight.py --lane normal --claim --json
DISPATCH_AGENT_NAME=saffron-escalated python3 scripts/dispatch_worker_preflight.py --lane escalated --claim --jsonThe preflight result action decides the worker path:
clear/stuck— reply with the providedterminaland stop.pr-fix— update the existing PR only.resume-active-work— obey the returnednextActionexactly.claim-ready-issue— implement the returned claimed Ready issue.
Cron result text must match the terminal contract. Use:
printf '%s\n' "$FINAL_TEXT" | python3 scripts/worker_result_guard.pyBoard status contract:
status/backlog= needs triage/grooming, not ready for agents.status/ready= groomed/actionable and available to claim.status/in-progress= claimed or implementation started.status/in-review= PR opened/checks/review pending while the issue remains open.status/done= GitHub issue is closed/terminal only.
Hard rule: opening or updating a PR is not Done. An open issue with an unmerged PR must be In Review, not Done.
Work selection:
- PR-fix queue items from Dispatch have precedence.
- Workers consume exactly one actionable item per run.
- Workers prefer Ready work and do not consume Backlog unless explicitly requested.
- Renovate issues are excluded from agent queues unless explicitly requested.
- If Dispatch returns active work, a checkpoint, or
nextAction, workers obey that next action exactly, perform one bounded step, update Dispatch with the result/checkpoint, and stop.
Backlog grooming is an agent intelligence workflow. Scripts may collect candidate data and apply explicit Saffron-authored decisions through Dispatch, but scripts must not call models directly.
Collect backlog candidates for the Saffron heartbeat/sub-agent handoff:
python3 scripts/backlog_groomer.py --max 10Or call the lower-level collector directly:
python3 scripts/project_groom.py --no-sync --groom-backlog --groom-backlog-only --groom-backlog-max 10The collector writes a JSON request under
.state/backlog_grooming_requests/. Each candidate includes GitHub issue
metadata, labels, body, recent comments, and an agentBrief that a Saffron
heartbeat turn or Saffron sub-agent can use for the judgment step.
Saffron/sub-agent recommendations should be translated into Dispatch grooming actions:
ready->POST /api/issues/groomwithaction: "promote_to_ready".escalated->POST /api/issues/groomwithaction: "escalate"and then ensure the issue is ready/claimable when appropriate.needs-info->POST /api/issues/groomwithaction: "mark_needs_info".needs-human/ policy ambiguity ->POST /api/issues/groomwithaction: "mark_not_ready"and a clear reason.- blocked dependencies ->
POST /api/issues/groomwithaction: "mark_blocked".
Labels remain the source of truth: status/backlog is not worker-ready.
Promoting an issue to worker queues requires changing the GitHub/Dispatch
status label to status/ready.
The removed flags --groom-backlog-use-llm and --groom-backlog-apply now
fail intentionally. If a workflow needs judgment, spawn or run Saffron agent
work; do not reintroduce direct model calls into scripts.
Affected cron jobs:
(Saffron): MC: Normal— normal lane, uses Dispatch normal queue(Saffron): MC: Escalated— escalated lane, uses Dispatch escalated queue
Heartbeat (Saffron agent) owns the policy decision for worker cron enabled state. The decision is made explicit by running, in this order:
# 1. Probe both lanes. The probe is the source of truth for work.
python3 scripts/dispatch_work_probe.py --lane normal --json
python3 scripts/dispatch_work_probe.py --lane escalated --json# 2. If probe.hasWork == true, enable the cron for that lane:
python3 scripts/dispatch_worker_cron.py --lane normal --enable --reason "<probe verdict>" --apply --json
python3 scripts/dispatch_worker_cron.py --lane escalated --enable --reason "<probe verdict>" --apply --json# 3. If probe.hasWork == false, disable the cron for that lane:
python3 scripts/dispatch_worker_cron.py --lane normal --disable --reason "<probe verdict>" --apply --json
python3 scripts/dispatch_worker_cron.py --lane escalated --disable --reason "<probe verdict>" --apply --json# 4. If probe.action == "stuck", surface `needsAttention` in the heartbeat
# reply and do NOT silently enable or disable.scripts/dispatch_worker_cron.py is the only actuator allowed to mutate
worker cron enabled state. It only runs the whitelisted
openclaw cron edit <id> --enable|--disable command. It refuses to touch
schedule, model, prompt, delivery, alerts, or any other cron setting, and
defaults to dry-run. Pass --apply to actually mutate.
project_groom.py is grooming/reporting only. It must not call
openclaw cron edit or any cron mutation.
Secrets and credentials must never be committed. All token handling is done via environment variables injected at runtime.