Production profiles
Everything on this page composes features documented elsewhere; nothing here is a new switch. The value of a named profile is that its parts fail closed TOGETHER: each posture below lists what to turn on, what the posture guarantees, and, just as deliberately, what it does not.
Read-only diagnosis
The posture for investigation workloads: incident triage, repository research, audit sweeps. The run may read anything it is pointed at and must change nothing.
- Give agents read-only toolsets: the repository research toolset is built for exactly this shape, and its
record_evidenceentries feed the claim-consistency pool so conclusions stay tied to what was actually read. - Pin each profile's toolset with a toolset attestation, so a drifted or poisoned tool description refuses typed at spawn time instead of silently re-keying into the run.
- Compile permissions with a deny by default preset and
strictApprovals: true, so a blanket allow from a hook can never silently clear a tool that declaredneedsApproval, and withhookAllow: 'advisory'(RV4911), so it cannot clear a deny rule either: the deny tables speak before a hook's allow decides. The default'decisive'keeps the historical order, under which an engine level allow hook decides before the deny rules of any tool withoutneedsApprovalare read. - Declare evidence contracts on the reading agents and hold acceptance to them with
requireEvidenceFloor, so a child that read too little cannot be promoted into the roster that steers synthesis. - Bound the money before the first call: a run budget ceiling, the in-flight exposure cap, and the strict pricing gate together refuse surprise spend instead of reporting it afterward.
- Importing tools from an MCP server? Declare
requireBounds: trueon the source (RV1808), so the four discovery bounds (maxTools,maxPages,maxSchemaBytes,timeouts.discoveryMs) must all be stated and an unbounded sweep against a remote registry cannot happen by omission; the cycle guards need no configuration. See MCP. - Gate downstream automation on the (
status,completion) pair and the envelope facts, per the terminal contract for consumers.
What this posture guarantees, and on what condition: no tool with side effects is reachable through the deny tables as long as the chain runs under hookAllow: 'advisory' (under the default 'decisive' an allow from an engine level hook decides before the tables are read), no approval is silently waived under strictApprovals, and an accepted result names the evidence it stands on. What it does not guarantee: that the model read everything relevant; the evidence floor bounds under reading, not judgment.
Since RV1606 the profile half of this list ships assembled: pilotAgentProfile(options) (async, because the attestation pins the RESOLVED toolset) wraps researchAgentProfile and returns { profile, evidence, attestation } with the toolset attestation recorded, one deny rule over every risk class outside declared reads (write, network, execute, destructive, and undeclared), strictApprovals armed, inheritPermissions off, and isolation pinned to 'none'. Read the guarantee with its conditions. The deny rule is evaluated at dispatch, in the permission chain the spawn compiled, before execute runs and before any effect: the model receives an error tool result and the turn continues. It is absolute only under hookAllow: 'advisory', which the factory does not set (the profile declares no hookAllow, so the engine defaults, a hookAllow: 'advisory' on the profile's own permissions, or the regulated compile below must arm it); under the default 'decisive' an engine level hook that answers 'allow' decides before the rule is read. A write risk tool smuggled in through extraTools is still attested (the pin covers what the factory resolved) and still meets the rule at dispatch under the advisory mode; a registration that drifts from the pin refuses typed at spawn, before any provider call. Before 1.253.0 the chain had no advisory mode, so one engine level allow hook silently retired this rule for every tool without needsApproval (the Codex review of 973add91 that the owner confirmed found it); 1.253.0 closes it with hookAllow, which the regulated floor forces. The engine level halves of the posture (budget ceiling, exposure cap, strict pricing, acceptance floors) stay explicit engine and run options: a profile cannot set them, and the factory does not pretend to.
Three refusal times are named on this page, and they are not interchangeable. Compile time is compileRegulatedProfile: a typed ConfigError naming the field, thrown at construction, before any store mutation or wire. Spawn time is the toolset attestation and requireToolsetAttestation: a drifted or unpinned toolset refuses when the spawn resolves it, before the first provider call. Dispatch time is the permission chain: a deny verdict stops one tool call before its executor runs, the model sees the error result, and the agent goes on. The window between compile time and use is held by the RV4102 wrappers, which re read a construction's descriptor at every use of its risk seam, so a container seam loosened after compile refuses at its next run().
Isolated patch
The posture for workloads that produce changes without applying them: fix generation, migration drafts, review remediation.
- Run tool work out of process through the isolated executor (subprocess or container adapter), so a hostile or model-generated script cannot reach host capabilities.
- Give write access only inside worktree isolation: the child works on an isolated copy, and its changes come back as patch artifacts the host applies or discards. The two compose since RV4914, on this condition: the dispatch carries the acquired tree as
cwdon theIsolatedExecRequest(present under worktree isolation, absent under'none'and'readonly', so a request without one is byte identical), the subprocess executor starts the tool program there withRULVAR_SCRATCHnaming the ephemeral directory it would otherwise have started in, and the container executor bind mounts the tree as the work mount (/work, theworkMountoption) beside the ephemeral directory at/scratch(scratchMount, exported asRULVAR_SCRATCH), so the tool's writes land in the tree the patch is collected from, and both executors ledger the tree on the dispatch row (cwd, plusworkMountfor a container). Before 1.253.0 the request carried no directory and both reference executors ran every call in an ephemeral directory removed after the call, so a worktree spawn dispatching through them drafted its change where the patch collector never looked and the patch came back empty, silently (the Codex review of 973add91 that the owner confirmed found it); 1.253.0 closes it. - Under the regulated floor a container executor attests every flag it runs with since RV4915: the descriptor carries
image,capDrop,memory,cpus,pidsLimit,workMount,scratchMountandextraDockerArgsverbatim, and the strict judge refuses by field name anything butnetwork: 'none', a read only root,capDroplistingALL, an image pinned by digest (name@sha256:<64 hex>), the resolved limits and mounts present, andextraDockerArgsempty; the executor also places any extra flags BEFORE its hardening flags, so a repeated single valued flag resolves to the fixed value and a conflicting--networkfails the dispatch at the daemon instead of running with it. Before 1.253.0 the descriptor carried the network mode and the root posture and required neither, hashed none of the image, capabilities, limits or extra flags, and the executor appended the extra flags after its hardening flags, so['--network', 'host']ran with the host network beneath a fingerprint that attestednone(the Codex review of 973add91 that the owner confirmed found it); 1.253.0 closes it. - Keep the effect ledger's boundaries in mind: it records what the executor observed, and what the ledger is NOT (not an outbox, not authorization, not exactly-once) is the reason the APPLY step below stays with the host.
- Put every apply behind an approval with an explicit
defaultDecisionon unattended flows, so an expired approval resolves the way the host declared, never a library-invented accept.
What this posture guarantees, and on what condition: the blast radius of a bad patch is the worktree it was drafted in, as long as the executor received that tree (RV4914: a dispatch under 'none' or 'readonly' isolation carries no cwd) and the tool program writes under its working directory rather than under the ephemeral scratch directory, which is removed after the call; and applying the patch is a host decision recorded on the host's side. What it does not guarantee: patch quality (validators and review own that), and a process boundary from a subprocess or a worktree alone, which are isolation conveniences; the container adapter is where the process boundary holds, and only under the regulated floor is its seam attested.
The regulated floor: one call, refusals typed
Every assurance posture in this library is an opt-in knob, which is correct for a library and hazardous for an unreviewed config: a deployment that hand-assembles twelve options can silently omit the one that mattered. compileRegulatedProfile(input) (RV4009) is the one-call composition for workloads that must not run loose. It takes ordinary { engine, run, orchestrate? } options and returns the same shapes with the regulated floor applied:
permissions.strictApprovals: trueon the engine defaults (the monotonic mode; a profile cannot un-arm it).permissions.hookAllow: 'advisory'on the engine defaults (RV4911): the deny tables speak before a hook's allow decides, an explicit'decisive'refuses by field name at either level, and the posture map records every permission layer the options reach: hook counts andcanUseToolpresence (closures are counted, never read), the deny and ask tables verbatim, the preset, and theinheritPermissionsopt in, which the floor accepts because the layers a child can inherit are exactly the hashed ones.billingReceipts: 'intent', so every provider wire journals its intent before it can bill.determinism: { mode: 'error' }: bare nondeterminism in workflow bodies refuses instead of warning.strictPricingarmed and a positive finitebudgetUsdrequired (RV4107: NaN and Infinity are not ceilings), underbudgetPolicy: 'immutable-lifetime', so the recorded ceiling binds every later segment.scoperequired (RV4007): a regulated run has an owner, recorded at genesis. The compile normalizes it (RV4107) and enforcesscopePolicy: { unknown: 'reject' }(RV4205): an unknown dimension refuses typed by name at compile time (a silently dropped dimension is a dimension nothing downstream recorded or bound), an empty or malformed scope refuses the same way, and the named dimensions (tenant,account,project,legalDomain,region,providerAccount, and since RV4408sponsor, the principal on whose behalf and at whose expense the work runs, distinct from the owning tenant and the billing account) enter the hashed posture.- When
orchestrateoptions are present:budget.acceptanceReserve: 'require',citationAuditmust be declared, andclaimConsistencymust be declared with stage'final'or'both', running atcoveragePolicy: 'strict-final'on the shipped document, not the draft (RV4103). Absence is the loosest claim posture there is: an orchestration with no claim machinery runs no pass, grades no coverage, and arms no gate, so the floor refuses the omission exactly like an explicit loosening; it does not autofill a judge it would have to invent billable defaults for. - The findings postures fail closed too (RV4201, the sixth comparison experiment):
claimConsistency.onFoundandcitationAudit.onFoundrefuse'report'and'carry'(the observing postures let a run settle accepted over what its own judge found; the sixth run shipped a judged contradiction and five unsupported citations through exactly them), fill'fail'when absent, and license an armed'repair', fillingcoverageRepair: truebeside it so the one bounded round serves every defect class. AcoverageTargetbelow 1 refuses (the regulated acceptance requires the'full'grade, unreachable under a pass sized to cover less), and a STANDINGclaimConsistency.waiverrefuses outright: regulated acceptance admits either no exception or the pinned-hash form. The compile then writes thesemanticAcceptancedeclaration from the postures it enforced (or judges a declared one for mismatches, the RV4107 rule: the floor judges its own intake), so a regulated run physically cannot settle accepted over a non-'full'grade, a surviving contradiction, or a surviving unsupported citation, and the only waiver it can honor is a signature under one reviewed document'sjudgedHash. - The deliverable contract is the floor too (RV4303, v4): with
orchestrateoptions present,finishValidationmust be declared with the host's own validators, because without the contract there is no deliverable verdict and no candidate chain, and the floor does not invent acceptance criteria (the RV4103 rule, symmetric withclaimConsistency). Inside it the compile fillscandidatePersistence: 'hash-only'(the auditability minimum: every verdict carries the candidate identity, and an absent blob reads as declared policy, never as loss;'transcript'is the legal richer declaration), and the legacyretainRejectedCandidatesboolean refuses at BOTH values, the fail-closed migration: a silent canonical rewrite would compile a lineage posture the host never wrote. The citation audit's resolver generation is pinned the same way:citationAudit.resolverfills2(the bounded logical-unit resolver, RV4208) and an explicit1refuses, because the fixed four-line window is the diagnostic resolver whose truncation manufactured the sixth comparison run's false negatives. - The audit's SCOPE stays the host's call, with one recommendation (RV4407, not floor): for critical document classes, declare
citationAudit.auditScope: 'all', the census mode that judges EVERY anchor row of the document instead of the default deterministic sample. The seventh comparison experiment's terminal counted 10 unsupported of 24 SAMPLED rows over a 105-citing-sentence document, and the sample-versus-census gap was the loudest open question of its post-mortem. The cost arithmetic is prompt-shaped, not pass-shaped: the census is still ONE judge invocation (two under an armed round, exactly the sample's worst case), its rows ride the prompt, so at the seventh run's shape the census prompt is roughly four times the 24-row sample's andjudge.estCostshould be sized accordingly (the declared estimate enters the acceptance tail unchanged, one term per pass). The floor question has since been settled by evidence, in both directions at once: census rejudges of two refused candidates showed the SAMPLE is honest, the judge making zero errors over honest resolver 2 windows and the RV4401 fix collapsing the sampled unsupported count to exactly the candidate's real defects, so a mandate would buy no correctness; and the CENSUS is completeness, surfacing roughly three times the real defects the sample could reach for under five times the judge's price, and holding at scale (215 rows, one invocation, a full verdict bijection back). So the floor still does not require the census, now on evidence rather than expectation: sample buys honesty, census buys completeness, and the document class chooses. Declaring it, sizejudge.limits.maxOutputTokensPerTurnfor the bijection: the RV4706 guard refuses a declared cap below 70 tokens per judged row plus 500 BEFORE any provider call, the arithmetic both census rejudges had to do by hand. - Any profile that declares
toolsmust carry a toolset attestation, and since RV4204 the pin must be a FULL one: a legacy contract-only pin (noauthorityHash) refuses outright, because authority drift (risk, needsApproval, executor, executorSpec) passes it silently by its own documented posture. Re-record withattestToolset(). - The attestation floor is armed engine-wide (RV4204):
defaults.requireToolsetAttestationfillstrue, so a spawn that resolves a NON-EMPTY toolset under a profile with no pin (the per-call-tools hole the profile pins could not see) refuses typed at spawn time, before any provider call. - Constructions the options reach must hold their posture (RV4101): an
mcp()source must rundrift: 'refuse'with every discovery bound declared, and abridgeAiSdk()adapter must keepproviderExecutedTools: 'deny'. Since RV4204 the first-party constructions attest too:anthropic()andopenai()report their egress (official, acustom-base-urlwhose origin the hash pins, or apreconstructed-clientnamed honestly) and the caps pagination bound,subprocessExecutor()andcontainerExecutor()report their ledger, env allowlist, ceilings and isolation seam, and the walk coversengine.executorsand the sandbox runner beside adapters and toolsets. A regulated executor without aToolEffectLedgerrefuses by field name: an effect no ledger records is an effect nobody can reconcile. Since RV4915 the container descriptor carries the whole seam (image,capDrop,memory,cpus,pidsLimit,workMount,scratchMount,extraDockerArgsverbatim), and the floor refuses a container executor by field name unlessnetworkis'none', the root is read only,capDroplistsALL, the image is pinned by digest (name@sha256:<64 hex>), andextraDockerArgsis empty: a raw docker flag list is refused outright rather than denylisted, because list valued flags such as--cap-addaccumulate whatever the argv order, so no order and no finite denylist can make one safe. A construction exposing nodescribeRegulatedPosture()is counted into the hash asunrecognizedrather than implied verified, and the opt-inconstruction: 'require-recognized'turns that count into a typed refusal naming the blind constructions, satisfiable now that the first-party surface attests.
The compile REFUSES what it cannot keep: a field that loosens the floor (billingReceipts: 'async', determinism: { mode: 'warn' }, a missing budget or scope) throws a typed ConfigError naming the field, never a silent overwrite. A config that compiles is a config whose author either stated the floor or left it to be filled; a config that fights the floor fails loud at construction, before any wire.
The returned profileHash is a sha256 over the enforced posture map, and the compile writes it into run.configFingerprint as regulated:5:<hash> (the 5 is the posture map version: RV4101 added the construction key, RV4203 added the semantic postures, because the sixth comparison experiment's headline finding was exactly that the v2 map hashed none of them: a run configured report beside a standing waiver and a run configured fail closed carried the identical fingerprint, so the attestation could not tell a diagnostic posture from a production one; RV4303 added the deliverable contract, because candidatePersistence lives inside a finishValidation the v3 floor never required, so a regulated orchestration could run with no deliverable verdict at all; and RV4911 added the permission layers, because the v4 map hashed none of them: a config whose engine allow hook retired every deny rule carried the same fingerprint as one without it). The v5 map hashes the findings postures of all three passes, the waiver mode and its declared terms, the citation audit's sampling, judge parameters and resolver generation, the semanticAcceptance declaration in full, the required-contract fact with its candidate persistence mode, the declared toolset attestation pins (contract and authority hashes, so an upgraded pin moves the fingerprint), and the permission layers (the forced hookAllow, hook counts and canUseTool presence, the deny and ask tables, presets and the inheritPermissions opt in, for the engine defaults and every profile that declares permissions). The existing fingerprint machinery does the rest: genesis records it, and a resume asserting a different fingerprint refuses before ownership. No new meta surface, no engine branch: the compiled options are DATA, applied like any others.
Since RV4101 the hash covers the constructions too. A risk-bearing construction exposes describeRegulatedPosture(), a pure snapshot of what it chose at build time: an mcp() source reports its drift and discovery bounds, bridgeAiSdk() reports its provider-executed-tools seam. The compile walks every construction the options reach (adapters, named toolsets, profile toolsets), refuses a loosened posture by field name (construction['mcp:http:...'].drift must be 'refuse'), and folds the sorted descriptors into the hashed posture map beside an unrecognized count of the constructions that exposed nothing, so the hash names its own blind spot instead of implying totality. The RV4009 rule stands, sharpened: a hash must not imply what it cannot verify, and what it CAN verify it now does. The window between compile time and use is held too (RV4102, the RV1608 template): the compiled options carry each attested construction wrapped, so every use of its risk seam (tools() on a source, stream() on an adapter) re-reads and re-judges the descriptor first; a posture loosened after compile refuses with the same field-named error, any other movement refuses naming the drift, and everything else (close(), caps(), identity fields) passes through untouched. The cross-process half of the window never needed a wrapper: a mutated construction compiles to a different profile hash, and the RV3210 resume assertion refuses it.
Merge and deploy authority: not claimed
There is no rulvar profile for merging to a protected branch, deploying, or mutating production data, and that absence is a design position, not a missing feature. The library gives a host strict hooks and fail-closed gates; it does not own fleet budgets, IAM, transactional outboxes, or multi-region consensus, and a workflow result is facts, never permission. A deployment that wants agent-produced changes takes them as artifacts from the isolated-patch posture and pushes them through the same review and release machinery humans use, on infrastructure that owns authorization. Anything that promises otherwise is claiming authority this library deliberately refuses to hold.