Durable task runs: a plan of tasks with disjoint write scopes, attempts you can recover after an interruption, independent verification, and a separate acceptance step.
When several tasks must be done and you want proof that each one was done, the Nexus agent can keep them in a durable run. The nexus command is that run’s ledger. You describe the tasks in a JSON plan; each task owns a folder or file it may write (its scope), and a list of check commands that decide whether its result is acceptable. The ledger records who tried what (an attempt), keeps the state across crashes, runs the checks itself, and only then lets you accept the task. A worker saying “done” is never evidence: the checks are. This page is for people who drive Nexus by hand, from CI, or who want to know what the agent does under the hood.
Host identity, observation or retry request for an action that mutates a task, or the lock observation for recover-lock. Regular file, at most 1 MiB, JSON object.
--json
-
off
Print the durable state and evidence as JSON instead of the short summary.
Nexus keeps three questions apart. Execution: did the worker finish? A host reports it with record, or the foreground supervisor collects the real exit of the process it started. Verification: does the work pass the plan’s checks? Nexus runs them itself, with literal arguments and no shell, and compares the declared artifacts and the whole write scope before and after. Acceptance: is the current workspace still the one that passed? accept rechecks artifact hashes, protected resources and the accepted state of every dependency. A failed check prevents acceptance even if the worker succeeded. Nexus never commits, merges, copies files or deploys.
{
"id": "example",
"maxParallel": 1,
"config": { "language": "English" },
"tasks": [
{
"id": "result",
"objective": "Produce the requested result in work/result.txt.",
"acceptance": ["The result satisfies the independent check."],
"dependsOn": [],
"scope": ["work"],
"artifacts": ["work/result.txt"],
"resources": ["checks/check-result.cjs"],
"checks": [
{
"id": "result-content",
"command": "node",
"args": ["checks/check-result.cjs"],
"cwd": ".",
"timeoutMs": 30000
}
],
"maxAttempts": 2,
"deadlineMs": 3600000,
"idempotent": true,
"authorizations": ["Edit work/ and run the listed local check."]
}
]
}
Plan fields and their rules
Field
Rule
id
Run id: 1 to 64 lowercase letters, digits, _ or -. Same rule for task ids.
maxParallel
Between 1 and 32 attempts active at once.
config
Non-secret primitive settings only. Secret-looking keys are refused.
tasks
1 to 200 tasks. Ids unique; dependencies must exist and must not form a cycle.
objective, acceptance
What the task must produce and the statements the result must satisfy.
dependsOn
Task ids that must be accepted first.
scope
Files or folders the worker may write. Scopes of different tasks must be disjoint, even when one task depends on the other. Never .git or Nexus’s own state.
artifacts
Files that must exist inside the task’s scope when it is verified.
resources
Protected files: the verifier and every instruction file given to the worker. They are hashed and must lie outside every scope.
checks
1 to 30 per task. command is node or an absolute executable path; args is a literal array; every file argument must be a declared resource; timeoutMs from 10 to 300000.
maxAttempts
1 to 10. Verification rounds are bounded by it too.
deadlineMs
100 to 86400000. A deadline is an observation, not proof that the work stopped.
idempotent
Boolean. A non-idempotent task needs an explicit retrySafe: true reconciliation before a retry.
authorizations
Statements of what was authorised. Workers cannot edit them.
execution
Optional. Selects a process backend instead of a host (see below).
By default a task is host-owned: your assistant does the work with its own tools, and tells Nexus the outcome with record. Nexus then verifies. Adding an execution object to a task makes it a process task instead: launch starts the program in the foreground, waits for it, and stores a checksummed receipt of what really happened. There are two adapters.
You supply the command; it is never inferred from task text. The executable is pinned by SHA-256. Zero exit means completed; a code listed in blockedExitCodes means blocked. No shell, no environment override.
codex-exec
command (absolute path to the installed codex), input, instructions, sandbox, optional model
Runs the installed Codex CLI non-interactively. sandbox is read-only (default) or workspace-write. Completion needs exit zero plus the thread-started and turn-completed events; only an explicit approval_required error becomes blocked.
Warning· Not a sandbox
Workers share your project and your operating-system user. They can ignore their declared scope; checksums detect accidental change but are not signatures. There is no daemon, no scheduler and no process-tree supervision: each launch is one foreground command. workspace-write is a request to the installed Codex host, not a Nexus sandbox. Use an isolated project or host policy when the worker is not trusted.
State: .bmad-plus/nexus/runs/<run-id>.json, a checksummed envelope written through a flushed temporary file and an atomic rename.
Locks: .bmad-plus/nexus/writer.lock (one mutation at a time) and, during an exceptional recovery, .bmad-plus/nexus/recovery.lock. A removed lock is archived under .bmad-plus/nexus/lock-recovery/.
inspect writes nothing and keeps working while a verification runs. All other actions except create change an existing run; create makes it.
Reads your plan and input files, the declared resources and artifacts, and snapshots the write scopes. Verification runs your check commands; launch runs the task’s process. Those are the only processes Nexus starts, and only when you invoke them.
Check commands get a minimal environment (PATH, SYSTEMROOT, WINDIR, TEMP, TMP, TMPDIR plus fixed locale C and timezone UTC). Credentials and NODE_OPTIONS are not forwarded. Put no secret in arguments or in output.
verify did not pass; launch did not end completed; collect did not end completed; or any error. A non-zero code on launch or collect says what the process did, never that the task is acceptable.
The shortest complete run needs three small files next to nexus-plan.json. The verifier is the one the plan declares as a resource; host.json is the identity your assistant gives to start; rec.json is the outcome it reports to record, with the attempt id that start printed.
checks/check-result.cjs, the verifier
const fs = require("node:fs");
const text = fs.readFileSync("work/result.txt", "utf8");
if (!text.includes("done")) {
console.error("work/result.txt must contain done");
process.exit(1);
}
$ npx bmad-plus nexus create --plan nexus-plan.jsonNexus run example — durable task coordinationresult: not started; verification=pending; acceptance=pendingUse inspect --json for recorded evidence and reconciliation observations.$ npx bmad-plus nexus start example result --input host.jsonNexus run example — durable task coordinationresult: running; verification=pending; acceptance=pending Attempt: c3bbafd1-62ef-4b8b-ac72-0d9b09932427; host: my-assistant/session-1Use inspect --json for recorded evidence and reconciliation observations.$ npx bmad-plus nexus record example result --input rec.jsonNexus run example — durable task coordinationresult: completed; verification=pending; acceptance=pending Attempt: c3bbafd1-62ef-4b8b-ac72-0d9b09932427; host: my-assistant/session-1Use inspect --json for recorded evidence and reconciliation observations.$ npx bmad-plus nexus verify example resultNexus run example — durable task coordinationresult: completed; verification=passed; acceptance=pending Attempt: c3bbafd1-62ef-4b8b-ac72-0d9b09932427; host: my-assistant/session-1Use inspect --json for recorded evidence and reconciliation observations.$ npx bmad-plus nexus accept example resultNexus run example — durable task coordinationresult: completed; verification=passed; acceptance=accepted Attempt: c3bbafd1-62ef-4b8b-ac72-0d9b09932427; host: my-assistant/session-1Use inspect --json for recorded evidence and reconciliation observations.$ npx bmad-plus nexus inspect exampleNexus run example — durable task coordinationresult: completed; verification=passed; acceptance=accepted Attempt: c3bbafd1-62ef-4b8b-ac72-0d9b09932427; host: my-assistant/session-1 Accept only after rechecking current artifacts and resources.Use inspect --json for recorded evidence and reconciliation observations.# The words after “acceptance=” and the extra hint lines depend on the run state; read them with inspect --json.
npx bmad-plus nexus create --plan nexus-plan.json
npx bmad-plus nexus start example result --input host.json
npx bmad-plus nexus record example result --input rec.json
npx bmad-plus nexus verify example result
npx bmad-plus nexus accept example result
npx bmad-plus nexus inspect example
Output captured from a real run. Source: Replayed with the 0.23.0 package in a throwaway Git project; the attempt id changes on every run
Running verify right after create is refused, because nothing has been recorded yet: Nexus: Nexus: Verification requires completed, unaccepted, reconciled work. Start and record first, as above. Note the doubled prefix: the runtime’s own messages already begin with Nexus:, and the command adds its own.
Each task line reads <task>: <execution>; verification=<status>; acceptance=<state>, followed by the last attempt with its backend. inspect --json adds, per task, resourcesCurrent, evidenceEligible, stale, staleReason and overdue, so an old accepted record cannot claim that files edited later still pass. In JSON mode, errors are printed as {"schemaVersion":1,"status":"error","error":"..."}.
An interrupted or disconnected attempt keeps its capacity until reconcile settles it. The observation needs ownerStopped: true, a statement from you that you checked, not telemetry. For a task that is not idempotent, a retry also needs retrySafe: true.
cancel only records a request. For a host task, the host stops its own work; for a process task, only the original foreground supervisor may signal the child it created. Nexus never kills a saved PID.
retry (or launch with {"retry":true}) opens a new attempt and consumes maxAttempts. A new backend session does not reset the limit.
A verification that times out is recorded as ambiguous, because descendants of the check could still have effects. Inspect before retrying.
recover-lock takes the exact lock hash from inspect, ownerStopped: true and a non-empty note. A live local writer blocks it. Age alone never authorises removal.
Errors are printed as Nexus: <message> on the error stream. The runtime’s own messages already start with Nexus:, so most errors show the prefix twice (Nexus: Nexus: Run ID must use ...).
nexus errors
Message
Cause
What to do
Use nexus create --plan FILE, inspect RUN, or start|launch|collect|record|reconcile|verify|retry|cancel|accept RUN TASK.
Unknown action or missing run/task ids.
Check the synopsis.
--plan requires a JSON file. / --input requires a JSON file.
The option is missing for an action that needs it.
Add the file; see the actions table.
--input must name a regular JSON file no larger than 1 MiB.
Not a regular file, a symbolic link, or too big.
Use a plain JSON file.
verify executes its recorded contract and takes no input file.
--input given to verify or accept.
Remove --input.
Overlapping exclusive write scopes: <a> and <b>
Two tasks share a path in their scopes.
Make the scopes disjoint.
Verifier/resource lies in a worker write scope: <file>
A protected file is inside a task’s scope.
Move the verifier outside every scope.
Run already exists; inspect it instead.
create with an existing run id.
Use inspect, or a new plan id.
Stale or missing attempt identity. / Backend handle/session does not own this attempt.
The input names another attempt, handle or session.
Copy the exact attempt id and backend returned by start or launch.
Protected verifier or agent resource changed since the plan was created.
A declared resource was edited after create.
Restore it, or create a new run from an updated plan.
Dependency has not been accepted: <task>
A dependency is not accepted yet.
Verify and accept the dependency first.
Run capacity is occupied until active work is reconciled.
Active or interrupted attempts fill maxParallel.
Collect, record or reconcile them.
Another mutation owns writer.lock; inspect before explicit recovery.
Another command is running, or one was killed.
Wait; if it was killed, use inspect then recover-lock.
Corrupt or foreign run state; original bytes were preserved.
The run file does not match its checksum.
Restore the file from a backup; nothing is auto-repaired.
Current attempt has no eligible passed verification.
accept before a passing verify.
Run verify; fix the work if it fails.
Stale evidence: checked artifacts or write scope changed.
Three independent changes touch different folders. You want each one verified by a check you wrote, not by the worker’s word.
In your AI assistant
Nexus, split the approved work into independent tasks, each with its own write folder and a check command I can run. Create a durable run with `bmad-plus nexus create`, start one attempt per task, and do not accept anything until `bmad-plus nexus verify` has passed for it.
What you should get Nexus writes a plan with disjoint scopes and runs create, which refuses overlapping scopes or a verifier inside a scope. For each task the host does the work and records the outcome; verify runs your check and stores its receipt; accept succeeds only when the evidence is current. A task whose check fails stays unaccepted and can be retried within maxAttempts.
The machine restarted while a task was running. You are not sure whether the worker still holds files open.
In your AI assistant
Nexus, inspect the run `release-prep` with `bmad-plus nexus inspect release-prep --json`. Tell me which attempts are unresolved and what I must check before you reconcile them. Do not retry anything on your own.
What you should getinspect is read-only and reports each attempt with its backend, the recorded outcome and the observations (stale, overdue). Unresolved attempts keep their capacity. Once you have checked the effects and confirm the owner stopped, reconcile records completed, failed or cancelled; only then can a retry open a new attempt. If a writer lock was left behind, inspect without a run id shows its hash for recover-lock.