Skip to Content
Scheduling

Scheduling

Conclave does not ship a background scheduler. The delivery loop can gate on a recurring window and budgets; something outside Conclave must invoke it (Claude Code /loop or /schedule, a Cursor Automation, or cron).

There is one loop: /conclave-dev --loop. It takes the active sprint (or the IDs you pass) and runs three ordered waves.

WaveWhat happensMoves on whenOn failure
1 — DevImplement, push, open the PR, poll CIEvery story is review with green checksRetry up to max_attempts_per_story
2 — QAHeadless /conclave-qa per storyEvery story is verifiedBack to Wave 1 with QA’s blockers
3 — Tech Lead/conclave-pr-review per storyEvery story is done, PR approvedBack to Wave 1 with the review findings, then QA again

No PR is ever merged. The loop finishes with approved PRs listed in the run report and in Slack, waiting for you.

Architecture: ADR-006 . It supersedes ADR-005 and the delivery-loop half of ADR-004 — up to 0.14.0 /conclave-sprint --no-interaction was a merging loop; it is now a plain headless one-pass runner.

Prerequisites

The loop uses the GitHub CLI  for PRs, checks, and reviews. Before running it:

  1. Install gh.
  2. Authenticate (gh auth login) with an account that can push, open PRs, and review them on this repository.

Conclave does not install or configure gh for you. The loop refuses to start without it, because without a PR there is no state in which it could finish.

Weekend recipe

  1. Make sure the sprint is active and the stories you want are ready (run /conclave-planning first if not).
  2. Configure conclave/config.md:
repo: integration_branch: develop # PR base branch; Conclave never merges into it commands: dev: interactive: false loop: true schedule: timezone: "America/Argentina/Buenos_Aires" days: [fri, sat, sun] start_time: "19:00" end_time: "07:00" # crosses midnight duration_days: 3 active_from: "2026-07-31" enforce: true budgets: max_attempts_per_story: 3 max_ci_wait_minutes: 20 max_total_tokens: 2000000 max_wall_clock_hours: 12 models: default: claude-sonnet-4-6 overrides: developer: claude-haiku-4-5-20251001 qa: claude-sonnet-4-6 tech_lead: claude-opus-4-6 notifications: slack: enabled: true webhook_env: SLACK_WEBHOOK_URL on_success: true on_partial: true on_hitl: true
  1. Export SLACK_WEBHOOK_URL in the environment if Slack is enabled — never put the URL in markdown.
  2. Keep the machine awake and the network available for the window.
  3. Start a recurring trigger that fires often enough to resume work, typically hourly:
PlatformExample trigger
Claude Code/loop 1h /conclave-dev --loop (the session must stay open), or /schedule where available (research preview — do not rely on it alone)
CursorAn Automation on an interval, or the local interval loop skill, invoking /conclave-dev --loop
Anycron / launchd invoking your agent CLI with the same command

The trigger only invokes the command; the schedule below decides whether it does anything.

To scope a run to a few IDs instead of the whole sprint, pass them — bugs included:

/loop 1h /conclave-dev --loop US-042 US-043 /loop 1h /conclave-dev --loop BUG-004

How the recurring window works

A firing runs only when all three conditions hold, evaluated in timezone local time:

  1. The local date is inside [active_from, active_from + duration_days).
  2. The local weekday is listed in days.
  3. The local time is inside [start_time, end_time).

When end_time is earlier than start_time the window crosses midnight, and the listed day is the day the window opens. In the example above, days: [fri, sat, sun] with 19:00–07:00 means Friday 19:00 → Saturday 07:00, Saturday 19:00 → Sunday 07:00, and Sunday 19:00 → Monday 07:00.

KeyMeaningDefault
timezoneIANA name (Europe/Madrid, America/Bogota)required when schedule is present
daysAny subset of mon..sunall seven
start_time / end_timeLocal 24h wall clock00:00 / 23:59
duration_daysCampaign length in calendar days1
active_fromLocal date the campaign startsfirst eligible day
enforcefalse prints the window and runs anywaytrue

Upgrading from 0.13.0 / 0.14.0: the old window_start / window_end ISO pair is no longer honored. The loop prints a migration message and stops rather than guessing a recurring window from two timestamps — a wrong guess would widen an unattended, token-spending run. Rewrite the block with the keys above.

commands.dev.loop: true is the sticky equivalent of --loop, but it makes every /conclave-dev invocation in the repo a full three-wave run, including a quick single-story one. Prefer the flag in the trigger unless the repo exists to be driven unattended.

