omp packages

pi-owed0.30.0

owed — the acceptance ledger for multi-agent task graphs. No receipt, not done.

GitHub Actions Published 10 hours ago MIT ~4.6K downloads/mo

Untested on omp

This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.

Did it work for you?

Result

One report per person. A new report replaces your old one.

Reports 0

No reports yet.

Readme

pi-owed

No receipt, not done. Owed is an acceptance ledger for multi-agent task graphs in one Git repository: measured checks, independent reviews, owner waivers and remaining debt guard each merge. The runner does the work; owed records acceptance, not promises. Current release: 0.31.0.

Why use it?

  • Keep completion tied to a merge receipt, not an agent's success message.
  • Reuse evidence only when its content-derived keys still match.
  • Make risks, waivers and debt visible instead of hiding failures.
  • Let the driver run routine work and wake the main agent for decisions.

The main agent is the parent and delegated owner. Pi owner tools act as owner:pi without a dialog unless OWED_CONFIRM=owner; CLI owner commands normally use owner:cli. Writers and reviewers never act as owner. Each acceptance-easing act needs a reason; the ledger records it for audit.

Quickstart

Requires Node.js >= 22.19, Git with merge-tree --write-tree, Bash and a configured Git identity. The driver uses pi-durable-subagents >= 1.0.27 with writer/reviewer agents available; >= 1.1.0 is needed for durable-writer waits, seal diagnostics and yield/priority leases (older dsa keeps 0.28 writer-wait and lease behavior), and >= 1.2.0 for structured asks (earlier runners retain plain asks).

pi install npm:pi-owed          # reload pi after installation
ln -sf "${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}/npm/node_modules/.bin/owed" ~/.local/bin/owed
# Standalone CLI alternative: npm i -g pi-owed

pi install npm:pi-owed does not put owed on PATH, but agents, scripts and owed's printed commands call owed; use this link with ~/.local/bin on PATH, or npm i -g pi-owed at the same version as the pi package.

In a disposable repository, save this plan as plan.yaml (examples/plan.yaml):

version: 1
trunk: main
closure: [plan.yaml]
invariants: []
nodes:
  - id: greeting
    title: Add a greeting
    brief: Create hello.txt containing exactly hello followed by a newline.
    writes: [hello.txt]
    checks:
      - id: content
        run: 'test "$(cat hello.txt)" = hello'
        timeout_s: 10
        reads: [hello.txt]
    review: {count: 1, min_rank: 1}
mkdir owed-demo && cd owed-demo
# Save the plan above as plan.yaml, then configure Git identity if needed.
git init -b main
git add plan.yaml && git commit -m "Record acceptance plan"
owed start --plan plan.yaml

Start preflights, initializes/updates the plan and starts or reuses the detached driver. It dispatches an isolated writer, measures the candidate, obtains independent review and guards the merge. Wait for Needs you: / Done: wakes; act with owed decide <node> --text "<decision and why>", owed answer, or the command the line gives. Use owed why <node> for outstanding work, --full for evidence. Done means owed shows greeting: merged. Later owed start rereads the recorded source; --stay waits for a growing plan. Never poll in a loop or launch a second writer for a quiet one.

Reference: concepts and acceptance

Owed provides a CLI, a pi extension and an agent skill. It is not an agent scheduler, a sandbox or a replacement for code review: your runner schedules execution; owed keeps acceptance evidence and guards merges.

When to use owed

