Repository navigation
Releases: Koroqe/OPOS
Release list
v0.24.1 — the steward never uses git stash
[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 stashto 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 astash popin one worktree can apply another's entry. Separately, in a linked worktree on git 2.15 agit stash && git merge --ff-only && git stash popsequence was observed once to leave the index with thousands of staged deletions while every file on disk was intact (a baregit commitwould 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-core0.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-syncalready said "Do not stash" and is unchanged.
Migration
- None required.
v0.24.0 — sessions remove their own merged worktree (worktree-finish)
[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 (--applyremoves), targets the current worktree or--path <dir>, and--all-mergedlists 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 byskip-worktreeorassume-unchangedare found withgit ls-files -vand 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_Storefiles,*.pycdirectly in__pycache__, anode_modulesgit itself reports as an ignored directory, and OPOS state), whose HEAD is not an ancestor ofrefs/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,--applyonly), is not a worktree of this repository (20), has a non-empty.claude/.current-task(21: another live task remains in that worktree, in--pathand--all-mergedmode 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--pathis 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.mdandsettings.local.jsonare user content and block removal. Removal usesgit worktree removeon git 2.17 or newer andrm -rfon older git and then deletes only the target's ownworktrees/<id>admin dir (never a globalgit 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 fromgit worktree listand every other worktree is still listed (else 17), thengit 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 againstorigin/<default>) is the ref dropped with a guardedgit update-ref -d; any other branch error keeps the branch and exits 17. Not covered, as with git's ownworktree 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 andrm -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 letsworktree addreuse 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--pathnever picks one record by list position; the removal deletes only the admin dir named by the target's own.gitfile 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-mergedand refused by--pathunless--unregister-missingis 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 asgit status). A failinggit statusis exit 26 with git's message instead of "not a repo".--all-mergedskips (25) a brand-new worktree whose reflog shows only its creation (a live session's fresh worktree is trivially "merged");--pathis 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'snode --testlist.
Changed
task-complete0.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-rolledrm -rfstays under Explicit approval, and removing worktrees the session did not create (--all-mergedover 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-mergedis Confirm. Thetools:frontmatter is unchanged. .claude/CLAUDE.mdgit rule gains one line stating the same.
Migration
- None required. To clear an existing backlog:
node shared/scripts/worktree-finish.mjs --all-mergedto 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)
[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 anAskUserQuestioncall before the turn ends, in interactive sessions. Prose such as "reply1or2" 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 inpreview, 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 afounder-actionissue (Capture duty 3 is unchanged and remains the only prompt in headless, scheduled and subagent contexts, whereAskUserQuestiondoes not exist). First-touch gains step 6: after the greeting, the openfounder-actionissues owed by the current human (--assignee @me, plus the company's owner-label mapping if its rootCLAUDE.mddeclares one) are asked, deadline first, at most 4, with the remainder counted in one line. Fresh scaffolds and an unauthenticatedghskip it silently..claude/CLAUDE.mdcarries one bullet pointing at the section. shared/scripts/human-prompt-gate.mjs, aStophook. At the end of a turn it analyses only the FINAL assistant message (last_assistant_messagefrom 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 anAskUserQuestioncall still blocks. English and Russian patterns live in one exportedCTA_PATTERNSarray 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 typenumeric" does not match. Only paired fenced code blocks are ignored (a stray ``` hides nothing), so paste-prompts do not trigger it.stop_hook_activeprevents 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-gatepattern 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 inhuman-prompt-gate.test.mjs. Thehooks.Stopandhooks.UserPromptSubmitrationale inrequired-settings.jsonnow describe the check accurately.human-prompt-gateround 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 like5"never opens a span), '…' of 3+ words,>blockquote lines (except a callout, see below), and the rest of the sentence aftererror:/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-gateround 5 (still 0.23.0). Stripping quoted text no longer fuses the next sentence (a span that ends a sentence leaves.);said/says/replied/respondedare no longer lead words (reported speech is an action handed to the reader),error/error was/messageremain and stop at a dash; a>line with an explicit action-required callout is kept;5"and'90sare 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 baresoboundary is, soonly. Tests added.human-prompt-gateround 6 (still 0.23.0). Baremessage:is no longer a lead word (it swallowed real requests such as "I drafted the client message: please review it and approve"); onlyerror:,error was:anderror 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-gateround 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 anyen-header/ru-headerphrase ("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. Theerror:strip also stops at a spaced ASCII hyphen, soError: 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.jsongainshooks.Stop. A consumer with noStopkey receives it as before.reconcile-settings.pynow also appends a manifest hook entry to an existinghooks.<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 theSessionStartentries earlier releases could not add to a consumer that already had aSessionStartarray. An explicit alias list (LEGACY_COMMAND_ALIASES) marks the legacy inlinepython3 -cscheduler re-arm command as the same hook asscheduler-rearm-notice.mjs, so a settings file that still carries it is not given a second re-arm reminder; the.claude/settings.jsonshipped to new scaffolds now uses the.mjsentry.permissionsis 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/) getsCannot find modulefrom every OPOS hook, including the newStophook. Pre-existing and not fixed here; tracked in #67.
Migration
- None required. The
Stophook reaches a consumer through the next settings reconciliation (sync-from-core,auto-sync, or the unattended updater). To switch it off for a machine, setOPOS_HUMAN_PROMPT_GATE=off. A consumer whose rootCLAUDE.mdtells 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
[0.22.2] - 2026-10-21
One consumer-proposed fix (#65).
Fixed
adopt-proposaldeleted every bundle file except SKILL.md and PROCESS.md (#65). Step 4 moved only those two files intointended_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 sheetPROPOSAL.mdwith 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.mdso 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 itsfeat(core): adopt <name> process from draft proposalcommit.
v0.22.1 — cloud scheduled runs reach the default branch, CI runs the loop-notice tests
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, sogh pr create/gh pr mergefail there too. Everyreview-historyandauto-syncrun on thecloudruntime therefore left its record and edits on a session branch that a human had to merge by hand. The newshared/cloud-integration.mddefines 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 saysintegration: pending, andreview-historyreconciles it against the merged PR. If the merge is refused, the PR stays open and a follow-up commit setsintegration: pr-open <url>. If no PR can be opened, the record sayslocal-only. A record that is on the default branch counts as integrated, whatever itsintegration:value says. Anauto-syncrun 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-historyalso 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) plusgit remote get-url, never a bare-X POST repos/*, and a test asserts that./schedule-processnow 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, socopier updatecannot change it. See "Existing routines" inshared/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'snode --testlist.
v0.22.0 — loop-stall notice, steward closes its own work, six consumer fixes
[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 whenproposed_deltaentries thatreview-historywould select are older than 14 days andreview-historyhas 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 aSTALE: review-historyLoop 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-resultPASS, 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 usegit commit -- <paths>.
Fixed
task-update --statuscould empty an issue body (#57). Step 9 piped the status patch intogh issue edit --body-file -; when the canonical**Status:**line was missing the helper refused, butghstill 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 (outcomepartial) instead of aborting after the comment is posted.PROCESS.md.tmplfrontmatter was not valid YAML (#58). The## Verificationsection 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## Stepsand## 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, likegha: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
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.jsononly 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 aschore(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
Fixed
- The unattended updater never delivered settings changes.
.claude/settings.jsonis consumer-owned, socopier updatenever touches it; a release that adds a hook or a settings key reaches a company only throughreconcile-settings.py.sync-from-coreandauto-syncalways ran it, but the script-based driver introduced in v0.19 (opos-auto-update.mjs, thesync-oposjob) did not — so no settings change since v0.19 reached any company unattended. Measured on the canary: v0.21.0 applied, itsUserPromptSubmithook andautoCompactWindowabsent. The driver now runs the merge aftercopier 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
Added
- A context-size notice before the next expensive turn. A
UserPromptSubmithook (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: 250000inshared/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),--autocompactinclaude --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-updatesany more.task-register,task-update,task-completeand 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-coreruns in a subagent, and applies every pending release in one run.- The session updater's per-clone
.git/info/excludelist now also covers.claude/.update-probeand.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
UserPromptSubmitkey receives the hook automatically fromsync-from-core/ the updater's settings reconciliation, andautoCompactWindowlikewise when unset. A consumer with its ownUserPromptSubmithooks keeps them and must addnode shared/scripts/context-budget-notice.mjsby hand (the additive class never rewrites an existing value).
v0.20.0 — the risk-class operating model: decision rights, maker/checker, autonomous R&D
[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 onefounder-actionissue). 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 atshadow.result-checkeragent (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 returnsPASS | FAIL | UNVERIFIABLEwith evidence. Tools are exactlyRead, Grep, Glob, Bash, WebFetch: noWrite,EditorTask, pinned by a unit test.check-resultprocess (ownercoo). 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;UNVERIFIABLEkeeps the action one class higher.- Operating-model fields in
PROCESS.md.tmpl— optionalexecutor:,checker:,decision_classes:,autonomy_ladder:, plus a## Verificationbody 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) inDEPARTMENT.md.tmplandSUBDEPT.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-staffroutes 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 listsleaseand ownscheck-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 becomeacquire-resourcerequests for the whole class of task; product code in another repository is orchestrated, not edited from here.eng-leadmoves tosonnet(execution roles on the faster model; leads, the reviewer and the checker stay onopus). design-processdrafts the new PROCESS fields;design-agentapplies a zone test and states the model-tiering default;design-department/design-subdeptfill<<DECISION_RIGHTS>>.deliberate-decision, the consumer README,company/decisions/README.mdand 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-resultgains aphase). - 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
shadowwhose 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 inon, 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-cdpprofiles 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 wideningauthority: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/*anddepartments/*/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).