A task is one unit of work inside a project: a folder holding task.json (every field a script
reads) and task.md (the goal, in prose, that nothing parses). This skill makes one, moves an old
one into place, changes its state, splits it into a parent with children, or sets its run mode. It
does not run any of the six stages, and it does not pick which task is active: that is
/aida:next.
Every action below needs the active project's own folder (the one holding project.json, never
the code folder). Resolve that first, with the project skill, before using anything here.
Determine the run mode
Look for a stated run mode on the task active in this conversation, when one is already active.
Found, and it says autonomous or light: act autonomously through this whole invocation, passing
--run-mode autonomous on every call to the script below. Anything else, including no active
task, such as the moment create itself runs: act interactively, the safe default. Decide this
once, at the start. A mode that names stages in brackets covers this call only when it names the
stage this call runs inside.
task-actions.sh never asks a question on its own. Every fact below that this skill would
otherwise ask for must be decided before the script runs; the script only writes what it is given
and reports what happened. Every action prints summary lines: task-file: with the path, id:,
state:, parent:, children: and runMode:, with the stages the mode covers in brackets when
it covers fewer than all. It never prints the record. Read the file at the
printed path when another field is needed.
create <name>
Makes the task and nothing else: no contract, no interview, no stage. It takes a name and a goal. Run it only when the person asked for this task in this conversation, by name or by a yes to an offer. Another skill's hand-off carries that yes. Nothing here invents one.
1. Name. Ask what to call it, unless already said. Check it against ^[a-z0-9][a-z0-9-]*$:
lowercase letters, digits and hyphens, starting with a letter or digit. The worktree folder takes
this name and becomes a hostname label, and DDEV lowercases and rewrites the rest. Existing
tasks keep their ids. On a mismatch, say so and ask again; the script refuses it too, so this
check only saves a round trip.
Autonomous with no name given: halt. A task cannot be filed without one.
2. Goal. Ask what this task is for, in the spirit of a user story: what someone wants to accomplish and why, not a ticket. Write it back in one or two sentences and confirm with a plain yes or no before writing anything. Autonomous with no goal given or implied by the conversation: halt. A task with no stated goal is not a record of anything.
3. Write it. Run, with the run mode set as decided above. Add --after <task-id> when the
person said this task builds on another task of this project:
"${CLAUDE_PLUGIN_ROOT}"/skills/task/scripts/task-actions.sh --run-mode <interactive|autonomous> \
create --project "<projectPath>" --name "<name>" [--after <task-id> [--in-tree]] -- <goal...>
It writes the folder, task.json with state: "new", and task.md with the goal under ## Goal.
It then makes the task's own git worktree in one folder per repository beside the code path, at
<parent of codePath>/<slug of the code folder>.worktrees/<slug of the code folder>-<name>.
The tree is on the branch feature/<name>. The script records both in task.json and commits. The folder name is the
slug of the code folder plus the task name, so a site name is predictable and unique. The tree is
outside the code path for one reason. A nested worktree is invisible to a tool that registers
projects by folder, and DDEV hands it to the parent project. A tree that task.json already
records on disk stays where it is.
The tree is cut from HEAD of the folder this call runs in, when that folder is in the code
repository. So a task made from inside another task's tree starts from that task's work. From
anywhere else the tree is cut from HEAD of the code path. With --after, the tree is cut from
that task's branch, and task.json records after. Use it for a chain of tasks made up front,
because a tree cut now from trunk holds none of the earlier builds. While that task's build is
unfinished, no tree is made: worktree: reads none and after-build: reads unfinished. Skip
steps 4 and 5 then. start makes the tree once that build is finished.
Add --in-tree with --after when the person runs the chain one task after another. The task
then takes over that task's tree, on the new branch feature/<name> from its tip. So the chain
pays one checkout and one dependency sync. That task's review runs in its tree, so the take-over
waits until that review has closed. While it is open, create makes no tree and prints in-tree:.
Skip steps 4 and 5 then. start takes the tree over, or cuts a new tree if the review is still
open, and says which. That tree must hold no uncommitted change. Both tasks
then record one tree, and the branch checked out there says which task builds in it now. A stage
action of the other task exits 3, and its message names the git switch that gives it the tree.
When no tree holds that task's branch, a new tree is cut as with --after alone.
Show the whole output. Exit code 3 has five causes. The name collided with an existing task,
or it failed the name rule. --after named a task this project does not hold, or --in-tree
came without --after. Or the worktree could not be made. In the last case the folder is removed. Say what it printed. For a name, ask for a different one. For the worktree, name the
repair the message gives and stop.
4. Enter the tree. Every stage action of this task runs inside that worktree, and refuses
from anywhere else. The worktree: line names it. Call the EnterWorktree tool with that path,
so scoping in this same window is not refused. The tool asks for approval, because the path is
outside .claude/worktrees/; that is expected. From a window outside the code repository the
tool refuses on first entry, and from a session already inside a worktree it refuses too. A
person may also decline the prompt, which is not a refusal. After either outcome, do not call the
tool again. Take the other route, and read /aida:next for the whole rule. The scripts stay
reachable without entry: start every Bash
call with cd <path> &&. Every call needs it, because the shell's directory resets between
calls. The cd part asks for approval, because the tree sits outside this window's directory.
The exit 79 message names that form too. The way in is /cd <path>, typed by the person: it
moves this session into the tree and keeps the conversation (Claude Code 2.1.169 or later).
Print the path and say so. Go on to step 5 either way.
5. Offer the site. Runs here after step 4, and again at start whenever the task record
still has no environment, whoever called start. A worktree has the branch's files and no
site, so a review or a baseline taken there would capture the served checkout instead. Dispatch
catalog-identifier once per point, as the surfaces skill does. The role reads any word that is
not on a point: line as a framework. The first dispatch has the line
point: worktree-environment, then every framework the project records and the project folder.
When the project record has surfaces.e2e.enabled, dispatch the role again with
point: e2e-setup and the same other lines. When it has surfaces.visualRegression.enabled,
dispatch it again with point: visual-regression. Light: send all these points in one
dispatch, one point: line each. up then installs that harness in the tree.
Pass the worktree-environment answer as --recipe <framework>=<path> or
--lookup-failed <framework>=<word>, one flag per framework. Pass each setup recipe as
--setup-recipe <kind>=<path>, where the kind is e2e or visual-regression. A setup lookup
that returns no path gives no flag, and up says that harness is not installed. Then run
environment <name> show.
The word is no-recipe, listing-unreachable or fetch-failed; the script refuses any other.
not-applicable from show means no framework has a recipe: record it with
environment <name> not-applicable -- <that reason>, and say once that this worktree has files
and no site. show runs the precondition checks and exits 3 when one fails. A failing check
means the site cannot come up, so make no offer. Show the check's output and the remedy it names.
When the remedy says to commit, give the environment: lines that name the branches. Record
nothing, so the offer comes again at start. Otherwise, interactive: show the commands and the
prose, and ask once whether to
bring the site up now. A yes runs environment <name> up with the same flags. A no is recorded
too, with the person's reason, through the same not-applicable call, so nothing offers again.
Say up with the same flags still brings it up later. Autonomous: never bring it up, record
nothing, and say once that the offer waits for a person.
environment <task-id> <show|up|down|not-applicable>
The worktree's own running site, from the framework's worktree-environment recipe. The recipe
holds the commands; this plugin holds none. show and up take the recipe flags step 5 names,
and the --setup-recipe flags. down takes none: it reads the recipe path the record holds.
not-applicable takes the person's reason after --.
"${CLAUDE_PLUGIN_ROOT}"/skills/task/scripts/task-actions.sh --run-mode <interactive|autonomous> \
environment --project "<projectPath>" "<task-id>" <show|up|down> <recipe flags>
"${CLAUDE_PLUGIN_ROOT}"/skills/task/scripts/task-actions.sh --run-mode <interactive|autonomous> \
environment --project "<projectPath>" "<task-id>" not-applicable -- <reason...>
not-applicable writes environment as the reason alone, and commits. It is a person's answer,
so it refuses unattended at 70. up replaces it; down with it recorded says nothing was up.
show prints the recipe path, the preconditions prose and the build-in-place prose. It prints
the token, bring-up, address and tear-down commands with {codePath} filled, and runs none of
them. It prints the paths of the ## Files blocks, the files up writes, and one
precondition: line per ## Preconditions command up runs.
show runs each ## Preconditions line the way up does, with the same code. It removes the
files it wrote for the check and puts back the ones it replaced. So it commits nothing and leaves
the tree as it found it. A failing
check exits 3 and prints what up prints. A worktree not on disk prints
precondition check: not run, because only up makes a tree again.
A recipe with no bring-up block, or no address block, exits 3 from show too, so its exit code
says what up would do.
When the recipe has a ## Status block and the worktree is on disk, show runs its one line
there. Exit 0 prints status: up. Any other exit prints status: down and the line's first
output. The line may run a script from ## Files. The script is written where absent, and taken
out after the line. The implementation step runs the same line before an order's verify lines.
up is a person's yes, so it refuses unattended at 70. It runs in the task's worktree, with each
command as it ran and that command's own output in records/environment-up.txt, in this order,
and one line in that record marking the address command, so down can find that command's output
later.
First it writes each ## Files block absent from the worktree. A file that holds the block of an
earlier version of the recipe is replaced. Any other file with other content refuses at 3. The
earlier versions are the ones the navigator store holds, so nothing is fetched. Then it runs each
## Preconditions line, after the files because the check is a script the recipe ships. A
failing line stops at 3, prints its output, removes the files this run wrote, puts back the ones
it replaced, and commits nothing. When a line ran and failed, it also names the task branch and the branch the worktree
was cut from. An older task names the branch the code path is on now, and says so. A failed
commit, or an interrupt before the commit, also removes the written files, their index entries
and the output file, and says so. Then it commits the written and replaced files alone, so other changed or staged work is
never taken in. Then it records in worktree.recipeChanges each ## Files path, and each file the
## Preconditions prose names that the branch changed, with the content each holds. It skips a
file git tracks neither at HEAD nor where the branch started. No order owns
them, so the owned-files checks in the build and in review set them aside until an edit changes
that content. A site brought up before this record existed has none. Run up again to write it:
files already committed are not committed again. Then
each ## Tokens command, whose first output line is the token's value. A token command that
prints nothing or fails refuses at 4 by the token's name. Then it writes the marker into
environment: state: coming-up, the recipe, and the time. The record names the site before the
site exists, so a failure during the bring-up leaves a site down can still find. The marker
keeps every other field the record held, so a second up over a site that is up does not drop
that site's address while the bring-up runs again. Then the
bring-up lines before the
## Address heading. Then one line marking the address command in the record, so down can find
that command's output later. Then the address command, whose output is key: value lines.
address: is required, and every other key is a token for the later lines and for the
tear-down. A root:
line that is not the worktree stops at 3 before the later lines: the environment resolved to
another tree, and the marker stays for down. Then it completes the record, in place of the
marker: the address, the recipe, when, and the other address keys. Then the bring-up lines after
the heading. Then, for each surfaces kind the
project has on, the ## Install lines of the setup recipe given as --setup-recipe. It
commits nothing after that; what the install left uncommitted is named and stays for the task's
own commit. With no path for a kind it says so and goes on, and the
harness is the person's next step. A line still holding an unfilled {token} stops at 3 and
names it. A failing line stops at 4 with a first: line. Show that line; do not bring the site
up by hand. Any refusal after the marker leaves the marker, so run down before up again.
It prints environment: coming-up, then address:. Running it twice is safe: the recipe
promises every step runs again cleanly.
down runs the tear-down lines, each one and its output to records/environment-down.txt, and removes
environment from task.json. It runs unattended too: tearing a copy down loses nothing. With
nothing up it says so and exits 0. It reads the recipe from the marker as it reads it from a
finished record, so a site that was coming up is torn down the same way. A marker with no address
key holds none of the other address keys either. up marks the address command in
records/environment-up.txt before it runs it. down fills a tear-down token from the output
under that mark, and from nothing else in that file. No mark means the address command never ran,
so down reads nothing there. A token nothing fills stops it at 3 and names that token.
Run it before the worktree is removed, or the framework keeps
an orphaned registry entry; the completion body names it when a site is up or coming up. Review
and baseline read environment.address before asking for a base URL.
The files in worktree.recipeChanges are for the worktree's own site, and trunk must not
receive them. Completion takes the site down. Then it commits each file back to its content
where the branch started. A DDEV worktree then holds the main checkout's name: line again. So a
site command there can reach the main checkout's site. The content decides, not who made the
commit or its subject. The test reads the changed files that existed where the branch started.
When HEAD or the working tree holds one of them at that content again, up and down refuse at
3. They name the latest commit on those files. show then prints status: not run. A ## Files
script the branch dropped does not count, because it names no site. To work on the site again,
put the change back first. The test can refuse wrongly: a file the preconditions prose only names,
changed before up and then changed back, counts too. It also misses a recipe that names the
site in a file absent where the branch started.
prune [<task-id>]...
The worktrees of complete tasks. A worktree kept after completion costs disk and a DDEV project each, and a tree removed too early loses uncommitted work. So this lists first, asks per tree, and removes only what a person named. Run it from the main checkout, never from inside a tree it may remove.
"${CLAUDE_PLUGIN_ROOT}"/skills/task/scripts/task-actions.sh --run-mode <interactive|autonomous> \
prune --project "<projectPath>" [--all] [<task-id>]...
1. List. With no id the script prints one id: line per complete task that records a
worktree. The line holds the path, the branch, and whether the branch is merged into the code
path's current branch. It also says whether the tree is on disk and whether a site is up,
which reads coming-up for a tree whose bring-up did not finish.
prune: none means nothing to remove. Show the lines. Autonomous: this is the whole action.
Say once that a tree goes only on a person's yes, and stop. The script refuses an id unattended
at 70.
2. Ask per tree. For each listed tree ask a plain yes or no, one at a time, with the line. Never ask once for all of them. An unmerged branch is a reason to say so before asking: the tree goes, the branch stays.
3. Remove. Run the action once with every id that got a yes, in the order given, or with
--all when every tree got one. For each tree the script tears the site down when one is up,
then removes the tree. It deletes the branch when it is merged. It clears worktree and
environment from task.json and commits. It prints one pruned: line per tree naming what
happened to the branch. A tree that another task also records stays, and its line says kept:
a later task made with --in-tree builds in it. Show them. Exit 3 names the tree it stopped at and why. The task is not
complete, git refused a tree with uncommitted changes, or the tear-down failed. Nothing after
that tree was touched, and nothing is ever forced. Say what it printed and stop.
repair <old-task-folder>
Moves an old task into the project's own tasks/ folder, the first time it is opened. This is the
only move in the whole skill: everything else here changes a field, never a location.
Given the path to a task folder still sitting under implementation_process/in_progress/ or
implementation_process/completed/ inside the project folder, or under a repaired parent's own
in_progress/ or completed/ in tasks/, run:
"${CLAUDE_PLUGIN_ROOT}"/skills/task/scripts/task-actions.sh repair --project "<projectPath>" "<old-task-folder>"
No confirmation is needed: nothing here is destructive. The script refuses outright when the
destination already exists. It reads the goal under ## Goal, or under ## Problem when there
is no ## Goal, the heading some version 5 records used. With neither it refuses, naming both,
rather than moving something it cannot verify. It prints goal-heading: with the one it read.
It reads the goal, and the parent and children when an old header carries them, back from the
new location before it reports success. Show the whole output either way.
A version 5 parent task holds its children under in_progress/ and completed/ inside its own
folder. The same call moves each one to tasks/<child id>/, sets the child's parent to that
task and adds it to the task's children. It prints one MOVED: line per child. A child it
cannot move, because its goal heading is missing or its destination exists, stays where it sits
with a LEFT: line naming why. The parent's own repair is never refused for a child. Fix the
child there, then run this action on that path. It moves to tasks/<child id>/, takes the
parent from the folder it sat in, and joins that parent's children.
Inside the new folder it renames alignment.md, research.md, architecture.md and research/
to alignment.v5.md, research.v5.md, architecture.v5.md and research.v5/, each when
present. It prints one KEPT: line per rename. Version 6 writes under those names, and the old
files are the input the first run of each stage reads. It refuses, before moving anything, when
a .v5 name already exists in the old folder.
Which old tasks still need this is /aida:next's job: it lists them as kind: legacy and runs
this action on the one it loads.
start <task-id>
A task becomes in progress the moment a stage first writes an artifact into it. Each stage's own
script calls this once, before its first write: scope init, research start, design start,
implement start and review checks. It skips the call when the task is already in progress. So
it is not usually a person typing a command. By hand, run:
"${CLAUDE_PLUGIN_ROOT}"/skills/task/scripts/task-actions.sh --run-mode <interactive|autonomous> \
start --project "<projectPath>" "<task-id>" -- <why...>
Already in_progress: prints UNCHANGED and does nothing further. Already complete: refused,
since a completed task is not reopened here. Otherwise it writes the new state, commits, and runs
the task check. Show the whole output. The check reports and never repairs, so a finding here is
the one thing to repair now, before the stage writes anything.
A task made with --after starts only when the task it builds on has finished its build. That
task then holds implementation/finished.json, or reads complete. Otherwise start prints
REFUSED and exits 1, and the stage that called it writes nothing. Say which task to build first.
When it starts, start cuts the tree from that task's branch, and worktree: names the tree.
That task may be complete with its branch merged and pruned. The tree is then cut by the base
rule above, because that work is on trunk. A tree that cannot be cut exits 3, and no state is
written.
Enter it as create step 4 says.
When the output holds environment: none, run create's step 5 now, whoever called start: a
person by hand, or a stage's script through scope's init. A stage's script passes that line
through. Unattended, the line says the offer waits for a person, and nothing is recorded.
When the output holds environment: coming-up, a bring-up did not finish and a site may be
running. Run environment <task-id> down before any stage runs. Then offer the bring-up again.
complete <task-id>
Writes state: complete. This action is the one writer of that state, and the completion skill
calls it last. A person runs the completion skill, not this action: completion reads the review
verdict, records on what grounds the task closed, and writes the pull request body. This action
records none of that, so a task closed here has a state and no grounds.
The completion skill passes the summary the person gave, after --. The script refuses an empty
one outright. By hand, the call is:
"${CLAUDE_PLUGIN_ROOT}"/skills/task/scripts/task-actions.sh --run-mode <interactive|autonomous> \
complete --project "<projectPath>" "<task-id>" -- <summary...>
It sets the state to complete, appends a dated ## Completed section holding the summary to
task.md, and commits this task's folder, tasks/<id>, alone. Every task action commits that
way, never tasks/ whole, so another task's uncommitted file is never taken. That commit carries
the completion record and the body when completion called it. Already complete: prints UNCHANGED. Show the
whole output.
split <parent-task-id>
Turns one task into a parent with two or more children. Nothing moves, so there is no temporary build, no atomic swap and no rollback copy: version 5 needed all of that because splitting moved folders; here nothing does.
Every fact this needs must already be decided before calling it: which children, each child's own
goal, and which criteria hand down to it. This skill never derives them. The research skill's
split advisor recommends the children and their criteria after research closes. That skill's
split-read action checks that every criterion is claimed once, before this action runs. This only performs
the mechanical split.
Each child takes the parent's after, so it waits for the same build, and gets its tree as
create gives one. The two-level limit stays: a task that already has a parent cannot be split again, and a child
always lives in the same project as its parent, never another one. Run:
"${CLAUDE_PLUGIN_ROOT}"/skills/task/scripts/task-actions.sh --run-mode <interactive|autonomous> \
split --project "<projectPath>" "<parent-task-id>" \
--child "<child-id-1>" --goal "<goal-1>" [--criterion "<text>"]... \
--child "<child-id-2>" --goal "<goal-2>" [--criterion "<text>"]...
Repeat --child ... --goal ... [--criterion ...] once per child, at least twice. It creates each
child's folder and task.json with parent set to the split task's own id, writes each child's
goal and any handed-down criteria into its task.md, makes each child's own worktree the way
create does, adds every new id to the parent's own
children list, reads all of that back, and commits everything together. Exit code 1 means it
stopped before writing anything: NOT FOUND says the named task does not exist, REFUSED says
the two-level limit stopped it. Say which and stop. Show the whole output otherwise.
set-run-mode <task-id> <autonomous|light|interactive> [--stage <stage>]...
Run mode is written only when a person explicitly asks for an autonomous or a light run on this task. Nothing above asks about it on its own, and nothing here proposes it either. Only call this when asked. Run:
"${CLAUDE_PLUGIN_ROOT}"/skills/task/scripts/task-actions.sh --run-mode <interactive|autonomous> \
set-run-mode --project "<projectPath>" "<task-id>" <autonomous|light|interactive> [--stage <stage>]...
autonomous writes the field. --stage, repeatable, limits it to the stages named, one of
scope, research, design, implement, review, completion. A person who asks for the build
alone unattended passes --stage implement. No --stage covers every stage. interactive
removes the field and the stages: there is no "interactive" value to write, since the field's
absence already means that. Show the whole output.
light is an autonomous run over every stage that skips named steps, and it takes no --stage.
Each skip is logged in COMPROMISES.md in the task folder. The path-script: line says
whether end to end is on. Light keeps one script that walks the demo path, and review
fails the task without it. When end to end is off, say that a person sets it up with
/aida:surfaces e2e before the run.
When the mode covers review, two lines can follow, on light or autonomous. The sign-off: line
names the criteria a person answers, so an unattended review writes no verdict. The
review-tools: line names the review tools that preconditions found absent. Before
preconditions has run, it says that preconditions will check them. Read both to the person.
set-budget <task-id> [--dispatches <n>] [--minutes <n>]
A budget is the ceiling on one implementation run, and a person sets it. Nothing above asks about it, and nothing here proposes one. Only call this when asked. Run:
"${CLAUDE_PLUGIN_ROOT}"/skills/task/scripts/task-actions.sh --run-mode <interactive|autonomous> \
set-budget --project "<projectPath>" "<task-id>" [--dispatches <n>] [--minutes <n>]
--dispatches counts the roles implementation dispatches over the whole run. --minutes counts
wall-clock time from the ledger's own start. Either one alone is a ceiling, and both may be given.
Neither is refused. Each takes a whole number of 1 or more, and the action refuses anything else.
The task check does not read the schema's minimum, so a zero would pass it and halt the first
dispatch. A number this call does not name keeps the value it had. There is no action that removes
a budget.
The build halts the order it was about to dispatch when either number is reached, with budget spent. To answer that halt a person raises the number here first. Then they run grant-attempt in
the implementation skill, which clears the halt segment. The order is the whole point. The spend is
recomputed from the ledger at every dispatch, so a grant before the raise brings the halt straight
back. Show the whole output.
save <task-id>
A person stops mid-stage, and a decision this conversation made is in no record yet. This writes it down for the next window. Only a person invokes it; nothing dispatches it.
The current stage is the stage the next skill's report prints for this task, the first whose
close record is absent.
That is scope without records/scope-distill.json. A light task is at scope only when
alignment.json also lacks pluginVersion, which scope's close stamps. Research is the stage without
records/research-check.json at exitCode 0, and design without design-closed.json. A task in state new, or whose stage has no
record on disk yet, skips the distiller: nothing exists to distill. The stage's first record is
alignment.json for scope, research/*.json for research, design/*.json for design. Say which
stage it would have been and that none exists, then go on to the list. Otherwise, when the
stage's sidecar records/<stage>-distill.json exists, run the stage's own distill action from
the task's worktree, the call started with cd <worktree> &&:
"${CLAUDE_PLUGIN_ROOT}"/skills/research/scripts/research-actions.sh distill "<task_folder>"
"${CLAUDE_PLUGIN_ROOT}"/skills/design/scripts/design-actions.sh distill "<task_folder>"
Scope needs no call, because its sidecar is its close record. The action prints standsAlone: stale when the sidecar is older than the last change to the stage's records. A stale sidecar
counts as absent. So does one out of shape: the action sets it aside and exits 4. When none
exists, dispatch the distiller role
with the run mode, the task folder, the stage, and the stage's record paths, one per line. Then
read the sidecar it writes. A record path absent mid-stage is normal; the distiller names it as
a gap.
Name what this conversation decided that neither the sidecar's decisions nor the stage's own
files hold. Each is one sentence: what was decided and what it applies to. Show the list and ask
for a plain yes or no. Nothing is written before yes. Nothing to save is said in one line, and
save still runs with no text. That records the time, which clears the compaction hook's refusal.
Yes: run once per sentence, or once with all of them:
"${CLAUDE_PLUGIN_ROOT}"/skills/task/scripts/task-actions.sh --run-mode <interactive|autonomous> \
save --project "<projectPath>" "<task-id>" -- <text...>
It appends the text to <task_folder>/notes/<date>.md under a ## <UTC time> heading. It
records savedAt in task.json, commits, and prints savedAt: and note: with the path. With no text
it writes no note and prints savedAt: only. When the stage had no record, it prints distill: none naming the stage. Show the lines.
A note is never a stage record: the stage action that later records the same decision makes it
stale, and the record wins. The session-start hook names the newest note after Stage:, and
/aida:next lists its date, so the next window reads it before its first turn.