← All documentation

How-to guides

Building a Mod

A developer's guide to packaging an .oomu file.

A Beta 2 Mod is a content package. It carries knowledge files an assistant can read and a small set of keys it can persist. It does not carry code, and nothing in it executes.

If you are arriving from Beta 1, read What changed first. It will save you building something OOMU will politely decline to run.

The complete format is in the Mod package specification. This page is the recipe.


What you can actually build

You want toCan a Mod do it?
Ship reference material an assistant draws on: a style guide, a glossary, a domain primer, structured reference dataYes. This is the use case.
Keep a small amount of state between sessionsYes, via declared keys.
Change how the assistant writes, via a system promptNo. Use agent instructions.
Run code, call an API, register a slash command, hook a turnNo.
Add a tool the assistant can invokeNo. Use a local MCP tool.

Step 1: Lay out the folder

house-style/
├── manifest.json
└── knowledge/
    ├── style.md
    └── terms.json

Content files must live under knowledge/ or content/, and must be .txt, .md, or .json. Those are the only two folder names and three extensions the ABI accepts.

Keep it small. The archive can hold 256 entries and 200 MB uncompressed, but a Mod that ships a handful of well-written reference documents is more useful than one that ships a library.


Step 2: Write the manifest

manifest.json must sit at the archive root.

{
  "id": "com.example.mods.house-style",
  "name": "House Style",
  "version": "2.0.0",
  "author": "Example Ltd",
  "description": "Our editorial standards, terminology, and worked examples.",
  "category": "writing",
  "entrypoint": "knowledge/style.md",
  "beta2Abi": {
    "schemaVersion": 1,
    "capabilities": [
      { "id": "mods.asset.read", "assets": ["knowledge/style.md", "knowledge/terms.json"] }
    ]
  }
}

Rules worth committing to memory, because each one rejects the package if broken:

FieldRule
idReverse-DNS. Lowercase only, plus digits, ., _, -. 3–200 characters, alphanumeric at both ends.
name1–120 characters.
version1–64 characters, no whitespace.
author1–120 characters. Shown to the user.
description1–500 characters. Shown to the user.
entrypointOptional, but if present the file must exist in the archive.

entrypoint is validated and recorded, never executed. Point it at your main knowledge file, or leave it out.

Adding state

If your Mod needs to remember something, declare both halves with the same keys:

"capabilities": [
  { "id": "mods.asset.read",    "assets": ["knowledge/style.md"] },
  { "id": "mods.state.read",    "keys": ["last-reviewed", "revision"] },
  { "id": "mods.state.replace", "keys": ["last-reviewed", "revision"] }
]

Declaring state.replace without a matching state.read, or with a different key set, rejects the manifest. Keys follow the same character rules as id, capped at 64 characters.


Step 3: Package it

A plain ZIP, renamed:

cd house-style && zip -r ../house-style.oomu manifest.json knowledge

Then rename to .oomu if your tooling did not:

mv ../house-style.zip ../house-style.oomu

Two things to check before you ship:

  • manifest.json is at the root, not inside a wrapper folder. zip -r ../out.oomu house-style/ produces a nested layout that OOMU rejects.
  • No two entries differ only in case. Knowledge/style.md and knowledge/style.md in the same archive is an error.

Step 4: Install and check it

Open Mods → Install Mod and pick your file. Then:

  1. Confirm the badge reads Unsigned with "Local package without a signature. OOMU verifies its files before use.": expected for a package you built yourself.
  2. Expand {count} features and confirm every capability you declared is listed, and nothing you did not declare appears.
  3. Choose Verify. It re-hashes the installed files against the recorded digest.
  4. Enable it, then give an agent access under Chat → Agents → Mods.

If something you expected is marked Not supported in this Beta 2 build, you have declared a Beta 1 feature. See below.


Signing

Signing is optional, and it changes the badge, not the capabilities. A Mod's powers come entirely from its beta2Abi declaration; a signature does not widen them.

BadgeHow to earn it
UnsignedShip nothing extra.
Verified publisherSign the canonical payload digest with your Ed25519 key and include the public key in capability_bundle.publisher.publicKey.
OOMU reviewedRequires a signature from OOMU's own review key. Not something you can self-issue.

Both use Ed25519 over domain-separated messages, and the declared payloadSha256 must match the computed payload digest exactly. If you include trust material that does not verify, the package is rejected outright: a broken signature is treated as worse than no signature, not equivalent to it.

Exact envelope shapes are in the specification.


What changed from Beta 1

Beta 2 has no Mod code runtime. These declarations still parse, and are reported to the user as unsupported, but do nothing:

Beta 1Status
entrypoint: "engine.wasm" or .jsNot executed.
default_system_prompt / promptNot applied.
hooks, including shield_gate.on_promptNot registered.
commands, slashCommands, triggers, regexNot registered.
endpoints, permissions.allowed_hostsRecorded, not granted. No network access.
capability_bundle as a capability declarationRead only for signature material.

Porting a Beta 1 Mod

If your Beta 1 Mod was primarily a default_system_prompt, its content belongs in an agent's instructions, not in a package.

If it shipped reference material alongside code, keep the material: move it under knowledge/, declare it with mods.asset.read, drop the rest, and you have a working Beta 2 Mod.

If it was genuinely a tool (it called an API or ran a process) a Mod is the wrong vehicle now. Build it as an MCP server and connect it under Connections → Tools on this Mac, where you choose the executable yourself and control per-tool whether it may make changes.