AIP-33: SDK.md — agentsdk/v1 (in-process SDK driver specialisation)
A markdown + frontmatter format for declaring an in-process SDK driver — an npm/pip/cargo/go package that exposes named functions implementing one or more abstract TOOL contracts directly in the host runtime, without subprocess spawn or network hop. Specialises AIP-30 DRIVER for the `kind: sdk` case.
| Field | Value |
|---|---|
| AIP | 33 |
| Title | SDK.md — agentsdk/v1 (in-process SDK driver specialisation) |
| Status | Draft |
| Type | Schema |
| Family | Driver |
| Driver kind | sdk |
| Specialises | AIP-30 DRIVER.md |
| Requires | AIP-14 (TOOL), AIP-16 (IO), AIP-17 (RUNNER), AIP-19 (SECRETS), AIP-30 (DRIVER) |
| Resources | ./resources/aip-33 — SDK.schema.json, ADAPTER.md, EXAMPLES.md, SKILL.md |
Abstract
SDK.md is the kind: sdk specialisation of
AIP-30 DRIVER. It packages an in-process SDK
integration — an npm / pip / cargo / go package whose named functions
implement one or more abstract TOOL contracts. No subprocess, no
network, no IPC: the host runtime imports the package and dispatches
directly.
SDK drivers serve three use-cases:
- Self-hosted models (local Llama via
@meta/llama-sdk, local SDXL via@host/sdxl-runner) — fastest dispatch, lowest cost, pii-safe. - First-party convenience wrappers that codify host-specific
conventions (e.g.
@agstudio/billing-sdkwrapping Stripe with tenant-scoped contexts). - Performance-critical paths where subprocess (CLI) or network (HTTP, MCP) latency is unacceptable.
The frontmatter inherits everything from DRIVER and adds
SDK-specific fields: package, package_manager, per-tool
function_ref and optional args_template. The format pairs with
defineDriver({ kind: "sdk", ... }).
Motivation
In-process tool execution is a real category that CLI / HTTP / MCP don't cover. Today such tools are just "code in the same repo as the agent" — they bypass the driver model entirely, which means:
-
No catalog visibility. A function is a function; nothing declares "this implements
image.create". Catalog UIs can't list it; the resolver can't pick it. -
No safety contract. The function ships side effects, but the contract layer doesn't enforce
mutates/approval/risk_level. Audit logs miss it. -
No multi-driver routing. When a self-hosted local model is one of three candidates (alongside OpenAI HTTP and Replicate HTTP), the resolver should pick it for pii-tagged workspaces. If the local SDK isn't a registered driver, the resolver doesn't know it exists.
-
Versioning collision. A package declares one version; the contract declares another; without a binding manifest, the two drift silently.
-
Auth state still matters. Even in-process drivers may need secrets (API keys, model paths, license tokens). DRIVER's
authblock applies; without SDK.md, every wrapper reinvents secret resolution.
SDK.md slots into AIP-30's family, gives in-process tools first-
class catalog status, and lets the resolver pick them when policy
favours self-hosted execution.
Design principles
-
Inherit, don't repeat. Identity, auth, install (npm/pip install), version_check (npm-version / pip-version), policy_tags, region all come from DRIVER. SDK only adds package + function refs.
-
No subprocess, no network. The defining property. SDK drivers run in the host's own process. The
network.egressfield declares network NEEDED by the SDK (some SDKs make HTTP calls under the hood); it's still enforced. -
Function refs are resolvable at module-load. When the host loads the SDK driver, it imports the package and resolves the declared function refs at registration time. Refs that don't exist fail registration, not first call.
-
Args templates for renaming. Like HTTP's
body_template, SDK drivers can rename input keys viaargs_template. Most SDK functions take inputs verbatim, soargs_templateis rarely needed. -
Sandbox is best-effort. True process-level isolation isn't available in-process. Hosts MAY enforce capability sandboxing (e.g. denying
child_processaccess for sandboxed npm packages), but the strong sandbox guarantee belongs to CLI / HTTP / MCP. SDK drivers SHOULD declarepolicy_tags: ["self-hosted"]andnetwork.egresshonestly. -
Streaming is natural. Unlike CLI, SDK functions can be async generators / observables. v1 supports streaming through generator functions when the contract permits it.
Specification
File location
SDK drivers live in a single folder:
.drivers/
host-sdxl-sdk/
DRIVER.md ← this AIP, kind: sdk
driver.ts ← optional entry (custom dispatch / streaming)
SECRETS.md ← AIP-19 inventory (when API keys needed)
package.json ← when the SDK itself is co-locatedConvention: id ends with -sdk. The folder name SHOULD match id.
Frontmatter
Inherits all fields from AIP-30 DRIVER.md plus the SDK-specific fields below.
Required SDK-specific fields
| Field | Type | Description |
|---|---|---|
kind | const "sdk" | Per AIP-30. |
package | string | Package name in the language's registry. (@host/sdxl-runner, boto3, tokio, …). |
package_manager | enum | "npm" | "pnpm" | "yarn" | "pip" | "poetry" | "cargo" | "go" | "local" (workspace-relative). Drives the install block default. |
Optional SDK-specific fields
| Field | Type | Default | Description |
|---|---|---|---|
package_version | string | inferred from version_check | Semver range pinned for this driver. |
entrypoint | string | language default | Module path within the package (dist/index.js, lib/main.py, src/lib.rs). Most languages have a clear default; declare only when non-standard. |
import_style | enum | "esm" | "esm" | "cjs" | "python" | "rust-crate" | "go-module". Drives how the host loads the package. |
streaming | object | none | Default streaming config for tools the SDK can stream. v1: { mode: "async-iterator" | "callback" }. |
Per-tool metadata.sdk (in implements[N].metadata.sdk)
Every entry in implements[] adds:
| Field | Type | Description |
|---|---|---|
function_ref | string | The named export to call. "default" for the package's default export; "createImage" for a named export; "images.create" for a nested namespace. |
args_template | object | Optional rename: contract-input keys → SDK-function arg names/positions. Default: identity (input passed as the function's first arg). |
result_extract | string | JSONPath-lite to extract the contract's outputs shape from the function's return value. Default: identity ($). |
streaming | object | Per-tool streaming override. Used when the contract supports streaming AND the SDK function is an async generator. |
Function refs
function_ref is dot-notation pointing at the export to call:
| Ref | Resolved to (TS/JS) | Resolved to (Python) |
|---|---|---|
"default" | pkg.default (or pkg for CJS) | pkg.__default__ (rare; usually pkg.main) |
"createImage" | pkg.createImage | pkg.create_image |
"images.create" | pkg.images.create | pkg.images.create |
"Client.images.create" | new pkg.Client(opts).images.create | pkg.Client(...).images.create |
The fourth pattern (constructor + method) requires the SDK runtime
to instantiate a client per driver; the constructor args come from
auth.state.env resolved secrets plus optional client_options.
Args templating
args_template is sparse — most SDK functions accept a single object
argument matching the contract's input shape. Use args_template
only when:
-
The SDK uses positional args:
args_template: _0: "${input.prompt}" # first positional _1: { model: "${input.model | default('llama-3-70b')}", temperature: 0.7 } # second positional, computed -
The SDK uses different field names than the contract:
args_template: text: "${input.prompt}" # contract.prompt → SDK.text options: max_length: "${input.max_tokens}" stop: "${input.stop_sequences}"
When omitted, the host calls fn(args.input) for object-arg SDKs or
fn(args.input.prompt, args.input.options) for positional-arg SDKs
(positional inferred from the function's introspectable signature
in TS/JS via reflection).
Result extraction
Same JSONPath-lite as AIP-31. Examples:
"$"(default) — return value passes through."$.choices[0].message.content"— OpenAI-shaped LLM responses."$.images[0].url"— image-generation responses.
For streaming results (async generators), result_extract applies
to each yielded chunk.
Streaming
When the contract's outputs support streaming (per AIP-14's streaming roadmap) AND the SDK function returns an async generator or accepts a callback:
streaming:
mode: async-iterator # the function is an async generator
implements:
- tool: ./tools/chat-completion/TOOL.md
metadata:
sdk:
function_ref: "Client.streamChat"
result_extract: "$.delta"
streaming:
mode: async-iteratorThe host iterates the generator and yields each chunk to the caller.
Callback-style streams use mode: callback and require the optional
entry's custom dispatch.
Auth shapes
SDK drivers' auth typically uses:
- Env-var injection at module load (
OPENAI_API_KEYset in the process env,OpenAI()reads it). - Constructor args from secrets (
new OpenAI({ apiKey: secrets.OPENAI_API_KEY })). - No auth for self-hosted models reading from local disk.
auth:
ref: ./SECRETS.md
state: { env: ["LLAMA_MODEL_PATH"] } # path to local model weights
expiry: { detect: "exception:ModelLoadFailed" }Stable identity
Per AIP-30. Bumping package_version to a new major requires
bumping the driver's major. Bumping the SDK function signature
(rename, removed param) is a breaking change at the SDK layer that
propagates: schema-narrowing changes in args_template are major.
The defineDriver({ kind: "sdk", ... }) signature
interface DriverDefinition<"sdk"> extends DriverDefinition {
kind: "sdk"
package: string
packageManager: "npm" | "pnpm" | "yarn" | "pip" | "poetry" | "cargo" | "go" | "local"
packageVersion?: string
entrypoint?: string
importStyle?: "esm" | "cjs" | "python" | "rust-crate" | "go-module"
streaming?: { mode: "async-iterator" | "callback" }
// Optional behavioural adapters.
buildArgs?: (args: BuildArgsArgs) => unknown[]
parseResult?: (args: ParseResultArgs) => DriverResult<unknown>
loadModule?: (args: LoadModuleArgs) => Promise<unknown> // custom module-load
}A frontmatter-only SDK driver works for SDKs whose function signature matches the contract input verbatim. Custom entries handle constructor-based SDKs (auth in constructor), positional-arg SDKs, and streaming-via-callback patterns.
Authoring with SKILL.md
The canonical way to generate an SDK DRIVER.md is via the
paired author-sdk skill.
The skill walks the agent through:
- Identify the package + package manager.
- Identify the function refs implementing each TOOL contract.
- Map auth (constructor args vs env vars).
- Author
args_templateonly when names diverge. - Author
result_extractonly when the SDK's return shape differs from the contract. - Validate against
./resources/aip-33/draft/SDK.schema.json.
Example
---
name: OpenAI SDK (in-process)
id: openai-sdk
description:
OpenAI SDK as an in-process driver. Wraps the official `openai` npm
package; implements image.create and chat.completion via direct
function calls. No subprocess, no extra network hop beyond what the
SDK itself does.
version: 1.0.0
kind: sdk
package: "@agentproto/driver-sdk"
package_manager: "npm"
package_version: "^4.50.0"
import_style: "esm"
install:
- { method: npm, package: "openai", global: false }
version_check:
cmd: "node -e \"console.log(require('openai/package.json').version)\""
parse: '(\d+\.\d+\.\d+)'
range: ">=4.50 <5"
auth:
ref: ./SECRETS.md
state: { env: ["OPENAI_API_KEY"] }
expiry: { detect: "exception:AuthenticationError" }
network:
egress: ["api.openai.com"]
region: ["global"]
policy_tags: ["third-party-llm"]
implements:
- tool: ./tools/image-create/TOOL.md
version: "^1.0.0"
schema_narrowing:
drop_inputs: [seed, negative_prompt]
cost_override: { cost_units_per_call: 4 }
metadata:
sdk:
function_ref: "Client.images.generate"
args_template:
model: "dall-e-3"
prompt: "${input.prompt}"
size: "${input.aspect | default('1024x1024')}"
result_extract: "$.data[0].url"
- tool: ./tools/chat-completion/TOOL.md
version: "^1.0.0"
cost_override: { cost_units_per_call: 0.5 }
metadata:
sdk:
function_ref: "Client.chat.completions.create"
args_template:
model: "${input.model | default('gpt-4o')}"
messages: "${input.messages}"
stream: false
result_extract: "$.choices[0].message.content"
tags: [openai, in-process, sdk]
---A self-hosted local-model SDK driver:
---
name: Local SDXL (SDK)
id: host-sdxl-sdk
description:
In-process Stable Diffusion XL model. Uses the host's own GPU.
No third-party API; PII-safe.
version: 1.0.0
kind: sdk
package: "@agentproto/driver-sdk"
package_manager: "local" # workspace-relative, not a registry
import_style: "esm"
install:
- { method: vendored, path: "./packages/sdxl-runner" }
network:
egress: [] # fully offline once model loaded
region: ["self-hosted"]
policy_tags: ["self-hosted", "pii-safe", "no-third-party"]
auth:
ref: ./SECRETS.md
state: { env: ["SDXL_MODEL_PATH", "SDXL_DEVICE"] }
implements:
- tool: ./tools/image-create/TOOL.md
version: "^1.0.0"
cost_override: { cost_units_per_call: 0.5 } # GPU electricity ≈ free
metadata:
sdk:
function_ref: "default" # default export from @host/sdxl-runner
result_extract: "$.imagePath"
tags: [sdxl, self-hosted, pii-safe]
---See ./resources/aip-33/draft/EXAMPLES.md
for additional patterns.
Compatibility
With AIP-30 DRIVER.md
Strict specialisation. Universal fields validate against AIP-30's schema; SDK-specific fields validate against AIP-33's. Hosts run both schemas in sequence at registration.
With AIP-14 TOOL.md
SDK drivers reference TOOL.md contracts. The contract's
inputSchema is what the runtime validates; args_template reshapes
for the SDK function signature. The contract's outputSchema is
what result_extract produces from the SDK function's return value.
With package managers
The install block's method choice MUST be consistent with
package_manager. Hosts MUST refuse mixed declarations (e.g.
package_manager: npm with install.method: cargo).
With AIP-17 RUNNER.md
SDK drivers run in the host's process. runner is typically
omitted or declared as { engine: "in-process" }; if declared,
host's RUNNER must be in-process — SDK drivers cannot run in a
sandboxed subprocess (that's CLI's job).
Security considerations
- In-process means full host privileges. SDK drivers run in the same process as the agent and can access anything the agent can. Hosts MUST treat SDK drivers with the same trust model as first-party code; they're NOT a sandbox.
- Module load is side-effect-prone. Some SDKs run code at import time (registering global handlers, opening files). Hosts SHOULD scan import logs and refuse drivers whose module load triggers unexpected I/O.
- Secrets in constructor args. When auth is via constructor
(
new Client({ apiKey: secrets.X })), the secret enters the SDK's internal state and may be logged by the SDK itself. Hosts MUST vet SDK behaviour around secret handling before approving. - Streaming generators leak. Async generators that aren't fully
consumed leak memory. Hosts MUST cancel generators on caller
abort (
signal) and MUST NOT leak iterators across calls. - Versioning lock-step. When the SDK's package major bumps, the
driver's major MUST bump too. Hosts SHOULD reject loading an
SDK whose installed version doesn't match
package_version.
Open questions
- Polyglot SDK drivers. v1 declares one package + one
package_manager. A driver that wraps the same logic in TS and Python today needs two drivers; v2 may add multi-language declaration. - In-process sandbox. Strong process-level isolation isn't
available; capability-level (deny
child_process, denyfs) varies by language. v2 may codify language-specific sandbox primitives. - Hot-reload. SDK packages can be hot-replaced during dev; the driver runtime needs a reload hook. v2.
- Bundle size and load time. Some SDKs (Anthropic's, OpenAI's,
AWS') are heavy. Cold-start cost is real. v2 may add
lazy_load: truefor drivers that defer module-load until first call.
See also
- AIP-14 — TOOL.md — abstract contracts SDK drivers implement
- AIP-17 — RUNNER.md — runner block (
in-processfor SDK drivers) - AIP-19 — SECRETS.md — auth-surface inventory
- AIP-29 — CLI.md — sibling specialisation,
kind: cli - AIP-30 — DRIVER.md — abstract supertype
- AIP-31 — HTTP.md — sibling specialisation,
kind: http - AIP-32 — MCP.md — sibling specialisation,
kind: mcp - Driver family — index of related AIPs
./SDK.schema.json— JSON Schema validator./ADAPTER.md— implementer's guide./EXAMPLES.md— additional patterns
Resources
Supporting artifacts for AIP-33. Links open the file on GitHub — markdown and JSON render natively in GitHub's viewer. Browse the full resource tree →
AIP-32: MCP.md — agentmcp/v1 (MCP driver specialisation)
A markdown + frontmatter format for declaring a Model Context Protocol driver — its server reference, transport (stdio / SSE / HTTP), per-tool MCP tool-name binding, prompts/resources composition, and lifecycle. Specialises AIP-30 DRIVER for the `kind: mcp` case. Wraps Anthropic-spec MCP servers (filesystem, github, postgres, …) as agent tools.
AIP-34: WORKSPACE.md — agentworkspace/v1 (workspace identity manifest)
A markdown + frontmatter format for declaring a workspace's identity — globally addressable id, owner, storage choice, defaults, publish posture. The root manifest of every AIP-organized workspace; pairs with STORAGE.md (AIP-35) for the storage policy block.