module.config.ts
The entry point for every module. Declares identity, configuration, env requirements, emitted hooks, and the boot lifecycle in one place.
export default defineModule({
name: "discord",
description: "Discord bot integration — receive messages and send replies.",
env: {
DISCORD_BOT_TOKEN: {
required: true,
description: "Bot token from the Discord developer portal.",
},
},
options: {
mentionOnly: { type: "boolean", default: false },
channelIds: { type: "string", required: false },
},
emits: {
"discord:message.received": {} as {
content: string
username: string
reply: (text: string) => Promise<void>
},
},
async setup({ axon, options }) {
const token = axon.env.require("DISCORD_BOT_TOKEN")
// ...
},
})
Axon reads the static fields at install time without executing any code. setup runs
once at agent boot.
name
The module's registry identifier. Lowercase, hyphenated. Combined with your account scope on publish:
name: "discord" // → @you/discord on the registry
description
One line. Shown in the registry and the TUI module browser.
env
Env keys the module requires. The installing agent adds these to their .env. Axon
validates presence at boot.
env: {
DISCORD_BOT_TOKEN: {
required: true,
description: "Bot token from the Discord developer portal.",
},
DISCORD_GUILD_ID: {
required: false,
description: "Restrict to a specific server. Leave empty for all servers.",
},
}
Inside setup, use axon.env.require() to validate and retrieve:
const token = axon.env.require("DISCORD_BOT_TOKEN") // throws if missing
const guild = axon.env.get("DISCORD_GUILD_ID") // returns undefined if absent
options
User-configurable settings for the module. Set by the installing agent in
axon.config.ts. Passed to setup as the options parameter.
options: {
mentionOnly: {
type: "boolean",
default: false,
description: "Only trigger when the bot is @mentioned.",
},
channelIds: {
type: "string",
required: false,
description: "Comma-separated channel IDs. Empty means all channels.",
},
}
The installing agent configures them:
// axon.config.ts
export default defineAgent({
modules: {
discord: {
mentionOnly: true,
channelIds: "1234567890",
},
},
})
Options are validated against the declared types before setup runs.
emits
Named hooks the module fires during its lifetime. The type cast ({} as { ... })
is the payload type — it flows through to the subscription site so the agent author
gets typed payloads.
emits: {
"discord:message.received": {} as {
content: string
username: string
channelId: string
reply: (text: string) => Promise<void>
},
}
Inside setup, fire hooks with axon.callHook:
await axon.callHook("discord:message.received", {
content: msg.content,
username: msg.author.username,
channelId: msg.channelId,
reply: (text) => msg.reply(text),
})
policy
Declares what capabilities the module uses. Advisory — the parent agent grants actual access through its own policy block. A module cannot expand policy, only declare its intentions.
policy: {
network: { needs: ["discord.com", "gateway.discord.gg"] },
fs: { needs: ["data/**"] },
procs: { needs: ["git *"] },
}
modules
Other modules this module depends on. Resolved and merged automatically when this module is installed — the agent author sees one install.
modules: ["@axon/fs", "@you/shared-prompts"]
setup
Runs once at agent boot. Narrow scope: env validation, external service connections, hook registration, and teardown.
async setup({ axon, options }) {
const token = axon.env.require("DISCORD_BOT_TOKEN")
const client = new Client({ ... })
client.on(Events.MessageCreate, async (msg) => {
await axon.callHook("discord:message.received", { ... })
})
await client.login(token)
axon.hook("axon:agent:shutdown", () => client.destroy())
}
For polling modules, wait for server:ready before starting — all plugins need to
be registered before the first hook fires:
axon.hook("server:ready", () => startPolling())