Action admission#

Gate’s action-admission API is a disabled extension boundary for deterministic checks around agent tool calls. It does not register new hooks or change current tool behavior by itself.

The model is:

native attempt -> adapter contract -> enrolled policies -> resolved effect -> native effect -> receipt

An adapter may claim only capabilities proved for its exact native event, phase, target, and version. A policy may inspect one complete JSON-primitive candidate field and return one of five actions:

  • abstain: no judgment;

  • allow: the candidate passed an applicable check;

  • deny: block the native action;

  • repair: replace the complete owned field once, before execution; or

  • observe: record bounded evidence without claiming prevention.

There is no executable policy discovery. The caller constructs an AdmissionKernel with an explicit policy collection. Enrollment captures each validated descriptor as the policy’s immutable authority record; replacing a policy object’s later descriptor attribute cannot change its class, applicability, vocabulary, ownership, or override status.

Capability contract#

Adapters advertise capabilities separately. A hook matcher or notification is not proof that the host can block or replace a call.

Capability

Meaning

pre_admission

The event runs before the native action.

input_complete

The candidate contains the complete field value.

target_identity

The adapter can identify the native target.

deny

A denial prevents native execution.

repair

The host executes the replacement and never the original.

feedback

The host can show a bounded diagnostic to the agent.

post_observe

The adapter can observe a completed action.

result_fields

The post event exposes proved, allowlisted result fields.

retry

The host owns a bounded retry mechanism. Gate does not invoke it.

Requested and enacted actions are distinct receipt fields. A pre-execution denial requires direct evidence for pre_admission, input_complete, target_identity, and deny. A repair also requires repair. A post-execution observation requires post_observe, result_fields, and target_identity; no post event can report a prevention effect. Missing proof yields adapter.capability_missing. A direct adapter that can deny but cannot repair turns a requested repair into a denial, so the agent can correct the input and retry. Gate never reports an unavailable effect as enforcement.

Resolution#

Policies run in stable identifier order. A required deterministic failure requests denial; an optional failure is a visible abstention. Semantic policies may only observe or abstain, even when marked required: semantic uncertainty, failure, timeout, or invalid output never acquires denial authority. Each descriptor enrolls a static reason-code vocabulary and bounds candidate and diagnostic bytes. Its overrun_threshold_ms is a post-return measurement threshold, not an execution deadline. The synchronous v1 kernel records an overrun only after policy code returns; it cannot preempt a blocked policy. Enrollment therefore remains limited to reviewed, in-package, terminating code. The attempt deadline likewise prevents a policy from starting late and detects a return after the deadline, but cannot interrupt an evaluation already in progress.

A repair owns one complete candidate field. Identical replacements may coalesce, while different replacements, undeclared ownership, an invalid digest, or a second repair round request denial. Repair conflicts, rejected repairs, and second-round repairs are kernel safety denials: their binding includes the contributing proposal digests and kernel rule, and they are never overridable. The repaired candidate is evaluated once more before the adapter may enact it. No successful decision is cached across attempts.

Receipts include adapter and event identities, requested and enacted actions, and candidate and proposal digests. Policy reason codes must come from the descriptor’s static enrollment; malformed field types or dynamic codes become bounded kernel failure codes. Receipts do not include candidate values, recovery text, feedback text, override actors, or override reasons. Kernel-generated failure rules remain valid static codes even when a policy ID uses the maximum length; an overlong readable rule is replaced by a content-derived policy.failure.* code.

An attempt may also bind one immutable PrimitiveSchema. Its complete metadata and digest enter the attempt digest, while the receipt retains only that outer digest. Gate rejects stale schema metadata before policy evaluation. The v1 schema vocabulary is deliberately limited to bounded integers and booleans; richer host schemas remain outside this protocol.

Overrides#

An override applies only when the adapter enacted a denial. It cannot turn a requested denial that degraded to abstention into allow, and Gate does not consume authority on that path. Constructing an Override does not confer authority. The caller must supply an OverrideAuthority backed by its authenticated operator channel. Gate validates the binding first and captures the authority’s validated static identity once; the same captured identity is used in the receipt even if mutable authority state changes during authorization. The authority then authenticates the actor and reason and atomically consumes the override ID. It must return authorized, unauthorized, or reused. Gate rechecks both override expiry and the attempt deadline after the authority returns; a slow call may consume the identifier, but cannot authorize stale state. An override must be unexpired, reasoned, and bound to all of the following:

  • attempt digest;

  • denied-policy digest and exact rule IDs;

  • adapter ID;

  • session ID; and

  • an authenticated actor and reason held by the authority.

