How-to guides
Building a Mod
A developer's guide to packaging an .oomu file.
A Mod is a content package. It carries knowledge files an assistant can read, a small set of keys it can persist, and a short piece of guidance. It does not carry code, and nothing in it executes.
If you are arriving from Beta 1, read What changed from Beta 1 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 to | Can a Mod do it? |
|---|---|
| Give an agent a standing house style or tone | Yes. An ambient Mod: up to 600 characters of instructions, applied whenever an agent that uses it is in a chat. |
| Ship reference material an assistant draws on: a style guide, a glossary, a domain primer, structured reference data | Yes. Knowledge files, read on demand. |
Offer named tasks people call with a tag, such as @audit.guide | Yes. A tagged Mod with tasks. |
| Keep a small amount of state between sessions, per project or everywhere | Yes, via declared keys. |
| Let a tagged task use a built-in OOMU action, such as creating a Gmail draft | Yes, if you declare it. The action runs through OOMU's own verified executor, never your code. |
| Run code, call an API, register a slash command, hook a turn | No. |
| Add a brand-new tool the assistant can invoke | No. Use a local MCP tool. |
Step 1: Lay out the folder
supplier-audit/
├── manifest.json
└── knowledge/
└── audit-guide.md
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 accepted.
An ambient Mod needs nothing but manifest.json.
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. Current Mods use the declarative manifest, oomu.mod.manifest.v2.
An ambient Mod
{
"schemaVersion": "oomu.mod.manifest.v2",
"id": "com.example.mods.executive-tone",
"name": "Executive Tone",
"version": "1.0.0",
"author": "Example Ltd",
"publisher": "example",
"description": "Concise executive communication.",
"category": "Writing",
"activationMode": "ambient",
"instructions": "Lead with the answer. Write short, clear sentences. Separate observed facts from assumptions.",
"stateScope": "global"
}
An ambient Mod may contribute only instructions, at most 600 characters. Tasks, knowledge, and required capabilities are rejected on an ambient Mod.
A tagged Mod with tasks
{
"schemaVersion": "oomu.mod.manifest.v2",
"id": "com.example.mods.supplier-audit",
"name": "Supplier Audit",
"version": "1.0.0",
"author": "Example Ltd",
"publisher": "example",
"description": "Evidence-led supplier reviews within the current project.",
"category": "Business",
"activationMode": "tagged",
"tag": "@SupplierAudit",
"shortAlias": "audit",
"instructions": "Review suppliers using evidence and the audit guide. Distinguish observed facts from assumptions.",
"tasks": [
{ "id": "guide", "displayName": "Read the guide", "tag": "@audit.guide",
"action": "mods.asset.read", "targetAsset": "knowledge/audit-guide.md" },
{ "id": "save", "displayName": "Save audit notes", "tag": "@audit.save",
"action": "mods.state.replace", "stateKey": "audit-notes" },
{ "id": "notes", "displayName": "Read audit notes", "tag": "@audit.notes",
"action": "mods.state.read", "stateKey": "audit-notes" }
],
"requiredCapabilities": ["mods.asset.read", "mods.state.read", "mods.state.replace"],
"knowledgeIndex": [
{ "path": "knowledge/audit-guide.md", "name": "Supplier audit guide",
"description": "Evidence checklist and reporting conventions." }
],
"stateScope": "project",
"beta2Abi": {
"schemaVersion": 1,
"capabilities": [
{ "id": "mods.asset.read", "assets": ["knowledge/audit-guide.md"] },
{ "id": "mods.state.read", "keys": ["audit-notes"] },
{ "id": "mods.state.replace", "keys": ["audit-notes"] }
]
}
}
beta2Abi keeps its historical name. It is still where a Mod declares exactly which files it may read and which keys it may keep.
Rules worth committing to memory, because each one rejects the package if broken:
| Field | Rule |
|---|---|
id | Reverse-DNS. Lowercase only, plus digits, ., _, -. 3–200 characters, alphanumeric at both ends. |
name | 1–120 characters. |
version | 1–64 characters, no whitespace. |
author | 1–120 characters. Shown to the user. |
description | 1–500 characters. Shown to the user. |
instructions | Up to 600 characters for an ambient Mod, 2,000 for a tagged one. No control characters except newline and tab. |
tag, shortAlias | Letters and digits only. The alias is 1–8 characters. Names OOMU uses itself, such as mail, calendar, search, document, and web, are reserved. |
tasks | Up to 16. Every task tag must start with the Mod's tag or alias, and every task with an action needs a tag and must list that action in requiredCapabilities. |
requiredCapabilities | Up to 32, no duplicates. |
knowledgeIndex | Up to 32 entries, each a valid knowledge file path with a name and a short description. |
| Whole declaration | At most 8 KB once encoded, so it fits in a turn without crowding out your work. |
State: per project, or everywhere
stateScope is project (the default) or global. Project state is tied to the chat's project, so notes saved for one client are never read in another client's project, and the model cannot pick the project itself.
Declare both halves of state with the same keys:
{ "id": "mods.state.read", "keys": ["audit-notes"] },
{ "id": "mods.state.replace", "keys": ["audit-notes"] }
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.
Built-in actions
A tagged Mod may also list built-in OOMU capabilities in requiredCapabilities, for example a Gmail draft. When someone invokes the Mod's tag, those are the only capabilities available for that turn. If one of them needs a connection the person has not set up, the Mod shows Needs Setup until they do. The action itself always runs through OOMU's native executor and verifier; the Mod only declares that it wants it.
Step 3: Package it
A plain ZIP, renamed:
cd supplier-audit && zip -r ../supplier-audit.oomu manifest.json knowledge
Then rename to .oomu if your tooling did not:
mv ../supplier-audit.zip ../supplier-audit.oomu
Two things to check before you ship:
manifest.jsonis at the root, not inside a wrapper folder.zip -r ../out.oomu supplier-audit/produces a nested layout that OOMU rejects.- No two entries differ only in case.
Knowledge/guide.mdandknowledge/guide.mdin the same archive is an error.
Step 4: Install and check it
Open Mods → Install Mod and pick your file. Then:
- Confirm the card reads Unsigned with "Local package without a signature. OOMU verifies its files before use.": expected for a package you built yourself.
- Open Details and confirm everything you declared is listed, and nothing you did not declare appears.
- Choose Verify. It re-hashes the installed files against the recorded digest.
- Enable it, then give an agent access under Chat → Agents → Mods.
- For a tagged Mod, open a chat with that agent, type @, and run each task once.
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 declarations; a signature does not widen them.
| Badge | How to earn it |
|---|---|
| Unsigned | Ship nothing extra. |
| Verified publisher | Sign the canonical payload digest with your Ed25519 key and include the public key in capability_bundle.publisher.publicKey. |
| OOMU reviewed | Requires 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.
A managed Mac's organization can refuse unsigned Mods, so if you are building for a company fleet, plan on signing.
Exact envelope shapes are in the specification.
What changed from Beta 1
There is no Mod code runtime. These declarations still parse, and are reported to the user as unsupported, but do nothing:
| Beta 1 | Status |
|---|---|
entrypoint: "engine.wasm" or .js | Not executed. |
default_system_prompt / prompt | Not applied. Use the bounded instructions field instead. |
hooks, including shield_gate.on_prompt | Not registered. |
commands, slashCommands, triggers, regex | Not registered. Use a tag with tasks. |
endpoints, permissions.allowed_hosts | Recorded, not granted. No network access. |
capability_bundle as a capability declaration | Read only for signature material. |
Porting a Beta 1 Mod
If your Beta 1 Mod was mostly a default_system_prompt, cut it to its essentials and ship it as an ambient Mod's instructions, or put it in an agent's instructions.
If it shipped reference material alongside code, keep the material: move it under knowledge/, declare it in beta2Abi and knowledgeIndex, add a task to read it, drop the rest, and you have a working Mod.
If it was genuinely a tool (it called an API or ran a process) a Mod is the wrong vehicle. 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.
Related
- Mod package specification: every field, limit, and rejection message.
- Installing a Mod: the user's side.
- Connecting services: MCP, for capabilities that need to execute.