> ## Documentation Index
> Fetch the complete documentation index at: https://aomilabs-victor-docs-redirect-build-overview.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Building an App

> How an Aomi App is built. The standard crate file split, the aomi.toml manifest, the preamble, tools, and host namespaces. Authoring only; compiling and shipping link forward.

<Info>Verified against aomi-sdk\@main on 2026-06-04.</Info>

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.

<Note>
  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.
</Note>

## 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:

<CardGroup cols={2}>
  <Card title="DeFi protocols" icon="coins">
    Wrap a DEX, lending market, or staking protocol as tools the chat can drive. See `defillama`, `morpho`.
  </Card>

  <Card title="Prediction markets" icon="chart-line">
    Market discovery, search, and trading flows. See `polymarket`, `kalshi`.
  </Card>

  <Card title="Cross-chain intents" icon="shuffle">
    Bridge and intent-order clients. See `khalani`, `across`, `lifi`.
  </Card>

  <Card title="Social and accounts" icon="user">
    Feeds, posts, user data, wallet and account tooling. See `x`, `neynar`, `para`.
  </Card>
</CardGroup>

## 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.

```text theme={null}
my-app/
├─ Cargo.toml
├─ aomi.toml          (contributor apps only — see "The aomi.toml manifest")
└─ src/
   ├─ lib.rs          App registration + preamble (via dyn_aomi_app!)
   ├─ client.rs       HTTP client + typed models + tool arg structs
   └─ tool.rs         Tool implementations (DynAomiTool)
```

| File                | What it owns                                                                                                                                                      |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`src/lib.rs`**    | The preamble string and the `dyn_aomi_app!` registration. This is your manifest surface: app name, version, the tool list, and the host namespaces you depend on. |
| **`src/client.rs`** | HTTP client setup, auth headers, response models, and the typed argument structs your tools accept. Outside API details stay here.                                |
| **`src/tool.rs`**   | The tool implementations. Each tool implements `DynAomiTool`, carries a description the model reads, and maps client responses into stable JSON.                  |

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:

```rust theme={null}
use aomi_sdk::*;

mod client;
mod tool;

const PREAMBLE: &str = r#"## Role
You are a lightweight example app for market lookup and simple HTTP API integration.

## Purpose
- Demonstrate the recommended Aomi app file structure
- Show how to wrap a public JSON API with typed tools
- Keep the example free of private infrastructure assumptions

## Workflow
1. Use `search_coins` to find a CoinGecko asset ID.
2. Use `get_coin_price` to fetch the current USD price for that asset.
"#;

dyn_aomi_app!(
    app = client::HttpJsonExampleApp,
    name = "http-json-example",
    version = "0.1.0",
    preamble = PREAMBLE,
    tools = [client::SearchCoins, client::GetCoinPrice,],
    namespaces = []
);
```

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:

| Field        | Meaning                                                                                                            |
| ------------ | ------------------------------------------------------------------------------------------------------------------ |
| `app`        | Your app type. It is a small struct (often unit-like) that holds any shared state.                                 |
| `name`       | The app slug. Kebab-case. This is how the runtime and the URL refer to your App.                                   |
| `version`    | The app version string.                                                                                            |
| `preamble`   | The system prompt that shapes behavior (see below).                                                                |
| `tools`      | The list of tool types the model can call. Keep it small: 3 to 8 tools is typical.                                 |
| `namespaces` | The host capability sets your App depends on. `[]` for a read only HTTP wrapper (see "Declaring host namespaces"). |

## 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:

<Steps>
  <Step title="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."
  </Step>

  <Step title="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."
  </Step>

  <Step title="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.
  </Step>

  <Step title="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`."
  </Step>
</Steps>

Here is the shape, trimmed from the real `defillama` app:

```rust theme={null}
const PREAMBLE: &str = r#"## Role
You are **DeFi Data Assistant**, a read-only analyst backed by DeFiLlama's free public API.
Your job is to answer questions about token prices, protocol size, chain activity, and yield
opportunities — never to execute trades.

## Capabilities
- **Token prices** -- `defillama_get_token_price` for current price.
- **Protocols** -- `defillama_list_protocols` ranks protocols by current TVL.
- **Chains** -- `defillama_get_chain_tvl` returns the chain leaderboard by TVL.

## Conventions
- **No auth.** DefiLlama is a free public API; no key is needed.
- **Coin identifiers** use `coingecko:<id>` (e.g. `coingecko:bitcoin`) or `<chain>:<address>`.
- **Protocol slugs** match DefiLlama URLs (e.g. `aave-v3`, `uniswap`, `lido`).

## Formatting
- Format USD amounts: TVL > $1B as `$X.XXB`, > $1M as `$XXX.XM`, otherwise `$X,XXX`.
"#;
```

## Defining tools

```text theme={null}
You are the MyCoinDex trading assistant.

## Capabilities
- Check real-time token prices
- View user portfolio and P&L
- Execute trades (with user confirmation)
- List available trading pairs

## Rules
- Always confirm with the user before executing a trade
- Show prices in USD unless the user requests otherwise
- If you don't have data for a token, say so clearly
- Never fabricate prices or portfolio data

## Tone
- Professional but approachable
- Concise responses unless the user asks for detail
- Use bullet points for lists of data
```

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.

