Plugin authoring · Go and Rust
Start with one hook.
A Torana plugin receives a shared request or response format and returns a typed result. Start with the generated request hook, make one change, and test the compiled bundle before adding it to your pipeline.
First install Torana and put torana on your PATH. You also need the Go or Rust toolchain for the plugin you create. The example directories below are relative to your current working directory and can become their own repositories.
Keep the SDK and host together.
SDK v0.5.1 implements ABI major 1, contract revision 1. The SDK package version, plugin ABI, and Edge binary release are separate version numbers. The manifest declares "abi_version": "v1"; the SDK exports the exact contract value that the host checks when loading the bundle.
plugin new pins the SDK used by the Edge build that generates your project. Current Go scaffolds use module tag v0.5.1. Current Rust scaffolds include this dependency in Cargo.toml:
torana-plugin-sdk = "=0.5.1"Keep the exact crate version and the generated Cargo.lock so the project stays on the SDK release used by the host.
Create a Go plugin.
The Go SDK requires Go 1.25 or newer; the Go toolchain used to build Edge also satisfies that requirement. Choose a new or empty destination directory:
torana plugin new ../my-go-plugin --language go
(cd ../my-go-plugin && go test ./...)
torana plugin lint ../my-go-plugin
torana plugin build ../my-go-pluginThe scaffold creates the module and dependency checksums, plugin.wasm.go, a native test, plugin.json, and schema.json. Its request hook passes through unchanged. Register Go hooks in init(): the WASM reactor does not call main().
plugin lint checks Go source and manifest declarations, including hook registration and attributable capability use. plugin build runs that Go lint step and compiles plugin.wasm in the plugin directory with the required -buildmode=c-shared reactor mode.
Create a Rust plugin.
Install Rust 1.85 or newer, Cargo, and the Protocol Buffers compiler protoc. Add the WASI target, then generate and test the project:
rustup target add wasm32-wasip1
torana plugin new ../my-rust-plugin --language rust
cargo test --manifest-path ../my-rust-plugin/Cargo.toml
torana plugin build ../my-rust-pluginThe scaffold creates Cargo.toml, src/lib.rs with a typed request hook and native test, plugin.json, and a README. The hook logs the model name and passes the request through; its manifest requests env.log. The build command compiles for wasm32-wasip1 and places plugin.wasm in the plugin directory.
The source linter is Go-only. For Rust, use native cargo test checks and the compiled-bundle scenario below. Review and keep the Cargo.lock produced by Cargo before installation, which builds with --locked. Cargo build scripts execute native code during compilation; clone and review remote Rust projects before installing a local path.
Test the compiled bundle.
Create ../my-go-plugin/scenario.json yourself; scaffolding does not generate scenario files. This first case asserts that the starter hook preserves the request and produces no block, synthetic-response, route, or identity verdict:
{
"request": {
"model": "example",
"messages": [{"role": "user", "blocks": [{"text": {"text": "hello"}}]}]
},
"expected_request": {
"model": "example",
"messages": [{"role": "user", "blocks": [{"text": {"text": "hello"}}]}]
},
"expected_verdicts": {}
}torana plugin test ../my-go-plugin
torana plugin test ../my-go-plugin --scenario ../my-go-plugin/scenario.jsonThese are equivalent commands: scenario.json inside the plugin directory is the default. For the Rust starter, save the same case in ../my-rust-plugin/scenario.json and run torana plugin test ../my-rust-plugin. Rebuild the bundle after editing plugin code.
The scenario runs the compiled WASM through the production runtime and hook validation without contacting a provider. It uses the shared protobuf JSON format, so request messages contain ordered blocks. Update the input and expected output for your plugin’s behavior; an omitted expectation makes no assertion.
Scenarios can also cover responses, streams, HTTP hooks, ticks, and deterministic host-service fixtures. The test runtime approves the test bundle and grants its manifest’s requested permissions for that run. This does not approve your installed copy. See the scenario format and service fixtures ↗.
Declare what the hook needs.
Use typed pass-through or replacement results and handle helper errors. Declare every host call and the narrow ir.*.write permissions your changes exercise in plugin.json. Resource access also needs manifest declarations and operator bindings; the host checks those permissions when the plugin runs.
Keep request mutations deterministic and use the SDK’s provenance-aware helpers when changing content. Ordered blocks, signed content, tool results, and prompt-cache markers have preservation rules. The SDK’s author guide ↗ and plugin semantics ↗ explain those rules.
Give every harness the same plugin tools.
For setup and examples from the current plugins, start with the MCP tools guide.
A plugin can expose a small typed interface through Torana’s MCP server. Define the operation once and any connected harness can discover it—without rebuilding the same command for Claude Code, Codex, or another agent.
Add agent.json beside the plugin manifest and WASM bundle:
{
"schema_version": 2,
"namespace": {
"title": "My plugin",
"summary": "Inspect and control my workflow.",
"categories": ["workflow"]
},
"operations": [{
"id": "session.status",
"method": "GET",
"path": "/status",
"description": "Read this conversation's status.",
"risk": "read",
"idempotent": true,
"model_access": "read",
"conversation_binding": "required",
"output_schema": {"type": "object"}
}]
}The plugin’s manifest name is its namespace. model_access controls what the model may do: reads can run directly, changes can require the user’s confirmation, and private maintenance operations can stay model-inaccessible. Use conversation_binding: "required" for conversation-specific state; Torana supplies the verified conversation and tool-call identity instead of accepting an ID from the model.
Confirmed changes must declare a separate, model-inaccessible undo_operation. Store the previous value under the verified tool-call ID, and refuse an undo if something newer has replaced it. Torana keeps the confirmed input and binding encrypted; the user confirms and undoes through their harness, the local UI, or the CLI.
See the agent-operation authoring contract ↗ for the complete reversible-write example, and the control-plane guide ↗ for local API and CLI access.
Write for shared content, check both APIs.
Protocol bridges use the existing SDK v0.5.1 and ABI v1; the bridge feature itself does not require an SDK pin change or a rebuild of existing plugins. Request hooks receive shared client content with the upstream model alias; response hooks receive the actual upstream’s shared response before client serialization. Host accounting uses upstream-native usage, which can differ from the converted client counters.
Custom plugins that read Responses instructions, max_output_tokens, temperature, or top_p through raw provider extensions must move to the shared system-message and generation-parameter fields. Replacements that put those canonical wire members in provider_extensions_json are rejected. Follow the upgrade notes ↗.
Test accepted mutations against each intended bridge destination as well as native routes. The versioned upgrade guidance covers cache-prefix changes and rebuilding existing bundles.
Install and try it in your pipeline.
With your native and compiled-bundle checks passing, install the local project:
torana plugin install ../my-go-pluginUse ../my-rust-plugin for the Rust project. Follow the installation guide to select the running instance’s plugin directory, review the built bundle’s permissions, approve its digest, and enable it through the CLI or local UI. Then send a request through the provider endpoint and harness you intend to support.
When changing ABI contract revisions, rebuild plugins with the SDK used by your target Edge build and approve each new digest. The host rejects incompatible bundles at load time. Keep the previous host and its approved bundles together if you need to roll back.
Document the SDK and Edge revisions, permissions, configuration, and harnesses you tested. Keep your source in your own repository and request a catalogue listing when you want to share it.