agentproto

AIP-14: TOOL.md — agenttool/v1 (abstract agent contract)

A markdown + frontmatter format for declaring a single agent tool's abstract contract — its identity, input/output schemas, side-effect profile, approval class, and resource budget. Pairs with the standard `defineTool` signature any implementation exposes. Implementation-specific concerns (transport, code, runner, auth, sandbox) live on the AIP-30 DRIVER layer.

FieldValue
AIP14
TitleTOOL.md — agenttool/v1 (abstract agent contract)
StatusDraft
TypeSchema
Domaintools.sh
RequiresAIP-7 (governance), AIP-16 (IO), AIP-30 (DRIVER)
Implemented byAny AIP-30 DRIVER declaring the tool in its implements[]
Resources./resources/aip-14SKILL.md, ADAPTER.md, TOOL.schema.json, EXAMPLES.md

Abstract

TOOL.md is a markdown-with-frontmatter file format that packages the abstract contract of a single agent tool — its identity, input/output schemas, side-effect profile, approval class, and resource budget. It is the what layer of the AIP series: what the agent calls, what it expects in, what it returns, and what governance gate it sits behind.

The format is paired with a standard entry-point function, defineTool(...), whose signature any implementation in any language exposes. The signature describes the contract (schemas, mutates, approval, requires) — not the body. Implementation bodies, transport bindings, install lifecycle, auth, and sandbox all live on the AIP-30 DRIVER layer.

A single TOOL.md MAY be implemented by zero, one, or many drivers — the registry's runtime resolves driver per call based on capability, policy, cost, and region. This is the layering's payoff: one contract, many backends, no duplication.

The file is human-authored, version-controlled, machine-parseable, and grep-able — same posture as SKILL.md, INTENT.md, CANVAKIT.md, DESIGN.md.

Motivation

A tool's logical shape — what it does, what it takes in, what it returns, what side effects it has, what approval class it requires — is independent of how the tool is packaged for any given backend. A tool authored once SHOULD be loadable wherever an agent runs, and implemented by whichever backend best fits the deployment's policy.

Five problems disappear when the contract layer is split from the implementation layer:

  1. Driver swap stops being a refactor. Today a tool that calls OpenAI is a hard-coded OpenAI tool. To swap to Replicate, you fork the tool, change the body, point callers at the fork. With the contract/driver split, swapping drivers is editing a config: the tool stays unchanged, a new DRIVER.md declares it implements the same contract.

  2. Multi-driver routing becomes data. "Pick the cheapest driver that satisfies workspace policy and supports my style input" is now a runtime decision the resolver makes from declared metadata — not an if/else chain in the agent prompt.

  3. Governance gates the contract, not the implementation. Approval class, mutates, requires capabilities — these are contract properties enforced regardless of which driver serves the call. An OpenAI driver can't downgrade approval below what the contract declares.

  4. Catalogs render from data. "What can this agent do?" is answerable by walking the TOOL.md registry. Each tool's matching drivers are derived from the resolver's filter chain, so the catalog can show "create-image (3 drivers, 1 unauthed)" without reading any code.

  5. Testing decouples. The contract has its own examples (input/ output pairs that every driver MUST satisfy). Each driver may add its own examples (driver-specific edge cases). The two layers test independently.

TOOL.md is the contract layer. AIP-30 DRIVER is the implementation layer. The two together replace what AIP-14 formerly bundled into one file.

