Skip to main content
Verified against aomi-sdk@08b21f9 on 2026-06-29.
This page is about authoring an Aomi App: how the crate is laid out, what goes in each file, and how you wire it together. An App is a Rust crate that compiles to a dynamic plugin the Aomi runtime hot loads. It wraps an external API as a small set of typed tools, ships a preamble that tells the model how to use them, and declares which host capabilities it needs. When you are ready to compile and ship, follow the forward links at the bottom of this page. This page does not cover the build or deploy steps.
Throughout this page, App (capitalized) means the deployed unit: a cdylib crate the runtime loads. The earlier docs talked about aomi_chat::CoreAppBuilder. That is an internal runtime crate and is not how you author a plugin. The real authoring model is the one below: the dyn_aomi_app! macro plus the DynAomiTool trait from the public aomi-sdk crate.

What you are building

The Aomi SDK lets you wrap any crypto API as a dynamic plugin. The runtime loads your compiled plugin and routes chat to it. The apps in the SDK repo show the range you can cover:

DeFi protocols

Wrap a DEX, lending market, or staking protocol as tools the chat can drive. See defillama, morpho.

Prediction markets

Market discovery, search, and trading flows. See polymarket, kalshi.

Cross-chain intents

Bridge and intent-order clients. See khalani, across, lifi.

Social and accounts

Feeds, posts, user data, wallet and account tooling. See x, neynar, para.

The standard file split

Every App follows the same layout across three files. Keep this split even for small apps. It keeps the surface the model sees easy to scan and keeps API details out of your tool logic.
The fastest way to start is to copy sdk/examples/app-template-http from the SDK repo and adapt it. It is the canonical reference for this layout.

src/lib.rs: registration and preamble

lib.rs is short on purpose. It declares the other modules, holds the preamble, and calls dyn_aomi_app! to register the App. Here is the full lib.rs from the HTTP template:
The dyn_aomi_app! macro is the single registration point. It generates the plugin entry the runtime looks for, so you never write #[no_mangle] by hand. Its fields:

Writing the preamble

The preamble is the system prompt. It is the single most important thing you author, because it decides whether the model picks the right tool at the right time. Write it as plain prose with clear headings, not as code. A strong preamble does four things:
1

States the role

One or two sentences on what the App is and what it must never do. The DeFiLlama app opens with “You are a read only analyst… never to execute trades.”
2

Maps capabilities to tools

List what the App can do and name the exact tool for each job, so the model learns the mapping. “Token prices: defillama_get_token_price for current price.”
3

Pins down identifiers and conventions

Spell out the formats the API expects: coin id schemes, protocol slugs, chain names, timestamp units. This is where most tool call errors are prevented.
4

Gives workflow guidance

Tell the model how to chain tools for common questions. “Comparison questions: start with list_protocols, then dig into the winners with get_protocol_tvl.”
Here is the shape, trimmed from the real defillama app:

Defining tools

Tools live in src/tool.rs. Each tool is a type that implements DynAomiTool. The trait ties together your app type, the typed argument struct, the name and description the model reads, and a run function that does the work and returns JSON.
The pieces that matter:
  • NAME is the function name the model calls. Prefer names shaped by intent like search_*, get_*, build_*, and submit_* over raw endpoint wraps.
  • DESCRIPTION is read by the model when it decides whether to call the tool. Write it for the model, not for a human reader.
  • run returns Result<Value, String>. On error, return a short actionable string. Normalize upstream API errors so the model gets a clean message, not a raw stack.

Typed, documented arguments

The argument struct lives in src/client.rs. Derive Deserialize and JsonSchema, and put a doc comment on every field. Those doc comments become the parameter descriptions the model sees, so they directly shape how it fills the call.
Doc comments on argument fields are read by the model. A vague field comment is a vague tool. Give a concrete example value in each one, the way the template does.

The client and models

src/client.rs owns the HTTP plumbing so your tools stay readable. Build the client once, set a timeout, normalize errors into strings, and keep the response shapes here.
For full trait signatures (DynAomiTool, DynToolCallCtx, the dyn_aomi_app! macro, and the aomi_sdk::testing helpers like TestCtxBuilder and run_tool), see the SDK Reference.

Declaring host namespaces

The namespaces field in dyn_aomi_app! declares which host capabilities your App needs. A read only HTTP wrapper needs none, so it uses namespaces = [], like the template above. An App that reads chain state or stages transactions declares a namespace, the way the defillama app does:
Host capabilities are a public contract, not private infrastructure. Apps that execute transactions may assume the host runtime exposes tools such as: The transaction model is always stage first, then simulate, then commit. Describe the host capabilities you rely on in your tool descriptions and preamble. Do not refer to private namespaces, and do not assume a hidden fallback network. If a host does not implement a capability, it surfaces that absence rather than silently redirecting.
Apps that execute across several steps (like polymarket or khalani) return a ToolReturn with route hints instead of a bare JSON value, so the host can chain a wallet signature back into the next tool call. The full route hint contract lives in the SDK repo’s docs/host-interop.md and the SDK Reference.

The aomi.toml manifest

If you are building a community or partner App in your own source repo, you also author an aomi.toml. This manifest tells the deploy tooling where your App ships and how the backend should load it. (Official Apps that live in the SDK repo do not use aomi.toml; they ship through the repo’s release pipeline instead.)
Field reference:
access_token must point at an environment variable, not a literal token. Write access_token = "$MY_GH_TOKEN". A literal like "ghp_xxxx" is rejected at parse time so a committed config can never leak a secret. The public community-apps repo does not need this field at all; omit it.

Pin the SDK version

Your Cargo.toml must pin aomi-sdk to the exact version the platform expects, and your crate must build as a cdylib:
=3.0.3 is the pin the community-apps platform expects today. Always confirm against platform.json’s required_sdk_version in the platform repo before you deploy; a mismatch fails with an sdk_version mismatch error.
Pin the platform requirement, not the crate’s own version. The aomi-sdk crate ships at 3.0.4, but the community platform requires =3.0.3. Pin what platform.json names under required_sdk_version, today =3.0.3, not the crate’s latest. A build pinned to anything else is rejected at activation.

Authoring guidelines

  • Prefer one App crate per external product or ecosystem.
  • Keep the toolset small. 3 to 8 tools per App is typical for a clean workflow.
  • Keep tool arguments typed and documented with JsonSchema. The doc comments are read by the model.
  • Normalize upstream API errors into short actionable strings.
  • Keep assumptions about one host out of your prompts.
  • If your App needs signing or execution, depend only on the public host capabilities above.

Next steps

You have authored the crate. Now compile it, test it, and ship it.

Compile and run

Use the aomi-build CLI to compile your plugin and aomi-run to exercise it locally before you ship.

Deploy and activate

Use aomi-build deploy to send your source to the backend, then aomi-build activate so the backend loads it.

SDK Reference

Full trait definitions: DynAomiTool, the dyn_aomi_app! macro, the host-interop contract, and the testing helpers.

How it works

See the full request flow once your App is loaded on the runtime.
Last modified on July 27, 2026