Skip to content

Releases: Koroqe/OPOS

v0.24.1 — the steward never uses git stash

Choose a tag to compare

@Koroqe Koroqe released this 23 Oct 00:38
034ab54

[0.24.1] - 2026-10-23

The steward no longer recommends git stash for syncing a checkout.

Changed

  • Steward Dispatch step 4 gains one sentence after the shared-index guidance: never git stash to sync or tidy a checkout. In a shared checkout a stash carries another session's work away, and the stash stack (refs/stash) is shared by every worktree of a repository, so a stash pop in one worktree can apply another's entry. Separately, in a linked worktree on git 2.15 a git stash && git merge --ff-only && git stash pop sequence was observed once to leave the index with thousands of staged deletions while every file on disk was intact (a bare git commit would have committed those deletions). The cause was not established and this is not claimed as a git bug. The guidance is to commit your own paths first, then merge.
  • sync-from-core 0.2.1 step 1: a dirty tree now aborts with "commit your own paths first; if the dirty paths are another session's, wait for it; do not stash" instead of "commit or stash first". Proposed by a consumer (#70). auto-sync already said "Do not stash" and is unchanged.

Migration

  • None required.

v0.24.0 — sessions remove their own merged worktree (worktree-finish)

Choose a tag to compare

@Koroqe Koroqe released this 22 Oct 14:09
b1be330

[0.24.0] - 2026-10-22

A session now removes its own merged git worktree instead of leaving it behind.

Added

  • shared/scripts/worktree-finish.mjs, a deterministic worktree cleanup (zero dependencies). Worktrees piled up because nothing ever removed them once their branch was merged; a consumer had about twenty, most long merged. The script is a dry run by default (--apply removes), targets the current worktree or --path <dir>, and --all-merged lists or removes every merged, clean linked worktree and skips the rest (a target that errors is reported as SKIP; the run never aborts mid-way). It refuses with a distinct exit code and a one-line message when the target is the main checkout (10), has uncommitted or untracked changes (11; read with --untracked-files=all --ignore-submodules=none, so user git config cannot hide a file; edits hidden by skip-worktree or assume-unchanged are found with git ls-files -v and compared to the index, a sparse-checkout absence is fine), has ignored files other than known OPOS local state, cannot have its ignored files enumerated, or contains a nested git repository anywhere below it (12; git does not descend into those, so the whole tree is walked and no subtree is skipped, allow-listed ones included; the allow-list is .DS_Store files, *.pyc directly in __pycache__, a node_modules git itself reports as an ignored directory, and OPOS state), whose HEAD is not an ancestor of refs/remotes/origin/<default branch> after a fetch (13; the default branch is detected, never hardcoded), holds leases (14), is locked (15; read from the repository's worktree admin directory, so it works when the directory is missing), contains the process's own cwd (16, --apply only), is not a worktree of this repository (20), has a non-empty .claude/.current-task (21: another live task remains in that worktree, in --path and --all-merged mode alike), or has another worktree inside it (22). Exit 17 means removal failed or could not be verified, 18 the default branch or fetch is unavailable, 19 --path is not a worktree of this repo. The ignored-file allow-list covers the framework's own per-clone state (.claude/.sessions/, .claude/.opos-update-*, .last-update-check, .update-probe, lease state, .clone-id, task caches); .claude/scratchpad.md and settings.local.json are user content and block removal. Removal uses git worktree remove on git 2.17 or newer and rm -rf on older git and then deletes only the target's own worktrees/<id> admin dir (never a global git worktree prune, which would orphan another worktree whose directory is temporarily missing), re-runs the status, hidden-edit, ignored and nested checks right before the delete, confirms the target is gone from git worktree list and every other worktree is still listed (else 17), then git branch -d; never -D, never a remote branch. Only when git reports the branch "not fully merged" (it judges against the main checkout's HEAD, while the merge is already proven against origin/<default>) is the ref dropped with a guarded git update-ref -d; any other branch error keeps the branch and exits 17. Not covered, as with git's own worktree remove: commits reachable only from the target's HEAD reflog (a detached HEAD that is not on origin/ is refused by 13), and, on git older than 2.17, a file created in the milliseconds between the final re-check and rm -rf. Round-3 hardening: exit 23 when more than one worktree record or admin dir claims the target's directory (git older than 2.20 lets worktree add reuse a registered path whose directory is gone; the stale merged record used to pass against the new clean directory and the removal deleted both admin dirs and an unpushed commit), in every mode, and --path never picks one record by list position; the removal deletes only the admin dir named by the target's own .git file after confirming it is the only match and its HEAD is the checked HEAD. Exit 24: a record whose directory is missing is skipped by --all-merged and refused by --path unless --unregister-missing is given, which unregisters only that record, keeps its branch and reports "unregistered (directory missing)" rather than "removed". Hidden-edit detection also compares on-disk size (and exec bit) to the index stat data, so a size-changing or mode-changing edit is caught even under a lossy clean filter (a same-size edit under such a filter is not, the same blind spot as git status). A failing git status is exit 26 with git's message instead of "not a repo". --all-merged skips (25) a brand-new worktree whose reflog shows only its creation (a live session's fresh worktree is trivially "merged"); --path is unaffected. Tests (shared/scripts/worktree-finish.test.mjs, 48) build throwaway origins and worktrees with real git, include a regression test for each review finding, and run under git 2.15; added to CI's node --test list.

Changed

  • task-complete 0.3.0 gains a last step 17: when no other active task remains in the worktree and its work is merged and pushed, remove it and its merged branch with the script, from the main checkout. A refusal is reported, never forced.
  • Steward permission tiers. Removing the session's own merged worktree and branch is part of finishing its own work (Notice tier). Only removal through worktree-finish.mjs's gates is Notice: a hand-rolled rm -rf stays under Explicit approval, and removing worktrees the session did not create (--all-merged over others') is added to the Confirm tier. The Hard refuse "deleting other people's branches" means remote or shared branches; deleting fully merged LOCAL branches together with their worktrees via --all-merged is Confirm. The tools: frontmatter is unchanged.
  • .claude/CLAUDE.md git rule gains one line stating the same.

Migration

  • None required. To clear an existing backlog: node shared/scripts/worktree-finish.mjs --all-merged to preview, then add --apply (Confirm tier, as the worktrees are not the session's own).

v0.23.0 — human prompts go through AskUserQuestion (Stop-hook gate)

Choose a tag to compare

@Koroqe Koroqe released this 21 Oct 22:33
e195530

[0.23.0] - 2026-10-21

The steward now asks the human directly, and a hook checks that it did.

Added

  • Human prompts go through AskUserQuestion. A new steward section makes every decision or action the human owes an AskUserQuestion call before the turn ends, in interactive sessions. Prose such as "reply 1 or 2" or a "What I need from you:" list is text the human can scroll past, and a law firm waited a month for an answer that sat in one. The section sets the shape: one question per item, at most 4 per call and more in consecutive calls, context plus the issue link in the question, the recommended option first with its cost, steps or draft text in preview, and "Done, verify it now" / "Not now" for actions the human must perform. The answers are acted on in the same turn; an answer that names a specific outbound action approves that action only. Anything still owed stays a founder-action issue (Capture duty 3 is unchanged and remains the only prompt in headless, scheduled and subagent contexts, where AskUserQuestion does not exist). First-touch gains step 6: after the greeting, the open founder-action issues owed by the current human (--assignee @me, plus the company's owner-label mapping if its root CLAUDE.md declares one) are asked, deadline first, at most 4, with the remainder counted in one line. Fresh scaffolds and an unauthenticated gh skip it silently. .claude/CLAUDE.md carries one bullet pointing at the section.
  • shared/scripts/human-prompt-gate.mjs, a Stop hook. At the end of a turn it analyses only the FINAL assistant message (last_assistant_message from the Stop input, else the last assistant message in the transcript, read from the end of the file, never in full) and blocks the stop once when that message asks the human for something in prose. Earlier messages of the turn are not re-checked, so a request from an earlier stop is not flagged again after a background-task wake-up; a leftover prose request in a final message that follows an AskUserQuestion call still blocks. English and Russian patterns live in one exported CTA_PATTERNS array and match addressed forms only: sentence-initial imperatives (reply, answer, choose, confirm, approve, let me know, type "approve"; ответь, подтверди, дай знать ...), headers ("What I need from you", "Next step for you", "Action required", "Что нужно от тебя"), direct questions (Should I, Want me to, Can you, Which ..., Хочешь ...; any paragraph for the strong forms, the closing paragraph for the rest), "needs your decision / approval / sign-off", a standalone "your call", and a founder-action addressed to the reader. Reporting text such as "should respond with 200", "routes answer 'unauthorised'" or "column of type numeric" does not match. Only paired fenced code blocks are ignored (a stray ``` hides nothing), so paste-prompts do not trigger it. stop_hook_active prevents loops, the hook reads `last_assistant_message` because the transcript can lag, and every error exits 0 silently. `OPOS_HUMAN_PROMPT_GATE=off` disables it. Claude Code gives a `Stop` hook no signal for headless runs, so a headless run may spend one extra turn; the block message tells the model to file `founder-action` issues there. Tests: `shared/scripts/human-prompt-gate.test.mjs` (in CI).
  • human-prompt-gate pattern adjustments (still 0.23.0). Russian past tense «Пришли ответы / деньги / письма» no longer blocks; «пришли» is an imperative only with an addressee or object («пришли мне / сюда / вывод / результат»). New forms: sentence-initial "Please " (sign in, log out, check, click, run, paste, reply, confirm, approve; "Please note" and "Please be aware" pass), lead-ins addressed to the human ("What's left for you:", "Two things for you:", "Your step:", «Тебе осталось:», «Твой шаг:»), and short closing questions ("Merge or hold?", "OK to send?", "I need your OK before ...", "Waiting for your go", «Отправляем?», «Подтверждаешь?»; a colon no longer lets a question escape: «Как поступим с инвойсом: платим сейчас или ждём?»). Negations pass: "does not need your approval", "Nothing here needs you", "What I need from you: nothing", "Action required: none", «Нужно от тебя: ничего». Tests added in human-prompt-gate.test.mjs. The hooks.Stop and hooks.UserPromptSubmit rationale in required-settings.json now describe the check accurately.
  • human-prompt-gate round 4 (still 0.23.0). Quoted third-party text no longer triggers any rule: «…», “…”, a straight-quoted "…" span (only where the opening quote follows whitespace, an opening bracket, a colon, emphasis marks * / _ or a dash, and the closing quote is followed by whitespace, punctuation, * / _ or a dash, so an inch mark like 5" never opens a span), '…' of 3+ words, > blockquote lines (except a callout, see below), and the rest of the sentence after error: / error was: / error message: (up to the sentence end or a spaced dash or hyphen) are stripped first (a 1-3 word quote right after reply / answer / type / say stays, so Reply "go ahead" still blocks, as does an instruction followed by a quoted draft). A negation ("nothing", "nobody", "no action", «ничего», «никто») now neutralises a "needs your ..." request only inside its own clause, ended by ; . ! ?, " but ", " however ", " so ", ", а ", ", но " or a dash. «Пришлите» (plural/formal imperative, never past tense) always blocks; «пришли» is unchanged. Tests added.
  • human-prompt-gate round 5 (still 0.23.0). Stripping quoted text no longer fuses the next sentence (a span that ends a sentence leaves .); said / says / replied / responded are no longer lead words (reported speech is an action handed to the reader), error / error was / message remain and stop at a dash; a > line with an explicit action-required callout is kept; 5" and '90s are not quote openers; a quoted answer token may be up to 6 words (Type "yes, merge it now"). A negation clause now also ends at , : - and , and; the bare so boundary is , so only. Tests added.
  • human-prompt-gate round 6 (still 0.23.0). Bare message: is no longer a lead word (it swallowed real requests such as "I drafted the client message: please review it and approve"); only error:, error was: and error message: strip the rest of the sentence. A single quote does not open a quoted span before a common elision ('em, 'cause, 'til, 'tis, 'twas, 'n, 'bout, 'round). Known limit, accepted: an unbalanced curly opener still pairs with the next closing quote. Tests added.
  • human-prompt-gate round 7 (still 0.23.0). Straight-quoted error texts wrapped in markdown emphasis or set off by dashes (*"Deployment failed. Please check the build logs."*, **"..."**, printed—"..."—) are stripped again. A > blockquote line is kept when, after removing the > and emphasis marks, it starts with any en-header / ru-header phrase ("Action needed", "Next step for you", "Нужно от тебя" ...; the same list as the rules) or holds one of the earlier callout keywords; plain blockquoted quotes still pass. The error: strip also stops at a spaced ASCII hyphen, so Error: build failed - please paste STRIPE_KEY into the settings. blocks. Tests added.

Changed

  • Settings reconciliation appends missing hook entries to an event the consumer already uses. required-settings.json gains hooks.Stop. A consumer with no Stop key receives it as before. reconcile-settings.py now also appends a manifest hook entry to an existing hooks.<Event> array when no entry there runs the same script (matched by script file name, otherwise by exact command), without editing, reordering or removing anything the consumer wrote and without duplicates. This also delivers the SessionStart entries earlier releases could not add to a consumer that already had a SessionStart array. An explicit alias list (LEGACY_COMMAND_ALIASES) marks the legacy inline python3 -c scheduler re-arm command as the same hook as scheduler-rearm-notice.mjs, so a settings file that still carries it is not given a second re-arm reminder; the .claude/settings.json shipped to new scaffolds now uses the .mjs entry. permissions is still never written. Tests: ui/tests/test_reconcile_settings.py.

Known issues

  • OPOS hooks are registered with project-relative paths, so a session opened in a subfolder (for example departments/finance/) gets Cannot find module from every OPOS hook, including the new Stop hook. Pre-existing and not fixed here; tracked in #67.

Migration

  • None required. The Stop hook reaches a consumer through the next settings reconciliation (sync-from-core, auto-sync, or the unattended updater). To switch it off for a machine, set OPOS_HUMAN_PROMPT_GATE=off. A consumer whose root CLAUDE.md tells agents to end reports with "reply 1 or 2" style instructions should reword them to refer to the steward's new section.

v0.22.2 — adopting a proposal keeps every bundle file

Choose a tag to compare

@Koroqe Koroqe released this 20 Oct 23:38

[0.22.2] - 2026-10-21

One consumer-proposed fix (#65).

Fixed

  • adopt-proposal deleted every bundle file except SKILL.md and PROCESS.md (#65). Step 4 moved only those two files into intended_placement: and then deleted the bundle folder. Anything else the design shipped was lost with it: a routine prompt, a launch checklist, a build spec, nested bundles, and the cover sheet PROPOSAL.md with its consultation record and UNVERIFIED list. Step 2 now lists every file in the bundle folder, nested bundles included, with where each one will land, so the human sees all of it before deciding. Step 4 moves every bundle file except the cover sheet into the placement, moves the cover sheet to <placement>/proposal-record.md so the decision record survives, and deletes the bundle folder only once step 2's listing shows every file moved. Skill text only; no code changed. Files lost in earlier adoptions are not restored by this release: if the bundle had been committed, they are in the parent of its feat(core): adopt <name> process from draft proposal commit.

v0.22.1 — cloud scheduled runs reach the default branch, CI runs the loop-notice tests

Choose a tag to compare

@Koroqe Koroqe released this 19 Oct 21:18

Two consumer-proposed fixes (#63, #64).

Fixed

  • Scheduled cloud runs never reached the default branch (#64). A Claude Code cloud routine session can push only to the claude/* branch it was assigned, and the cloud proxy blocks GraphQL, so gh pr create / gh pr merge fail there too. Every review-history and auto-sync run on the cloud runtime therefore left its record and edits on a session branch that a human had to merge by hand. The new shared/cloud-integration.md defines the fallback. A new step 0 records the session branch before anything else. After a refused push to the default branch, the run pushes that branch, opens a PR and merges it over REST (gh api). The committed record says integration: pending, and review-history reconciles it against the merged PR. If the merge is refused, the PR stays open and a follow-up commit sets integration: pr-open <url>. If no PR can be opened, the record says local-only. A record that is on the default branch counts as integrated, whatever its integration: value says. An auto-sync run that hits conflicts never pushes to the default branch and never opens a PR: it pushes only its session branch and files the conflict issue, as on other runtimes. An open conflict issue holds later runs, but an unmerged record PR never does. review-history also reads records still sitting on open PRs from earlier runs. The command manifests gain only three narrow REST write patterns (-X POST repos/*/pulls, -X POST repos/*/issues, -X PUT repos/*/pulls/*/merge) plus git remote get-url, never a bare -X POST repos/*, and a test asserts that. /schedule-process now writes the fallback instruction into the cloud routine prompt it composes. Existing cloud routines need two additions to their prompt by hand: those patterns in the allowed-command list, and the fallback sentence. A routine prompt is not a framework file, so copier update cannot change it. See "Existing routines" in shared/cloud-integration.md.
  • CI did not run the stalled-loop notice tests (#63). shared/scripts/scheduler-rearm-notice.test.mjs, shipped with v0.22.0's [opos-loop] notice, is now in CI's node --test list.

v0.22.0 — loop-stall notice, steward closes its own work, six consumer fixes

Choose a tag to compare

@Koroqe Koroqe released this 16 Oct 19:31

[0.22.0] - 2026-10-16

Six consumer-proposed fixes (#57-#62).

Added

  • A notice when the self-improvement loop has stalled (#59). The SessionStart notice script now also prints one [opos-loop] line when proposed_delta entries that review-history would select are older than 14 days and review-history has no run record in that window (or none at all). Until it runs, no recorded fix reaches local files or upstream, and a company that never registered it looked exactly like a fresh scaffold. The steward's greeting now must show a STALE: review-history Loop line when the notice is present and offer to run or register it first. File-only, fail-open, silent when there is nothing to say.

Changed

  • The steward finishes its own reversible work instead of asking (#61). Committing within the session's own lease scope, fast-forward merge of its own branch, non-force push of its own commits, closing the task issue it took after a check-result PASS, and releasing its own leases are now Notice tier. For that work the risk class wins over a stricter tier. Releases, force-push, history rewrites, destructive operations, anything outside the lease scope, and the top risk class still need a human. Gate exit codes must be checked explicitly (never behind ;, a pipe, or || true), and in a checkout shared by two live sessions commits use git commit -- <paths>.

Fixed

  • task-update --status could empty an issue body (#57). Step 9 piped the status patch into gh issue edit --body-file -; when the canonical **Status:** line was missing the helper refused, but gh still ran on empty input and replaced the body with nothing. The step now stages through files and chains with &&, and a missing status line leaves the body untouched with a notice (outcome partial) instead of aborting after the comment is posted.
  • PROCESS.md.tmpl frontmatter was not valid YAML (#58). The ## Verification section added in v0.20.0 had been spliced into the middle of a frontmatter comment, so a process copied from the template had an unparseable frontmatter (empty owner in the console, scheduling validator failure) and no Verification heading. The comment is restored and the section sits in the body between ## Steps and ## Done when. A new test renders the template and checks it parses.
  • Cloud routines were flagged for re-arming (#60). Registrations with a cloud:<trigger_id> routine id are hosted and durable, like gha: ones; the SessionStart re-arm notice no longer asks to re-register them. CI now runs the new test.
  • The lease registry failed through proxies that reject numeric-ID repository paths (#62). Hosted cloud sessions answered HTTP 403 once a registry passed 100 comments, so every lease command exited 6. Claims are now read page by page over repos/<owner>/<repo> paths, and label listings use the REST issues endpoint instead of GraphQL. An unreadable predecessor registry now prints a warning instead of silently hiding its live leases. Remaining GraphQL-backed call sites in other skills are listed in the PR and not changed here.

v0.21.2 — up-to-date companies receive missing settings

Choose a tag to compare

@Koroqe Koroqe released this 23 Sep 17:14
8ef9b03

Fixed

  • Companies already up to date still receive missing settings. The unattended driver only ever runs its installed copy, so the v0.21.1 fix alone would reach a company's .claude/settings.json only at the release after next. The driver now also reconciles settings when there is nothing to update: idempotent (nothing missing, nothing committed), stands down while a lease covers the file, adds only missing non-permission keys, and commits the result as chore(sync): reconcile .claude/settings.json with OPOS <pin> (automatic). This heals every company that applied v0.19–v0.21.1 unattended.

v0.21.1 — the unattended updater delivers settings changes

Choose a tag to compare

@Koroqe Koroqe released this 23 Sep 17:12
95555c0

Fixed

  • The unattended updater never delivered settings changes. .claude/settings.json is consumer-owned, so copier update never touches it; a release that adds a hook or a settings key reaches a company only through reconcile-settings.py. sync-from-core and auto-sync always ran it, but the script-based driver introduced in v0.19 (opos-auto-update.mjs, the sync-opos job) did not — so no settings change since v0.19 reached any company unattended. Measured on the canary: v0.21.0 applied, its UserPromptSubmit hook and autoCompactWindow absent. The driver now runs the merge after copier update, inside the same commit. Unchanged contract: it only adds missing non-permission keys, never overwrites a value the company set, and never writes permissions.

Companies that already applied v0.20.x or v0.21.0 unattended receive the missing keys with this release.

v0.21.0 — context economy

Choose a tag to compare

@Koroqe Koroqe released this 23 Sep 17:09
96075b4

Added

  • A context-size notice before the next expensive turn. A UserPromptSubmit hook (shared/scripts/context-budget-notice.mjs) reads the session's own transcript and, once the conversation passes 150k tokens of context (then every further 100k), tells the human and the steward: every request now re-reads all of it, so a new topic belongs in a fresh session and a continuing one wants /compact. Once per band, zero model requests, fail-open. Thresholds: OPOS_CONTEXT_NOTICE_AT, OPOS_CONTEXT_NOTICE_STEP.
  • autoCompactWindow: 250000 in shared/templates/required-settings.json. On 1M-window models Claude Code otherwise compacts at about 967k tokens. Verified three ways: the official model-config documentation (settings key, 100k–1M), --autocompact in claude --help, and the key in the shipped binary. A consumer's own value is never overwritten.
  • The steward's "Context economy" rules: one topic per session (write a handoff to the task issue, then /clear); delegate multi-step grunt work (investigations, sweeps, syncs, long Bash sequences) to a subagent with a fresh context; never run a sync in the main thread; never read a large file whole.
  • check-for-updates/check.mjs — the manual update probe as one Bash call. It reports only a newer release (semver): the old procedure reported any different tag, so a consumer pinned ahead of upstream's latest release would have been told to "update" to an older one. It keeps its own cache (.claude/.update-probe) and never touches the session updater's daily flag.

Changed

  • Nothing invokes check-for-updates any more. task-register, task-update, task-complete and the steward's First-touch ran it on every call, turn by turn: about five requests, each re-reading the whole session context. The daily SessionStart updater (v0.19.0) owns routine checks; the skill is on-demand only.
  • sync-from-core runs in a subagent, and applies every pending release in one run.
  • The session updater's per-clone .git/info/exclude list now also covers .claude/.update-probe and .claude/.sessions/.

Why

Measured at the reference consumer from its local transcripts: one interactive session ran 28 hours across five unrelated topics, reached 925k tokens of context with zero compactions, and alone was 80% of a week's usage — 81% of its 244M tokens were spent at more than 400k context, where a single git status re-reads ~0.9M tokens. The usage dashboard's "16% from /check-for-updates" was mostly attribution (it charges everything after a skill to that skill; a copier update sync run inside that session followed the check), but the skill's turn-by-turn execution and its "different, not newer" comparison were real defects.

Migration

  • A consumer without a UserPromptSubmit key receives the hook automatically from sync-from-core / the updater's settings reconciliation, and autoCompactWindow likewise when unset. A consumer with its own UserPromptSubmit hooks keeps them and must add node shared/scripts/context-budget-notice.mjs by hand (the additive class never rewrites an existing value).

v0.20.0 — the risk-class operating model: decision rights, maker/checker, autonomous R&D

Choose a tag to compare

@Koroqe Koroqe released this 23 Sep 17:06
fba5db1

[0.20.0] - 2026-09-23

An operating-model change. The agents were organised like a human company: an escalation chain (eng-lead → rnd-lead → coo → ceo → human), "design a sub-role when load justifies it", and "a deliberation costs ~15 calls, reserve it". Every hop was a wait paid in human attention, while an agent instance costs cents. At the reference consumer this showed up as a growing founder-action queue that was mostly errands an agent could have done with one-time access, finished work waiting days on a single "click", and lead agents carrying spend thresholds ("> $X") that nobody had ever set — so they escalated everything. This release moves decisions to whoever holds the right for that risk class, and puts an independent checker between agents and the world, because the costliest agent mistakes are confident false claims, and a fleet multiplies those as fast as it multiplies output.

Added

  • company/policies/autonomy-and-decision-rights.md (STARTER). Decision classes by risk, not rank: R0 reversible and internal (act), R1 visible to the team (act and report), R2 costly to undo or production (act only after a checker PASS), R3 money, anything a counterparty sees, access, contracts, hiring, adopting an agent, priorities (a human decides; the agent prepares it to one click as one founder-action issue). Escalation goes straight to the holder of the right. Also: a holders table only a human edits, maker/checker, the earned-autonomy ladder (off → shadow → on + cap, moved only by the holder), role vs worker (scale by instances; a new role needs a new zone), C-level agents as governance, dispatch over the issue queue, durable runtimes, model tiering, a thresholds section (unset = R3), and adoption switches — R2 autonomy ships off (R2 is handled as R3 until a human switches it on). The five never-automate invariants are never lowered; ladder actions under an invariant stop at shadow.
  • result-checker agent (company tier, opus). Re-establishes a claimed result by a different method than the maker used — the live system, the primary source, a recomputation — and returns PASS | FAIL | UNVERIFIABLE with evidence. Tools are exactly Read, Grep, Glob, Bash, WebFetch: no Write, Edit or Task, pinned by a unit test.
  • check-result process (owner coo). The maker/checker gate: a fresh checker instance before any R2 action and any "done" above R0; at least two methods for R2; only PASS unlocks; UNVERIFIABLE keeps the action one class higher.
  • Operating-model fields in PROCESS.md.tmpl — optional executor:, checker:, decision_classes:, autonomy_ladder:, plus a ## Verification body section. Documentation fields; nothing enforces them at runtime.
  • "Zone and decision rights" in AGENT.md.tmpl, and a Decision rights section (new <<DECISION_RIGHTS>> token) in DEPARTMENT.md.tmpl and SUBDEPT.md.tmpl.
  • A "Decide by risk class, not by rank" principle in .claude/CLAUDE.md, so the posture reaches existing consumers on their next sync.
  • Steward dispatch. chief-of-staff routes each queue item by zone → owner, executor (agent / agent after an access grant / human), decision class per action, and lease; independent items go to parallel workers.

Changed

  • ceo: keeper of priorities and metrics, weekly strategy review, arbiter of zone conflicts between agents; prepares R3 decisions for the human CEO instead of approving work.
  • coo: operator of the agent fleet — queue and dispatch health, stuck work, leases, runtime, process SLAs; now lists lease and owns check-result.
  • ops-manager: designed processes declare executor, checker and classes; designed agents have a zone; "consult with purpose".
  • Department leads (commercial-, finance-, legal-, people-, pr-lead): owners of a zone's process portfolio and metrics, with a Decision rights table; R0/R1 without asking. The unset "$X" thresholds point at the policy's thresholds section; the commercial "major deal" threshold is dropped (anything a counterparty sees is already R3). "Sub-roles when load justifies" is replaced by role vs worker.
  • R&D agents (rnd-lead, eng-lead, eng-reviewer — STARTER): R&D does infrastructure through registered resources, R2 after a PASS; access gaps become acquire-resource requests for the whole class of task; product code in another repository is orchestrated, not edited from here. eng-lead moves to sonnet (execution roles on the faster model; leads, the reviewer and the checker stay on opus).
  • design-process drafts the new PROCESS fields; design-agent applies a zone test and states the model-tiering default; design-department / design-subdept fill <<DECISION_RIGHTS>>.
  • deliberate-decision, the consumer README, company/decisions/README.md and RISKS 27: "reserve for decisions whose stakes justify the cost" becomes "spend with purpose" — the test is whether a full panel changes the outcome. The guidance on when not to use it is kept.
  • Starter department charters: Decision rights tables and the role-vs-worker rule.
  • chief-of-staff's framework counts brought current: 28 skills, 15 agents, 19 templates.

Hardened after a consumer's adversarial review

Before release, a consumer adopted the policy and had four of its lead agents attack it (59 findings). The generic lessons are folded in:

  • Access management is R3, always. Writing secrets, creating/changing/revoking access, aliases and memberships, and new or changed scheduled workflows were R2 in the first draft — against never-automate invariants 1 and 3. A granted access no longer lowers any class: only using it can be R1/R2.
  • Nothing derived lowers a class. A triage, a plan or a dispatch projection is a hint; the acting agent classifies each action; the policy wins. Choosing the higher class when unsure is never a violation, and a process may gate more strictly than its class.
  • R2 = plan check before, result check after, with rollback on a result FAIL (check-result gains a phase).
  • Checker evidence is pointers (path, hash, query), never client data, correspondence or personal data. A PASS is evidence for the responsible human, not a replacement of their check for client, money and document classes.
  • Own area = a live lease scope and, where tasks carry owner labels, the session's human as owner. R3 is one decision per issue, but per-message send approvals keep their own process channel; R1/R2 reports go to the issue, chat gets digests, R3 asks are batched per human per day.
  • The ladder was tightened: classes defined positively, a per-draft compliance checklist in shadow whose failure resets the streak, a never-laddered list (outbound a client contract bars, securities/investment-solicitation content without counsel's opinion, accepting or changing terms), anyone demotes / only the holder promotes, automatic demotion on edits, FAILs, complaints and unsubscribes, an independent checker call per output in on, and a company-wide outbound pause flag.
  • Capacity: a decision order (more workers → a process → a new role with its own zone → a human) and a default cap of 3 concurrent workers per role until a spend ceiling is set.
  • Access grants carry an owner, an expiry, a revoke command and a listed task class; browser-cdp profiles are one per service and never the owner's personal mail or messengers; a break-glass second admin is named; one-shot revocations are verified from another session.
  • Deleting client data and moving it to a new service or region are R3; scheduled runs act only within both their authority: list and their class, and widening authority: is R3; until a consumer syncs, the steward's permission tiers keep governing commit, push, release and destructive actions.

No agent's tools: line changed. Tool grants are never-automate invariant 1 and need their own human sign-off; leads without Task state that check-result is run by the session that dispatched them.

Migration

  • CORE (all company-tier agents, the five department leads, templates, design skills, .claude/CLAUDE.md, the new agent and skill) arrive with your next sync.
  • The policy lives under company/policies/**, which is _skip_if_exists: it arrives on your next sync as a new file, and is yours from then on. Fill in §3 holders, §10 thresholds and §11 adoption switches. Nothing turns R2 on by itself.
  • STARTER files you already have are not touched: .claude/agents/rnd/* and departments/*/CLAUDE.md. To adopt the new versions, copy the R&D agents and the charters' "Decision rights" sections from this release (or ask the steward to merge them).