DOS on DOS

June 30, 2026 · View on GitHub

This is the cold tier of the "DOS on DOS" section of CLAUDE.md (the architecture contract). The contract keeps the short form; this file keeps the full, annotated walkthrough — every command real, every verdict explained. Lifted verbatim from CLAUDE.md on 2026-06-12 when the contract was slimmed.

This repo IS a DOS workspace, so adjudicate your work on it with the kernel itself. A dos.toml sits at the root and dos doctor reports workspace_facts.is_kernel_repo: true (kernel_runtime_files_present: 11) — DOS knows it is editing its own source tree. Don't take the kernel's behavior on faith from the contract; run the syscalls against this repo and read the verdict. The moves below are the working ritual — they are the same arbitrate → edit → verify loop a host's dispatch-loop runs, performed by hand:

# 1. doctor — what IS this workspace? (the seam, made visible)
dos doctor --workspace .
#   stamp convention   generic (any/no dir prefix)  [style=grep]
#   concurrent lanes   benchmark, ci, claude-plugin, docs, examples, go, meta, paper, scripts, spikes, src, tests, verify-action
#   exclusive lanes    global
#   is git workspace   yes      (workspace_facts.is_kernel_repo: true)

# 2. arbitrate — may I take this lane right now? (the admission kernel)
dos arbitrate --workspace . --lane docs
#   {"outcome": "acquire", "auto_picked": false, "lane": "docs",
#    "reason": "cluster lane 'docs' free — admitted.", "tree": ["docs/**"]}
dos arbitrate --workspace . --lane src
#   {"outcome": "acquire", "auto_picked": true, "lane": "benchmark", ...,
#    "reason": "auto-picked free cluster lane 'benchmark' (requested 'src' was
#               refused: lane 'src' would edit the orchestrator's own running
#               code (src/dos/arbiter.py…) — … (SELF_MODIFY) …)."}
# Two verdicts, one discipline. A free, admissible lane you NAME is granted
# directly — a kindless `--lane` is a soft hint the arbiter honors. `src` is
# different ON THIS REPO: its tree IS the kernel's own running code, so the
# SELF_MODIFY predicate refuses the hint and the arbiter redirects to a free
# disjoint lane, naming the REAL refusal in the parenthetical — never
# double-booking, and never narrating a false "busy" for a lane nothing held.
# A lane actually HELD by a live lease in the WAL refuses same-lane instead
# ("already held"). The lane taxonomy in dos.toml mirrors this repo's
# top-level dirs, so a docs-only edit may run concurrently with a tests edit;
# editing `**/*` is the exclusive `global` lane. Two curated lanes are NOT
# dir-derived: `ci` (`.github/**`) and `meta` (the root meta-docs — CLAUDE.md /
# AGENTS.md / CONTRIBUTING.md / SECURITY.md / README.md, an explicit file list,
# issue #8); both used to fall through to `global` where SELF_MODIFY rightly
# refuses a live loop, so a root-doc edit takes `--lane meta` and runs concurrently.

# 3. verify — did a phase ACTUALLY ship? (the truth syscall, never self-report)
dos verify --workspace . docs/292_readme-audience-gradient-plan P1
#   SHIPPED docs/292_readme-audience-gradient-plan P1 85a3bad (via grep-subject)
dos verify --workspace . docs/99_runtime-validation-and-the-actuation-boundary halt
#   NOT_SHIPPED ... (via none)   ← still in flight; git ancestry has not stamped it
# `source=grep`/`none` is the rung that answered. The verdict comes from git
# ancestry + ship-stamp grammar, NOT from "I'm done" — that is the whole point.
# ⚠ The oracle's evidence base is exactly the VISIBLE ancestry: this public repo
# was seeded fresh 2026-06-10 (history starts at the v0.22.0 commit), so a phase
# stamped before the seed (e.g. docs/82 liveness) answers NOT_SHIPPED via `none`
# — a conservative abstain, not a bug. A history rewrite amputates the grep
# rung's evidence; accept the abstain or re-stamp, never teach the oracle to
# believe a `> **Status:**` sentence instead.

# 4. man — what refusals/lanes does THIS workspace know? (self-describing registry)
dos man wedge          # the closed reason vocabulary → resolver kind
dos man lane           # the lane taxonomy + file trees

