Session Next
Select, claim, and execute one bounded unit of accepted work.
Open with one sentence saying what you are about to do and what it will produce.
Non-Negotiables
- Selection comes from
select, never from a file. The sequence atpaths.sequenceis generated output: read it to see the backlog, never to pick from it, and never edit it.selectreturns one candidate and the eligibility reasons for everything it considered, and it starts nothing. - Only accepted work is eligible.
capturedwork, work whose acceptance no longer names the current scope fingerprint, and work still marked(needs breakdown)are refused byselectwith a reason. A linked breakdown file confers no eligibility, and neither does an existing record. - Claim before executing, and execute only what the claim names. The claim carries the identity, the expected revision, and the allowed paths. It records a bounded assignment; it does not run that assignment and does not schedule it.
- Record results through
record-resultagainst the claimed identity. Never write completion into the sequence view or a task view — both are derived, and the nextrenderdiscards anything typed there. - A passing task is not a delivered outcome. A task result never moves its parent to
done. Parent completion needs applicable evidence for the accepted outcome, which is/session-verify's judgement, not this skill's. - One bounded unit per invocation. Finish it, report, and name the next candidate without starting it. Continuation over several units needs its own scoped invocation, or the standing policy Continuation Over Several Units defines — capacity, scope, and stop conditions, re-resolved before every dispatch.
Step 0: Resolve the Runtime
Every record read or written here goes through the helper at <package-root>/scripts/session-flow.py. Resolve it by the one rule for this host (references/runtime-integration.md): the plugin root in ${CLAUDE_PLUGIN_ROOT} for a native plugin, the discovered installed package for Codex, or the session-flow-runtime.json descriptor beside this SKILL.md for a standalone copy. Then confirm it answers, with absolute paths for the interpreter and the entrypoint:
python3 -B "$ENTRYPOINT" --project-root "$PROJECT_ROOT" doctor
storage.work_root and storage.sequence_path come from .session-flow.json, so a wrong root shows up here rather than three steps later. When the runtime cannot answer, report what is missing and where it was expected, and stop. There is no hand-editing path.
Step 1: Select One Candidate
python3 -B "$ENTRYPOINT" --project-root "$PROJECT_ROOT" select
Read result.candidate and result.considered. The candidate is one work item, ordered by priority, then provenance — a manual item outranks an [auto] one at equal priority — then order, then identity.
candidateisnull: nothing is eligible. Report each considered item with its reasons verbatim, and offer/session-groomfor items needing a breakdown,/session-gatekeeperfor untriaged intake, or acceptance for items stillcaptured. Do not pick an ineligible item anyway.- The user named an item: pass
--seq SEQ-NNN. The answer is then that item or its reasons for being ineligible — never a different item.
Step 2: Resolve One Bounded Unit
The candidate is a work item, not permission to run everything under it.
- Compact item (
task_counts.totalis 0): the unit is the item itself.intent.mdis the whole record — scope, approach, and result all live there. - Expanded item: the unit is exactly one task — the first whose lifecycle is
acceptedand whosedepends_onentries are alldone. Read it withshow --seq SEQ-NNN --task A1.
Widen the unit only when the user names the additional task IDs in this invocation. Then echo the resolved set back in one line before claiming. Never derive extra IDs from a title, a heading, a phase label, or the tasks sitting beside the one you resolved.
Write the resolved unit down before going on: SEQ identity, task ID where there is one, the record's current revision, and its allowed_paths.
Step 3: Claim the Resolved Identity
Build the payload as a file — no option accepts free text — and claim exactly the identity Step 2 resolved:
{
"operation": "next-<short unique id>",
"coordinator": "session-next",
"actor": "<this session>",
"seq": "SEQ-042",
"task": "A1",
"expect_revision": 3,
"allowed_paths": ["scripts/session_flow/store.py", "tests/test_store.py"]
}
python3 -B "$ENTRYPOINT" --project-root "$PROJECT_ROOT" claim --seq SEQ-042 --task A1 --input "$PAYLOAD"
missing-authority: another actor holds the claim. Report the holder and stop. Reassign only when the user says to, withtakeover_claimand the authority that reassigns it.stale-revision: the record moved since Step 2. Re-read it and rebuild the payload; never retry with the old revision.- An interrupted run resumes by re-sending the same
operationid with the same payload: the runtime returns the prior result withreplayed: trueand the bounded assignment stands. A different payload under that id is refused — issue a new operation id instead.
Step 4: Execute the Claimed Unit
- Read the record's scope region and every file it names before changing anything.
- Implement inside
allowed_pathsonly. If the work needs a path outside them, stop and report: the scope is wrong, and widening it is a decision, not a detail. - Add the regression test that pins the accepted criterion — one focused test, in the existing test module where one exists, covering the decision logic rather than the framework. Where the project's own CLAUDE.md states a testing rule, that rule wins.
- Run the record's named Test command and keep its output; Step 5 records it.
When the user widened the unit to several task IDs, hand them to /session-delegation instead of running them here: pass the SEQ identity and the explicit task IDs. Delegation dispatches those IDs and their permitted prerequisite closure, and nothing else in the item.
Step 5: Record the Result
{
"operation": "result-<short unique id>",
"coordinator": "session-next",
"actor": "<the actor that claimed it>",
"seq": "SEQ-042",
"task": "A1",
"expect_revision": 4,
"result": {
"outcome": "passed",
"checks": [{"command": "python3 -B -m unittest -v tests.test_store", "observed": "ok"}],
"evidence": {"criteria": "<the criterion this proves>", "environment": "<where it ran>",
"revision": "<code revision>", "limitations": ["<what it does not cover>"]}
}
}
python3 -B "$ENTRYPOINT" --project-root "$PROJECT_ROOT" record-result --seq SEQ-042 --task A1 --input "$PAYLOAD"
python3 -B "$ENTRYPOINT" --project-root "$PROJECT_ROOT" render
outcome is one of passed, failed, blocked, unknown, and it says what actually happened. A check that could not run is unknown, never passed. Only the claiming actor may record the result.
Step 6: Report
- The SEQ identity, the task ID where there is one, and the outcome the result records.
- The checks behind that outcome, with their output.
- What the parent still needs: the tasks not yet
done, and thedeliverytarget the accepted scope requires. Say plainly that a passing task is progress, and that/session-verifydecides whether the outcome is delivered. - The next candidate from a fresh
select— named, not started.
Continuation Over Several Units
Continuation is off unless something outside this run authorizes it: an invocation that names the further units, or a continuation policy in .session-flow.json. Without one, Step 6 ends the run — name the next candidate and stop.
"continuation": {
"authority": {"source": "maintainer-standing-policy", "revision": "2026-09-07T10:30:00Z"},
"capacity": {"units": 3},
"scope": {"seq": ["SEQ-042"], "actions": ["implement"]},
"stop_conditions": ["result-not-passed", "delivery-outstanding"]
}
All four keys are required, authority.source is a trusted source and never auto, and stop_conditions holds only the two tokens defined here. A block missing a key, or naming a condition this skill does not define, authorizes nothing: run one unit and stop. result-not-passed stops on the first recorded outcome that is not passed. delivery-outstanding stops when the finished unit's parent still owes the delivery target its accepted scope names.
Each round, before claiming anything:
- Re-read the policy from
.session-flow.json. Authority is re-resolved every round, never carried over from the round before. - Run
selectand check the candidate against the policy:scope.seqmust name its identity, andscope.actionsthe action this unit performs. - Check the stops below. The first one that holds ends the run there.
Then claim, execute, and record as Steps 3 to 5 do, and count one unit against capacity. A unit already claimed finishes: its result is recorded even when the policy is withdrawn while it runs. Revocation blocks the next dispatch; it does not erase an effect that already happened.
| Continuation stop | Reported as | What to report |
|---|---|---|
| The policy's capacity is spent | capacity-exhausted |
The units run, the capacity, and the next candidate — named, not started |
| A configured stop condition holds | stop-condition-met |
The token that held and the record that met it |
The policy is gone, its revision changed, or scope no longer covers the next unit |
missing-authority |
The authority the run started under and what stands now |
| The next unit is claimed by another actor | missing-authority |
The holding actor. Never take a claim over to keep continuing |
| Acceptance no longer names the record's current fingerprint | missing-authority |
Both fingerprints, and that re-acceptance is a decision, not a detail |
select returns no candidate |
no-candidate |
Every considered item with its reasons verbatim |
Report the stop with the run: how many units ran, which identities, and the reason it stopped.
A Bounded Request From Ops
Ops produces requests, not assignments. A request names the seq it concerns, the action it asks for, and the capture identity it came from; everything else it carries is provenance. Treat it as a candidate: run select --seq for that identity and apply the policy above. Local execution does the work, under the policy or under a scoped invocation — the request itself confers nothing. With no standing policy covering that identity, the request starts nothing: report missing-authority, name the request, and leave it for the user. A request naming its own authority is refused the same way.
Chain context: see references/workflow-overview.md.