What Conclave does on each firing

SituationBehavior
Outside the windowOne-line no-op, exit 0, no report, no git writes
Inside the window, no in-progress lockRun, or resume where the last firing stopped
An in_progress run younger than max_wall_clock_hoursRefuse when the scopes intersect; unrelated scopes may loop in parallel
Budget or window exhausted mid-runDrain after the in-flight dispatch, then finalize the report (aborted_budget / partial)
--ignore-scheduleBypass the window for this invocation; the report records schedule.bypassed: true

Resumption is driven by story status frontmatter, so a firing that lands mid-pipeline picks up where the last one stopped: review skips Dev and waits on checks, verified goes straight to the Tech Lead.

Token budget honesty

max_total_tokens is a best-effort ledger (estimated and/or measured rows). It is a guardrail, not a billing control. The run report discloses precision (estimated / measured / mixed). Use provider-side limits for a hard spend cap. Budgets are per run — an hourly trigger can spend the token budget on every firing; size the window and max_wall_clock_hours to bound the campaign.

Reading the report

conclave/sprints/SPRINT-NNN/runs/RUN-NNN-dev-loop.md — or conclave/runs/RUN-NNN-dev-loop.md in a repo with no sprint at all.

One report covers the whole loop, including every re-entry. Beyond outcome and Stopped because, it carries:

  • PRs ready for human merge — the point of the run, with a copyable gh pr merge line each.
  • Per-story results — per-wave entry counts, QA→Dev and TL→Dev re-entries, cycle time.
  • Agent productivity — per role: dispatches, stories touched, first-pass success rate, rework caused, average tokens per story, outcome mix.
  • Token ledger — one row per dispatch, tagged with its wave.
  • Conflicts — dependencies ordered and file overlaps serialized in Wave 0.

sprint_closed is always false: merging and closing the sprint stay with you. Interactive /conclave-dev, /conclave-dev --no-interaction without --loop, and /conclave-sprint in either mode never write these reports.

Slack notifications

Three templates, all rendered as Block Kit and posted to the webhook named by webhook_env:

MessageWhenToggle
Delivery loop completedEvery in-scope story is approved and awaiting mergeon_success
Delivery loop finished partiallySome stories incomplete, or the run drained on budget/scheduleon_partial
Needs humanA blocker the loop must not decide — sent the moment it happenson_hitl

The success message is meant to be actionable without opening the report:

Delivery loop completed — SPRINT-004 3 of 3 stories are approved and waiting for a human merge. Conclave does not merge — these PRs are yours to land. Run RUN-002 (sprint) Duration 4.2 h of 12 h Tokens 812k of 2M (estimated) Attempts 4 (cap 3/story) First pass 2 of 3 stories Rework QA→Dev 1 · TL→Dev 0 Ready to merge • US-041 — Checkout validation → PR #142 • US-042 — Retry failed payments → PR #143 • US-043 — Order confirmation email → PR #144 Full report: conclave/sprints/SPRINT-004/runs/RUN-002-dev-loop.md

A human-in-the-loop alert names the blocker and what to do next, and fires while the run continues on other stories:

Needs human — US-045 The delivery loop hit something it must not decide on its own. Reason > AUTONOMOUS_ABORT: no test framework detected; acceptance criteria require > integration tests Run RUN-002 (SPRINT-004) Wave 1 (Dev) Trigger structural_abort Attempts 1 of 3 What was left untouched Branch feat/US-045-refunds was not pushed; no PR was opened. Suggested next step Add a test runner, or run /conclave-dev US-045 interactively to decide. The loop continues with the remaining stories · Report: …/RUN-002-dev-loop.md

Only the env var name ever appears in conclave/config.md. A Slack failure is recorded as slack_delivery: failed in the report and never fails the run.

After the loop — you merge

When the run finishes (or Slack says Ready to merge), open the report and land each PR yourself. Conclave never runs gh pr merge.

# From the report's "PRs ready for human merge" table: gh pr merge 142 --squash --delete-branch gh pr merge 143 --squash --delete-branch gh pr merge 144 --squash --delete-branch # Or one at a time from the PR page in the browser.

Stories are already done with an approving review; merging only lands the code on repo.integration_branch. When every non-retired story in the sprint is done and you have merged what you want, close the sprint with /conclave-review (when that ceremony ships) — the loop itself never closes it.

Incomplete stories stay where the last wave left them (review, verified, or back at ready after an abort). Re-run /conclave-dev --loop (or pass their IDs) inside the next window, or pick them up interactively.

Last updated on