Small, low-risk fixes can be committed off trunk, then landed by the parent with one owed adopt --commit <c> --note "<what changed and why it is acceptable>" (using the owner's delegated identity unless a parent adoption allowance applies). Use owed nodes with independent review and red tests for authentication, concurrency, persistence, deletion/ownership, public contracts and multi-agent waves. Routine evidence and recovery are the main agent's job; reports to the user name what was delivered and what is genuinely blocked, not routine halts the main agent already resolved.

How acceptance works

  • Items and keys: an item identifies a subject, an obligation, and a content-derived key. Checks use declared read sets and the pinned check closure. Matching keys allow evidence reuse; relevant changes require fresh evidence.
  • Receipt: E means evidenced, W means visibly waived by the owner, and D means debt. why --full shows actual logs, counts when available, reviewers, blocks, untested changes, and plan downgrades; default why shows outstanding work. Speech and a successful agent exit are not evidence.
  • Blocks: execution failures and negative reviews survive new candidates. Attribution reruns explain old execution failures. A judgment block requires the original reviewer at sufficient rank, a higher-rank reviewer, or an explicit owner risk waiver. A different reviewer at the same rank cannot clear it. When the brief or plan is ambiguous or contradictory, or the fix needs a product or contract decision, the reviewer records owed review <node> --block --needs-parent ...: why and status show the block as needing a parent ruling, and owed drive halts instead of sending the writer a repair until the parent records owed decide <node> --writer --text "<decision and why>" (a * ruling does not count); the driver then repairs with the ruling quoted.
  • Superseded blocks: when a plan update changes a check's definition (the check spec excluding selftest, plus setup, exec and referenced quarantine_set entries, if any) or removes the check, the execution blocks recorded under the old definition become superseded: they no longer block, get no attribution rerun and need no waiver. The new definition must still pass on the current candidate. why --full, status --full and report show each one as #<seq> superseded by plan #<p> (check <id> definition changed) (or removed). Only a parent or owner can change a definition, and weakening a check is still an owner downgrade. A block whose definition did not change goes flaky as before.
  • Waivers: an owner can waive a specific node obligation with a reason and explicit block sequence numbers in accept_risk. Waivers remain visible; they do not turn into measured passes. Invariants cannot be waived.
  • Debt: missing evidence, failures, conflicts, and deferred invariants remain visible in the pending queue. defer permits specified invariant debt for one merge; it does not erase it.
  • Oracle strength: a node check may list mutants (globs of patch files inside the closure) and min_kill (fraction in (0, 1], default 1). The node then owes strength:<check id>: for each mutant patch, read from the base and never from the candidate, owed applies it to the materialized candidate and runs the check. A mutant is killed when the run exits non-zero or reports failing tests; a zero-test run or a patch that does not apply is not a kill. The obligation passes when killed/total reaches min_kill; the receipt shows strength k/n and the log lists every mutant.
  • Merge guard: owed tests the prospective synthetic merge commit (HEAD^1 = trunk tip, HEAD^2 = candidate); checks must not pin the candidate base. It checks node obligations and invariant debt, then advances the trunk with a compare-and-swap guard. An accepted candidate alone does not guarantee its merge is safe.

Large candidates

owed submit streams the full binary diff when computing the review identity, preserving its UTF-8 decoding and hashes. Buffered Git calls, including blob show and mutant cat-file, refuse output above 256 MiB with code refused, naming the subcommand.

Optional top-level plan limits: {file_mib: 100, diff_mib: 500} sets positive, finite MiB limits (fractions allowed). Submit checks the largest changed file against file_mib and the sum of added/changed candidate blob sizes against diff_mib, using Git metadata without reading content. Full blobs count once per changed path, including mode changes; deleted files and gitlinks contribute no bytes. Exceeding a limit refuses submit with code refused, naming the limit and up to five largest files, with nothing recorded. Neither limit means no size check. Limits enter no acceptance key, invalidate no submitted candidate and add no ledger field. Older owed ignores them; use 0.14+ to enforce them.

Reference: plans and nodes

Authoring checks

Checks and invariants can use run_file: {path: checks/unit.sh, sha256: <64 lowercase hex>} instead of run or template, with optional args: ["literal argument"] (at most 64 strings, no NUL). Path segments use only A-Z a-z 0-9 . _ @ + , = -, excluding . and ..; paths must be normal and repository-relative. Plan admission verifies uncached script bytes from the current worktree, or the commit selected by owed plan --rev. Every execution installs those bytes with mode 0755 after setup and overlays; supply a usable shebang. Candidate script edits cannot replace the pin, but loaded helpers still follow ordinary reads/closure rules. Pins and args enter execution keys (SPEC §3.6).

Use owed check try <node> <check> --red <rev> --green <rev> [--plan <file>] or owed check try --invariant <id> ... (tool owed_check_try) to try the disk plan before recording it; at least one revision is required. Each revision uses a fresh exact-commit tree without closure/test overlays, with normal setup, counts, timeout and repeat behavior. Even red: true is measured as an ordinary check: red expects fail, green expects pass, and errors meet neither. No ledger entries are recorded; logs are retained. Exit codes: 0 all met, 1 unmet, 2 usage, 75 lease busy; --no-lease or plan lease: false opts out.

Optional selftest: [{commit: <40 or 64 hex>, expect: pass|fail}] (1–8 entries) runs before recording new/changed checks or fixtures. Failed expectations or errors refuse without ledger entries or replacing the edited plan file; success records a selftest/v1 note with the plan. Unchanged checks are not rerun; new/changed checks without fixtures warn not self-tested, and previews only announce pending selftests. Fixtures change no key, candidate or block and are not acceptance evidence. owed edit accepts checks.<id>.selftest, checks.<id>.run_file.path, checks.<id>.run_file.sha256 and checks.<id>.args (omit checks.<id>. for invariants). See SPEC §3.10.

Parallel slots and a moving trunk

In 0.20.1, owed dispatch retries compare-and-swap (CAS) refusals with fresh state, including a changed plan, up to three total attempts (initial plus two retries). Before creating paths/worktrees each attempt rechecks node membership, hold, readiness/dependencies and writes overlap; removed, held, unready or overlapping nodes refuse normally. Worktree, branch, directory and setup-exclude rollback finish before retry; rollback errors and non-CAS failures (including setup) are not retried. The driver reports exhausted CAS retries as retry next pass, never Needs you or a halt; standalone dispatch returns the original refusal class/code. owed plan/amend/edit retain the three-attempt policy only while the ledger plan sha is unchanged. Dispatch rollback uses git update-ref -d refs/heads/<branch> <base> to compare the tip and delete atomically, regardless of the main worktree's HEAD. A ref that moved is kept and the CAS failure is reported under the existing git branch -d rollback label. A branch still checked out in a worktree is kept too.

owed dispatch <node> refuses when the node's writes overlap (using conservative literal prefixes before wildcards) those of another node with an open slot and names that node; --allow-overlap dispatches anyway and records the overlap in the dispatch entry. Default owed status and owed why <node> explain ready nodes skipped by the same predicate as the driver: writes overlap (with owed dispatch <node> --allow-overlap), active-attempt capacity, or an owner decision. max counts non-accepted open attempts; accepted attempts are waiting to merge outside max, but all open attempts count toward the 2 × max cap and reserve writes. Same-pass reservations and removed nodes with open slots consume active capacity. With pending merges, status adds active <a> of max <m>, waiting to merge <p>; without them it keeps the prior output. Owner-needed ready nodes appear once under Needs you. Current status capacity/ready lines and why use a live recorded --max override when available; view callers outside summary/why read paths use plan drive.max (SPEC §9.8).

When trunk moves under an open slot, there is no need to abandon and redispatch. owed rebase <node> (the parent, or the writer inside its worktree) moves the slot base to the current trunk and invalidates the open candidate; the writer then runs the printed git rebase --onto <new base> <computed upstream> in the same worktree and submits again. Review blocks still bind the node. owed why <node> --full shows the previously reviewed patch and a hint, git range-diff <old base>..<old commit> <new base>..<new commit>, so a reviewer only has to review the conflict resolution. Recovery names the actual interrupted Git operation, including git cherry-pick --abort and git revert --abort, rather than always suggesting rebase abort.

owed rebase <node> without --apply already records the slot-base move and prints the writer's git rebase --onto <new> <old> (or computed upstream); it is not a dry run. Parent/owner --apply additionally performs the Git rebase and executor submit when safe. Submit refuses a commit containing trunk commits after the recorded slot base: the parent records owed rebase <node> first, then the writer runs the printed rebase and submits (SPEC §6.1, §7.25).

A plan change that alters a node's spec, setup, exec or closure invalidates its open candidate, because the keys were computed under the old plan. Since 0.10, when the node's spec changed only in checks, writes, allow_only (0.30), type or drive (plan-wide setup, exec and closure changes do not matter), owed plan carries the candidate in the same lock: it appends a carry submit by executor:owed for the same commit at the same base with keys recomputed under the new plan, and prints Carried <node>: … (a carry it cannot make, e.g. the commit is gone, prints Not carried <node>: <reason> and JSON notCarried; that candidate stays invalidated). Reviews and other evidence on unchanged keys still count; the next attest measures only the changed checks (or writes). owed why <node> --full and owed status --full show candidate #C carried by plan #P from submit #S. Any other change (brief, deps, review, title, approve, evidence) still needs the writer to submit again, and the driver tells it so: plan #P changed this node's spec (<fields>); resubmit … (reason submit), not a rebase. A ledger with a carry submit needs owed ≥ 0.10.0 to replay.

Waiting, reports and backlog (0.13)

An open writer slot automatically waits while any dependency in the current plan has not merged: why, status and the driver show waiting (deps: a, b). The driver sends no submit/repair/ruling follow-up and no missing-candidate halt; questions still wake the parent. Adding a dependency while the writer runs sends one durable notify per plan entry saying which dependencies are unmerged and do not submit until told. When they merge, owed uses its existing slot rebase and writer rebase follow-up before continuing.

owed resume <node> --after a,b --note "wait for both" (tool owed_resume after: "a,b" or ["a", "b"]) waits for every member, in addition to implicit dependencies. A single string still works; a new resume replaces the explicit wait. owed resume <node> --expect report --note "diagnose first" (tool expect: "report") asks the next writer follow-up to report findings and stop, not submit. A successful sealed report without a new submit wakes the parent with its last 40 lines (at most 4000 characters), appends no ledger entry, and parks the node as reported (resume #n). It stays parked until a rule naming it, another resume, a plan entry changing it, or abandon; a new submit proceeds normally. Reported receipts live in the driver's private runtime cache, not acceptance keys.

Mark backlog nodes later: true: they are never ready or dispatched, and status/brief list and count them as Backlog (later). Remove the field in a plan update to make them eligible; no check/invariant key changes. Staying idle wake-ups include ready, open, waiting, reported and later counts. drive.idle_rewake_min (default 60 minutes, 0 = off) repeats the wake while idle with no ledger change. Waiting/reporting slots still surface questions.

Invariant ids in CLI/tool defer accept unit and inv:unit; unknown ids list the valid spellings. owed why inv:unit (or why unit when it is not a node) shows its trunk receipt. Invariants still cannot be waived. Compatibility: owed 0.12 rejects resume lists/expect as an ordinary refusal; from 0.13 unknown resume shapes are version errors. The new driver send kinds/reasons also require 0.13; a single-string resume remains readable as before.

Quarantine, trunk evidence and tracked debt

Top-level quarantine_sets: {name: [{test, fix, note?}]} defines reusable lists; a check/invariant selects quarantine_set: name, optionally with own quarantine. Duplicate exact names across the lists refuse. Own lists permit 20 entries, sets 200; set names match [A-Za-z0-9_.-]+, set test names contain no control characters, and every fix names a plan node. Adding/reassigning entries or adding/changing a set reference is owner-only, never covered by allow; removal is free. Active own/set names are sorted in keys and disabled when the fix merges, including its own merge tree. Referenced sets enter check definitions and can supersede blocks; checks without sets keep their old definitions and keys (SPEC §3.6).

Eligible check/invariant runs receive OWED_QUARANTINE_FILE: an absolute private temporary file outside the measured tree, sorted exact names one per line with a final newline, unique per execution and removed afterward. Other jobs, including setup, slot setup, red/mutants, sweep and timeout hooks, do not inherit it. Own names with CR/LF cannot be delivered; the note explains why. Scripts may read it, but delivery never rewrites their exit. Runs without a native protocol say quarantine file: <n> names (owed excused nothing); natively refused Node/Cargo runs keep quarantine not applied: <reason>. Container wrappers must make the host path visible; scripts that skip names cannot prove recovery.

Native excusal supports plain allowlisted node --test exit 1 with complete TAP, and cargo [+toolchain] test --no-fail-fast exit 101 with complete libtest blocks, results, summaries and consistent failed-target diagnostics. Leading environment assignments are allowed; shell quoting/operators/scripts are not native syntax. Every failure must be an exact active name and remaining tests must meet min_tests; excused names count neither passed nor failed. Cargo compile/crash, incomplete/mixed/terse/truncated output, duplicate/unlisted failures and more than 50 failures fail closed; templates, red/mutants and timeouts are never excused. Complete native trunk/merge runs can record sorted obs.quarantine_passed names, including explicit/automatic trunk probes. Candidate-only passes and skipped, TODO or ignored results do not qualify. Debt suggests removal if green persists, identifying pure trunk or the merge tree of <node> / batch <a, b>, never calling a batch pass a trunk pass. Feedback changes no verdict/key/count and never lifts quarantine automatically. Cargo records an excused exit as 0, while TAP retains its original observed exit 1; both use excused counts for verdicts. See SPEC §7.16 for the latest-observation display limit.

CLI owed attest <node> --probe-trunk [--check ID] [--runs N] needs an open slot/candidate; default selection is checks with active/flaky exec blocks. Runs are 1–10, default 1, sequential per check and stop at the first non-pass; existing probes do not skip explicit runs. Red/mutants checks and tests new in the candidate refuse; probe cannot combine with rerun/remeasure/trunk/genesis/all. The lease, node attest lock and per-run plan/candidate/slot/trunk CAS apply. Exit 0 means probes passed even if the candidate owes work, 1 a non-pass/moved CAS, 2 usage, 75 Busy. Probes never satisfy/block obligations; latest non-error current-trunk evidence changes trunk-red diagnosis, and merge reuses matching probes but still refuses the failed merge check. A failed explicit or automatic probe naming a candidate-added file absent from trunk records error with probe not applicable: trunk lacks <path>, which the candidate adds, not trunk-red; outside-checkout absolute paths are ignored. The pi attest tool has no probe parameters (SPEC §7.27).

Complete test-only node-check failures may match same-definition/closure trunk probes before or after the failure, with equal signatures or name-only matching when neither side has one. Invariant rules are unchanged. Hints name the block, trunk and probe, say not this node's change, and suggest a tracked waiver with a matching fix candidate or a request to add one. Writers ask the parent instead of changing code outside writes. Passing probes clear no block.

owed waive … --tracked-by <fix> (tool tracked_by) requires another unmerged plan node. Debt identity is (node, obligation, key, accept_risk block): re-waiving a failure replaces only its tracker; different risks keep separate lines even on one key. No-risk waivers keep node/obligation/key identity. Default status/brief/summary omit only debt whose own tracker merges; full status/report keep settled by <fix> (merge #<seq>). An unmerged tracker leaving the plan keeps debt visible; why <fix> lists its debts. A tracker change is a new act, not a retry. Settlement changes no waiver/ΔO⁻/gate and never proves a flake gone (SPEC §9.9).

Upgrade every reader to 0.27 before the first 0.27 writer records shared quarantine policy, obs.quarantine_passed, waive.tracked_by or ignorable. Upgrade CLI, every driver and pi session loading owed, restarting drivers/sessions, and upgrade executors/result consumers. Recovery is automatic on existing own quarantine too: finish before any 0.27 executor measurement, not just enabling sets or merging. The checked 0.26 parser ignores set keys rather than refusing them; old executors cannot enforce shared policy. It refuses new obs fields and strict-kind markers but ignores extra waiver tracker metadata. From 0.27 readers honour every listed unknown top-level ignorable field, not a registry whitelist; known fields still validate and unknown kinds/channels/enums still refuse. Older readers still refuse the marker where they refuse unknown fields. Never rewrite receipts for them; plan keys/nested shapes have no marker extensibility. See SPEC compatibility.

Manual demonstration

The quickstart above uses the driver. The following low-level commands demonstrate the same sample without automatic orchestration. Do not run a second writer/reviewer beside a running driver.

Initialize the repository, commit the plan, and initialize the ledger as its owner (by default the main agent, delegated):

mkdir owed-demo
cd owed-demo
# Save the plan above as plan.yaml here.
git init -b main
git add plan.yaml
git commit -m "Record acceptance plan"
owed init plan.yaml                # delegated owner act; explicit confirmation only under OWED_CONFIRM=owner
owed status
owed dispatch greeting

In pi, the main agent (the owner) can instead call the owed_init tool (plan, optional cwd): delegated by default (under OWED_CONFIRM=owner it shows the trunk commit, plan sha, node count and invariants in a confirmation dialog), it records genesis and measures the invariants in the background, showing progress in owed_status and sending one message when done. If a genesis attest stops early (Genesis attest incomplete: …), the ledger is initialized; run owed attest --genesis, or let the next attest/merge measure the missing invariants first.

Dispatch prints the packet once and returns its slot worktree and branch (by default .owed/wt/greeting-1 and owed/greeting/1). Always use the returned paths, not guessed ones. Since 0.16 it also runs the plan's setup in that worktree before recording dispatch. Optional slot_setup: "<command>" overrides setup for slots; slot_setup: false skips it. Commands use the measurement shell/environment/wrapper (bash -lc, exec.env, exec.wrap, CI=1 OWED=1 OWED_TREE=<slot worktree>) and a 600-second timeout. Success adds Setup: ran \` in this worktree (exit 0)to the packet, escaping the command onto one line (newlines as literal\n, backslashes doubled). Failure refuses with the exit code or timeout and the last 20 lines of combined output, rolls back the worktree (including setup artifacts), unchanged branch and newly created directories, and records no entry. Setup outputs never enter the candidate (SS5): a successful setup changing tracked paths refuses with the paths and the same rollback. Newly created untracked, not-ignored paths, found by comparing git status --porcelain -z --untracked-files=normalbefore/after, are appended once to the commoninfo/excludeas anchored literal/rules (directories retain/); existing rules stay, already ignored outputs are skipped, and repeated dispatches add no duplicate rules. Glob/backslash/leading !or#/trailing-space characters are escaped; newline or carriage-return paths refuse with one-line escaped names and rollback. After appending, SS6 verifies the outputs are actually ignored using git status --porcelain -z --untracked-files=all, including directory descendants. A tracked .gitignorenegation such as!/artifactoverridesinfo/exclude: any still-visible output refuses with one-line escaped paths and guidance to ignore setup outputs in the repository or change slot_setup. Rollback removes only this dispatch's appended exclude bytes, preserving pre-existing rules, removes the slot worktree/branch and records nothing; owed never edits tracked ignore files. The successful packet then ends (exit 0; excluded: /a, /b). Rules apply to all worktrees and persist. Rebase reuses the worktree without rerunning setup (rerun on rebase is deferred). slot_setupenters no key and changing it alone invalidates no evidence or candidate; the existingsetupkeys are unchanged. No ledger kind or field is added, so 0.15 and 0.16 share a ledger. The field needs a 0.16 parser: 0.15 ignores unknown top-level fields, includingslot_setup`, and runs no setup at dispatch; mixing versions changes no acceptance, only whether dispatch runs setup.

If the writer needs a requirement clarified, the parent can record and deliver one decision from the repository root:

owed decide greeting --text "Keep hello.txt to one line with a trailing newline."

This also answers the node's sole open question and resumes it if halted or held. Use --writer when an existing candidate must be changed and submitted again, or --no-resume to record the decision while retaining a halt. That halt becomes held by ruling #N under In progress, without a resume need or halt wake; owed resume <node> releases it. Other obligations, review questions and debt remain actionable.

Act as the writer in that worktree:

cd .owed/wt/greeting-1
printf 'hello\n' > hello.txt
git add hello.txt
git commit -m "Add greeting"
owed submit greeting
cd ../../..
owed attest greeting
owed why greeting

At this point attest exits 4 because the required independent review is still missing, even though the content check passed. Have an independent reviewer inspect the diff and receipt, then record that review using their identity:

git diff main...owed/greeting/1
owed review greeting --ok --rank 1 --as reviewer:demo-reviewer --note "Inspected greeting and acceptance evidence"
owed merge greeting
owed report
owed verify

For this small demonstration a person can perform the independent review. In a real agent workflow use a fresh reviewer who has never been the node's writer; assigning a different name to the writer does not make it independent. The example has no trunk invariants or red check; production plans should declare appropriate ones. The shell check verifies the greeting text; it is not a test-counted suite.

Reference: the driver

Start and preflight

owed start [--plan <file>] [--max N] [--stay] [--restart] / owed_start {plan?, max?, stay?, restart?, cwd?} checks the repository, Git author/committer identity, plan parser and shell lint, trunk ref, update permissions and driver lock before recording init/plan. On a fresh repository supply a plan; otherwise omission rereads the latest recorded source, not a guessed filename. Canonical equality ignores formatting-only changes. A changed source becomes the default only when a changed plan is recorded.

Fresh init is delegated owner:pi; the driver performs genesis measurements through the existing attest/merge lifecycle. Under OWED_CONFIRM=owner, start refuses with explicit owed init <file> guidance and never opens a dialog. An uncovered downgrade refuses and names owed plan <file> --note "<why>"; record that explicit owner act with --as owner:pi, then start again. Allowance-covered changes retain their downgrade accounting. Refusals name the failed preflight and remedy. Plan/init remains recorded if a later launch fails; fix the launch and start again, not init again.

An unchanged plan and live driver says already running, with no entry or log rotation. max (positive integer) and stay change only a newly started driver. Tool output has three lines: action (with lint warnings folded into it), driver state and next step. CLI start adds followed by pi session <first 8 characters> when the driver's session is known, including reuse; otherwise it adds:

note: no pi session follows this driver; its wakes reach only pi sessions opened in this repository. To be woken elsewhere, run `owed watch` (for example as a dsa command call).

JSON/tool results are {initialized, updated, plan, warnings, driver}, with optional driver alreadyRunning; CLI JSON also has followedBy: <id> | null. These are runtime fields. A tool start follows a reused driver from the current log end, a new one from its fresh beginning. owed drive --detach prints the same follower line.

An agent without a pi session can wait with owed watch [--after <offset>[:<ino>]] [--timeout s] [--json], for example in a durable dsa command call that wakes its caller on completion. Watch writes no files and revalidates wakes against the ledger and dsa. Without --after it starts at the current log end. Exit 0 delivers a wake or driver exit; exit 5 reports no wake within <s> s. Both print next: owed watch --after <offset>:<ino>: reuse that cursor to continue. An inode change starts the current log at zero; bare byte offsets remain accepted. Timeout seconds must be a nonnegative integer. Exit 3 says no owed driver is running in <repo>; start one with owed start with no next: line. JSON is {lines, next, exit}, with a cursor string in next. An old cursor can show a repeated wake line again.

owed start --restart / owed_start {restart:true} drains a live driver's current action and in-flight measurements, waits up to ten seconds for its process to exit, then starts the replacement. A timeout refuses with the pid and says repeating restart is safe; owed drive --stop --now is the explicit immediate stop. Without a live driver it is ordinary start. Graceful restart and owed drive --stop without --now signal each pid/start-time process at most once: repeated requests wait, never escalate. Preflight and foreign-host protection still apply (SPEC §10.2, §12.8).

Stopped writers and parent questions

A successful sealed/pruned writer without a current candidate waits while dsa reports outstanding durable wakes: no submit reminder or stopped/no-submit halt, including after repair/ruling/rebase follow-ups; due follow-ups still run. Use the packet's pi-durable-subagents run --request owed-<project>-<node>-<attempt>-<name> --wake caller -- <command…>, then end the turn without submitting. A live driver's status/why show writer waiting on durable <request> under In progress with no wait-generated parent wake; without a driver it is unknown. Non-ok seals retain their halt/recovery route and name outstanding requests. Wait age uses the described seal time, otherwise first observation of that generation (reset on driver restart); it affects display only.

Otherwise, when a successful writer finishes without a candidate, the driver inspects its worktree. A clean HEAD at the handed commit halts without a submit reminder; a new commit or dirty worktree can receive one reminder, then halts if nothing is submitted. Plan-changed resubmissions still allow the same clean commit. After a pending-rebase follow-up, no new work means clean HEAD at any of the last submitted commit (or original dispatch base) and each later rebase's pre-rebase base; work outside that set can receive one reminder carrying the rebase instructions. Unavailable inspection facts halt conservatively.

Stopped-writer wakes, default why and status quote writer says: "<final text>" (or writer gave no final text) and offer owed decide <node> --text "<ruling>" --writer, also for non-ok seals. The whole reply is flattened: up to 600 Unicode code points in full, otherwise head 200/tail 380: the head prefers a late sentence ending, then whitespace/CJK; the tail keeps the original ending and adjusts only its start to a nearby whitespace/CJK boundary. It retains up to three omitted commit and durable IDs each. Escaping is unchanged and recorded halts are not rewritten. dsa seal diagnostics append the ended-process total and at most three pid/commands (80 code points each). Full evidence retains the cause/run/failures; pruned text requires runner retention. Real questions keep their existing answer flow.

A due ruling follow-up takes precedence over a pending-rebase halt. After that follow-up, if the writer seals again without a new submit or outstanding durable wakes, the driver halts naming it and sends no further submit reminder. This applies when the ruling follow-up follows both the rebase follow-up and latest resume, even with dirty files or an unsubmitted commit outside the handed set (SPEC §12.5.4).

Writer packets/follow-ups ask for decision_needed (one sentence), numbered options with label/scope/cost/risk and one preferred choice, facts, tried and classification_candidates with evidence and unproven explanations. Use structured ask fields when available, else text starting Decision needed:. Structured notices/wakes lead with DECISION and options, then context, details and each question's answer command; answer "option <n>; <limits>". Clipped fields prefer sentence/word/CJK boundaries and end in …; clipped details add (full: pi-durable-subagents describe --key <rid> --json) for the whole question. Plain asks keep their existing flow. Never submit a candidate believed to fail checks; submit reminders also say to ask when stopped for a parent decision.

After answer delivery, owed answer/owed decide and their tools report the answered call's observed state, including queued, still asking, asking again, sealed or unknown. A describe failure never resends; a retry without the original question's call address reports unknown (SPEC §12.5.1).

Quiet output and evidence

Flaky exec needs name the failed commit/observation and same-key attribution pass, plus the merge key when applicable and the separate current candidate outcome. An older different-key candidate flake is held from owner/repair requests until the current candidate has a counted pass: errors keep the hold, failures take the repair route, a pass exposes the original risk. Same-key/merge-key flakes remain immediate; held blocks still bind acceptance/merge and can be waived. Combined needs, wakes, why, waiver hints and repairs show trunk-red first with parent guidance and the or owed waive … --accept-risk N alternative, then shared-check-on-trunk diagnosis, then the full flaky same-key pair, never lost.

Waive CLI/tool replies start with debt recorded, then effect, then compact why; --full / tool full:true restores evidence. Accepted counting blocks say in effect now and name the risk/pair even on merge keys. Candidate satisfaction cites actual evidence (including reviews) or is omitted; only a waiver accepting no counting block can say it is not needed while the candidate stays satisfied.

Plan/edit shell/reads lint repeats only for new checks/invariants or changes to run, reads or timeout, matched by invariant id or node/check id. Closure warnings repeat on relevant reads/closure changes; near-timeout warnings only when their admission text changes. Initialization checks everything, status/cost keep full views; general check-less/loop warnings retain their own rules. With sets, new/changed Cargo/Node test commands lacking both quarantine fields warn; an explicit empty own list suppresses this warning.

Unresolved needs-parent reviews say review asks: <ask> and give one owed decide <node> --text "<ruling>" --writer command in wakes, status and why. The first case-insensitive needs-parent marker supplies the ask; without one, the final sentence is used (one line, at most 300 characters). Full why keeps the note. Review acknowledgment validates before append: insufficient or underivable ack refuses with nothing recorded and names the required watermark. Read the rulings, then use --ack-rulings latest / tool ack_rulings:"latest", a sufficient number, or a comma list of numbers (optionally including latest). Lists select their maximum; latest selects the latest applicable ruling. The tool also accepts a nonempty integer array; all forms record one numeric watermark, and malformed lists refuse. Trunk repair guidance names failing tests and the recorded plan path, or owed edit --node-new when no source exists. Existing repository notices retain their red text, waiting membership and remedies.

Default owed why <node> starts with outcome, then unsatisfied obligations, active blocks/conditions and their next commands. It retains unacknowledged model warnings, the complete Ruling to forward: payload, measuring/merge-wait state and the diff-derived review requirement (context, not counted as outstanding). The total counts unsatisfied obligations and active blocks, not merge-wait context or every displayed line. Default ΔO⁻ lines are restricted to entries after the current dispatch or latest resume; --full retains older history (also on CLI rebase and candidate attest). owed why <node> --full / owed_why {full:true} restores observations, counts/logs, satisfied items, history, superseded entries, other ruling text, ΔO⁻, selected/used models, acknowledged warnings, backlog (later) and quarantine lifts. JSON and act/refusal receipt cards stay detailed. Non-node/trunk subjects receive trunk measurement or plan-repair commands, never a node command they cannot use.

Writer repairs list only failures since the latest non-refused repair follow-up to that run, with new failed-test/reviewer notes, plus one line naming earlier obligations still failing. Ruling steers, report/rebase/interrupted follow-ups and refused sends do not reset the boundary; before the first repair all failures are new. An empty delta says so. Rulings come first, then fix/commit/submit and owed why <node> --full for everything else; owed reruns checks itself. Driver restart recovers refused sends from retained halt reasons, recent driver reports and runner receipts, so refused repairs do not advance that text boundary. Removing all refusal evidence prevents recovery; binding failures remain in the full receipt.

A single or batch CLI merge gives <node> merged (<commit12>); measured <n>, waived <n>, debt <n> per member. Measured means evidenced check/red/strength/writes/invariant obligations, not reviews or manual evidence; debt is defers on this merge's keys only, not historical defers. Single merges retain debt/coverage lines and lease annotation; batches retain conflict dropouts and Batch of <n>: <nodes>. The post-commit receipt read is best-effort: a shortened <node> merged (<commit12>) still means success. No supplied current-key debt count means no debt field.

Driving the loop with dsa

owed start is the normal preflight/start entry. The low-level owed drive controls below remain for an initialized repository; unlike start, detached drive start refuses an already running driver. Without a pi session, use owed watch to wait for its wakes.

owed drive runs the mechanical loop — dispatch, writer, submit, attest, reviewers, merge — with pi-durable-subagents (≥ 1.0.27) as the process runner, and stops for decisions. Every intent is recorded in the ledger before dsa is called and retried with the same bytes and id, so killing the driver at any point is safe. Attest and merge run under pi-durable-subagents hold machine --shared --priority 10 --no-wait and are retried later while the machine is busy; with an older dsa that rejects --no-wait the attempt is halted with that error. Unknown --priority/--yield options fall back only after a separate no-op hold confirms dsa's refusal; measured commands are never replayed by fallback. One driver per repository (.git/owed/drive.lock); a lock left by a dead driver on this host is taken over, but a lock from another host is not: check that host, then remove the file by hand. Attests and merges run in the background while the driver serves other nodes (at most drive.measure at once, default 2, and one merge). A first Ctrl-C stops after the current action and waits for them, a second stops at once.

owed drive            # until idle; run it in a terminal or a systemd-run --user unit
owed drive --once     # one pass (also the pi tool owed_drive)
owed drive --detach   # the same loop as a detached background process; prints its pid and log
owed drive --detach --stay   # when idle, keep running and wait for ledger changes (a growing plan)
owed drive --status   # running (pid, host, since) or not, its last exit record, the last 10 log lines
owed drive --stop     # stop it after its current action and in-flight measurements (--now: at once)

The plan's optional drive: block sets the writer and reviewer dsa agents (and models) for every node; a node may override them with its own drive (0.7.0), e.g. to route routine nodes to cheaper models. The packaged default reviewer is owed-reviewer: it must record its verdict with owed review and may run checks in a scratch checkout, without editing the writer's or main checkout. To keep a custom profile, explicitly set drive.reviewer.agent globally or on the node; existing recorded launches keep their agent:

drive:
  writer:   { agent: worker,   model: "example/model-large:high" }
  reviewer: { agent: owed-reviewer, model: "example/model-large:high" }
nodes:
  - id: docs-pass
    drive:
      writer:   { agent: worker-cheap }            # agent set: replaces the plan's writer (its own default model)
      reviewer: { model: "example/model-small" }   # model only: the plan's reviewer agent with this model

Batch merge measurements survive an unrelated node-only plan edit only when every intervening plan, projected onto members plus all global settings, is canonically unchanged. Relevant or transient member/global edits refuse; facts are recomputed after unrelated edits and indirect changes still refuse. Trunk, slot and candidate CAS and the latest guard remain binding (SPEC §7.21). A refused driver batch's candidates merge alone across trunk changes, new partners and restarts, until their commit changes, the node merges or its slot closes. Re-submitting the same commit keeps isolation. Missing or unreadable isolation state means no remembered isolation; status's ledger-only batch preview can still propose isolated members.

After a review-missing halt, owed resume <node> gives the same sealed run one new verdict follow-up; another seal without a review halts again. Rejected or pruned runs cannot receive it, so their remedies omit resume. Builtin read-only reviewer guidance includes owed edit --drive --set reviewer.agent=owed-reviewer --note "<why>" for a new run of a later candidate, then owner review or owed abandon <node>. The edit does not replace the existing run.

A node drive is not an obligation: changing it is never a downgrade, keeps the submitted candidate and affects only later launches (a re-launch resends the recorded bytes). owed why <node> --full shows Drive: writer <agent> (<model>) · reviewer <agent> (<model>) for a node that sets it. owed 0.6.x ignores a node's drive and launches with the plan's drive.

In 0.20.1, owed why compares selected and used models by provider/id, ignoring the trailing :thinking even for same-writer/reviewer warnings; ids with several slashes stay intact. Selected names resolve through dsa pools in $DSA_HOME/config.json (default ~/.pi/durable-subagents/config.json), including nested pools and {model, afterMs} fallbacks. Any discovered member matches. Unknown/empty/cyclic/malformed or partially unresolved pools cannot prove a mismatch; why instead shows selected <name> (pool membership unknown) for unmatched used models. A valid branch can still prove membership. Config is read locally when rendering, not snapshotted; missing launch blobs are unknown and owed does not alter dsa routing.

Set drive.same_model: accepted globally or in a node's drive to acknowledge only the informational same-model warning. Other values refuse; a node may acknowledge without role overrides, otherwise inheriting the global key. Compact why omits acknowledged warnings; why --full restores (acknowledged) and JSON keeps {message: string, acknowledged: true} alongside ordinary warning strings in modelWarnings. Selection mismatch warnings stay visible. Adding/removing this key changes no obligation key, downgrade, review block or acceptance rule; removing it restores the warning without invalidating evidence. Upgrade every reader before using it: older parsers reject the key.

Environment failures wait instead of halting (0.11). A measurement that exits non-zero with no failing test counted and ENOSPC, No space left on device or Disk quota exceeded in the last 50 output lines (TAP/cargo test-name lines are ignored, so a test title is never evidence; any counted failing test makes it a plain fail), a check/invariant (red included) exiting 75 whose last non-empty line matches ^hold: .*(not running the command|the command was stopped) \(exit 75\)$, or that fails before its command runs (worktree preparation, spawn, setup), is recorded as error with a note starting environment: . The driver does not count those toward "attest recorded no verdict twice"; it waits 1, 2, 5, 10, then 30 minutes before the node's next attest (a resume starts again) and notifies once per streak. Before it starts a measurement it checks the free space of the git common dir and the temp dir against drive.min_free_mb (default 2048, 0 disables) and starts none while either is lower (one disk low: … notify per change). Lease errors say the check did not run or yielded; trailing spaces after (exit 75) and other exit-75 output keep their ordinary verdict. A disk-full error escaping a pass logs a disk-full event (a wake), sleeps 60 s and continues.

--detach refuses while a driver holds the lock (naming pid, host, start and log). Otherwise it keeps the previous log as .git/owed/drive/log.jsonl.1, starts owed drive --json in its own session with stdout and stderr appended to .git/owed/drive/log.jsonl and returns once that driver holds the lock; if it has not taken the lock within 30 s, --detach stops it (SIGTERM), prints the log tail and exits 1. A concurrent --detach or --stop waits until that is decided. The log's last line is the exit record {"event":"exit","code":C,"reason":"idle|stopped|killed|error","at":…}; only SIGKILL or a crash leave none, and --status then says it ended without an exit record (an empty log says no driver output yet). The exit record is written before the lock is released, so once --stop says stopped it is in the log. --stop signals the lock's pid only when its process start time matches (never a reused pid, never another host). In pi, owed_drive with action: "start" does the same, and the session is woken with one message when the driver halts, needs the owner, a run asks a question, it is stalled, dsa events fail or the driver exits (merges ride along with the next message). One wake per new fact: a halt or notify of a node wakes again only when its text changed or the ledger gained entries for that node not written by the driver (e.g. the writer resubmitted, a ruling); otherwise it only rides along with the next message as <text> (repeat n, no new ledger entries). Trunk moved outside owed is not a halt: the driver merges nothing and notifies once per change with owed adopt --note "<why>" (or the git update-ref that restores a rewound trunk); after the adopt its next pass merges. Every top-level pi session opened in the repository later follows a running driver from then on and is woken too. Every owed tool call in a top-level session attaches to its target repository, including refusals. Sessions also discover their live drivers across repositories at most once every ten seconds, using the starting DSA_SESSION with PI_SESSION_ID as fallback outside dsa calls. Registry attachment starts at the log end: a wake written before attachment is seen again only when repeated or at the idle re-wake. Sessions inside dsa calls (DSA_EXEC/DSA_CALL set) never attach. Nobody has to poll action: "status"; action: "stop" stops it. The driver survives pi exiting. The driver carries no dsa call identity (DSA_EXEC and DSA_CALL are removed from its environment; DSA_HOME and the rest are kept). Started from inside a dsa call, start still works but prints note: started from inside a dsa call; if that call's processes are contained, the driver may end with it — prefer starting it from a top-level session or systemd-run --user.

Notices start with Needs you: <node> — <decision> → <one command>, then Done: <merged nodes> when any, and Driver continues (<n> in progress) or Driver stopped: <reason>. Each question/reason and its command appear once; raw node lines and internal identifiers are not echoed unless needed to act. Full repository notices and reported findings stay because they are the payload. There is no repeating Details: block; Next: owed status / owed why <node> stays last. Action log inProgress counts open slots.

An answer recorded with owed answer suppresses the question in both the driver and pi delivery, even while the run remains asking in the model queue. The quiet log says <node>: answered #<seq>, waiting for the run to resume (runtime outcome answered). If that question remains open after drive.answer_grace_s (integer seconds 0..900, default 600, 0 = immediately), one reminder per rid/qid in that driver run gives the exact answer retry command. Its answerRetry: true log flag keeps this reminder deliverable while the answered question stays open. The reminder uses <node>: answered at #<seq> (HH:MM), still asking after <m>m: retry with owed answer <node> --rid <rid> --qid <qid> --rev <rev> -- <text>, not a new question. The QN answer grace and this line share the same threshold. While a driver runs, status obtains this line from its runtime cache; it disappears when the run resumes or the slot closes. Without a driver the status line is absent, not proof of delivery. The field changes no obligation key; plans omitting it replay as before, and older plan parsers reject it as an unknown drive key. No new ledger entry is added.

Each JSON loop log line carries driver: {pid, start?, at}. A terminal line from an earlier driver is checked against the current repository owner at delivery: when a newer driver runs, it rides along with the next message as earlier driver (pid P, started T) <event>; driver pid Q (started U) is running. Current-owner terminal lines, and those with no live owner, still wake normally.

In pi a wake is never handed to the session while its agent is running: the follower holds it until the agent settles, then drops what was resolved meanwhile (a question no longer open in dsa, a node line whose node gained ledger entries) and delivers the rest, with (<n> wake(s) resolved before delivery) in the wake; if nothing is left the session is not woken. With --stay (owed_drive stay: true) an idle driver does not exit: it wakes the session once (idle: nothing open and nothing ready; staying until the ledger changes (owed drive --stop ends it)), keeps the lock and resumes when the ledger changes (a plan update, a ruling); --status then says idle, waiting for ledger changes since <at>. After owed plan / owed_plan, when nodes are ready to dispatch and no driver runs, the output adds ready: <ids> (<n>); no driver is running: owed drive --detach --stay (pi: owed_drive {action:"start", stay:true}); nothing starts automatically.

With pi-durable-subagents ≥ 1.0.31 the driver's runs are listed in the pi session that started it: owed reads DSA_SESSION (ignored inside a dsa call), records it in drive.lock, hands it to a detached driver and passes --session <id> to every run. owed drive --status, /owed and owed_drive status say runs are listed in pi session <id>, or no pi session: runs show only in pi-durable-subagents status / the CLI for a driver started outside pi. An older dsa that refuses --session is used without it (one log line, never a halt). To move a repository's runs to another pi session, stop the driver and start it from that session; earlier runs stay where they were.

The driver never answers a question, waives, changes the plan or forces a dsa restart: questions and owner decisions are printed, and a halt (owed status, owed why) waits for an action on the node by the main agent (the delegated owner; owner halts list the command that resolves them). A ruling, submit, review, rebase, abandon or resume on the node clears a halt, and the next pass resumes. A decision with --no-resume instead keeps it held by ruling, with no halt resume request/wake; independent needs remain. A ruling recorded while a driver-launched writer or reviewer call is running reaches it as a steer (reason ruling, recorded) once the node has nothing else to do; sealed calls get rulings with the next follow-up (submit, rebase and repair follow-ups list the undelivered rulings first; a ruling steer dsa rejected, e.g. because the call had just ended, counts as undelivered), and reviewers still acknowledge them with --ack-rulings. Launch entries and follow-ups record the rulings their message carried, so a ruling recorded while one is being sent is steered afterwards. A ruling naming the node gives the attempt a fresh repair budget (repairs counts the repairs since the latest such ruling or plan change of the node's spec), and a sealed writer the driver would otherwise halt for (finished without submitting, repairs exhausted) gets it as one ruling follow-up instead; never while a reviewer run of the candidate is running or when the candidate has no block. Since 0.8 a due ruling comes before any other halt too: a sealed writer gets it before the owner-needed notify (a flaky block, a rank 2 review block), before the sealed <status> halt and before the stalled: halt; when a stalled candidate owes only the rulings acknowledgment, its sealed reviewer gets the ruling as a follow-up (or a new reviewer is launched) and its ok with --ack-rulings lets the node merge. Each ruling is sent once; if the node still needs the owner afterwards, the notify or halt follows as usual. A flaky block's hints also offer owed rule "<what the writer must change>" --nodes <node> for when the check or test itself must change. A check that ran the same number of passing tests twice, below its min_tests, halts for the parent (the plan's threshold may be wrong) instead of another repair. Calls you launched by hand stay yours to steer. Driver calls carry a dsa run name (owed <node>#<attempt> writer / owed <node>#<attempt> reviewer <n>). Detailed asking diagnostics retain dsa addresses and answer forms; plain wakes give each question its text and one owed answer command. Even while halted, the driver reports asking writers and reviewers of the current candidate with the same answer hint and once/quiet behavior; it does not clear that halt or send anything. Ordinary questions still use the driver's asking channel. While a driver runs, status additionally shows runtime-cached answered-but-still-asking questions after the grace period; brief otherwise stays ledger-only.

A rejected run stays fixed for the attempt (D15.1): fix the cause (plan, agent, model), then owed abandon <node> so the driver dispatches a new attempt. A rejected send need not lose the attempt (0.16.1, SR1–SR4). Row 4 skips it, never re-sends it and lets later rows act if a later ledger entry is a resume or submit of the same node/attempt, or a non-rejected send of that attempt to the same rid (even one not yet confirmed applied). A rejected steer to a sealed or pruned run is also skipped: the sealed-run rows decide (unknown gets the interrupted follow-up). At send action time a rejected steer records only the refusal and outcome rejected; next pass decides. A steer to a still running/asking/queued run with nothing later still halts then; rejected follow-ups and notices keep their action-time behavior, including existing ruling-steer and waiting/ruling-notice exemptions. No ledger format or key changes. The send halt says: the attempt is not lost: continue the run (e.g. a dsa follow-up), then owed resume <node>; a later resume, submit or send to this run supersedes this send; or owed abandon <node>. After that resume it does not re-raise the same send halt; abandon is the last option. Halts that name a driver run also give dsa's call address (to:"<wid>/<key>") when dsa reported one, so you can steer or inspect the call.

Same-key reviewer reuse (0.16.1)

An evidence-only resubmit with unchanged required review and closure-review keys reuses reviewer runs launched since the start of that contiguous review epoch in the open attempt. These runs still report questions and receive rulings; the driver counts them instead of launching redundant reviewers. A review-key change starts a new epoch with the existing launch behavior. A sealed carried reviewer's verdict follow-up names the current candidate, and its earlier review on the same key counts. Satisfied owed why --full / operation receipt items show each principal once, with its latest qualifying review; this is view-only deduplication and changes neither replayed state nor acceptance rules. Stopping already-surplus reviewer runs remains deferred (SPEC RR4); 0.16.1 adds no driver stop effect.

Reference: parent and owner acts

The main agent acts as parent and delegated owner: no user needs to operate owed. Pi owner acts use owner:pi, CLI owner acts owner:cli, with channel delegated; the brief lists these audit acts after outcome and Needs you. Downgrades, waivers, deferrals and adoptions require reasons; approval accepts a note, which the main agent should provide and report. Under OWED_CONFIRM=owner explicit confirmation restores default owner:human. Earlier CLI versions used that identity even without the gate; an explicit --as owner:human retains the label, not stronger authority.

Parent decisions and recovery

Prefer owed decide <node[,node...]> --text "<decision>" (pi owed_decide {nodes: ["A"], text: "..."}) for guidance that combines ruling, question answer and recovery. It answers at most one open question per node across writer and reviewer runs and resumes halted/held nodes by default. --writer requires resubmission; --no-resume keeps the halt as held by ruling #N, without a halt resume request/wake. owed resume <node> releases it; other needs remain. When owed rule finds a known writer call neither running nor asking, its receipt says the ruling was recorded but not sent now; the driver delivers it on its next pass. A halted node not held by a decision needs owed resume <node>; decide records and resumes in one step. owed rule never sends follow-ups itself (SPEC §9.3). Add --writes path (or +path) to amend the recorded plan with the same authority as amend, --defer INV as owner to retain current merge-key debt, --after FIX to wait, or --remeasure to request same-key invariant measurement. After/remeasure imply resume and cannot combine with no-resume. Every part validates before one append; a refused part records nothing. Answer delivery is external: use the exact retry command if it fails. Decisions do not clear review blocks or acknowledge rulings for reviewers. Remeasure replies name each failed merge-tree invariant key. If trunk/candidate moved since failure, the next merge builds a new tree and remeasures the unchanged key or measures a new one normally; no Git tree is built at decide/resume time. Active merge-check blocks follow attribution on their original key; already-flaky ones still need accepted risk or a fix, because remeasure measures invariants only.

For one shared failure blocking several nodes, inspect the repository notice and fix the common cause once; name the failed test in the fix node's brief. An accepted, non-halted fix candidate takes precedence automatically. To retain the debt knowingly, use owner owed decide A,B --defer inv:integration --text "<reason>". A narrow owner allow[].defer can authorize executor deferral when both a matching-signature shared failure and a pass exist on the merge key. Waiting and name-only matching never grant acceptance. See SPEC §7.24.

The separate commands below remain valid. Use owed answer to disambiguate multiple questions or retry a recorded answer; owed resume for a retry with no new ruling or --expect report; owed rule for global/prospective policy; owed amend for a standalone writes amendment, and owed edit for other plan changes.

Answer a selected writer or reviewer question with owed answer <node> [--qid Q] [--rid R] [--rev N] "<text>" (pi owed_answer {node, text, qid?, rid?, rev?}, parent or owner). Omit qid when one open question matches; rid disambiguates questions across the node's current writer and reviewer runs. Ambiguity refuses and lists the open questions. The command records one rule naming the node, writer: false, text Answer to <role> question <qid>: <text>, and answer: {rid, qid, rev}, then sends dsa kind: answer with request id owed-answer-<rule seq>. A delivery failure retains the rule and reports its seq and an exact retry command including --rev. Use -- before text beginning with --; exact retry commands include it. The driver does not send that answer rule back to the target rid as a ruling steer or follow-up. Other runs receive it normally and reviewers still acknowledge it. Successful text names the answered role/question/run and rule, then any other-role delivery result; --json and the tool retain the request id and full details.

Revision-aware answers (0.16, dsa 1.0.38 re-asks): identity is rid+qid+rev. An open matching question always uses its current rev from dsa describe, even if --rev names an old rule. A newer re-ask records a new rule and request id, with either the same or different answer text. Same rid+qid+rev and text retry the original request; different text at that rev refuses and names the rev. With no matching open question, --rev N retries that prior revision; omit it to retry the latest prior rule for rid+qid. The rid may be found from current-attempt launches when history identifies exactly one run; an explicit rid/qid retry also works after the slot closes. Without matching history, "No matching open question" refuses. --rev / tool rev must be a positive integer, otherwise a usage error. The driver still covers only questions at revisions <= the recorded answer rev, so q@2 after a q@1 answer remains an open question.

Compatibility: a re-ask answer records supersedes: <seq> of the latest prior answer rule for the same rid+qid with a lower rev. It is required exactly when that history exists and absent otherwise. Owed 0.15 and 0.16 share a ledger until the first rule with supersedes; after that every reader needs 0.16 (0.15 refuses with code version and an upgrade message). Historical s