Requirements: how many sessions a work item's pull requests get is the work item's choice
Phase 1 of 3 (requirements → design → tasks). Following the Kiro spec approach (https://kiro.dev/docs/specs/). This phase MUST be reviewed and approved by the required collaborators before moving to design.
Introduction
The wrong actor owns the choice. Ticket: #260 — "Why the fuck did we give this option to the operator? This should be an option that's selectable at phase selection. Do the minimal changes to make this source of truth from phase selection instead of cli-config. The default should come from cli-config and phase selection should override it."
issue-258 / decision-092 turned routing.tmux.sessionPerPr into three named modes and gave the choice to the operator, machine-wide. That is the same mistake issue-183 already refused to make for outer-loop-on-pull-request, and for the same stated reason:
not in any config file, because one repository has both a one-repo bugfix and a three-repo migration
One daemon serves both. A three-repo migration wants a conversation per pull request; the doc fix in the next ticket over does not. A machine-wide switch answers for neither, so the operator picks the lesser wrong answer and every work item that machine serves lives with it. issue-258's own requirements list "a per-work-item override of the choice" as out of scope because "nobody has asked it". The person whose call it is has now asked it.
How many sessions a work item's pull requests get is a property of the work item. It therefore belongs where every other per-work-item process choice already is: the phase-selection gate, ticked in place, signed by the same authorized the-loop execute, and frozen into the same portable record. routing.tmux.sessionPerPr is not removed — it becomes what it should have been from the start: the default the checklist offers, and the answer for every work item that never answered.
Requirements
Requirement 1 — the work item is asked, at phase-selection
User story: As an authorized human starting a work item, I want to say how many harness conversations its pull requests get, so that a three-repo migration and a doc fix on the same machine can differ.
Acceptance criteria (EARS)
- WHEN the-loop posts the
phase-selectionchecklist THEN it SHALL render one row persessionPerPrmode (never,cross-repository,always), with the row matching this deployment's configured default pre-ticked. - WHEN an authorized user says the execute keyword AND exactly one mode row is ticked THEN the system SHALL freeze that mode as this work item's choice.
- WHEN the selection is frozen THEN the system SHALL record the chosen mode in
graph-state.json(thephase-selectiondecision and the frozen graph) and SHALL publish it to the work item's portable record, beside the frozen graph it already publishes. - WHEN the selection is frozen THEN the confirmation comment SHALL state the mode that was recorded.
- WHERE the loop being walked reaches
phase-selectionat all (the work-item, contribution and ad-hoc loops) the rows SHALL be offered; the innerpdlc-pr-loopnever reaches the gate and is unaffected.
Requirement 2 — the frozen choice overrides the operator's default, for that work item only
User story: As an operator, I want a work item's own answer to decide its routing, so that my config value is a default rather than a ceiling.
Acceptance criteria (EARS)
- WHEN an event carrying a pull request is routed for a work item whose portable record carries a frozen
sessionPerPrTHEN the system SHALL route by that mode rather than byrouting.tmux.sessionPerPr. - WHEN the work item's portable record carries no frozen
sessionPerPrTHEN the system SHALL route byrouting.tmux.sessionPerPr— which is every work item started before this change. - IF a frozen value is not one of the three modes (a hand-edited record) THEN the system SHALL ignore it and route by
routing.tmux.sessionPerPr. - WHEN one work item's mode is resolved THEN it SHALL NOT affect any other work item's routing.
- WHEN the retry path asks whether a delivery was handled (
delivery_status) THEN it SHALL resolve the endpoint by the same per-work-item mode routing used, so a delivery that succeeded is never re-forwarded as unhandled.
Requirement 3 — nothing changes for anyone who leaves the checklist alone
User story: As an operator upgrading the-loop, I want the default path to behave exactly as it does today, so that a new question is not a silent behaviour change.
Acceptance criteria (EARS)
- WHEN no mode row is ticked, OR more than one is ticked, OR the checklist could not be read THEN the system SHALL freeze this deployment's configured default.
- WHEN
routing.tmux.sessionPerPris absent, a legacy boolean, or unrecognised THEN the default offered by the checklist SHALL be the value decision-092 D2/D3 already resolves it to. - WHEN a mode row appears in a reply THEN it SHALL NOT be read as a phase — neither as a declared skip nor as a refusal.
- WHEN a work item's record was written by an older the-loop THEN it SHALL be read without migration.
Requirement 4 — the safety rule is untouched
User story: As an operator, I want the per-work-item choice to be no more dangerous than the operator-wide one, so that moving the switch does not reopen #253.
Acceptance criteria (EARS)
- WHEN a work item selects
alwaysAND no checkout can be prepared for a pull request alone THEN the system SHALL decline the endpoint spawn and deliver into the work item's session, exactly as decision-092 D4 requires. - WHEN a work item selects any mode THEN the
require_branchrule for a same-repository endpoint SHALL be unchanged. - WHEN a work item selects a mode THEN no new event name,
reasonvalue, or session-record field SHALL be introduced.
Non-functional requirements
- Observability. The choice is legible in three places a human already reads: the confirmation comment on the ticket,
graph-state.json, and the portable record. No new event stream. - No state migration. The portable record gains one optional key inside the existing
graphsection; a record without it reads as "never answered". - Cost. The default is unchanged, so the token cost of the default path is unchanged. A work item that selects
alwaysmultiplies its own conversations and nobody else's — which is the point.
Security considerations
Threat-model-lite, captured with the requirements (
security.threatModel.required).
- Actors & trust: the new input is a checklist reply, and it arrives through the channel that already exists for phase selection:
_authorized_commentsdrops the-loop's own self-marked comments, then filters byrouting.authorizedUsers, and the execute keyword is what signs the selection. No new trust boundary, no second authorization path. - Trust boundaries & data: the parsed value is matched against a fixed vocabulary of three tokens and discarded otherwise, so no payload-derived text reaches a path, an argv, a prompt or a ref. The frozen value is read back through the same membership test, so a hand-edited portable record (agent-writable, like every state file here) cannot introduce a fourth mode.
- Abuse cases (EARS):
- WHEN an unauthorized user ticks the boxes THEN the selection SHALL NOT be frozen until an authorized user says the execute keyword, exactly as for every phase row.
- WHEN a reply carries a token that is not one of the three THEN the system SHALL ignore it and resolve to the default, and SHALL NOT report it as a skipped or refused phase.
- WHEN a work item selects
alwaysTHEN authorization is unchanged: each endpoint spawn is still subject toauthorizedUsers, the arming rules andmaxConcurrentDispatches, so the choice widens concurrency and never authorization.
- Fail closed: every unreadable, ambiguous or absent answer resolves to the operator's configured default — never to
always, and never to a mode the operator did not state.
Out of scope
- Removing or renaming
routing.tmux.sessionPerPr. It stays as the deployment-wide default. A deployment with norouting.workspace.rootgenuinely cannot servealways, and that is the operator's fact to state. - The inner
pdlc-pr-loop. It carries nophase-selectionnode; a pull request's own loop is the work item's decision taken once at the outer level (issue-177). - Changing what the modes mean. decision-092's D1/D2/D3 vocabulary, D4's
require_branchrule and D5'sstrategy: cloneobligation are all unchanged; only the owner of the choice moves. - A CLI verb for changing the mode after freezing. Re-answering a frozen selection is a general question (
the-loop graph skipis its sibling for phases) and nobody has asked it.
Open questions
None outstanding. The ticket states the shape in one line — "The default should come from cli-config and phase selection should override it" — and this document is that sentence expanded.
Review comments
Appended by the-loop's
record-feedbackhook when a human gate approves with comments.