pi-owed0.30.0
owed — the acceptance ledger for multi-agent task graphs. No receipt, not done.
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.
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 --fullshows 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 ...:whyandstatusshow the block as needing a parent ruling, andowed drivehalts instead of sending the writer a repair until the parent recordsowed 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, plussetup,execand referencedquarantine_setentries, if any) or removes the check, the execution blocks recorded under the old definition becomesuperseded: 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 --fullandreportshow each one as#<seq> superseded by plan #<p> (check <id> definition changed)(orremoved). 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.
deferpermits 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) andmin_kill(fraction in (0, 1], default 1). The node then owesstrength:<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 reachesmin_kill; the receipt showsstrength k/nand 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