Design principles

  1. Contract before implementation. The manifest declares what the tool does, not how it's served. The body of any TOOL.md is informational prose; the executable body lives on a DRIVER that declares this tool in its implements[].

  2. Side effects are first-class. Every tool declares its mutation surface (mutates), its required capabilities (requires), and its approval class (approval). Governance (AIP-7) reads these fields directly from the contract — no separate audit metadata, no per-driver re-declaration.

  3. Schemas are JSON Schema. Inputs and outputs are JSON Schema draft 2020-12. Every runtime can consume JSON Schema; framework- specific schema libraries (zod, pydantic, attrs) project to/from JSON Schema. Authors may write zod/pydantic in the entry file and the adapter generates the JSON Schema, but the manifest stores the canonical form.

  4. Drivers narrow contracts; widening is forbidden. A driver MAY declare schema_narrowing.drop_inputs to drop optional contract inputs the backend doesn't support (the resolver refuses calls using dropped inputs). A driver MUST NOT add inputs the contract doesn't know — that's a different tool.

  5. Unopinionated about backend kind. The contract says nothing about CLI vs HTTP vs MCP vs SDK. Any of those can implement any tool. Per-call kind preference (cheaper-to-dispatch wins) is the resolver's heuristic, not the tool's concern.

  6. Composable with siblings. TOOL.md may live alone in a tool folder, alongside driver folders that implement it, or in a shared catalog directory. The manifest is the index; driver files are the bodies.

Specification

File location

Tools live in a single folder, separate from the drivers that implement them:

.tools/
  pricing-snapshot/
    TOOL.md          ← this AIP
    README.md        ← optional long-form

.drivers/
  apollo-pricing-http/
    DRIVER.md      ← AIP-30, implements: pricing-snapshot
  diffbot-pricing-http/
    DRIVER.md      ← AIP-30, implements: pricing-snapshot

The folder name SHOULD match the manifest's id. Tools MAY be nested under a category (e.g. .tools/finance/pricing-snapshot/); consumers MUST NOT depend on directory depth.

A tool MAY co-exist with a single driver in the same folder when the implementation is provably 1:1 (e.g. host-builtin):

.tools/fs-read/
  TOOL.md
  DRIVER.md        ← kind: builtin, single implementation

When more than one implementation is anticipated, separate folders make the multi-driver layout explicit and easy to scan.

Frontmatter

YAML frontmatter, delimited by --- lines. All fields are case-sensitive.

Required fields

FieldTypeDescription
namestringHuman-readable display name (1–80 chars).
idstringMachine identifier. Lowercase, digits, dashes, dots. 2–80 chars. Dots denote namespace (image.create, github.pr.merge). Unique within the registry that hosts the tool.
descriptionstringOne-paragraph purpose, written for the LLM caller. ≤2000 chars.
versionsemver stringSpec version of THIS file. Bump on breaking change to inputs/outputs/side-effect profile.
inputsJSON SchemaThe IO inputs block defined by AIP-16. Draft 2020-12. May be { "type": "object", "properties": {} } for nullary tools.
outputsJSON SchemaThe IO outputs block defined by AIP-16. Draft 2020-12. Errors are out-of-band (see Errors).

Optional fields

