Skip to content

RFC-0015: Effective Tool Argument Authorization

Generated from the repository source of truth. Source file: /docs/RFC-0015-effective-tool-argument-authorization.md. Status: Accepted.

  • Status: Accepted
  • Date: 2026-07-15
  • Accepted: 2026-07-15
  • Owners: aioc maintainers
  • Depends on: RFC-0001, RFC-0002, RFC-0004, RFC-0005
  • Amends: RFC-0002, RFC-0005
  • Amended by: RFC-0016

RFC-0002 requires policy evaluation before tool execution, and RFC-0005 binds approval evidence to a canonical tool proposal. The original implementation decoded provider arguments as JSON, evaluated policy and computed the proposal hash, then applied the tool’s Zod schema immediately before execution.

Zod schemas may add defaults, coerce values, strip properties, or transform payloads. The value evaluated by policy could therefore differ from the value passed to the tool. Invalid schema input could also produce an allow decision before execution failed.

AIOC prepares tool arguments before policy evaluation:

raw provider arguments
-> strict JSON decoding
-> requested arguments
-> one tool-schema parse
-> parsed arguments
-> canonical proposal fingerprint
-> policy evaluation
-> tool execution with the same parsed arguments

The following invariants are normative:

  1. JSON decoding and tool-schema validation happen before tool policy.
  2. Invalid arguments do not reach policy or tool execution.
  3. Tool-schema parsing runs exactly once per execution attempt.
  4. parsedArguments, argsCanonicalJson, and proposalHash describe the schema output that the tool will receive.
  5. An allowed tool receives the same parsedArguments object evaluated by policy.
  6. A policy that mutates the canonical argument value is converted to the deterministic deny reason policy_mutated_arguments.

ToolPolicyInput exposes three distinct views:

export interface ToolPolicyInput<TContext = unknown> {
agentName: string;
toolName: string;
rawArguments: string;
requestedArguments?: unknown;
parsedArguments: unknown;
proposalHash: string;
argsCanonicalJson: string;
runContext: RunContext<TContext>;
turn: number;
}
  • rawArguments is the exact provider string.
  • requestedArguments is the JSON-decoded model request before the tool schema. It is additive and optional for compatibility with externally constructed policy inputs.
  • parsedArguments is the tool-schema output after validation and normalization. It is the authorization and execution payload.

Policies may inspect requestedArguments to detect coercion or removed input, but authorization must be based on the operational meaning of parsedArguments.

Malformed JSON and schema rejection produce ToolArgumentsValidationError with stage json or schema respectively.

Validation failure is not a policy decision:

  • tool policy is not invoked;
  • no allow, deny, or approval-required decision is emitted;
  • the tool is not executed.

Synthetic handoff calls share strict JSON decoding because they use the same provider function-call envelope, but this RFC does not add a handoff schema or normalization contract.

This RFC preserves the existing terminal error delivery behavior. Returning validation failures as model-visible tool results is deferred.

For tool proposals, argsCanonicalJson and proposalHash are derived from parsedArguments.

SuspendedToolProposal adds the optional requestedArguments field and keeps parsedArguments as the effective, schema-normalized proposal:

export interface SuspendedToolProposal extends SuspendedProposalBase {
kind: "tool";
toolName: string;
rawArguments: string;
requestedArguments?: unknown;
parsedArguments: unknown;
argsCanonicalJson: string;
}

Approval reviewers should treat parsedArguments as the action being approved and requestedArguments as diagnostic evidence about the model request.

The runtime canonicalizes parsedArguments before invoking policy and repeats canonicalization when policy returns. A change produces policy_mutated_arguments and prevents execution.

This check protects the proposal/effect invariant from accidental in-process policy mutation. It does not make application-owned policy code an isolated or tamper-resistant security boundary.

The TypeScript change is additive: requestedArguments is optional and the existing parsedArguments field remains present.

The runtime semantics intentionally change:

  • existing policies now observe defaults, coercions, stripping, and transforms through parsedArguments;
  • policies that need the pre-schema request should use requestedArguments;
  • proposal hashes may change for schemas that normalize input;
  • outstanding approval grants created from pre-schema hashes must be reissued.

Accepting both old and new proposal hashes is not supported because it would reintroduce ambiguity about the action being approved.

  • No schema digest in proposal fingerprints.
  • No general immutable JSON contract for Zod transform output.
  • No model-visible validation recovery protocol.
  • No typed handoff payloads.
  • No exact suspended-proposal executor.
  1. Zod defaults are visible to policy and tool execution.
  2. Coercions and transforms produce the same policy and execution value.
  3. Stripped input remains visible only in requestedArguments.
  4. Semantically equivalent normalized inputs produce the same proposal hash.
  5. Malformed JSON and schema-invalid input do not invoke policy or tool code.
  6. Policy mutation produces policy_mutated_arguments and no execution.
  7. Suspended tool proposals retain requested and parsed argument views and use the effective proposal hash.

Accepted. Implemented in src/run.ts, src/policy.ts, src/errors.ts, src/suspended-proposals.ts, and src/run-record.ts, with focused regression coverage in src/tests/unit/tool-arguments.unit.ts.