Codex and antigravity roles no longer spend many times their budget_tokens in one turn (#550). Both CLIs report the whole prompt as input, cached part included, and it was charged against budget_tokens as uncached input, so one turn could spend 7-35 times a role's budget and close every other role at the org-wide ceiling. The runners now move the cached part into cache_read / cache_creation (the tokens total is unchanged), so these roles are charged on the same basis as Claude roles. They also meter each agy step and each codex exec as it completes instead of once per message: an agy exec is killed at the step that exhausts the budget, a codex role starts no further tool round once it is spent, and neither starts an exec with less than 5% of the role's token cap left or after its session was closed for budget. The role is then closed for budget rather than counted as a failed turn. A codex exec that has started still runs to its end, since codex reports usage only then. See Token budgets on codex and antigravity roles. A role closed because it is under that floor counts as out of budget: the coordinator's notice gives its numbers, and org reload reopens it only once its budget is raised above the floor.
agent exec and agent test report the same split: the input_tokens of a codex or antigravity run's usage event now excludes cached input, as it already did for Claude.
The org-wide token budget stops deferred lazy spawns (#552). When run_config.budget_tokens ran out, the org closed every open mailbox and stopped pending spawns, but a role already waiting for a max_concurrent_agents slot kept its retry loop. The closure freed slots, so the role spawned with an open mailbox after "closing all roles" and ran until idle-stop. The closure now cancels those deferrals (a deferred-spawn-cancelled audit per role) and holds their tasks with the budget reason. A deferred spawn, including one waiting on host resources, checks the org-wide ceiling and the role's own budget closure (its own caps or the #550 turn floor) right before it starts. spawnRole refuses while the ceiling is spent and logs a spawn-refused audit. A reload that raises the ceiling puts the roles back to pending, dispatches their tasks and delivers the messages queued for them. A role a reload adds while the ceiling is spent is set aside the same way, and its tasks are held instead of reported as unresolved.
- A role replacement (
org_respawn_role) no longer runs past the ceiling. If the ceiling closes while the replacement starts, the replacement is stopped instead of published, the old incarnation stays closed like the other roles, and its tasks are held. The org's spend stays exact: the old incarnation is counted once, and what the stopped replacement spent while starting is counted too. The cancelled replacement doesn't use up a max_role_respawns attempt. A reload that raises the ceiling reopens it with the mail it had swept up.
A role deferred by max_concurrent_agents stays a known assignee (#551). The deferral took the role out of the pending roles, so every later dispatch pass reported dispatch-assignee-unresolved: assignee "X" does not resolve for a role the org defines, and messages to it failed with "unknown recipient". Its tasks now wait ready with one concurrency-limit audit each ("deferred by max_concurrent_agents") and go out when the role spawns, whichever path deferred it. A second deferral of the same role joins the first retry loop instead of starting another. If no slot frees in time, the tasks waiting on the role are failed with the reason and the coordinator is told, instead of sitting ready with no owner. The role goes back to pending, so later work for it defers again. The give-up is written to the org state (abandonedRoles), and the coordinator is also told when only queued messages were waiting.
A role deferred under host resource pressure gets the same treatment: its tasks wait with one resource-pressure audit each and go out when it spawns.
A deferred role that spawns now delivers only its own queued messages. It drained the whole org inbox, so messages queued for other roles that weren't running yet were lost.
deliver() says "waiting for a concurrency slot" or "waiting for resources" to match the deferral.
A freed max_concurrent_agents slot goes to the role that was deferred first (#589). Each deferred role polled for a slot on its own timer, so the first timer to fire after a slot freed took it. A role deferred later could start ahead of one deferred earlier, and could keep doing so each time a slot freed. A role now takes a free slot only when no role deferred before it is still waiting for one; its concurrency-limit audit then says which roles it is waiting behind. A role that was not deferred, started directly by a message or task, can still take a freed slot before the next deferred role polls.
An approval a decider owns now holds the tool call until the decider rules, instead of denying it as "pending human approval" (#553). monomind now routes a request the way mono-agent does, using a port of its decision class, tier and level × tier routing. With mid or full autonomy and a model, boss, parent or jev decider, a call that mono-agent sends to a rule or a decider waits for the verdict. For example, tool:Bash is routine unless a tool:Bash or tool:* tier says otherwise. The wait lasts up to decider.timeout_seconds + 25s: the timeout defaults to 120s, and the total wait is capped at 300s, below the 600s PreToolUse hook timeout. The call does not wait when mono-agent sends the request to a person: at mid, that covers irreversible tiers and grants with no known tier. It also does not wait at manual, during an active paused_until, or when the autonomy block is missing or not understood. The autonomy block is read fresh from the org file on each check. When the verdict is approve, the call runs. A denial names who decided. If no decision comes in time, the message says a decider or a human will resolve it and an [approval] message follows. A call that got its verdict while waiting never gets an [approval] message telling it to repeat, so it never runs twice. That holds even when a human resolves it together with other entries. Mailbox messages name the resolver: a rule, the decider, or a human (--by name). Waits end when the org stops or the role closes. Known gap: monoagentcli org autonomy pause writes only mono-agent's database, so monomind does not see the pause until mono-agent adds paused_until to the org file. Known limit: a grant's own tier lives only in mono-agent's database. At mid, a grant that mono-agent rates consequential goes to its decider, for example one with no outbound workflow nodes. monomind can't see that tier, so the call does not wait; it is denied as pending at once, as before. A grant:<alias> or grant:* tier in the org file avoids this. Every runtime goes through the same in-process gate: claude's canUseTool and PreToolUse hook, and the in-process org-tool calls of codex, grok, hermes, antigravity and vercel, all wait inline. Approvals owned by a human are unchanged.
Vercel and openrouter roles post one chat event per assistant message (#563). The vercel runner yielded every streamed text delta as its own message, so the org bus carried a chat event per chunk ("I", "'ve asked the question", ...), which filled history and event limits. It now joins the deltas and yields one message when the model step ends, like the claude, codex and aider runners. A turn that is aborted or fails midway still reports its text once. agent exec still streams the deltas, since it asks for partial messages.
- A vercel turn whose stream carries an
error or abort event is now reported as failed (error_during_execution, is_error) with the error message, like a failed claude turn. It was reported as success, so the circuit breaker never counted it and agent exec exited 0 instead of reporting a rate-limited, auth, quota or runner-error code.
agent test pi-rpc no longer fails with turn failed (error_max_turns) when the model runs one of pi's own tools before it answers (#564). pi counts one turn per model call, so a model that lists or reads the temporary directory before replying ok needs a second turn, and agent test capped the turn at one: the runner aborted the answer. agent test now allows 3 turns for every runtime. Plain replies were never affected; checked against pi 0.87.1, which starts exactly one turn for them, retries included.
A blocking ask_human question now stops org_complete until it is answered (#564). A boss could ask a blocking question and end the run in the same turn with an answer it made up. While any blocking question in the org is unanswered, org_complete is refused with a tool error naming the question ids; it works again once the answer arrives. The one outcome still allowed is partial with blocker human, also under run_config.completion: 'dag' while runnable work remains, so a run whose human never answers can still be ended honestly. Non-blocking questions don't hold it. The blocking receipt now tells the role to end its turn and not call org_complete. A blocking question still does not hold the role's turn open: as documented, the answer arrives as a new message.
The session ledger and its session-run audit name the runtime a role actually runs on (#562). They used role.runtime ?? def.runtime ?? 'claude', so a role whose runtime comes from provider.kind (vercel-api-key runs on vercel, codex on codex, antigravity on antigravity) was audited as claude and had its task sessions filed under claude. Both now use the runtime that runner selection resolves. A task session an older build filed under claude for such a role is still resumed on org run --resume and moves to the new key when it is. Only a role with no runtime of its own or of the org's reads that old key, because only its entries there came from its own runner.
- The full-access check and its audit, the sandbox and authority-mask choice,
org validate's sandbox findings and org sign's review use that runtime too (#567). A provider.kind role was checked as Claude, so a vercel role declaring policy.access: full passed the runner check it fails, so a signed grant made it full on a runner without full access; a vercel role was given an authority mask its in-process runner never applies; and org sign counted a codex role as covered by Claude's sandbox. A role that sets runtime itself is checked as before. A MONOMIND_RUNTIME value no runner answers to is checked as claude, the runner that then runs.
An open ask_human question can be dismissed, and a removed role's question can be closed (#572). An open blocking question holds org_complete (#564), and until now the only way to close one was to answer it. monomind org questions dismiss <org> <id> [--reason <text>] closes it without an answer: questions.json records state: "dismissed", dismissedAt and the reason, the org_complete gate and the idle-watchdog hold are released, and the asking role is told that no answer is coming (live, or queued while the org is stopped, without waking it). A running org logs a decision-resolved audit event with verdict: "dismissed". The dashboard's Human Input view has a Dismiss button (POST /api/questions/dismiss, the same human-session auth as answering), and the daemon route is /api/dismiss-question (operator credential only). Answering a question whose role is no longer in the org definition failed with "role not found", so it could never be closed. The answer or dismissal is now recorded and delivery is skipped, with a delivery: "skipped" note in the audit event.
org approve, org deny, org answer and org gate-approve / gate-reject no longer resolve a decision behind a refusing daemon (#572). Security: when the daemon running the org refused the call, for example with a 403 because the operator credential was missing, the command fell back to writing approvals.json, gates.json or questions.json (plus the inbox) directly. That approved a tool call or gate, or answered a question, without the operator check, the audit event or delivery to the waiting role. A refusal now fails with "nothing recorded" and exit code 1. The offline write happens only when the daemon can't be reached. The commands also ask only a daemon that hosts this project's org, the same lookup the dashboard uses, so a same-named org run from another project no longer receives the decision.
A role's blueprint is now covered by the org signature (#571). A role with a blueprint and no skills gets its skills and skill_pool from the catalog's blueprint.json when the org starts. That file lives in the project's .monomind/catalog/, which the #502 signature did not cover and which a role may be able to write, so the skills (and the MCP tools they grant) a role got could change without the org being re-signed. The signed hash now records a sha256: digest of each named blueprint's blueprint.json under a new blueprints key, next to the instructions_file digests; the same single read feeds the review, --check, --expect-hash and the start-time check. org run and org reload use a blueprint only while its bytes still have the digest the signature was checked with. An org that uses a blueprint verifies as changed after the upgrade and needs monomind org sign once; an org without blueprints hashes exactly as before. org sign's review lists each role's blueprint. The hash is documented in org sign with a pinned example.
Org roles can no longer change the skill catalog (#576). The content of a role's catalog skills and their grantedTools (which decide the MCP tools the daemon grants) are read at org start from the project's .monomind/catalog/, which the org signature does not cover beyond skill names. That directory was writable to roles, and monomind catalog ran inside them, so a role could re-stage or re-activate a catalog skill and change what another role got on its next start without the org being re-signed. .monomind/catalog/ is now an operator-protected path, like .monomind/org-skills/: denied to the file tools, in the SDK sandbox's denyWrite and read-only under the bubblewrap mask, for the org root and a role's cwd, and created empty before a role starts so it can't be planted (an empty directory still means the catalog is not configured; a refused catalog stage no longer removes it again while roles run). monomind catalog stage, approve, activate, disable, quarantine, release, revoke, and project/unproject --apply, refuse inside an org role or agent exec process tree, as org sign does. Roles can still read the catalog and run the read-only verbs and projection dry runs; a signed policy.sandbox.allowWrite entry naming .monomind/catalog is the opt-in. See Skill Catalog.
agent exec --runtime claude works inside an org role again (#559). ~/.monomind/deps is read-only to roles (#527), so a role could never do the first-use install of the Claude Agent SDK and failed with EROFS. The deps directory stays read-only: it holds code the unsandboxed daemons load. Instead, the org runtime installs the pinned SDK on the host, through the same hash-pinned installer, before it starts a role session whose effective runtime is claude (the runtime runner selection resolves: role, org, provider kind, then MONOMIND_RUNTIME). Roles on other runtimes get no download, so a codex-only org never fetches the 300 MB SDK. An SDK installed without its bundled binary (#522) counts as installed. The install is logged as a role-deps-installed audit. A failed install is logged as a role-deps-missing audit, does not stop the role, and is not retried for 10 minutes; a session start waits for it at most 5 minutes or until the org stops. MONOMIND_NO_AUTO_INSTALL=1 turns the host install off, for offline or no-network hosts. Inside a role, a missing SDK now fails at once, before any install attempt, and names the operator command. The new monomind deps install command installs the SDK even with MONOMIND_NO_AUTO_INSTALL set, since it is an explicit request, and refuses to run inside an org role.
The test suites no longer inherit an org role's environment (#560). A suite run from inside an org role picked up MONOMIND_ORG_ROLE, which moves cline's scoped dir under the role's TMPDIR, so cline-runner.test.ts and cline-runner-scoped.test.ts failed. The shared test setup (tests/setup/isolated-home.global.ts) now unsets the markers a role session and its runners set: MONOMIND_ORG_ROLE, MONOMIND_ROLE_ID, MONOMIND_ORG_NAME, MONOMIND_ORG_DIR, MONOMIND_ORG_RUN, MONOMIND_ORG_ROOT, MONOMIND_AGENT_EXEC, MONOMIND_SDK_AGENT, MONOMIND_CLINE_TURN, MONOMIND_AIDER, MONOMIND_HOOK_QUIET, MONOMIND_GRAPH_GATE, MONOMIND_GIT_LEVEL and MONOMIND_EXEC_TREE.
Other runtimes' project config is now operator-protected from org roles, .agents/skills included (#580). monomind catalog project --surface platform:agents writes skills to .agents/skills, and the operator's Codex, Gemini, Kimi, OpenCode and other sessions load them, but unlike .claude/ a role could write it, so it could plant or change a skill a later operator session loaded. The same held for the rest of what those sessions load and act on from the project: .gemini/ (settings.json MCP servers and hooks, the status-line helpers), .codex/ (config.toml MCP servers and hooks, the hook scripts), .kimi-code/ (MCP servers, plugin hooks), .opencode/ and opencode.json/opencode.jsonc (plugins, and the node_modules OpenCode installs for them), .agents/monomind/ (the hook bridge rendered hooks run), agy's .agents/hooks.json, .agents/plugins/ and .agents/skills.json, .qwen/ (settings, .env, commands, agents, skills, extensions), crush.json/.crush.json, crushrc/.crushrc and .crush/skills, .pi/ (settings, extensions, skills, prompts, themes, system prompt, installed packages), Cline's .clinerules/hooks and workflows, .aider.conf.yml, .cursor/, .vscode/mcp.json, .kiro/ and .factory/: a role that wrote one got code run in the operator's next session there. Where a runtime writes into its own project dir while it runs as a role, only the parts it loads are protected, so it keeps working: OpenCode writes .opencode/.gitignore at every start and exits when it can't, pi creates .pi/settings.json.lock to read its settings, Qwen Code writes .qwen/worktrees/ and batch/, and agy writes .agents/teamwork/. With the org root or a role cwd at $HOME, the runtimes' own home state dirs (.codex, .gemini, .kimi-code, .qwen, .pi, .opencode, .cursor, .factory, .kiro) are left alone, since those runtimes write their sessions there. Each (at the org root and a role's cwd) is now denied to the file tools, in the SDK sandbox's denyWrite and read-only under the bubblewrap mask once it exists. Like .claude/ none is created at org start, so repos get no stray directories; while one is missing it is on the planted-path watch, so one a role creates is quarantined (one the operator creates while an org runs, for example with monomind init or catalog project --apply, is quarantined too and restored with the mv … && monomind org approve-paths … the notice gives). Instruction files (AGENTS.md, GEMINI.md, QWEN.md, rules) stay writable, as CLAUDE.md does, and so does runtime state such as Crush's .crush/ data dir. A full-access pi role in coder mode whose trusted project settings declare packages needs a signed allowWrite for .pi/npm / .pi/git to install them. A signed policy.sandbox.allowWrite entry naming the path is the opt-in. .claude/skills and .agents/skills are the catalog's only projection targets.
A role can no longer block the operator's catalog changes with a lock file (#581). The catalog lock was .monomind/locks/catalog.lock, which roles can write, so a role could create it and make every monomind catalog mutation fail with "catalog is locked". The lock is now .monomind/catalog/.lock, inside the operator-protected catalog directory. A leftover lock at the old path from an older version is ignored and left where it is. A refused mutation in a project with no catalog no longer leaves an empty directory behind.
monomind cleanup --force keeps the skill catalog unless --purge-data is given (#579). .monomind/catalog/ (staged packages, approvals and tool grants) was not among cleanup's data paths, so --force alone deleted it. It is now data, like .monomind/org-skills/.
org status no longer reports a killed standalone run as running (#573). An org run killed from outside (SIGKILL, or together with the app that started it) never reaches its stop or crash handlers, so runtime.json kept saying running with its pid, and a kill -0 probe kept passing once that pid was reused by another process. runtime.json now records the process's start identity (pidStart: its kernel start time and boot id on Linux, ps -o lstart elsewhere), and every reader checks that the recorded pid is still that process: org status (text and --format json), org list, org stop, org delete, org migrate, org mark-complete, org logs --follow, the dashboard's runtime view and startup gap-fill, and the statusline. A record written before this change has no start identity and is judged by its pid alone, as before.
On the next org status, org run or org serve start, a running record whose run is dead is rewritten as crashed with closedBy: "liveness-check" and the reason (pid N gone, or pid N now belongs to another process), and a line is appended to the org's liveness.jsonl. org status shows it as crashed (pid N gone). The #274 cross-checks still apply first, so a run whose pid is stale but whose serve heartbeat or event log shows it alive is left alone. The run's checkpoint is kept, so org run --resume still works.
org run now refuses to start an org whose recorded run is still alive in another process ("already running (pid N)"), instead of starting a second process on the same runtime.json.
monodesign URL detection retries a Chrome launch that times out waiting for its CDP port (#583). On a cold Windows CI runner, Chrome's first launch could take longer than the 30s launch timeout to write DevToolsActivePort, so the first browser test failed while every later launch in the same job passed. The monobrowse detection driver now retries a CDP-port timeout once. The timed-out Chrome has already been killed by monobrowse, its profile dir is deleted, and the retry uses a new one, since a leftover Chrome process could still hold the old dir's lock. Other launch failures (no Chrome, Chrome exiting at startup, a port held by another process) are not retried. A failed launch no longer leaves its monodesign-cdp-* profile dir behind.
Marking a dead run crashed can no longer overwrite a run that just started (#586). The #573 check re-read runtime.json and then renamed a crashed record over it, so a new run that wrote its record in between lost it until its next state write. The check now moves the record aside, writes crashed only if it is still the dead run's record, and puts it back with an exclusive link, so a record a new run writes at any point in that sequence is kept. The running process's own writes are never blocked or delayed.