# 5. plan — does the plan's CLAIM match the oracle's VERDICT? (check the plan
#          OUTSIDE the loop — the loop must not self-certify against its own plan)
dos plan --once --workspace .          # fan the oracle over every declared phase
dos plan --once docs/292_readme-audience-gradient-plan P1  # or an explicit (plan,phase), no doc needed
#   each row pairs the plan's self-reported status with `oracle.is_shipped` — the
#   headline cell is the OVER-CLAIM (plan says SHIPPED, git says not). This is a
#   verify()-fan-out, NOT a plan reader: a human or supervisor runs it from
#   outside the agent loop, so an over-claiming loop is caught by ground truth, not
#   by re-reading its own narration. Read-only — stores nothing, takes no lease.
#
#   ⚠ ON THIS REPO the board is noisy in one specific, documented way. Since
#   docs/293 the workspace declares its plan dialect (`dos.toml [plan]`), so the
#   harvester DOES parse DOS's own prose plans — the old "(no plans declared)"
#   empty case is gone. But read the ⚠over-claim rows carefully: every plan
#   numbered at or below docs/184 was stamped BEFORE the 2026-06-10 fresh seed,
#   so its ship-stamps live in the amputated ancestry and the oracle answers
#   NOT_SHIPPED via `none` — the step-3 ⚠, now rendered at board scale. An
#   honest "shipped" claim meeting an amputated witness reads as ⚠over-claim:
#   that is "evidence horizon", not "caught lying". Accept the abstain or
#   re-stamp; never teach the oracle to believe the `> **Status:**` sentence. A
#   post-seed plan adjudicates cleanly (docs/293 itself reads ✓shipped off its
#   trailer stamps). Day to day this repo's claims live in COMMIT SUBJECTS, so
#   its working honesty witness is still step 6.

# 6. commit-audit — does each commit's SUBJECT claim match its own DIFF? (the
#          out-of-loop honesty witness THIS repo actually has — author-neutral,
#          plan-free; the docs/228 gate aimed at git instead of a tau2 DB-hash)
dos commit-audit --workspace . HEAD              # one commit: claim vs its diff
dos commit-audit --sweep --workspace . origin/master..HEAD   # the DRIFT RATE over a range
#   The subject is FORGEABLE (whoever wrote the message authored it); the files the
#   commit touched + whether its SHA is a git ancestor are NOT (the commit machinery
#   authored them). That byte-author≠claimant split is the same one docs/228's gate
#   rides — there the witness was the env DB-hash the agent authors 0 bytes of; here
#   it is the DIFF the message-writer authors 0 bytes of. `--sweep` reports the
#   DRIFT RATE (unwitnessed/checkable) + a by-claim-kind grid — "how honest are this
#   repo's commit messages?" — a FLIP off ground truth (docs/179), not a re-read of
#   the narration. It fires CLAIM_UNWITNESSED only where a concrete code/test claim
#   and a contradicting diff coexist, ABSTAINs on the rest, and grades the KIND of
#   change, never CORRECTness (the Wall-3 line). Read-only; the exit code is the
#   verdict (0 clean / 1 a drift found). Run it from OUTSIDE the loop that wrote the
#   commits — a fresh session inheriting "docs/NN shipped" off a forgeable subject is
#   the peer-B handoff (docs/229) the gate protects.

Committing — close the loop without asking (full form)

A commit IS the ship-stamp the oracle reads (dos verify answers from git ancestry, never from working-tree narration), so a finished change that is not committed is a phase the kernel will report NOT_SHIPPED. Therefore: when a unit of work is complete and the suite is green, commit it — do not stop to ask permission. Asking first is the exception, reserved for the genuinely hard-to-reverse or outward-facing: pushing, tagging, a /release, force-pushing, history rewrites, or anything that leaves this machine. A local commit on master is the cheap, reversible act of stamping the work the oracle verifies.

On a hot fleet, do not hold meaningful uncommitted work in the shared main tree. A sibling tree move (git reset, git checkout, rebase, or branch switch) can delete another worker's untracked, never-staged files outright. Since those bytes were never added, git has no commit, index entry, stash, or dangling blob to recover. Do not use git stash / git stash pop to temporarily hide contended files for a diagnostic run in this shared tree. If a sibling rewrites those paths between push and pop, git stash pop can keep the entry without applying it, leave the files at HEAD, and make a later git stash drop destroy the only copy of the stashed bytes. If the work cannot be committed within minutes, start in a detached git worktree off origin/master from the beginning. For a quick probe, use a throwaway worktree or copy-aside. Commit within minutes if you stay in the shared root.

