Deliver an IM Bot without AI
Goal: complete the loop from inbound message to business logic to reply or proactive notification. Allow 45–60 minutes; no model, Prompt, or Agent is involved.
Done means
/rollreplies reliably in Sandbox.- Saving code publishes a new generation.
- At least one real platform Endpoint is online.
- Proactive notifications use the Runtime outbound capability, not an Adapter singleton.
1. Start with a repeatable Sandbox
npm create zhin-app my-bot -y
cd my-bot
pnpm devConnect Console with the API Base printed by the Host and HTTP_TOKEN from .env. Open Sandbox under Conversations & Channels and verify /hello first.
2. Add a command
Create commands/roll.ts:
import { defineCommand } from 'zhin.js/command';
export default defineCommand({
description: 'Roll a six-sided die',
execute: () => `🎲 ${1 + Math.floor(Math.random() * 6)}`,
});Save and send /roll. The file path supplies the route; commands/gh/issue.ts becomes /gh issue.
3. Add a proactive notification
The schedule decides when work runs. Delivery still goes through outboundHostToken, so Sandbox and real platforms share one outbound path.
import {
definePlugin,
outboundHostToken,
scheduleHostToken,
} from 'zhin.js';
interface Config {
morningTarget?: {
adapter: string;
endpointKey: string;
conversation: { kind: 'private' | 'group' | 'channel'; id: string };
};
}
export default definePlugin<Config>({
name: 'my-bot',
setup(context) {
const target = context.config.get().morningTarget;
if (!target) return;
if (!context.resources.has(outboundHostToken)) return;
const outbound = context.resources.use(outboundHostToken);
const schedule = context.resources.use(scheduleHostToken);
context.lifecycle.add(schedule.register({
id: 'my-bot/morning',
cron: '0 0 9 * * *',
description: 'Send a morning greeting',
execute: async () => {
await outbound.send({
...target,
content: 'Good morning',
});
},
}));
},
});Use the complete Endpoint identity shown in Conversations & Channels as endpointKey. lifecycle.add() unregisters the old job when its generation retires.
4. Connect a real platform
npx zhin setup --adapters
pnpm install
pnpm devThe wizard updates package.json#zhin.plugins, zhin.config.yml, and .env together. Secrets go to .env; configuration keeps variable references.
One Adapter instance can own multiple Endpoints. Commands and components should not depend on the Adapter, so moving from Sandbox to QQ, Discord, or Slack does not rewrite business logic.
5. Accept the result in Console
- Confirm the Endpoint and send a message in Conversations & Channels.
- Confirm
/rollbelongs to the current generation in Runtime Capabilities. - Check Logs for command or notification failures.
- Restart the Host and repeat the real-platform test.
Boundaries to keep
zhin.config.ymlstores values; plugin topology belongs inpackage.json#zhin.- Proactive delivery uses
outboundHostToken, never a global Adapter instance. - Schedules use six-field cron: second, minute, hour, day, month, weekday.
- A plugin can use only Host tokens installed for its Runtime.
Next
- Add models and tools: AI Agent path
- Operate accounts and runtime state: Console path
- Learn command parameters and authority: Commands