How to implement a Reborn tool extension
This guide is for coding agents and engineers adding an IronClaw Reborn extension tool. It is intentionally Reborn-only. Do not use V1 extension, native-extension, pending-OAuth-map, or legacy tool-router patterns when following this document. The guide is grounded in the current GitHub, GSuite, and Notion implementations:- GitHub: bundled WASM capability provider under
crates/extensions/packages/github/. - GSuite: bundled WASM capability providers for Docs, Drive, Sheets, and
Slides; Gmail and Calendar are bundled
first_partyruntimes sharing the samegooglevendor. - Notion: bundled hosted HTTP MCP capability provider under
crates/extensions/packages/notion-mcp/, with product auth / OAuth DCR wiring in Reborn composition.
Success criteria
A Reborn tool extension is complete only when all of the following are true:- The extension package has a
schema_version = "reborn.extension_manifest.v3"manifest and every model-visible capability has schema, output schema, and prompt assets. - The manifest declares exactly one implementation:
[runtime]withkind = "wasm"orkind = "first_party", or a[mcp]section for hosted-MCP extensions. - The manifest exposes tools as
[[tools]]entries, each carrying anorigin_gate_matrix. Do not use the legacy[[host_api]]/[capability_provider.tools]or top-level[[capabilities]]shapes. - The runtime code does not read raw secrets, create its own HTTP client for external provider calls, bypass approvals, or dispatch directly into the agent loop.
- Network, credentials, approvals, and resource bounds are enforced by the Reborn host APIs and runtime services.
- Tests cover manifest validation, runtime dispatch behavior, credential/auth gates, and caller-facing behavior through the runtime or lifecycle call site.
Reborn extension flow
Use this mental model before touching files:Choose the runtime lane
Pick one lane first. Do not blend lanes to make a tool work.
There is no
script runtime kind in v3 manifests. Process/CLI work goes
through the built-in process sandbox capability
(system.process_sandbox.run), not an extension runtime lane.
For a new provider API like Linear, Jira, or a small internal SaaS API, start
with WASM unless you have a concrete reason not to.
Crates to touch
Touch only the smallest set for your lane.Common extension package work
Usually touch:crates/extensions/packages/<extension>/manifest.tomlcrates/extensions/packages/<extension>/schemas/<extension>/*.jsoncrates/extensions/packages/<extension>/prompts/<extension>/*.md- when adding a host-bundled extension to the built-in install catalog: a
package module
crates/extensions/ironclaw_extension_support/src/packages/<extension>.rsplus its row inPACKAGESin.../src/packages/mod.rs. Do not register assets in composition; the oldavailable_extensions.rshome (nowcrates/extensions/ironclaw_extension_host/src/available_extensions.rs) is being dissolved.
crates/extensions/ironclaw_extension_registry/src/v3.rs(or the legacysrc/v2.rs), unless changing the manifest contract itself.crates/contracts/ironclaw_host_api/src/*, unless adding a new shared host API type.crates/kernel/ironclaw_capabilities, unless changing authorization/approval orchestration for all capabilities.crates/kernel/ironclaw_approvals, unless changing approval lease semantics.crates/substrates/ironclaw_secrets, unless changing low-level secret storage/lease semantics.crates/substrates/ironclaw_network, unless changing global network policy/HTTP egress semantics.- agent loop crates for tool-specific routing. Tool selection must come from the published capability surface, not hardcoded model-routing logic.
WASM lane
Usually touch:crates/extensions/packages/<extension>/wasm-src/crates/extensions/packages/<extension>/wasm/<tool>.wasm- the extension manifest, schemas, and prompts.
- the package module in
crates/extensions/ironclaw_extension_support/src/packages/to embed the manifest, schemas, prompts, and WASM bytes if host-bundled.
crates/extensions/packages/github/wasm-src/src/lib.rscrates/extensions/packages/github/wasm-src/src/request.rscrates/kernel/ironclaw_host_runtime/src/wasm_credentials.rs
reqwest/HTTP client inside the WASM tool. Use the WIT host
HTTP import (near::agent::host::http_request) so Reborn can enforce egress,
inject staged credentials, and sanitize failures.
Hosted MCP lane
Usually touch:crates/extensions/packages/<provider>-mcp/manifest.tomlschemas/<provider>/...prompts/<provider>/...- the package module in
crates/extensions/ironclaw_extension_support/src/packages/if host-bundled.
crates/extensions/packages/notion-mcp/manifest.tomlcrates/extensions/ironclaw_extension_host/src/mcp.rscrates/domains/ironclaw_auth/src/engine/- composition provider wiring in
crates/app/ironclaw_composition/src/factory.rs
crates/extensions/ironclaw_extension_host/src/mcp.rs if the hosted MCP
runtime policy needs a new generic rule. Notion already demonstrates the common
shape: HTTPS-only endpoint, exact host/path match, no URL credentials, no query,
no fragment, host-mediated egress, staged product-auth token.
Auth/OAuth lane
Usually touch only when adding a new product-auth provider:crates/domains/ironclaw_authfor provider/scopes/account-domain vocabulary when it must be shared and durable.crates/domains/ironclaw_auth/src/engine/for generic recipe-driven OAuth/API-key exchange behavior.crates/extensions/packages/<extension>/manifest.tomlfor bundled first-party provider recipe data.crates/app/ironclaw_composition/src/factory.rsfor composition-time provider recipe wiring.crates/product/ironclaw_webui/src/product_auth/only for product auth HTTP setup/callback route surfaces.
ironclaw_auth /
ironclaw_secrets through Reborn composition.
Files not to touch
For a normal extension, do not touch these:- Reborn loop strategy code (
crates/loop/) to special-case your tool. crates/domains/ironclaw_llm/*to teach the model your tool name.crates/contracts/ironclaw_host_apifor one provider’s fields.crates/extensions/ironclaw_extension_registry/src/v2.rsto allow a one-off manifest shortcut.crates/substrates/ironclaw_networkto allow one provider host.crates/substrates/ironclaw_secretsto fetch one provider token.crates/kernel/ironclaw_approvalsto make one write operation easier.
Manifest v3 structure
All Reborn packages use:idmust be lowercase ASCII letters/digits plus_,-, or..- Capability IDs are
<extension_id>.<capability_name>. - Do not use slashes, uppercase, raw host paths, or
... - Registry extensions cannot claim effective first-party/system authority. Host composition decides effective trust.
All tool extensions: use [[tools]]
Publish each model-visible tool as a [[tools]] entry with its credentials
as [[tools.credentials]] blocks and an [auth.<vendor>] recipe for every
referenced credential vendor:
token_response is required for oauth2_code recipes — it maps JSON
pointers in the provider’s token response (at minimum access_token; add
refresh_token / expires_in for expiring tokens, as Notion does).
Host ports are derived from effects in v3 — do not declare
required_host_ports. The derived entries are validation vocabulary checked
against the host’s HostPortCatalog allowlist; concrete host-port adapters
are constructed by host-runtime services only after authorization and
obligation preparation, never from the manifest. The worked oauth2_code
examples (token-response captures, identity maps) are
crates/extensions/packages/slack/manifest.toml and
crates/extensions/packages/notion-mcp/manifest.toml; the worked api_key
example (form fields plus a validation probe) is in
crates/extensions/packages/github/manifest.toml.
Do not use the legacy [[host_api]] / [capability_provider.tools] shape or
top-level [[capabilities]] for new work. The registry still parses
already-installed v2 manifests for compatibility, but authoring is v3-only;
port a v2 file to [[tools]] when touching that extension.
Origin gate matrix
Every[[tools]] entry (and the [mcp] section) declares an
origin_gate_matrix: the per-origin approval-gate policy for who may invoke
the capability. Origins are loop_run (the model during an agent run),
product (a direct user gesture in the product), and automation (triggers,
cron, background jobs). Policies, from OriginGatePolicy in
crates/contracts/ironclaw_host_api/src/capability.rs:
forbidden— the origin may not invoke the capability at all. This is the default for every omitted origin, so a tool without a matrix cannot be invoked from any origin.ask_always— every invocation gates; persistent grants are never honored.gated_unless_granted— gates unless a scoped persistent/policy grant covers it. The normal choice for provider tools invoked by the model.consent_sufficient— the origin’s own gesture is the consent evidence (productonly; never valid forloop_runorautomation).ungated— no approval gate. Forloop_runthis requires a reviewed allowlist entry (UNGATED_LOOP_RUN_CAPABILITIES); additions are a security review, not a manifest edit.
loop_run policy on every capability by the
architecture ratchet
(crates/app/ironclaw_architecture_tests/tests/reborn_origin_gate_matrix_ratchet.rs).
The common provider-tool shape is
{ loop_run = "gated_unless_granted", product = "forbidden", automation = "forbidden" }.
Capability fields
Required per model-visible[[tools]] entry:
origin_gate_matrix: per-origin gate policy (previous section).id: stable<extension>.<name>capability ID.description: short, model-facing description.effects: accurate effects. Includeexternal_writefor provider writes, mutations, sends, deletes, comments, or workflow dispatches.default_permission: useaskfor writes and high-risk reads; useallowonly for low-risk read capabilities that policy deliberately permits.visibility: usuallymodel(the default).input_schema_ref: relative path to JSON schema. Required unless the tool binds astandard_op, which supplies the host-canonical schemas.output_schema_ref: relative path to JSON schema. Optional — omit it when the tool has no structured output contract (the gmail package declares none).prompt_doc_ref: relative path to concise operation guidance.[[tools.credentials]]: declare every credential the runtime may receive.
network_targets, max_egress_bytes, resource_profile.
Validation catches common mistakes:
- Credentials without
use_secretineffectsare rejected. This includes product-auth account credentials: product auth selects/refreshes the account, but runtime dispatch still uses a host-staged access-secret handle. - A credential
vendorwith no matching[auth.<vendor>]recipe is rejected, and an[auth.<vendor>]recipe no credential references is rejected. - Credential audiences must be HTTPS with a literal host — wildcards are rejected in v3.
- Duplicate effects and duplicate credential handles are rejected.
- Unknown fields are rejected throughout the manifest, with two deliberate
exemptions: the root
[metadata]table is free-form authoring metadata (ignored), and extra keys in an[auth.<vendor>].identitymap are named identity-claim pointers, not typos. - Schema and prompt refs must be relative package paths, not absolute paths,
URLs, backslash paths, or paths with
...
Effects and approvals
Use effects as authorization inputs, not as documentation. Common mapping:- Read-only API call with credentials:
["network", "use_secret"]. - Provider write: add
"external_write". - Local filesystem read/write: use
read_filesystem,write_filesystem,delete_filesystemas appropriate. - Process/CLI work: use
execute_codeorspawn_processas appropriate. - Money or irreversible financial actions: include
financial.
default_permission = "ask" is the normal default for anything with
external_write, financial, local write/delete, process execution, approval
mutation, extension mutation, or budget mutation.
Approvals are resolved by ironclaw_capabilities, ironclaw_approvals, and run
state. Runtime code must return a normal runtime error when blocked; it must not
prompt the user, mint approval leases, or resume turns directly.
Schemas and prompts
Schemas are part of the hot model-facing surface. They should make the desired input shape obvious and reject ambiguous or unsafe input before side effects. Follow these rules:- Use JSON Schema object inputs with
additionalProperties: falseunless the upstream provider truly requires arbitrary JSON. - Require the fields needed to construct one provider operation.
- Prefer provider-neutral names only when they are already established locally.
- Put path/ID/URL validation in runtime code too; schemas are not a security boundary.
- Output schemas may be provider raw JSON for compatibility, as GitHub and many Google WASM tools do, but typed output is better when the runtime owns the shape.
- What the tool does.
- Required identifiers.
- How to avoid common destructive mistakes.
- Any provider constraints the model should know.
HTTP and network integration
Runtime code must use host-mediated HTTP:- WASM tools call the WIT host HTTP import, as GitHub does through
near::agent::host::http_request. - Hosted MCP uses
McpHostHttpClientwithMcpRuntimeHttpAdapterand a host-owned egress planner.
- instantiate direct
reqwestclients in runtime code for provider API calls; - follow redirects yourself to bypass host policy;
- accept model-provided
Authorization, cookie, API-key, or token headers; - put credentials in URLs;
- widen global network policy for one extension.
- WASM credential injection is derived from manifest descriptors in
crates/kernel/ironclaw_host_runtime/src/wasm_credentials.rs. - Hosted MCP policy is planned in
crates/extensions/ironclaw_extension_host/src/mcp.rs. - GSuite WASM tools should declare narrow credential audiences and use host HTTP egress for Google API hosts.
- Shared HTTP enforcement and redaction live in
crates/kernel/ironclaw_host_runtime/src/egress/andcrates/substrates/ironclaw_network.
Accept,
Content-Type, API version, and User-Agent in runtime code. Credential headers
must come from [[tools.credentials]] and host egress injection.
Secrets and runtime credentials
Secrets are opaque handles in manifests and host API types. Runtime code should never see raw token material except as already-injected HTTP request data inside the host egress boundary. Declare a[[tools.credentials]] block for every credential a tool may
receive. The credential’s vendor names the credential authority; the
matching [auth.<vendor>] recipe in the same manifest defines how accounts
are set up (OAuth or API key). Host egress injects the selected account’s
access-secret handle at dispatch time:
handle: extension/runtime-local credential handle. Keep it stable.vendor: credential-authority namespace, for examplegithub,google, ornotion. Several extensions may share one vendor (gmail, drive, and calendar all usegoogle). This is not the extension id.scopes: scopes required for this capability. Used for account selection and scope mismatch checks.audience: exact HTTPS provider host (literal, no wildcards) the credential may be sent to. Optionalport.injection: header/query/path-placeholder injection target. Header is preferred.required: defaults totrue.
SecretStore::put, lease_once, or consume from an extension
runtime. Those are trusted setup/composition primitives, not tool APIs.
Product auth and OAuth
Use product-auth account sources for provider accounts. Current patterns:- GitHub uses provider
githuband injects a bearer token forapi.github.com. - GSuite uses provider
google, OAuth scopes per capability, and host egress to Google API hosts. - Notion uses provider
notion, DCR/OAuth recipe data wired by composition, and a bearer token formcp.notion.com.
- Add provider ID and shared scope vocabulary only if it must be shared across crates.
- Add the
[auth.<vendor>]recipe incrates/extensions/packages/<extension>/manifest.toml(method = "oauth2_code"or"api_key"), and keepironclaw_auth/src/engine/generic — the host engine runs the recipe; there is no per-vendor auth code. - Wire OAuth start/callback through product-auth services, not an extension-local map.
- Store access/refresh material as credential-account secret handles.
- Declare per-capability scopes on each
[[tools.credentials]]block. - Ensure auth-required dispatch errors map to structured product-auth requirements instead of leaking provider or backend details.
WASM implementation pattern
WASM tools implementcrates/lanes/ironclaw_wasm/wit/tool.wit:
- Prefer operation selection from
req.context.capability_id, as GitHub does. Do not let the model choose a hiddenactionthat can mismatch the capability ID. - Deserialize with unknown fields denied.
- Validate provider path segments, refs, IDs, pagination, and limits in runtime code before HTTP.
- Use host HTTP imports for provider calls.
- Return stable, sanitized error codes. Do not echo raw host egress errors, provider credentials, provider response bodies containing sensitive data, or raw backend messages.
- Keep schema and runtime input expectations in sync.
operation_comes_from_host_context_not_param_shapeserde_rejects_unknown_fields_before_egresssanitizes_host_egress_errors_without_leaking_details- path/ref validation tests
Hosted MCP implementation pattern
A hosted-MCP extension declares a top-level[mcp] section instead of
[runtime] — the two are mutually exclusive:
namespace must equal the extension id, and max_tools must be at least 1.
Tools are discovered from the server’s live catalog; a package may
additionally pin static [[tools]] entries beside [mcp] that a successful
discovery replaces (worked example: crates/extensions/packages/nearai-mcp/manifest.toml).
For host-bundled hosted HTTP MCP, Reborn composition:
- accepts only HTTPS endpoint URLs;
- rejects userinfo, query strings, fragments, wrong scheme, wrong host, and wrong path;
- derives a locked network policy from the manifest endpoint;
- projects
[[mcp.credentials]]to staged credential injections when the capability and endpoint audience match; - uses
RuntimeHttpEgressinstead of ambient MCP HTTP clients.
crates/extensions/packages/notion-mcp/manifest.toml):
one [mcp] section, an [auth.notion] OAuth recipe, and a bearer credential
for mcp.notion.com.
Do not make a hosted MCP runtime call directly from an extension lifecycle or
agent-loop path. Let the MCP runtime and host egress planner own it.
Packaging host-bundled extensions
Every host-bundled integration is a self-contained package directorycrates/extensions/packages/<extension>/ (manifest + schemas + prompts + any
WASM) beside one package module
crates/extensions/ironclaw_extension_support/src/packages/<extension>.rs.
The package module:
- embeds the manifest, schema, prompt, and WASM assets via
include_str!/include_bytes!; - defines lifecycle summaries and onboarding text;
- is collected through its row in
PACKAGESincrates/extensions/ironclaw_extension_support/src/packages/mod.rs.
available_extensions.rs home is being dissolved.
When adding a host-bundled package:
- Add manifest/assets under
crates/extensions/packages/<extension>/. - Add the package module in
crates/extensions/ironclaw_extension_support/src/packages/<extension>.rsand its row inPACKAGESin.../src/packages/mod.rs. - Add assets for every
input_schema_ref,output_schema_ref, andprompt_doc_ref. - Add onboarding only if setup is needed.
- Add tests that every manifest asset ref is packaged.
/system/extensions/<id>/ through the same
manifest host API path.
Publication to the model
Hot model-facing publication happens in:crates/kernel/ironclaw_host_runtime/src/capability_catalog.rs
- input/output schema files are bounded to 64 KiB;
- prompt docs are bounded to 16 KiB;
- schema files must parse as valid JSON Schema;
- only
visibility = "model"capabilities enter the model-facing catalog.
Approval and auth outcomes
A capability can stop before runtime dispatch for authorization or approval. That is expected. Do not bypass it. Approval path:Tests to add
Minimum tests for a Reborn tool:Manifest and packaging
- manifest parses as
reborn.extension_manifest.v3; - capability IDs use the extension prefix;
- every capability declares an
origin_gate_matrixwith aloop_runpolicy; - every capability has matching schema and prompt assets;
- credential capabilities include
use_secretand every credential vendor has an[auth.<vendor>]recipe; - write capabilities include
external_writeand default toask; - bundled package assets include every manifest ref;
- extension manifests use
[[tools]]entries, never the legacy[[host_api]]/[capability_provider.tools]or top-level[[capabilities]]shapes.
crates/extensions/ironclaw_extension_registry/tests/manifest_v3_contract.rscrates/app/ironclaw_architecture_tests/tests/reborn_origin_gate_matrix_ratchet.rscrates/kernel/ironclaw_host_runtime/src/capability_catalog.rstests
Runtime behavior
For WASM:- operation comes from invocation context capability ID;
- unknown fields are rejected before egress;
- unsafe provider paths/refs are rejected;
- host egress errors are sanitized;
- auth status maps to auth-required rather than leaking backend detail;
- output-size/body-limit cases map to stable errors.
- planner denies wrong provider, wrong host, HTTP scheme, wrong path, query, fragment, and URL userinfo;
- planner emits locked network policy for the canonical endpoint;
- manifest runtime credentials project to staged injections.
Integration/caller-facing
Add a test through the actual call site that gates side effects:CapabilityHostor runtime adapter dispatch for capability invocation.- Extension lifecycle install/readiness path for package publication.
- Product-auth setup/callback path for OAuth-backed credentials.
Review checklist
Before opening a PR, verify:- No V1 architecture paths were touched.
- No runtime code fetches raw secrets.
- No runtime code creates ambient external HTTP clients for provider calls.
- Every provider write has
external_writeand defaultask. - Every credential audience is HTTPS and as narrow as possible.
- Every schema/prompt ref is package-relative and packaged.
- Auth-required paths include provider/scopes/requester extension context.
- Error messages are sanitized and stable.
- Relevant docs/specs and
FEATURE_PARITY.mdwere checked if behavior changed. - Targeted tests pass.
Concrete examples to copy
Copy these runtime, credential, and security patterns, not legacy manifest shape. If a manifest you encounter still uses the legacy v2 shapes ([[host_api]] / [capability_provider.tools] or top-level
[[capabilities]]), port the semantics into v3 [[tools]] entries before
extending it.
- GitHub WASM operation dispatch:
crates/extensions/packages/github/wasm-src/src/lib.rs - GitHub host HTTP request wrapper:
crates/extensions/packages/github/wasm-src/src/request.rs - GitHub manifest credential/effect semantics:
crates/extensions/packages/github/manifest.toml - Google Drive WASM OAuth scopes by operation:
crates/extensions/packages/google-drive/manifest.toml - Gmail and Google Calendar are bundled
first_partyruntimes sharing thegooglevendor — a reference for the first-party lane, not for WASM. - Notion hosted MCP credential/effect semantics:
crates/extensions/packages/notion-mcp/manifest.toml - Hosted MCP egress planner:
crates/extensions/ironclaw_extension_host/src/mcp.rs - Notion OAuth provider wiring:
crates/app/ironclaw_composition/src/factory.rs - Hot capability catalog:
crates/kernel/ironclaw_host_runtime/src/capability_catalog.rs - Host HTTP egress service:
crates/kernel/ironclaw_host_runtime/src/egress/ - Manifest v3 contract (v2 remains the legacy/resolved model):
crates/extensions/ironclaw_extension_registry/src/v3.rs
Quick implementation checklist
- Pick the implementation: WASM (
[runtime] kind = "wasm"), hosted MCP ([mcp]), or a channel surface ([channel]). - Create the package directory
crates/extensions/packages/<extension>/withmanifest.toml,schemas/,prompts/, and anywasm/module. - Write a
reborn.extension_manifest.v3manifest with[[tools]]entries (each with anorigin_gate_matrix),[[tools.credentials]], and an[auth.<vendor>]recipe per credential vendor, and make it flow through extension registry discovery/publication. - Add schemas and prompt docs for every model-visible capability.
- Implement runtime code using host services only.
- Declare credentials with narrow HTTPS audiences and provider scopes.
- Add packaging/onboarding only if host-bundled.
- Add manifest, packaging, runtime, auth/approval, and integration tests.
- Run targeted tests.
- Check docs/specs and
FEATURE_PARITY.mdfor behavior-status updates. - When your tool is packaged and tested, submit it to IronHub so other agents can discover and install it. See Contributing.