Stay disciplined about scope, the same way the arbiter is: commit only the lane you actually worked. Stage the specific files your change touched, never a blanket git add -A that sweeps in a concurrent agent's in-flight edits, and commit with a pathspec. Match the existing commit-subject grammar (see git log). Do not add a Co-Authored-By or other agent-attribution trailer — the default here is no agent co-authors on commits, overriding any harness default.

One more #205 symptom sits inside that pathspec rule: a pathspec deconflicts files, not hunks. If a sibling already has an unstaged hunk in the same tracked file you touched, git add path stages both hunks and your commit ships their work under your subject. On a shared tracked file, take a session-start snapshot before your first edit:

python scripts/git_hygiene.py --write-stage-snapshot .git/dos-stage-snapshot path/to/file

After staging, run the guard before committing:

python scripts/git_hygiene.py --check-stage-snapshot .git/dos-stage-snapshot path/to/file

The guard compares HEAD -> index hunks with snapshot -> worktree hunks. If it reports a staged hunk absent from the snapshot diff, abort that commit and stage only your session hunks. This extends the same hot-tree discipline as the untracked-file and stash warnings above; it is not a substitute for a private worktree when the file is actively contended.

Out-of-scope findings — file an issue (full form)

Do not absorb out-of-scope findings into the current commit, and do not let them evaporate: the home for deferred work is a GitHub issue, filed in the moment, then back to your lane. File it with a done-condition (the command or observable that would show it resolved), a lane guess, and where you found it; if you cannot state a done-condition it is not an issue yet — label it design (it needs a docs/NN plan first) or take it to Discussions. Search for a duplicate before filing. Issue text is public, and the leak gate never sees it (the pre-push scan reads tracked files, not gh calls) — pipe every drafted body through python scripts/leak_scan.py --stdin < draft.md or --text-file <path> (draft outside the repo — a Bash command that merely names a guarded kernel path gets refused — then gh issue create --body-file). A hit is a refusal: scrub and re-scan, never post over it; if the scanner is absent, the hand rule (no dev-machine paths, hostnames, private-process prose) is the floor. Never close an issue on your own say-so: put Fixes #N in the commit BODY (the subject keeps its grammar) and GitHub closes it when the commit lands on master; a fix without the reference closes only through .claude/skills/issue-verify/. An issue that grows design weight gets a plan and stays open as the public tracking handle. Triage labels: ready (done-condition present), design (plan first), human-only (operator judgment; the fleet skips it).

Hand the baton (full form)

A finished session usually knows what the next session should do — when the work leaves a concrete next move, end the final report with a goal prompt the operator can paste into the next session (/goal …); skip it only when nothing is genuinely queued. A good baton is small and self-grounding: name the handle (the issue number, docs/NN phase, or failing test — never "continue improving X"); state done as a witness (the command whose verdict closes the goal — dos verify …, a named test going green, a CI run turning green — so the goal-gate adjudicates from evidence the agent did not author); name the first command (the cheapest probe that loads the state — often python scripts/backlog_triage.py --top 12, the typed ordered backlog queue). The memory note is the archive; the baton is the dispatch.

Use this loop when working in here, not just to demo it. Before claiming a docs/NN_*.md plan-phase is done, dos verify it — the contract says the oracle answers from git, so let the oracle, not your narration, close the phase. And after committing, dos commit-audit the work (step 6) — the oracle witnesses that a phase shipped; commit-audit witnesses that each commit's subject is backed by its own diff. Two witnesses, both reading bytes the claimant did not author (git ancestry; the diff), both run from outside the loop that wrote the claim — that is the docs/228 lesson applied to this repo: the only witness worth trusting is one the claimant can't forge, and on this repo that is git, never a > **Status:** sentence or a commit subject taken at its word. When two changes touch different top-level dirs, they are disjoint lanes and safe to land independently; when one touches src/dos/'s own running path, that is the SELF_MODIFY / global-lane hazard the kernel is built to refuse. Eating our own dog food is the cheapest proof that the litmus tests still hold: if dos verify ever needs a plan to answer, or dos doctor ever stops seeing this as a git workspace, a contract has drifted from the code.