Repository navigation
Description
Problem Statement
I don't want to type commands that can be automated. Today every Run and Spec run starts with me typing thirdshift <Issue URL>, even when the issue is already labelled ready-for-agent and nothing more is needed from me. I want the factory to take those issues on its own.
Three things make that unsafe today:
ready-for-agentstays on an issue after its work ends, whether the pull request is waiting for review, the Run failed, or the Run is still going. Anything that looked for the label would take the same issue again and again.- Nothing on GitHub shows that a Run is in flight. The Issue branch reaches
originonly once the implement session has ended, so for tens of minutes a running issue looks untouched. - In the Day shift, a Spec gets
ready-for-agentwhen it is published, minutes or days before its Tickets are attached. A Spec with no Tickets yet looks exactly like a standalone Ticket.
Solution
A new command, thirdshift pickup, starts a Pickup run: one pass, with no Issue URL, that takes the lowest-numbered Ready issue in the repository and starts a Spec run or a Run on it, exactly as thirdshift <Issue URL> would. thirdshift does not schedule itself (ADR 0009): the operating system's scheduler starts each pass, from one crontab line per repository:
PATH=/home/you/.local/bin:/home/you/.cargo/bin:/usr/local/bin:/usr/bin:/bin
*/30 * * * * cd ~/repos/widgets && thirdshift pickup base main >> ~/.thirdshift/logs/pickup-cron.log 2>&1
Four things make it safe to leave running:
- A Ready issue is a narrow thing. Open, labelled
ready-for-agent, with no label that makes an Unready Ticket, not a sub-issue, not a Base fix's issue, with no open blocker, never started, and left untouched for ten minutes. - The Claim. Every Run and Spec run that is not a Ticket's Run or a Base fix swaps the issue's
ready-for-agentforin-progresswhen it starts, however it was started. A Claimed issue is never a Ready issue. - One unattended run per repository at a time on a machine. A Pickup run shares the lock an Architect run takes, and is skipped while it is held.
- The Claim limit. A Pickup run takes nothing while three or more open issues carry a Claim, so a broken Base branch can't fail every Ready issue in turn, and pull requests can't pile up unreviewed.
A skipped pass exits 0, says why on stderr, and sends no Run notification. A pass that takes an issue ends as the Spec run or Run it dispatched does, and sends one notification when asked to.
The Day shift's copies of the spec and ticket skills change so that ready-for-agent on a Spec means its Tickets are published.
User Stories
- As a thirdshift user, I want
thirdshift pickupto take aready-for-agentissue and run it, so that I don't typethirdshift <Issue URL>for work I have already marked ready. - As a thirdshift user, I want a Pickup run to be the ordinary command started by my operating system's scheduler, so that there is no daemon to keep alive.
- As a thirdshift user, I want the README to give a complete crontab line for a Pickup run, so that I can paste and edit it.
- As a thirdshift user with several repositories, I want one crontab line per repository, so that thirdshift needs no list of repositories.
- As a thirdshift user, I want
thirdshift pickup base <branch>to name the Base branch, so that the pass doesn't depend on which branch my clone is on. - As a thirdshift user, I want a Pickup run to take the lowest-numbered Ready issue, so that older work goes first.
- As a thirdshift user, I want a Pickup run to take one issue per pass, so that the scheduler's interval, not thirdshift, sets how much work is started.
- As a thirdshift user, I want only issues explicitly labelled
ready-for-agentto be taken, so that an unlabelled issue is never built unasked. - As a thirdshift user, I want an issue that also carries
ready-for-human,needs-info,wontfixorneeds-triageto be passed over, so that a contradictory label errs on the side of not running. - As a thirdshift user, I want a sub-issue never to be taken on its own, so that a Ticket's work always goes through its Spec's Spec branch.
- As a thirdshift user, I want a Spec to be taken only when the Spec itself is a Ready issue, so that labelling one Ticket doesn't build its unlabelled siblings.
- As a thirdshift user, I want a pass to tell me when it passed over a
ready-for-agentTicket because its Spec isn't ready, so that I know to label the Spec. - As a thirdshift user, I want an issue with an open "blocked by" issue to be passed over, so that work waits for what it depends on.
- As a thirdshift user, I want a Base fix's issue never to be taken, so that the Run that opened it stays its owner.
- As a thirdshift user, I want an issue that already has an Issue branch or any pull request to be passed over, so that a Pickup run never does a Continuation and failures wait for me.
- As a thirdshift user, I want an issue labelled, given a sub-issue or given a blocker in the last ten minutes to be passed over, so that a Spec isn't taken while its Tickets are still being attached.
- As a thirdshift user, I want an Architect plan to be taken like any other issue, so that
thirdshift architect --plan-onlyat night andthirdshift pickuplater is a workflow. - As a thirdshift user, I want a pass to list each
ready-for-agentissue it passed over and why, so that the cron log explains why nothing started. - As a thirdshift user, I want every Run and Spec run to mark its issue
in-progressin place ofready-for-agent, so that the issue list shows what the factory has taken. - As a thirdshift user, I want a Run I start by hand to make a Claim too, so that a scheduled pass doesn't start a second Run on the same issue.
- As a thirdshift user, I want a hand-started Run on an issue with no
ready-for-agentlabel still to addin-progress, so that the label means the same thing however the Run started. - As a thirdshift user, I want the Run or Spec run an Architect run dispatches to make a Claim, so that there is one rule for every issue the factory takes.
- As a thirdshift user, I want a Ticket's Run in a Spec run and a Base fix to make no Claim, so that only the issue I would look at carries the label.
- As a thirdshift user, I want thirdshift to create the
in-progresslabel when the repository lacks it, so that I set nothing up. - As a thirdshift user, I want a Claim to stay while the issue's pull request waits for review, so that the issue isn't taken again.
- As a thirdshift user, I want a Claim to stay after a Failed run that left a branch or pull request, so that the failure waits for me and isn't retried blindly.
- As a thirdshift user, I want a Claim released when the Run left nothing on
origin, so that an outage, such as a usage limit or an expired login, costs one retry and doesn't strand the issue. - As a thirdshift user, I want a released Claim to put the issue's labels back as they were, so that it is a Ready issue again only if it was one before.
- As a thirdshift user, I want thirdshift to take
in-progressoff when its Self-merge closes the issue, so that closed issues don't carry a stale label. - As a thirdshift user, I want each Pickup run to take
in-progressoff closed issues, so that issues I merged by hand are cleaned up too. - As a thirdshift user, I want to send a failed issue round again by relabelling it and running it by hand, so that taking over stays a deliberate act.
- As a thirdshift user, I want a Pickup run skipped while another Pickup run or an Architect run on the same repository is running on the machine, so that two unattended runs never build one repository at once.
- As a thirdshift user, I want a Pickup run skipped when there is no Ready issue, so that an empty queue is not a failure.
- As a thirdshift user, I want a Pickup run skipped while the repository is at its Claim limit, so that a red Base branch doesn't fail my whole queue overnight.
- As a thirdshift user, I want to set the Claim limit in the User config, so that I can raise it on a repository where I review quickly.
- As a thirdshift user, I want the Claim limit to count every open Claimed issue, including ones I started by hand, so that a pull request waiting on me counts whoever started it.
- As a thirdshift user, I want a skipped pass to exit
0with stdout empty and one line of reason on stderr, so that cron sees no error and the log says what happened. - As a thirdshift user, I want a skipped pass to send no Run notification, so that a pass every half hour doesn't send 48 emails a day.
- As a thirdshift user, I want a pass that takes an issue to send exactly one Run notification when asked to, so that I hear how the work ended.
- As a thirdshift user, I want the dispatched Spec run or Run to send no notification of its own, so that I get one email, not two.
- As a thirdshift user, I want
merge,no-merge,base-fix,no-base-fix,parallel <n>,emailandno-emailto mean what they do forthirdshift <Issue URL>, so that there is nothing new to learn. - As a thirdshift user, I want the User config to fill in what the command leaves unsaid, so that how a Pickup run works an issue follows my machine's defaults.
- As a thirdshift user, I want
parallel <n>on a Pickup run to apply when the Ready issue is a Spec and be ignored otherwise, so that one crontab line works for Specs and standalone Tickets alike. - As a thirdshift user, I want a Pickup run to end as the run it dispatched does, with its exit code, its pull request's URL on stdout and its last stderr line, so that scripts treat the two alike.
- As a thirdshift user, I want a Pickup run to make the checks an Architect run makes before any work, so that a clone it can't use stops the pass before it Claims anything.
- As a thirdshift user, I want a Pickup run asked for a notification to check for an address and a Resend key before any work, so that a broken setup shows in the cron log on the first pass.
- As a Day shift user, I want
/to-specto ask whether Tickets will follow, so that a Spec that stands alone is marked ready at once and one that will be ticketed is not. - As a Day shift user, I want
/to-ticketsto mark the Specready-for-agentas its last step, so that the label means the Tickets are published. - As a thirdshift user who uses the upstream skills unchanged, I want the README to say what
ready-for-agenton a Spec must mean, so that I know not to label a Spec before its Tickets exist. - As a thirdshift user, I want
thirdshift helpand the README to describe the Pickup run, the Claim and the Claim limit, so that I can find them.
Implementation Decisions
- Command:
pickupis a command only as the first argument, likearchitect. It takes the flags an Architect run takes for the run it dispatches (merge,no-merge,parallel <n>,base-fix,no-base-fix), for its own notification (email [<address>],no-email) and for the Base branch (base <branch>), each at most once, with or without dashes. It takes no focus and no--plan-only. Any other argument is an argument error, exit2. - Base branch:
base <branch>as Nightly Architect runs: a cron recipe, and an Architect run that is safe to schedule #237 defines it forarchitect: the named branch, whatever is checked out. Without it, the branch checked out in the Launch directory. The dispatched run takes the Pickup run's Base branch, by the internal parameter Nightly Architect runs: a cron recipe, and an Architect run that is safe to schedule #237 added. - Order: arguments, User config, the Run notification's checks, the preflight checks an Architect run makes, the lock, the sweep, the Claim limit, the search for a Ready issue, then the dispatch, whose run makes the Claim.
- The lock: the one Nightly Architect runs: a cron recipe, and an Architect run that is safe to schedule #237 gives an Architect run: an OS advisory lock keyed by the repository, tried without waiting, held until the process exits. A Pickup run and an Architect run take the same lock, so each skips while the other runs. The Architect run's skip reason is reworded to cover both. Runs started by hand on an Issue URL still don't take it.
- The search: one listing of the repository's open issues labelled
ready-for-agent, lowest number first. Each is checked in turn until one is a Ready issue. One that isn't gets a line on stderr with the first reason that applies. - Ready issue rule, all of:
- carries none of the Unready Ticket labels, read from the one home those labels have;
- has no parent issue;
- is not labelled
base-fix; - has no open blocker, as GitHub's issue dependencies report it;
- has never been started: no Issue branch for it on
originand no pull request, open, merged or closed, from one; - is settled: ten minutes have passed since the latest of
ready-for-agentbeing applied, a sub-issue being added or removed, and a "blocked by" link being added or removed. These are read from the issue's timeline, because adding a sub-issue does not change the issue's update time. The ten minutes is fixed, with no setting.
- A
ready-for-agentTicket inside a Spec is never promoted to its Spec. Its line says its Spec is not ready, naming the Spec. - Architect plans: no special case.
architect-planneither helps nor hinders. - Dispatch: exactly as
thirdshift <Issue URL>would from the same clone, a Spec run when the issue has sub-issues and a Run otherwise, with the flags given and the User config for the rest.parallel <n>applies to a Spec run and is ignored, with no error, when the issue is not a Spec, unlike on an Issue URL or an Architect plan: the command can't know in advance which it will take. - Ending: the dispatched run's ending is the Pickup run's: exit code, the pull request's URL alone on stdout, the last line on stderr.
- The Claim: made by every Run and Spec run that is not a Ticket's Run in a Spec run or a Base fix, once its preflight checks pass and before its worktree is created: in one request,
in-progressis added andready-for-agentremoved if present, with the issue's other labels kept. thirdshift creates thein-progresslabel if the repository lacks it. A Continuation on an already Claimed issue changes nothing. - Release: when such a Run ends with no Issue branch on
originand no pull request for the issue, or such a Spec run ends with no Spec branch onoriginand no Spec PR, thirdshift puts the labels back as they were before the Claim. Any other ending leaves the Claim. A process killed outright releases nothing; the Day shift relabels. - Removal on close: where thirdshift itself closes the issue, after a Self-merge, it also removes
in-progress. A failure to remove it is awarning:line, like the other steps after a merge. - The sweep: each Pickup run, once it holds the lock, lists the repository's closed issues labelled
in-progressand removes the label from each. A failure is awarning:line and doesn't stop the pass. - Claim limit: a new User config key,
pickup.limit, a whole number from 1 up, default 3. A Pickup run counts the repository's open issues labelledin-progress, and is skipped when the count is at or over the limit. Setup writes the key at its default and doesn't ask about it. There is no command-line flag for it. - Skipped outcome: three reasons: the lock is held, there is no Ready issue, the repository is at its Claim limit. Each exits
0, with stdout empty and one line of reason on stderr, after any passed-over lines. No Run notification is sent, even when one was asked for. - Run notification: a pass that takes an issue sends the notification the dispatched run would have sent, and the dispatched run sends none, as with an Architect run. Its checks are made before any work, so a missing address or key stops every pass with exit
1, where the cron log shows it. - Architect run: the README's "the plan stays
ready-for-agent" becomes "the plan isin-progress". The open-plan check is unchanged, since it readsarchitect-plan. - Day shift skills: in the repository's own copies, not the Factory skills.
/to-specasks whether Tickets will follow: if so it labels the Specneeds-triage, and otherwiseready-for-agent./to-tickets, given a Spec, swaps the Spec'sneeds-triageforready-for-agentas its last step, after every Ticket and blocking link is published. The Factory skills already label a planneeds-triageand are not changed. - Triage label doc: says what
ready-for-agentmeans on a Spec, and thatin-progressis thirdshift's own label, not a sixth triage role. - README and help: a Pickup runs section with the crontab recipe above, what a Ready issue is, the Claim, the Claim limit, the skips, and the warning for users of the upstream skills.
thirdshift helpgains the command. The User config section gainspickup.limit. - Glossary and ADR: Pickup run, Ready issue, Claim and Claim limit, and ADR 0009, are written already in Glossary and ADR 0009 for the Pickup run (#212) #278.
Testing Decisions
- A good test here drives the real
thirdshiftbinary and asserts only on what a user or GitHub would see: the exit code, stdout, stderr lines, the labels on issues, the pull requests, and the emails the Resend stand-in receives. No test reaches into the code that decides. - One seam, the existing one: a scenario run against the fake
ghand the fakeclaude, as the Architect run's tests do. No new seam. - A new test file for the Pickup run covers: the lowest-numbered Ready issue is taken; each reason an issue is passed over, with its line; the three skips, each with exit
0, empty stdout and no email; a Spec and a standalone Ticket each dispatched and ended as that run ends; the flags and the User config reaching the dispatched run;parallelignored for a standalone Ticket;base <branch>; the sweep; the Claim limit at, under and over; one notification for a pass that took an issue. - The Claim is tested where each kind of run is tested already: a fresh Run, a Continuation, a Failed run that left work and one that left none, a Merge run that closes the issue, a Spec run and its Tickets' Runs, a Base fix, and an Architect run's dispatch.
- The lock shared with an Architect run is tested as One Architect run per repository at a time, with a skipped outcome #247 tests one Architect run against another.
- The settle time needs no clock seam: the fake
ghsets the timeline's timestamps, so a test makes an event recent or old relative to now. The Architect run's test that refuses a plan older than the run is the prior art. - The fake
ghgains what these need: listing issues by label and state, an issue's parent, its timeline events, and adding and removing a label. - User config tests gain
pickup.limit: its default, a value from 1 up,0and a wrong type refused, and Setup writing it. - The command-surface tests gain
pickupand its argument errors. - The Day shift skill edits and the crontab recipe are prose and are not tested. A person tries the recipe on a real machine when reviewing the Spec PR.
Out of Scope
- A daemon, a
watchcommand, an--everyflag, a GitHub Action or a webhook (ADR 0009). - More than one Pickup run per repository at a time, or a setting for it. More than one Pickup run per repository at a time on bigger machines #279 covers it, with Spec run default parallelism oversubscribes small machines #201.
- A priority label, or any order but lowest number first.
- Retrying a failed issue. A Pickup run never does a Continuation.
- Refusing a hand-started Run on an issue whose label says it isn't agent work (Refuse to start a Run or Spec run on an issue whose own triage label says it isn't agent work #228).
- A list of repositories in thirdshift, or one pass over several.
- An exact guarantee across machines. The Claim is best-effort between two passes that look at the same moment on different machines, as a Base fix's issue is.
- A setting for the settle time.
- Changing the Factory skills.
Further Notes
- Blocked by Nightly Architect runs: a cron recipe, and an Architect run that is safe to schedule #237:
base <branch>, the per-repository lock and the skipped outcome come from it. - The settle time is a second line of defence for users of the upstream skills. On Nightly Architect runs: a cron recipe, and an Architect run that is safe to schedule #237 itself the Spec was labelled at 03:47:43 and its four Tickets attached between 03:52:11 and 03:52:14, and the Spec's update time stayed at 03:47:43.
- The original request, from before the grilling: "as someone who doesn't want to manually run commands which can be automated, I want a way to run thirdshift such that it can initiate / pick up issues on a repo in an automated way / a way without my having to run
thirdshift <gh issue url>".
Activity
[-]make a way for thirdshift to pickup issues on its own[/-][+]make a way for thirdshift to pickup ready-for-agent labeled issues on its own[/+]Merge pull request #278 from JacobStephens2/212-grill-ready-for-agent
[-]make a way for thirdshift to pickup ready-for-agent labeled issues on its own[/-][+]Pickup runs: thirdshift takes ready-for-agent issues on its own[/+]