Take a plugin from scaffold to activation with aomi-build. Follow the verified workflow, then use the complete command and flag reference.
aomi-build scaffolds, compiles, deploys, and activates a plugin. It ships as a binary from the aomi-sdk crate on crates.io. A deployed plugin combines its tools and system prompt on the runtime. Aomi calls this deployed unit an App, which is the term used in CLI output, aomi.toml, and the Developer Platform.This page takes a plugin from your own GitHub repo to live on Aomi. You work in your own repo the whole time. You never open a PR against aomi-labs/community-apps, and you never need write access to it. The backend does that part for you through the Aomi GitHub App. The walkthrough comes first; the complete command reference follows it.
Verified 2026-09-22 against published aomi-sdk 5.1.1 at commit
2ef3e04. Version numbers move: always trust aomi-build sdk check over any
number written here.
Prefer a browser? The Developer Platform at build.aomi.dev supports connect, deploy, activate, and status. It also manages secrets, model provider keys, bots, and usage. The CLI and platform use the same backend, so you can move between them.
The whole flow is four commands:
connect → deploy → activate → status
aomi-build deploy runs the middle of that chain in one shot. Learn each step anyway, because when something fails you fix one step, not the whole flow.
The binary has no --version flag; --help is how you confirm the install. The cli feature builds aomi-build; add dev-runtime (--features cli,dev-runtime) to also get aomi-run, the local dev runtime for chatting with your plugin before you ship. There is no aomi-build on crates.io or npm: it is a binary built from the aomi-sdk crate, which is why the install command points at that crate.
Installed it before July 2026? Run the command again to update. Older builds are missing the --activation-token flag and a fix that lets deploy finish on its own after CI passes. Without them you will get stuck.
You can also run it without installing: prefix with cargo run -p aomi-sdk --features cli --bin aomi-build --. The install is just a convenience.
The CLI builds the plugin from the git repo root. It does not deploy a plugin that lives in a subdirectory such as app/ or apps/my-app/. If your plugin sits in a subfolder today, move Cargo.toml, aomi.toml, and src/ up to the repo root before you deploy. You can keep other folders, a ui/ frontend for example, alongside them.
name is the App slug. It must match the name in your dyn_aomi_app! macro.
platform is community.
git is your own source repo, the one you deploy from.
public = true lists your App in the community catalog. Set false to keep it private to you.
server_tags picks the tier your release loads on. ["prod"] goes live on production. Omit it and it defaults to ["staging"], which loads only on staging backends. Test on staging, then switch to prod to go live.
Do not skip this. A version mismatch fails the platform build, not your local one, so it is easy to miss until the deploy dies.
The version numbers above are only an example of the output shape. The required version moves often, sometimes more than once a week. Never hardcode it and never copy a number out of this page: run aomi-build sdk check and use whatever it reports. See When the platform bumps the SDK for what happens to an App that is already live when the number moves.
The backend deploys the commit you pushed to GitHub. Local changes you did not push do not exist as far as the deploy is concerned. If a deploy ever picks up old code, this is why.
aomi-build connect --platform community --repo you/my-app
This opens the install page for the Aomi Build GitHub App. In GitHub:
Pick the account or org that owns your repo.
Choose Only select repositories and select your plugin repo.
Click Install.
After you click install, GitHub sends you to a page that can look unrelated, even a 404. Ignore what the page shows. It is a callback, nothing more. The value you need is in the address bar: the URL ends in /installations/<number>. That number is your installation id.Back in the terminal, paste the installation id when the CLI asks, then paste your activation token. The CLI saves the backend URL, platform, and token to local config so later commands can drop those flags.If the browser cannot open from your terminal, print the URL instead:
aomi-build connect --platform community --repo you/my-app --no-browser
sdk check → preflight → deploy run → wait for ready → activate → verify loaded
It opens a PR on the platform repo for you, waits for the platform build, activates the release, and verifies the runtime loaded it. A full run looks like this:
Resolved source `you/my-app` to app_source_id 1554.Preflight passed for platform `community`. source_commit : 7601d95b9abd37ccbd7047509782331171e844b3 - my-app -> apps-144438915-r0ed7523bdf-my-app-7601d95b9abdDeployment started. id : dep_144438915_r0ed7523bdf_7601d95b9abd pr : https://github.com/aomi-labs/community-apps/pull/85 deployment : /path/to/my-app/.aomi/deployment.jsonWaiting for release readiness... build : building build : readyRelease is ready. - my-app : active=true artifact_ready=true loaded=trueDeployment verified: all activated apps are active, artifact-ready, and loaded.
The Waiting for release readiness step can sit for a few minutes while the platform build runs; that is normal. You are done when you see active=true artifact_ready=true loaded=true and the final Deployment verified line. The pr link is the platform PR the backend opened for your release; you can watch the build there. If it stops partway, do not rerun the whole thing blindly. Go to the step that failed; see When something goes wrong.
Deployment status platform : community deployment_id : dep_144438915_r0ed7523bdf_7601d95b9abd pr : https://github.com/aomi-labs/community-apps/pull/85 deploy_branch : publish local state : deployed=true activated=true backend : https://api.aomi.dev deploy state : ready - my-app (apps-144438915-r0ed7523bdf-my-app-7601d95b9abd) local : activated=true backend : active=true artifact_ready=true loaded
Add --json for machine readable output. You want active, artifact_ready, and loaded all true.Open chat.aomi.dev, find your App, and talk to it. If you deployed an update to an existing App, the new version replaces the old one. You will not see a duplicate.
This is the most common reason a working App stops working, and it happens without you touching anything.The platform pins a required aomi-sdk version. When that requirement moves, every release built against the old version stops being loadable. Your App disappears from the App picker in chat with no warning and no error message.The tell is in aomi-build deploy status:
- my-app (apps-...-my-app-...) backend : active=true artifact_ready=false not loaded
active=true still looks healthy, which is what makes this easy to miss. The signal is artifact_ready=false and not loaded. Confirm it with:
Your App is back once you see active=true artifact_ready=true loaded=true.
This applies to every deployed App, not just yours. If you deployed once and walked away, check aomi-build deploy status before assuming your App is still live.
It reads the release tag from .aomi/deployment.json, so run it from the repo root. Success prints release is ready. An App that built but never activated shows activate: false in status and never appears in chat.
Your activation token authorizes the deploy. There are two kinds:
An app token is scoped to one App. It works for building and app level actions.
A platform token authorizes platform level actions, including activation onto the prod tier.
Activation is a platform level action. If you activate with an app token you will see app token is not authorized for platform-level actions. Ask the Aomi team for a platform token and use that. The same platform token works across every App on the platform, so you do not need a new one per App.
If your plugin calls an outside API, declare the key in your plugin code, not on the platform. In src/lib.rs:
const API_FOOTBALL_KEY: Secret = Secret::new( "API_FOOTBALL_KEY", "API-FOOTBALL key for live fixtures. Optional: the app still runs without it.", false,);dyn_aomi_app!( app = tool::MyApp, // ... secrets = [API_FOOTBALL_KEY], namespaces = ["evm-core"]);
The third argument marks whether the key is required. Use false when the plugin still loads and does useful work without it. The Binance App in aomi-sdk is the reference for this pattern.For an operator-owned credential, read the value at tool call time with
resolve_secret_value, which checks three sources in order:
let key = resolve_secret_value(ctx, arg_value, "API_FOOTBALL_KEY", "no key set")?;// order: explicit tool argument → host secret vault → API_FOOTBALL_KEY env var
That order is why the same code path works locally, where the value comes from an env var, and deployed, where it comes from the host vault. For a key you own and are comfortable shipping in a public App, a fourth option is to add a default in code as the final fallback, so every user gets live data without setting anything.If every user supplies a separate credential, mark the declaration and use the
context-only resolver:
const USER_API_KEY: Secret = Secret::new("USER_API_KEY", "Your vendor API key.", true).user_owned();let key = resolve_user_secret_value( &ctx, "USER_API_KEY", "Add USER_API_KEY in App settings before using this tool.",)?;
User-owned credentials never fall back to a tool argument or process
environment variable. A missing user value therefore cannot silently use an
operator credential.
Only bundle a key in code if you are fine with it being public. A public App’s source and its built release are readable. Never hardcode a credential you would not put in a public repo.
Never log, persist, or return values from either secret resolver.
Once your App is active it shows up as a selectable agent in the Aomi chat. Here is how to open it and put it through its paces, using Goal Digger, a World Cup betting agent, as the example.
1
Open the chat
Go to https://chat.aomi.dev and sign in.
chat.aomi.dev, where your deployed App runs
2
Pick your App from the agent picker
At the bottom of the chat is the agent picker, the dropdown showing the current agent’s name. Open it and select your App. In the example that is Goal Digger.
3
Put it to work
Ask it what it can do, then give it a real task. For Goal Digger:
list your tools shows the full tool surface.
simulate Spain vs Germany runs its 50,000-simulation match engine.
who wins the World Cup? returns tournament odds.
best World Cup bet on Polymarket right now? finds the biggest edge versus the live market price.
Goal Digger listing its tools in the chat
That is the whole loop: you wrote a plugin, deployed it, activated it, and now anyone can select it in the chat and use it.
When you ask for help, send three clean blocks, each a command plus its output: the connect step, the deploy step, and the activate step. Leave out help text, compile logs, and doc excerpts. Isolated commands and their exact output are what let us reproduce your problem fast.
Reference verified against published aomi-sdk 5.1.1 at commit 2ef3e04 on 2026-09-22. deploy status and deploy activate are the canonical forms.
Beyond deploying, aomi-build scaffolds, compiles, and end to end tests a plugin. The full path from “external API docs” to “tested plugin” is a six stage pipeline. Every stage runs on its own, and new-app is the one shot orchestrator for the first stages plus the compile.
Running aomi-build with no subcommand launches an interactive wizard that walks connect then deploy then activate.Build and scaffold:
Subcommand
What it does
compile
Build every app plugin into plugins/. The everyday build command.
init <name>
Scaffold a bare app skeleton. Use when you are not driving from an OpenAPI spec.
new-app <p>
Orchestrator: gen-specs then gen-client then gen-tool then cargo build.
gen-specs <p>
Discover or fetch an OpenAPI spec and write the YAML.
gen-client <p>
Turn the OpenAPI YAML into a typed Rust client via progenitor.
gen-tool <p>
Scaffold the app crate and write one stub tool per operationId.
tighten-spec <p>
Sharpen loose additionalProperties: true schemas from real captured samples.
test-schema <p>
Validate the spec against the live API with schemathesis.
Deploy and activate:
Subcommand
What it does
connect
Install the Aomi GitHub App on your source repo and save your activation token. Run once, before your first deploy.
deploy
Send a deploy request to the backend. The backend reads your source through the connected GitHub App, opens a pull request, and CI builds the cdylib and cuts a release.
status
Read .aomi/deployment.json and the backend, and report whether the release is built and loaded.
activate
Tell a backend to fetch a published release by tag, validate it, and load it. Run with your activation token.
token
Mint, list, or revoke platform or app activation tokens.
apps
List a platform’s apps.
request
Legacy. Ask platform ops for onboarding details. Superseded by connect.
Here <p> is the platform slug, for example petstore or khalani.
Cross compile for a target triple, for example aarch64-apple-darwin.
--from-url <URL>
gen-specs, new-app
Direct spec URL when discovery does not find one.
--shared
stages 1 through 3, new-app
Treat artifacts as shared under ext/ instead of app local under apps/.
--no-tool
new-app
Stop after gen-client; skip tool scaffolding.
--force
gen-client
Regenerate even when output already exists.
--base-url <URL>
test-schema
Live API base URL to validate against.
Spec generation stages default to app local: every artifact lives under apps/<p>/. Pass --shared only when several plugins wrap the same upstream (say, multiple Apps over one exchange) and should reuse one client under ext/.
# One shot: gen-specs -> gen-client -> gen-tool -> cargo buildaomi-build new-app petstore# Point at the spec when discovery misses itaomi-build new-app petstore --from-url https://example.com/openapi.json# Stop after the client; skip tool scaffoldingaomi-build new-app petstore --no-tool
# No spec; hand author the toolsaomi-build init my-app
aomi-build compile # all apps into plugins/aomi-build compile --app x # one appaomi-build compile --release # release buildaomi-build compile --target aarch64-apple-darwin
After new-app finishes, the plugin compiles but its tools are mechanical, one per endpoint, with machine names. You make it useful by curating the tool layer (stage 4) with the authoring skills, then rebuilding.
aomi-build compile builds the apps inside an aomi-sdk style workspace and writes them into plugins/. If you are building a single standalone plugin crate, the kind you publish to community-apps, you do not need aomi-build. Build it with cargo build --release and find the plugin in target/release/.
# Infer concrete response types from real captured JSON.# Samples go directly in <platform>.samples/ named <operationId>.<status>.jsonmkdir -p ext/specs/khalani.samplescurl ... > ext/specs/khalani.samples/getQuote.200.jsonaomi-build tighten-spec khalani # prints the diff onlyaomi-build tighten-spec khalani --in-place # writes the tightened spec backaomi-build gen-client khalani --shared --force # regenerate with tighter types# Catch schema drift against the live APIaomi-build test-schema khalani --base-url https://api.hyperstream.dev
Each plugin carries one canonical e2e spec at apps/<platform>/test.json. It describes a real LLM run: an optional wallet seed, a list of user prompts, the tools expected per turn, optional wallet callbacks, and a final state assertion. The runner lives in the backend repo, not here. You point it at your compiled plugin with an env var:
cd apps/khalani && cargo buildAOMI_E2E_APP_PATH=.../apps/khalani/target/debug/libkhalani.dylib \ cargo test -p aomi-runtime --test local-app-e2e app_e2e_specs -- --nocapture
Env var
Required
Purpose
AOMI_E2E_APP_PATH
yes
Absolute path to the compiled dylib (or a manifest bundle directory).
ANTHROPIC_API_KEY
yes
Provider key for the real LLM call.
AOMI_E2E_SPEC
no
Override test.json discovery and run one explicit spec file.
test.json shape (abridged)
The spec runs turn by turn. expected_tools checks must_call (all listed) or any_of (at least one). final_assertion checks the user state, tool responses, and turn cap.
{ "user_story": "Plain English description shown in the test banner", "wallet_seed": { "address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", "chain_id": 1, "is_connected": true }, "turns": [ { "prompt": "Swap 100 USDC from Ethereum to ETH on Optimism via X.", "expected_tools": { "must_call": ["x_quote", "x_build_deposit"] } } ], "final_assertion": { "user_state": { "pending_txs": { "min_count": 1 } }, "no_errors": true, "max_turns": 30 }}
Two limits worth knowing. Host tools (stage_tx, simulate_batch, commit_txs) carry a model set topic arg, so listing them in must_call will not match; the runtime fires them internally during routed enforcement. And a terminal wallet:tx_complete callback consumes pending_txs, so assert max_count: 0 after a callback rather than min_count: 1.
The deploy half of aomi-build publishes your plugin source through the backend, then activates the resulting release. The CLI never clones a platform repo or pushes branches. It is a thin relay: deploy POSTs to the backend, and the backend reads your source through the connected Aomi GitHub App, opens a pull request, and lets CI build the cdylib and cut the release.Run these from your source repo, the crate that holds aomi.toml and src/lib.rs.
The backend identifies your source through the GitHub App install, recorded as app_source_id. The deployed App lands at apps/<installation-id>/<repo-key>/<app>/ on the community-apps publish branch, and CI publishes a release tagged apps-<installation-id>-<repo-key>-<app>-<short-commit>.
The first step for a new contributor. connect installs the Aomi GitHub App on your source repo and saves the activation token you use to activate releases. Run it once, before your first deploy.
It prints a browser URL to install the Aomi GitHub App. Install it on the repo that holds your plugin, then paste back the installation_id GitHub shows you. After that, every deploy reads your source through this install.
Flag
Meaning
--platform <NAME>
Platform to connect for. Scopes the install and the token check.
--installation-id <ID>
Connected GitHub App installation id. Prompted if omitted.
--backend <URL>
Backend base URL. Defaults to AOMI_BACKEND_URL, then saved config.
--activation-token <TOKEN>
Activation token to store, issued by your Aomi admin. Prompted if omitted.
--no-browser
Print the install URL instead of opening a browser.
# Preview the plan and run the preflight checks. Changes nothing.AOMI_BACKEND_URL=https://api.aomi.dev aomi-build deploy --dry-run# Real deploy.AOMI_BACKEND_URL=https://api.aomi.dev aomi-build deploy
deploy sends POST /api/platforms/:platform/deploy carrying your app_source_id. The backend reads your source through the GitHub App, opens a pull request, and CI builds and publishes the release. A successful deploy writes .aomi/deployment.json with the backend’s deployment record, including the release tags activate reads later.
Flag
Meaning
--repo <OWNER/REPO>
Source repository used to resolve its existing Project.
--backend <URL>
Backend base URL. Defaults to AOMI_BACKEND_URL.
--dry-run (alias of --preflight)
Preview the deployment manifest and run the preflight checks. No deploy.
--json
Print the plan or outcome as JSON.
--fix-sdk
Rewrite Cargo.toml/Cargo.lock to the backend-required aomi-sdk version before deploying when a mismatch is detected.
--dry-run is an alias of --preflight. Both preview the plan and run the checks without deploying.
Run by the app author with the activation token saved during connect. It tells the backend to fetch a release by tag, validate it, and load it. Run it from your source repo and it reads the release tags from .aomi/deployment.json, so usually you set only AOMI_APP_ACTIVATION_TOKEN and AOMI_BACKEND_URL and run aomi-build deploy activate.
# Activate every app from deployment.json.AOMI_APP_ACTIVATION_TOKEN=<your-activation-token> \AOMI_BACKEND_URL=https://api.aomi.dev \ aomi-build deploy activate# Activate a named subset.aomi-build deploy activate foo bar# Activate an explicit release tag.aomi-build deploy activate --release-tag apps-1-myrepo-foo-abc1234
deploy activate sends POST /api/platforms/:platform/apps/activate. By default it uses the release tags recorded in .aomi/deployment.json.
Flag
Meaning
[APPS]...
Apps to activate. Defaults to every app from .aomi/deployment.json.
--release-tag <TAG>
Activate this release tag. Repeat for multi-app activation.
--platform <NAME>
Platform tag. Falls back to deployment.json, then community.
--backend <URL>
Backend base URL. Defaults to AOMI_BACKEND_URL. Required.
--activation-token <T>
Your activation token. Defaults to AOMI_APP_ACTIVATION_TOKEN. Required.
--target-tag <TAG>
Backend server tag the release may load on. Repeatable.
--path <DIR>
Source repo for the deployment.json fallback. Default: .
When you pass app names with --release-tag, their count must match the tag count, and the backend verifies each app name matches its release tag.
Every deploy, including --dry-run, runs a validation pipeline and records the result in .aomi/deployment.json. It runs in four ordered stages. Each stage is a precondition for the next, so a failing gate short circuits the rest and downstream stages are recorded as skipped.Stages 1 and 2 are offline, computed from local git and aomi.toml. Stages 3 and 4 are online: they only run when a backend URL is available, and otherwise stay skipped.
Stage
Question it answers
workspace
Is the local tree shippable? (git_clean)
manifest
Does aomi.toml declare what we need? (platform_declared, git_declared)
platform
Can we resolve the platform repo and deploy branch? (backend_reachable, platform_resolved, branch_matches_contract, git_url_matches_platform)
backend
Will the backend actually accept this release? (server_tags_subset)
Each check is error (a gate that fails the stage and should block the deploy) or warn (advisory; downgrades the stage to warning but does not block). The two warn checks are git_declared and git_url_matches_platform, since a backend lookup can supply the repo and forks are tolerated. The big one to watch is branch_matches_contract: if your target branch is not the platform’s contractual deployment_branch, the push will not auto deploy.
A stage rolls up to passed (all checks passed), failed (an error check failed, blocked here), warning (only warn checks failed), or skipped (an upstream gate failed or inputs were absent, such as no backend URL).
.aomi/deployment.json is the deployment record the backend writes back next to your aomi.toml after a successful deploy. It carries the resolved plan, the release tags, and independent state flags:
deployed: the backend accepted the deploy and opened the pull request that CI builds.
activated: the backend wrote the app row with is_active = true.
A --dry-run deploy previews the plan and runs the checks but does not record a deploy. activate reads this file for its defaults, including the release tags, so running it from the same directory as a prior deploy lets you drop most flags.
Add .aomi/ to your .gitignore. It is a local artifact, and committing it tends to dirty your tree and trip git_clean on the next deploy.