The denied proposals remain in the decision and receipt. A reused or foreign override does not change the denial. A descriptor may mark a core rule non-overridable; no override can waive such a proposal. A successful receipt records the authority ID plus SHA-256 actor and reason digests, never their raw text.

Harness evidence matrix#

This table records the current narrow evidence boundary. It does not promise that the new admission API is registered on any host; registration requires a separate adapter probe and activation decision.

Harness surface

Current proved effect

Admission status

Claude PreToolUse on selected nested-launch tools

Direct deny in the existing superheavy hook

Adapter not yet wired

Codex PreToolUse on spawn_agent

Direct deny in the existing superheavy hook

Adapter not yet wired

Claude or Codex write/edit tools

No complete-candidate repair proof in this wave

Unavailable

Gemini tool events

No exact installed-version probe in this wave

Unprobed

Factory Droid, Cursor, Kimi, Antigravity, ZCode

No proved pre-execution prevention contract

Advisory evidence only

Vix

Current adapter is explicitly non-blocking

Advisory only

OpenCode and Kilo

No projected Gate hook surface

Unsupported

Unicode, bulletin, and argument-regularization policies must remain disabled until an adapter proves the exact target, complete candidate, pre-execution timing, and requested native effect. Generic Markdown rewriting remains a Corpus Controlled Prose responsibility.

Python surface#

class gate.admission.AdmissionKernel(policies: Iterable[Policy])#

Resolve static policy proposals within one adapter’s proved capabilities.

admit(attempt: Attempt, *, override: Override | None = None, override_authority: OverrideAuthority | None = None) → Decision#

Evaluate an attempt, validate one repair round, and return a receipt.

class gate.admission.Policy(*args, **kwargs)#

Public extension seam implemented by deterministic or semantic policies.

class gate.admission.PolicyDescriptor(policy_id: str, version: str, descriptor_digest: str, phases: frozenset[Phase], events: frozenset[str], reason_codes: frozenset[str], policy_class: PolicyClass, required: bool, field_ownership: frozenset[str], overrun_threshold_ms: int, max_candidate_bytes: int = 65536, max_diagnostic_bytes: int = 4096, overridable: bool = True, protocol_version: int = 1)#

Static enrollment record defining one policy’s authority and budget.

classmethod create(*, policy_id: str, version: str, phases: Iterable[Phase], events: Iterable[str], reason_codes: Iterable[str] = (), policy_class: PolicyClass = PolicyClass.DETERMINISTIC, required: bool = False, field_ownership: Iterable[str] = (), overrun_threshold_ms: int = 10, max_candidate_bytes: int = 65536, max_diagnostic_bytes: int = 4096, overridable: bool = True) → PolicyDescriptor#

Construct a descriptor with a canonical content-derived digest.

class gate.admission.Attempt(event_id: str, phase: Phase, event: str, tool: str, target: str, session_id: str, privacy: str, deadline_ns: int, candidate: Candidate, raw_payload_digest: str, adapter: AdapterContract, protocol_version: int = 1, schema: PrimitiveSchema | None = None)#

Immutable, content-bounded view of one native tool event.

class gate.admission.PrimitiveSchema(schema_id: str, version: str, expected: PrimitiveKind, minimum: int | None, maximum: int | None, allow_lowercase_boolean: bool, digest: str)#

Immutable host-owned schema for one bounded primitive argument.

classmethod create(*, schema_id: str, version: str, expected: PrimitiveKind, minimum: int | None = None, maximum: int | None = None, allow_lowercase_boolean: bool = False) → PrimitiveSchema#

Construct validated schema metadata with a content-derived digest.

class gate.admission.Proposal(policy_id: str, policy_version: str, descriptor_digest: str, action: Action, reason_code: str, rule_ids: tuple[str, ...] = (), recovery: str | None = None, replacement: Candidate | None = None, feedback: tuple[str, ...] = ())#

One enrolled policy’s bounded judgment about an attempt.

class gate.admission.OverrideAuthority(*args, **kwargs)#

Caller-owned authentication and atomic single-use boundary for overrides.

class gate.admission.Decision(candidate: Candidate, proposals: tuple[Proposal, ...], receipt: Receipt)#

Resolved candidate and receipt returned to a native adapter.

class gate.admission.Receipt(event_id: str, attempt_digest: str, requested_action: Action, enacted_action: Action, reason_code: str, original_digest: str, final_digest: str, adapter_id: str, adapter_version: str, evidence_grade: EvidenceGrade, proposal_digests: tuple[str, ...], override_id: str | None = None, override_authority_id: str | None = None, override_actor_digest: str | None = None, override_reason_digest: str | None = None)#

Content-free record of requested and natively enacted effects.

as_dict() → dict[str, object]#

Return a stable JSON-serializable representation without candidate content.