nodwyn.

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 --yolo too, 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 as plan.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 a run.checkpoint with actor unattended and 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; at judge.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 least judge.approve_threshold in config.toml (0.8). The decision is Go's: no Scheme, a repository's .nodwyn/init.scm included, 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 actor judge, a note with the judge's probability, and the judge's jev.req. --judge needs a judge ([judge] on a provider whose key is stored and bound, judge.content other than none) 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.