FieldTypeDefaultDescription
idempotentbooleanfalseSafe to retry without observable extra effect. Logical idempotency, contract-level (network-level retry safety belongs to DRIVER's retry_override).
mutatesstring[][]Resources the tool may modify. Format: <class>:<scope> (workspace:/path, network:hostname, database:<table>, secret:<slug>, external:<service>). Empty = pure read. Every driver implementing this tool MUST honour the declared set; surfaces audit logs.
requiresobject{}Capability requirements (governance gating per AIP-7). Subfields: network: string[], secrets: string[], tools: string[] (other tool ids it calls). Drivers MAY add to this set; MUST NOT remove from it.
approvalstring"auto"Approval class. "auto" (run without prompt), "always" (always prompt), "on-mutate" (prompt when mutates is non-empty), or "policy:<ref>" (defer to a named policy). Contract-level — drivers can't downgrade.
risk_level0–30Caller-side autonomy gate. 0 = read-only, 1 = scoped writes, 2 = external side effects, 3 = irreversible. Used by AIP-7 approval policies.
cost_classstring"trivial""trivial" (no metered call), "metered" (counts against quota), "expensive" (warn the user before invoking). Baseline; drivers MAY override via cost_override.cost_class.
timeout_msint30000Hard wall-clock contract ceiling. Drivers MAY narrow via timeout_override_ms; MUST NOT widen.
retryobjectnoneBaseline retry policy: { max_attempts: int, backoff: "fixed"|"exponential", initial_ms: int }. Drivers MAY override via retry_override.
default_implementationstring | nullnullOptional id of an implementation (a DRIVER.md or CODE.md) to use when the resolver has no other signal. Lets authors pin the canonical implementation while still permitting overrides via context.pinnedProvider. Null = pick by resolver policy. (Renamed from default_driver for terminological consistency with the post-AIP-26 model where CODE.md can also implement TOOL contracts.)
implementsstring | object | nullnullOptional AIP-39 ACTION reference declaring which abstract verb this tool implements. When set, the tool INHERITS the action's mutates, risk_level, approval, requires (with narrowing rules — see "Action implementation" below). POLICY.md (AIP-38) grants on actions, not tools, so multiple TOOLs implementing the same action share one grant. Format: bare action id ("storage:commit"), registry ref ("@agentik/actions/standard/storage-commit"), workspace path ("./actions/storage-commit/ACTION.md"), or inline action definition ({ inline: { id: "...", ... } }).
driver_constraintsobject{}Author-side allowlist/denylist on which driver kinds the contract permits: { forbid?: string[], require_kind?: DriverKind[] }. Use to declare "this tool MUST NOT be served via untrusted HTTP" (forbid: ["http"] for self-hosted-only tools).
tagsstring[][]Free-form discovery tags (finance, read-only, media).
examplesobject[][]Each { name, input, output, note? }. Driver-agnostic examples that EVERY conformant driver MUST satisfy. Driver-specific edge cases live on the DRIVER's own examples.
metadataobject{}Free-form. Authors MAY stash adapter-specific hints under namespaced keys (metadata.mastra.…, metadata.langchain.…). Consumers MUST tolerate unknown keys.

Removed (formerly part of AIP-14, moved to AIP-30 DRIVER)

FieldNew home
codedriver.code (per AIP-26 reference)
rundriver.run
runnerdriver.runner (per AIP-17 reference)
secretsdriver.auth.ref (points at SECRETS.md per AIP-19)
networkdriver.network.egress
entrydriver.execute[<toolId>]

A TOOL.md that still carries any of these fields is invalid under the post-refactor schema. Authors migrating from the pre-AIP-30 shape MUST move these fields to a DRIVER.md sibling.

Discouraged

async, streaming, priority, model, temperature are driver concerns, not tool identity. Driver entries declare streaming via the kind-specific metadata.<kind>.streaming block; the contract stays unary in/out for v1.

Body

Markdown body following the frontmatter. Recommended sections:

  • ## Description — long-form purpose, when to call, when NOT to.
  • ## Examples — inputs and expected outputs, paired with the examples frontmatter array if used.
  • ## Errors — error classes the tool can return + how callers should handle them.
  • ## Driver notes — anticipated drivers, known schema narrowings, routing recommendations. Optional but useful when multiple drivers are expected.

The body is informational. Tools MUST function with resolvers that read only the frontmatter.

Context-injected state (the contextSchema pattern)

Tools commonly need host-supplied state at invocation time — governance config, a database connection, an HTTP fetcher, a tenant id. The contract declares what it needs via contextSchema; the host validates and narrows the context before dispatching to the resolved driver's execute body.

This pattern is the contract-level equivalent of FastAPI's Depends(...), NestJS's request-scoped drivers, and gRPC's per-call metadata: state that varies per call without varying the contract.

const signArtifactTool = defineTool({
  id: "governance.sign-artifact",
  inputSchema:  signArtifactInputSchema,
  outputSchema: signArtifactOutputSchema,
  contextSchema: z.object({                     // ← contract obligation
    governanceConfig: governanceConfigSchema,
  }),
  // NO execute. The body lives on DRIVER (per AIP-30).
})

When a TOOL declares contextSchema, the host MUST validate args.context against it BEFORE calling the resolved driver's execute[<toolId>]. Drivers receive a narrowed, typed context and MUST NOT re-validate.

Hosts wire the per-call context. A multi-tenant runtime, for example, derives governanceConfig from requestContext.tenantId per invocation. The same TOOL contract is shared by every tenant; the same DRIVER serves every tenant; context flows in per-call.

Errors

Tools return errors out-of-band relative to outputs. The convention is:

type ToolResult<T> =
  | { ok: true;  value: T }
  | { ok: false; error: { code: string; message: string; retryable?: boolean; cause?: unknown } }

Hosts MUST wrap driver runtime errors into this envelope. The outputs schema describes the success shape only — drivers need not fork the schema for errors.

code SHOULD be one of the standard codes: "input_invalid", "input_unsupported" (per driver's schema_narrowing), "unauthorised", "auth_required", "not_found", "rate_limited", "timeout", "upstream_error", "no_route", "pinned_provider_unavailable", "internal". Tool-specific codes MAY use a domain prefix ("stripe:card_declined").

Stable identity

id + version together form the tool's stable identity. Two tools with the same id but different major version values MUST be treated as distinct. Caches, audit logs, and capability grants key on id@major.

When the contract changes (input schema gains a required field, an output property type narrows), the tool MUST bump major. Drivers implementing the tool see this as a contract-version bump in their implements[].version range; they MAY continue to declare "^1.0.0" and refuse to bind against the new contract until they update.

Side-effect declaration (mutates)

The mutates array drives:

  • Approval gating (AIP-7): an approval policy can refuse a tool whose mutates includes database: without an explicit ack.
  • Idempotency planners for orchestration: workflows (AIP-15) decide whether to retry vs route to compensation.
  • Audit logs: the post-call audit row records the contract's mutates so external auditors reconstruct effect graphs without re-reading driver code.

A tool MUST declare every class of mutation any of its drivers might perform. A driver whose body observably writes resources not declared in the contract MUST be refused at runtime — not silently allowed. (The contract is the safety floor, not a starting suggestion.)

Action implementation (implements: <action-ref>)

When a TOOL declares implements: <action-ref> pointing at an AIP-39 ACTION.md, the tool joins the action's implementor pool. The relationship is bottom-up — the TOOL declares which ACTION it implements; the ACTION knows nothing about its implementors (an optional implementations: hint on the action is non-authoritative).

Inheritance. At parse time, the host loads the resolved action and merges its fields into the TOOL view:

Action fieldTOOL behavior
descriptionTOOL MAY override (typically more specific).
mutatesTOOL MUST include all action mutates; MAY add more.
risk_levelTOOL MAY raise (more risky); MUST NOT lower.
approvalTOOL MAY narrow (autoalwayson-mutate); MUST NOT relax.
requires.networkTOOL MAY require more hosts; MUST NOT remove.
requires.secretsTOOL MAY require more slugs; MUST NOT remove.
requires.toolsTOOL MAY require more tools; MUST NOT remove.
categoryTOOL inherits verbatim; cannot override.
target_kindTOOL inherits verbatim; cannot override.
fires_eventsTOOL MAY fire more events; MUST NOT drop any action-declared event.

Narrowing rule, formally: a TOOL widens an action when its declaration would relax any constraint the action set as the floor. Widening is rejected at parse time:

function validateImplementor(tool: ToolDef): Result {
  if (!tool.implements) return { ok: true, orphan: true }
  const action = resolveAction(tool.implements)
  if (!action) return { error: `unknown action: ${tool.implements}` }

  // risk_level: floor is action.risk_level, tool MAY raise
  if (tool.risk_level !== undefined && tool.risk_level < action.risk_level)
    return { error: `widens risk_level: action=${action.risk_level}, tool=${tool.risk_level}` }

  // mutates: tool MUST be a superset
  if (tool.mutates && !isSuperset(tool.mutates, action.mutates))
    return { error: `drops mutates: ${diff(action.mutates, tool.mutates)}` }

  // approval: tool MAY narrow
  if (tool.approval && approvalRank(tool.approval) < approvalRank(action.approval))
    return { error: `relaxes approval: action=${action.approval}, tool=${tool.approval}` }

  return { ok: true, mergedView: { ...action, ...tool } }
}

Why bottom-up. Adding a new TOOL implementing an existing ACTION requires no edit to the action — same shape as TypeScript implements and npm package dependencies. Forks are clean: anyone can write a TOOL implementing your ACTION without touching your repo. POLICY grants on the ACTION cover all implementing TOOLs automatically.

Orphan tools (tools without implements) are valid — they're usable but cannot be policy-granted as a group. Recommended only for internal utilities and one-shot tools.

The defineTool standard signature

Every implementation that consumes TOOL.md MUST expose a function named defineTool whose signature matches the contract below. The function returns a value the host registers as a contract — not an executable.

Signature (TypeScript notation, normative)

defineTool(definition: ToolDefinition): ToolHandle

interface ToolDefinition {
  // Identity — mirrors the manifest fields with the same names.
  id:           string
  name?:        string
  description:  string
  version?:     string

  // Schemas — JSON Schema OR a value reducible to JSON Schema
  // (e.g. zod, pydantic). Implementations SHOULD accept both and
  // canonicalise to JSON Schema internally.
  inputSchema:  JSONSchema | unknown
  outputSchema: JSONSchema | unknown

  // Optional schema for the per-call context object. When provided,
  // the host MUST validate context against it before dispatching to
  // the resolved driver's execute body. The driver receives the
  // narrowed, typed context.
  //
  // Use this to declare the host-injected state the tool requires —
  // e.g. `{ governanceConfig, dbConnection, secrets }`. This is what
  // makes a TOOL contract reusable across tenants/workspaces: the
  // contract is static; the context flows in per-call.
  contextSchema?: JSONSchema | unknown

  // Side-effect declaration — same vocabulary as the manifest.
  mutates?:     string[]
  requires?:    Capabilities
  approval?:    ApprovalClass
  riskLevel?:   0 | 1 | 2 | 3
  costClass?:   "trivial" | "metered" | "expensive"  // baseline
  timeoutMs?:   number                                // ceiling
  retry?:       RetryPolicy                           // baseline
  idempotent?:  boolean

  // Driver routing
  defaultImplementation?:     string
  driverConstraints?: { forbid?: string[]; requireKind?: DriverKind[] }

  // Bookkeeping
  tags?:        string[]
  examples?:    Array<{ name: string; input: unknown; output: unknown; note?: string }>
  metadata?:    Record<string, unknown>

  // NO execute(). The body lives on DRIVER (per AIP-30).
}

type DriverKind = "cli" | "http" | "mcp" | "sdk" | "builtin"

Conformance rules

  1. One canonical name. The exported name MUST be defineTool. Implementations MAY also re-export under host-specific aliases (createTool, tool, registerTool); the canonical name is what TOOL.md adapters reference.

  2. No execute on the contract. defineTool MUST NOT accept an execute field. Bodies live on DRIVER (per AIP-30); hosts that find an execute field on a tool definition MUST surface a migration warning pointing at AIP-30.

  3. Schema canonicalisation. Implementations accepting zod/pydantic in inputSchema/outputSchema MUST canonicalise to JSON Schema for the manifest before hand-off to the registry. The audit log entry MUST record the canonical JSON Schema, not the framework-specific form.

  4. contextSchema is contract obligation. When declared, every conformant driver MUST accept the same context shape. Drivers MAY narrow via their own internal validation, but the contract's contextSchema is the floor.

  5. mutates is the truth. If any driver's body observably writes resources outside the contract's declared mutates set, the host MUST surface a spec violation (warn at registration, refuse at runtime, or both). Audit logs (AIP-7) consume the contract's declared mutates directly; undeclared writes corrupt the audit trail.

  6. No I/O at module load. The module containing defineTool MUST be safely importable as a side-effect-free unit. No tool contract should perform I/O — bodies do that on DRIVER, never here.

Implementer's guide

For step-by-step guidance on building a defineTool-conformant implementation in a specific language or framework, see ./resources/aip-14/draft/ADAPTER.md. The AIP only defines the contract; the resource doc walks an implementer through the projection.

Authoring with SKILL.md

The canonical way to generate a TOOL.md is via a paired SKILL.md — distributed at ./resources/aip-14/draft/skills/author-tool/SKILL.md — that an agent loads when asked to define a tool contract. The skill walks the agent through:

  1. Pick id, write name and description.
  2. Decide mutates and requires (the safety contract).
  3. Sketch inputs and outputs schemas.
  4. Decide approval class based on mutates.
  5. Identify the driver(s) that will implement this tool. For each, run the AIP-30 author-driver skill to produce a sibling DRIVER.md.
  6. Validate the manifest against ./resources/aip-14/draft/TOOL.schema.json.

The agent MAY install the skill, follow the steps, and emit the final TOOL.md (and one or more DRIVER.md siblings) without further instruction. This pattern — agents authoring their own tool contracts and driver bindings as portable manifests — is what makes the format valuable beyond convenience.

Example

---
name: Pricing Snapshot
id: pricing-snapshot
description: Fetch the current pricing tiers for a SaaS product from its public marketing pages and return a normalised tier list.
version: 1.0.0
idempotent: true
mutates: ["network:*"]
requires:
  network: ["*"]
approval: "auto"
risk_level: 0
cost_class: "metered"
timeout_ms: 20000
retry:
  max_attempts: 2
  backoff: "exponential"
  initial_ms: 500
inputs:
  type: object
  properties:
    productUrl:
      type: string
      format: uri
      description: Public marketing/pricing page URL.
  required: ["productUrl"]
outputs:
  type: object
  properties:
    tiers:
      type: array
      items:
        type: object
        properties:
          name:       { type: string }
          priceUsdMo: { type: number, minimum: 0 }
          features:   { type: array, items: { type: string } }
        required: [name, priceUsdMo]
    capturedAt: { type: string, format: date-time }
  required: [tiers, capturedAt]
driver_constraints:
  require_kind: ["http", "sdk"]    # no shell-spawning CLI for scraping
default_implementation: apollo-pricing-http
tags: [finance, scraping, read-only]
examples:
  - name: Stripe billing page
    input:  { productUrl: "https://stripe.com/pricing" }
    output:
      tiers:
        - { name: "Standard", priceUsdMo: 0, features: [pay-as-you-go] }
      capturedAt: "2026-04-28T20:00:00Z"
---

## Description

Use when the user asks for the current price of a SaaS product and
already provides the public pricing URL. Returns normalized tier
data. Do NOT use for products that gate pricing behind a sales call —
return an explicit `error.code: "private_pricing"` in that case.

## Errors

| Code | Meaning | Caller action |
|---|---|---|
| `not_found` | URL returned 404 | Surface to user; suggest a different URL. |
| `private_pricing` | Page exists but no public tiers detected | Surface to user; recommend contacting sales. |
| `rate_limited` | Upstream rate-limited us | Resolver's retry handles; don't re-call manually. |
| `upstream_error` | Page changed shape | File a bug; tool needs an update. |

## Driver notes

Two anticipated drivers:

- `apollo-pricing-http` — primary. Apollo's public price-extraction API.
- `diffbot-pricing-http` — fallback. Diffbot's structured-page API; more
  expensive but works on heavily-JS-rendered pages where Apollo fails.

Driver routing: default to Apollo (`default_implementation`); fall through
to Diffbot only when Apollo returns `upstream_error: parse_failed`.
This routing is encoded in the resolver via per-call retry-with-fallback,
not in the tool — the contract stays driver-agnostic.

A canonical DRIVER.md serving this contract:

---
name: Apollo pricing extraction (HTTP)
id: apollo-pricing-http
description: Apollo HTTP API for public-page price extraction.
version: 1.0.0
kind: http
auth:
  ref: ./SECRETS.md
  state: { env: ["APOLLO_API_KEY"] }
  expiry: { detect: "http_status:401" }
network:
  egress: ["api.apollo.io"]
implements:
  - tool: ./tools/pricing-snapshot/TOOL.md
    version: "^1.0.0"
    cost_override:
      cost_units_per_call: 5
    metadata:
      http:
        endpoint: "/v1/pricing/extract"
        method: POST
        body_template: { url: "${input.productUrl}" }
---

Compatibility

From the pre-AIP-30 bundled shape

A pre-refactor TOOL.md carried code/run/runner/secrets/ network/entry directly. Migration to the post-refactor shape:

  1. Author a sibling DRIVER.md per AIP-30 carrying the moved fields:
    • code / rundriver.code / driver.run
    • runnerdriver.runner
    • secrets (per-manifest bindings) → driver.auth.ref pointing at a SECRETS.md
    • networkdriver.network.egress
    • entry's execute body → driver.execute[<toolId>]
  2. Set driver.implements[0].tool to the (now-amputated) TOOL.md.
  3. Remove the moved fields from TOOL.md; add default_implementation pointing at the new DRIVER.md's id.
  4. Validate both files against the v1 schemas (no v1/v2 dual-shape machinery — we ship a clean v1).

This refactor is a single-PR operation. No deprecation window, no codemod release. Internal consumers migrate in lockstep with the spec rewrite. The previous shape lives in git history, not in the spec namespace.

With pre-AIP runtime-specific tool definitions

For authors arriving from runtime-idiomatic tool factories (Mastra createTool, LangChain tools, Anthropic SDK tool defs):

  1. Add TOOL.md for the contract: id, schemas, mutates, approval, requires.
  2. Add DRIVER.md for the implementation: kind, auth, sandbox, implements (per AIP-30).
  3. Re-export the existing body as the DRIVER's execute[<toolId>].
  4. Run both manifests through their schemas to verify shape.

Hosts MAY accept legacy runtime-specific tool registrations during a migration period; the audit-log shape from AIP-7 remains identical as long as mutates/requires/approval are populated on the contract side.

Security considerations

TOOL.md is declarative: a malicious manifest can lie about mutates or requires. Hosts MUST treat the manifest as untrusted input until verified (signature, hash on a trust list, or sandbox enforcement at the driver level). AIP-7's capability gating MUST run regardless of what requires claims.

approval: "auto" does not bypass user-side policies — it expresses the author's view that no per-call prompt is needed. Hosts compose this with their own policy; the more restrictive answer wins.

driver_constraints.forbid is a security-relevant declaration: a tool that handles PII can declare forbid: ["http"] to prevent the resolver from ever picking a third-party HTTP driver for it. Hosts MUST honour the declaration; driver authors MUST NOT route around it.

Open questions

  1. Streaming outputs. Tools that yield progressive results — do we extend outputSchema with a streaming block, or punt to driver-kind-specific extensions? Defer until concrete streaming drivers ship enough to settle the abstraction.

  2. Cost reporting. Should cost_class evolve into a numeric cost_units field tied to a per-host meter? Today drivers override with cost_units_per_call; the contract baseline could match.

  3. Schema-narrowing at the contract level. Currently schema_narrowing is a driver-side declaration. Should the contract also be able to declare which inputs are "optional-but-most-drivers-support" vs "optional-and-rare"? The resolver could prefer drivers covering more optional inputs at equal cost.

  4. Per-tool budget overrides. A workspace might want to cap "image.create" calls per user per day. The cap is a meter at the workspace level — but where does the meter key come from? Tool's id? Driver's quota_key? Both?

These remain open until enough adapters and drivers ship to settle the answers empirically.

See also

Resources

Supporting artifacts for AIP-14. Links open the file on GitHub — markdown and JSON render natively in GitHub's viewer. Browse the full resource tree →