```rust theme={null}
use crate::client::*;
use aomi_sdk::*;
use serde_json::{Value, json};

pub(crate) struct SearchCoins;

impl DynAomiTool for SearchCoins {
    type App = HttpJsonExampleApp;
    type Args = SearchCoinsArgs;
    const NAME: &'static str = "search_coins";
    const DESCRIPTION: &'static str =
        "Search CoinGecko for matching assets and return coin ids developers can use in follow-up price calls.";

    fn run(
        _app: &HttpJsonExampleApp,
        args: Self::Args,
        _ctx: DynToolCallCtx,
    ) -> Result<Value, String> {
        let client = CoinGeckoClient::new()?;
        let value = client.get_json("/search", &[("query", args.query.as_str())])?;
        // ...normalize into stable JSON...
        Ok(json!({ "query": args.query, "matches": value }))
    }
}
```

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.

```rust theme={null}
use aomi_sdk::schemars::JsonSchema;
use serde::Deserialize;

#[derive(Debug, Deserialize, JsonSchema)]
pub(crate) struct SearchCoinsArgs {
    /// Free-form search query such as `bitcoin`, `eth`, or `dogecoin`
    pub(crate) query: String,
}

#[derive(Debug, Deserialize, JsonSchema)]
pub(crate) struct GetCoinPriceArgs {
    /// CoinGecko coin id such as `bitcoin` or `ethereum`
    pub(crate) coin_id: String,
}
```

<Note>
  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.
</Note>

### 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.

```rust theme={null}
pub(crate) struct CoinGeckoClient {
    pub(crate) http: reqwest::blocking::Client,
}

impl CoinGeckoClient {
    pub(crate) fn get_json(&self, path: &str, query: &[(&str, &str)]) -> Result<Value, String> {
        let url = format!("{API_BASE}{path}");
        let response = self.http.get(&url).query(query).send()
            .map_err(|e| format!("CoinGecko request failed: {e}"))?;
        let status = response.status();
        let body = response.text().unwrap_or_default();
        if !status.is_success() {
            return Err(format!("CoinGecko error {status}: {body}"));
        }
        serde_json::from_str(&body).map_err(|e| format!("CoinGecko decode failed: {e}"))
    }
}
```

<Note>
  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](/reference/sdk-api).
</Note>

## 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:

```rust theme={null}
dyn_aomi_app!(
    app = tool::DefiLlamaApp,
    name = "defillama",
    version = "0.1.0",
    preamble = PREAMBLE,
    tools = [tool::GetTokenPrice, tool::ListProtocols, tool::GetChainTvl],
    namespaces = ["evm-core"]
);
```

Host capabilities are a public contract, not private infrastructure. Apps that execute transactions may assume the host runtime exposes tools such as:

| Host tool            | What it does                                                               |
| -------------------- | -------------------------------------------------------------------------- |
| `view_state`         | Encode calldata from ABI arguments and run a read only `eth_call`.         |
| `run_tx`             | Encode calldata and simulate a call that changes state without staging it. |
| `stage_tx`           | Stage an EVM transaction for later simulation and signing.                 |
| `simulate_batch`     | Simulate one or more staged transactions before prompting a wallet.        |
| `commit_tx`          | Ask the user wallet to sign and broadcast one staged transaction.          |
| `evm_commit_message` | Ask the user wallet to sign an EIP-712 typed-data message.                 |

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.

<Note>
  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](/reference/sdk-api).
</Note>

## 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.)

```toml theme={null}
[app]
name         = "my-cool-app"            # slug — kebab-case, becomes the release tag
display_name = "My Cool App"            # human-readable name
platform     = "community"              # the platform you publish to
git          = "https://github.com/aomi-labs/community-apps"
public       = true                     # visible to all backend users

# Optional. Which backend tier may load this release.
# Omit to default to ["staging"]. Set to ["prod"] once tested,
# or ["staging", "prod"] for both.
# server_tags = ["staging"]
```

Field reference:

| Field          | Required | Meaning                                                                   |
| -------------- | -------- | ------------------------------------------------------------------------- |
| `name`         | yes      | The App slug. Kebab-case. Becomes the release tag.                        |
| `display_name` | yes      | A name shown to people, easy to read.                                     |
| `platform`     | yes      | The publishing platform, for example `community`.                         |
| `git`          | yes      | The platform repo your App ships into.                                    |
| `public`       | yes      | Whether the App is visible to all backend users.                          |
| `server_tags`  | no       | The backend tiers this build may load on. Defaults to `["staging"]`.      |
| `access_token` | no       | Only for private platform repos. A `$ENV_VAR` reference, never a literal. |

<Warning>
  `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.
</Warning>

### 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`:

```toml theme={null}
[package]
name = "my-cool-app"
version = "0.1.0"
edition = "2024"

[lib]
crate-type = ["cdylib"]

[dependencies]
aomi-sdk   = "=0.1.20"          # match platform.json's required_sdk_version
serde      = { version = "1", features = ["derive"] }
serde_json = "1"
```

<Note>
  `=0.1.20` 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 CI with an `sdk_version mismatch` error.
</Note>

## 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.

<CardGroup cols={2}>
  <Card title="Compile and run" icon="hammer" href="/reference/cli-toolchain">
    Use the `aomi-build` CLI to compile your plugin and `aomi-run` to exercise it locally before you ship.
  </Card>

  <Card title="Deploy and activate" icon="rocket" href="/reference/cli-toolchain">
    Use `aomi-git deploy` to stage your source into the platform repo, then request activation so the backend loads it.
  </Card>

  <Card title="SDK Reference" icon="book" href="/reference/sdk-api">
    Full trait definitions: `DynAomiTool`, the `dyn_aomi_app!` macro, the host-interop contract, and the testing helpers.
  </Card>

  <Card title="How it works" icon="diagram-project" href="/concepts/how-it-works">
    See the full request flow once your App is loaded on the runtime.
  </Card>
</CardGroup>
