Bounded supervised runs
nodwyn run is a batch tool for scripts and benchmarks: it carries a task to
its end with no conversation. It is not a way of working in the console,
where every task is a chat and a long request is a message like any other
(ADR 35). It submits the task to a running daemon. The driver is frontend
policy above the existing core session, plan gate and turn loop. It adds no
tool permissions, bypasses no checker, and does not change the loop's host
interface.
nodwyn daemon
# In another terminal:
nodwyn run --task task.txt --repo . --budget-wall 2h --max-turns 20
# nodwyn run start ... is the same command, spelled out
nodwyn ui
# Or nodwyn tui (--socket PATH for a daemon on another socket)
The console does not start or resume runs. It shows a run started here,
and its Stop ends it; a message afterwards goes on with the task as a chat.
run.start and run.resume with auto_approve, judge or unattended
are refused to an actor relayed from a paired device, and no browser's
bridge relays a run method (browser view).
A run's writes in its folder go ahead without asking, as every session's
do ([approval] writes, ADR 35). A supervised run's other confirms are
answered as a chat's are, by [approval] commands: the judge first by
default, then the person. Its flags replace that, as below: the judge alone
with --judge, yes with --yolo, and declined when it is unattended.
Each turn sends the message (run-turn-prompt task complete-line turn
max-turns) makes, a Scheme prompt a project may redefine
(docs/scheme.md §9.1); the turn must plan before it acts (§9.5).
The task is UTF-8 text, at most 64 KiB. A run's limits are optional, as a
task's are (ADR 49, amended 2026-10-27): with none, it goes on until the
model reports the task complete, a turn stops it, or you do. --max-turns
(run.start {max_turns}) is 1–10000 when given. --budget-wall
(run.start {wall}) is the total run lifetime, including model waits,
approval waits and time spent stopped. A resume keeps both.
The run's task takes a task's own limits (run.start {limits}, as
session.open takes them): --max-tool-calls, --budget-tokens,
--budget-actions and --turn-wall stop each of its turns, and
--budget-cost USD is the whole run's spend, as far as known. Each is none
unless given, and the task keeps them when the run resumes; while the run
goes on, session.limits.set is refused. A provider response can exceed a
token limit; no further operation then starts. The turn that reaches the
spend limit stops with outcome spend_limit and the run ends limited, so
a resumed run stops at once until the limit is raised or removed (on the
task between runs). The judge's tokens are recorded (jev.req) and shown,
not counted against the token limit; its cost, when its prices are known
([judge], else its provider's), counts against a spend limit. Requests
with no known cost are not counted (the manual, "Choosing models").
A run's turn ends on evidence (docs/scheme.md §9): the default loop reminds a model that stops before changing anything, or without a test run after its last change, and offers an honest way out when the tests cannot run at all. Running its own check script in the scratch directory counts as a test run. In a git work tree git decides what changed, and files left in the repository that git does not track are named once before the turn ends. Before a plan is drafted, a read-only explorer looks at the repository, so the plan names real files.
A turn is one message to the model and the work it does until it replies without a tool call, or a budget stops it. It is the unit of the per-turn budgets, of the run checkpoint and of resuming. The plan is not the turn's: it is the run's.
Each turn requires a nonempty, durably recorded plan before execution. The
Scheme nodwyn-turn runs the plan gate first in every run turn, whatever agent-turn
a project defines (docs/scheme.md §9.5). The first turn drafts
the plan: the control model writes it in a context of its own, offered no
tools, and with a judge ([judge] on a provider whose key is stored) the score/revise path runs through the
guard; the noul decision cannot skip a required plan. Without a judge the
model still drafts a plan. Judge outages retain the plain-plan fallback.
Each later turn carries the run's plan: it records the same plan again
(gate.plan decision carried) with the steps marked so far, and nothing is
explored, drafted or scored. Only when every step is marked and the run
goes on does a turn draft a new plan, told the previous one and how each
step went. A resumed run carries the plan it had. The core refuses anything
irreversible until a plan is recorded and fails a turn that records none; a
project that redefines nodwyn-turn to plan its own way is held to the same
rule. Planner or plan-recording failure prevents execution.
The continuation asks for the plan's next unmarked step, marked with
plan-step when it is done, and a progress report; a plan that no longer
fits is set aside by marking its remaining steps skipped, with why. This is
an instruction to the model, not a parser that proves a step was completed:
a mark (plan.step) is the model's word, shown as such in the console's
plan panel, and the evidence is in the transcript.
Before another turn, the operator gets a run checkpoint. A one-time yes
continues the run; it grants no filesystem, command, credential or network
permission. The checkpoint's prompt ID and answering peer are recorded.
Tool prompts remain separate.
A successful plain model response whose exact final line is
NODWYN_RUN_COMPLETE ends the run as model-reported completion. This is not
proof that the task is correct or complete. Tool results, responses containing
tool calls, truncated responses, and marker substrings do not signal completion.
The final reply is the last one the conversation still shows: a reply inside a
folded context, such as a reviewer sub-agent's verdict, is not the model's
answer, so it neither completes a run nor hides the model's completion line.
Review the transcript and normal project checks. A turn stopped at one of its
task's limits permits a checkpoint and another turn; user refusal, cancellation, progress stops and
provider failures stop continuation.
Stop, inspect and resume
nodwyn run status SESSION
nodwyn run stop SESSION
nodwyn run resume SESSION
Each command accepts --socket PATH before the session ID. Aborting the
session (session.abort, the console's Stop) stops the whole run,
including a between-turn checkpoint. A client closing
or disconnecting does not stop it. The active run reserves its session: manual
send, eval, friction, policy, project-notes, file-restore and attach operations
are rejected until it stops. Prompt replies and inspection of recorded history
remain available.
The daemon stops and joins drivers before closing sessions on shutdown. There
is no automatic restart. Explicit resume reloads current session policy, retains
the original deadline and consumed turn count, and restores no temporary grants.
Completed, expired and exhausted runs cannot resume. An interrupted turn lacking
a durable turn.end outcome, or a truncated log tail, requires manual review;
the driver refuses to infer success or replay unfinished tools. Ordinary legacy
turn.end records without an outcome are unknown, not evidence of completion.
run.state records task, limits, disposition and initiating actor; run.turn
reserves a numbered turn; run.checkpoint records continuation choices. They go
through normal session redaction and persistence. Each submitted turn records
its task in the ordinary conversation and rebuilds context from history.
turn.end retains its historical text and adds optional outcome/error fields.
Unattended runs
nodwyn run --task task.txt --repo . --budget-wall 2h --max-turns 20 --unattended
An unattended run is one nobody answers (ADR 18 §5). It gets more guards, not fewer, and nothing in it ever waits for a person:
- Tool confirms are declined without asking: a command runs only if a
standing approval covers it (a trusted
.nodwyn/allow.toml, or user config), and a write only if one does; writes in the folder are one by default ([approval] writes, ADR 35). Any other is refused, logged, and the model is told. With--yolotoo, ordinary confirms are answered yes instead. Security confirms (a credential-shaped write, a protected path, a command built from tool output) are declined either way. - The run checkpoint is answered by progress. A turn that marked at least
one step of its plan done or skipped (
plan-step, recorded asplan.step; a plan that no longer fits is marked skipped, as the run's prompt says) is followed by the next; a turn that marked none stops the run, with the reason in its status ("turn 3 marked no plan step"), for a person to look before another. Each answer is arun.checkpointwith actorunattendedand its reason. A mark is the model's word: what the run proves is that the model kept claiming progress, step by step, in a log that can be reviewed step by step. - With
--judge, the judge answers instead. Each ordinary tool confirm is put to the judge: it is shown the task, the turn's plan and the call (the tool, why it needs approval, the file it writes or the command line it runs; atjudge.content = "full"also what it would write or send), and asked whether a call like this should run with nobody watching. The call runs only if the judge's probability of "allow" is at leastjudge.approve_thresholdin config.toml (0.8). The decision is Go's: no Scheme, a repository's.nodwyn/init.scmincluded, can approve a call the judge refused. A project may only refuse more, by defining(approve-refuse? call). A judge that fails or cannot be reached is a no; security confirms are declined without asking it. Each answer is the guard's decision with actorjudge, a note with the judge's probability, and the judge'sjev.req.--judgeneeds a judge ([judge]on a provider whose key is stored and bound,judge.contentother thannone) and excludes--yolo; it also works in a supervised run, where you still answer the run checkpoints. - Everything else is as for a supervised run: a recorded plan in every turn, the run's, carried until every step is marked, the turn and wall limits it was given, the progress guard, completion only by the model's final line, the same pin, denylist and binding table.
run.state records how the run was started (unattended, auto_approve,
judge, sandbox).
A resume never brings either back from the log: nodwyn run resume SESSION
resumes a supervised run, and --unattended (with --yolo or --judge if
wanted) must be said again, as the person resuming decides how it goes on. The console shows
an unattended or judged run as such ("Unattended run", "judged"), and its
Stop ends it. The console does not start or resume runs: nodwyn run
(or run.start and run.resume over the protocol) does.
Repository startup code and approval files require the external trust review in
ADR 45, including for unattended
runs. Neither --yolo nor --no-confirm grants project trust.
Sandbox runs
nodwyn run --sandbox container --task task.txt --repo /app --budget-wall 14m --max-turns 1
--sandbox container (run.start {sandbox: "container"}) says the machine
is a disposable container whose every file is the run's, as a benchmark's
task container is (ADR 55, manual).
The run is unattended and --yolo without saying so, and excludes
--judge; its tools may reach any path but nodwyn's own directories and
credential files; what its commands start in the background keeps running
after them and after the session; the folder need not be a repository, and
may be / or the home directory. The first turn that ends on the model's
word is asked once to check its result as someone who did not watch it work
would, and to replace or own up to anything that only makes its checks pass;
the same call four times in a row warns the model instead of ending the
turn. A resume is in sandbox mode only when it says --sandbox container
again.