RFC-0002: Deterministic Policy Gates for Tools and Handoffs
Generated from the repository source of truth. Source file:
/docs/RFC-0002-policy-gates-for-tools-and-handoffs.md. Status:Accepted.
- Status: Accepted
- Date: 2026-02-18
- Accepted: 2026-02-26
- Owners: aioc maintainers
- Depends on: RFC-0001
- Related: RFC-0003
- Amended by: RFC-0015
Context
Section titled “Context”RFC-0001 defines governance invariants but does not yet specify the runtime contract that enforces them on tools and handoffs.
This RFC introduces the minimum API and runtime behavior for deterministic policy gates.
Note (2026-04-01): this RFC is accepted for the introduction of deterministic policy gates, but parts of its original non-allow delivery terminology are historically outdated. The current runtime uses resultMode as the canonical non-allow delivery field, and approval-required outcomes are extended by RFC-0004 and RFC-0005.
Decision
Section titled “Decision”aioc introduces explicit policy contracts for tool calls and handoff transitions.
- The model can only propose.
- The runtime decides via deterministic policy evaluation.
- Default behavior is deny unless explicit allow is returned.
In scope:
- Tool proposal authorization.
- Handoff proposal authorization.
- Deny/allow decisions with mandatory reasons.
- Trace metadata for auditability.
Out of scope:
- Full handoff orchestration implementation.
- UI-level approval workflows.
- Provider-specific policy behavior.
- Run-level persistence schema and storage adapters (covered by RFC-0003).
Policy Contracts
Section titled “Policy Contracts”export type PolicyDecision = "allow" | "deny" | "require_approval";export type PolicyResultMode = "throw" | "tool_result";
export interface PolicyResult { decision: PolicyDecision; reason: string; publicReason?: string; resultMode?: PolicyResultMode; policyVersion?: string; expiresAt?: string; metadata?: Record<string, unknown>;}
export interface ToolPolicyInput<TContext = unknown> { agentName: string; toolName: string; rawArguments: string; parsedArguments: unknown; proposalHash: string; argsCanonicalJson: string; runContext: RunContext<TContext>; turn: number;}
export interface HandoffPolicyInput<TContext = unknown> { fromAgentName: string; toAgentName: string; handoffPayload: unknown; proposalHash: string; payloadCanonicalJson: string; runContext: RunContext<TContext>; turn: number;}
export type ToolPolicy<TContext = unknown> = ( input: ToolPolicyInput<TContext>,) => Promise<PolicyResult> | PolicyResult;
export type HandoffPolicy<TContext = unknown> = ( input: HandoffPolicyInput<TContext>,) => Promise<PolicyResult> | PolicyResult;
export interface PolicyConfiguration<TContext = unknown> { toolPolicy?: ToolPolicy<TContext>; handoffPolicy?: HandoffPolicy<TContext>;}Runtime Semantics
Section titled “Runtime Semantics”- Model emits a tool or handoff proposal.
- Runtime builds policy input deterministically.
- Runtime evaluates corresponding policy.
- If policy is missing: implicit deny (
reason = "policy_not_configured"). - If policy returns invalid output: deny (
reason = "invalid_policy_result"). - Only
decision = "allow"can proceed to execution/transition. - If
decision = "deny"andresultMode = "tool_result", runtime returns a denied tool result envelope and continues without tool execution/handoff transition. - If
decision = "deny"andresultModeis missing (orthrow), runtime raises typed denial errors.
Default-Deny Rules
Section titled “Default-Deny Rules”- Missing policy configuration MUST deny.
- Missing
reasonin policy result MUST be treated as invalid and deny. - Exceptions thrown by policy MUST deny (
reason = "policy_error").
Tool Result Envelope
Section titled “Tool Result Envelope”Tool and handoff outputs are normalized into a deterministic envelope:
export interface ToolResultEnvelope { status: "ok" | "denied"; code: string | null; publicReason: string | null; data: unknown | null;}- Allow path:
status = "ok",data = <tool_or_handoff_payload>. - Soft deny path (
resultMode = "tool_result"):status = "denied",code = reason,publicReasonfrom policy (or runtime fallback),data = null.
Trace Requirements
Section titled “Trace Requirements”For each evaluated proposal, runtime MUST produce deterministic decision traces through at least one enabled channel:
- run logger events (
tool_policy_evaluated,handoff_policy_evaluated) whenloggeris configured - run record
policyDecisionswhenrecordsink is configured
Each trace record carries:
agentturn- proposal identifiers (
toolNameorhandoffName, pluscallId) decision(allowordeny)reasonpolicyVersion(when available)metadata(optional structured details)
Error Behavior
Section titled “Error Behavior”- Denied tool proposal raises
ToolCallPolicyDeniedErrorwithout executing the tool when non-allow mode is hard (throwor omitted). - Denied handoff proposal raises
HandoffPolicyDeniedErrorwithout transitioning agent when non-allow mode is hard (throwor omitted). - In soft deny mode (
resultMode = "tool_result"), runtime must not throw and must emit a denied result envelope. - Policy engine failures must never bypass denial.
Security and Privacy Notes
Section titled “Security and Privacy Notes”- Policy code is the enforcement boundary for data minimization.
- Policies SHOULD inspect arguments/payload for sensitive fields and deny or redact according to privacy rules.
- Trace metadata MUST avoid leaking raw secrets.
Minimal Test Matrix
Section titled “Minimal Test Matrix”- Tool allow path: policy allows and tool executes.
- Tool deny path: policy denies and tool does not execute.
- Tool missing policy: denied by default.
- Handoff allow path: transition allowed.
- Handoff deny path: transition blocked.
- Policy throws: denied with deterministic reason.
- Invalid policy result: denied.
- Tool soft deny path: policy denies with
resultMode = "tool_result"and runtime emits denied envelope without tool execution. - Handoff soft deny path: policy denies with
resultMode = "tool_result"and runtime emits denied envelope without transition. - Allow path output: runtime emits normalized envelope with
status = "ok"anddata.
Adoption History (Completed)
Section titled “Adoption History (Completed)”- Add policy contract types and defaults.
- Integrate tool policy checks before tool execution.
- Integrate handoff policy checks before any transition.
- Add trace events for proposals and decisions.
- Add minimal test matrix and block merge on failures.
Implementation Status
Section titled “Implementation Status”- Policy contracts and default-deny behavior are implemented.
- Tool and handoff policy gates are enforced in runtime.
- Decision trace events are emitted for both tools and handoffs.
- Hard deny and soft deny (
resultMode = "tool_result") are both implemented. - Unit/integration/regression coverage is present and executed in
test:ci.
Post-Acceptance Refinement: Handoff Selection and Ordering
Section titled “Post-Acceptance Refinement: Handoff Selection and Ordering”Accepted on 2026-09-22.
For each model response, runtime processes ordinary tool calls sequentially in their original order, then processes only the first requested handoff. Handoffs are identified by the registry exposed for that request, not by a name prefix. All calls belong to the agent that issued the model request.
The selected handoff is the first requested, not the first allowed. It still
requires HandoffPolicy authorization. Denial, approval requirements, invalid
arguments, or errors MUST NOT trigger a fallback to another handoff from the
same response. The policy observes the context after the preceding tools.
Later handoffs are ignored without argument parsing, policy evaluation, or transition. As in Agents SDK, they do not produce history items, item stream events, policy decisions, or suspended proposals. They MUST NOT be reported as authorized or policy-denied. Recording ignored proposals is outside this refinement; agent execution evidence is defined in RFC-0003.
Existing resultMode semantics remain unchanged: a blocking tool error stops
the run before the handoff, while a non-allow tool result in tool_result mode
allows processing to continue. Earlier tool side effects are not rolled back
if the selected handoff is subsequently blocked or fails.
Migration: tools positioned after a handoff in the model output execute before it; multiple authorized handoffs no longer cause multiple transitions in one response. Applications must not rely on the last requested handoff winning.