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
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 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.