# System Patterns ## Type Definition Pattern Every module that has runtime-validated data uses a dedicated `types.ts` file: ``` src/config/types.ts ← Zod schemas + `export type X = z.infer` src/bus/types.ts src/session/types.ts src/cron/types.ts src/provider/types.ts ``` The implementation files (`loader.ts`, `manager.ts`, etc.) import types from the sibling `types.ts`. ## Tool Pattern All tools implement the `Tool` interface from `src/agent/tools/base.ts`: ```ts interface Tool { name: string description: string parameters: Record // JSON Schema object execute(args: Record): Promise } ``` `ToolRegistry` stores tools by name, exposes `getDefinitions()` (OpenAI function-calling format), and `execute(name, args)`. ## Message Bus Pattern Inbound and outbound messages are passed through a typed `AsyncQueue`. The queue uses a `Promise`-based dequeue that resolves when an item is available (mirrors Python `asyncio.Queue`). ## Provider Pattern `LLMProvider` (`src/provider/index.ts`) wraps the Vercel AI SDK `generateText()`. It: - Accepts a model string and resolves it to the correct AI SDK provider instance - Implements `chatWithRetry()` with 3 attempts on transient errors (429, 5xx, timeout) - Repairs malformed tool-call JSON with `jsonrepair` - Returns a normalized `LLMResponse` type ## Config Pattern - Config file: `~/.nanobot/config.json` (camelCase JSON) - Loaded with `loadConfig()`, validated by Zod, returns inferred `Config` type - `NANOBOT_` env vars can override fields (e.g. `NANOBOT_MODEL`) ## Mattermost Channel Pattern - Inbound: native `WebSocket` connecting to `wss://{server}/api/v4/websocket`, auth via hello message - Outbound: `fetch()` to `POST /api/v4/posts` - Session key: `mattermost:{channelId}` (or `mattermost:{channelId}:{rootId}` when `replyInThread`) ## Session Key Convention `{channel}:{chatId}` — e.g. `mattermost:abc123`, `cli:direct` ## Logging Pattern Use `console.error` / `console.warn` / `console.info` / `console.debug` — no external logger. Color via `picocolors` in CLI output only. ## CLI Command Pattern Each command is in its own file with a registration function: ```ts // src/cli/agent.ts export function agentCommand(program: Command, config: Config, workspace: string): void { program.command('agent') .description('...') .option('-m, --message ', 'Single message to process') .action(async (opts) => { /* ... */ }) } // src/cli/commands.ts (bootstrap) export function createCli(): Command { const program = new Command('nanobot')... const config = loadConfig(opts.config); const workspace = resolveWorkspacePath(config.agent.workspacePath); gatewayCommand(program, config, workspace); agentCommand(program, config, workspace); return program; } ``` ## File Layout ``` src/ config/types.ts + loader.ts bus/types.ts + queue.ts provider/types.ts + index.ts session/types.ts + manager.ts cron/types.ts + service.ts heartbeat/service.ts agent/ memory.ts skills.ts context.ts loop.ts subagent.ts tools/base.ts + filesystem.ts + shell.ts + web.ts + message.ts + spawn.ts + cron.ts channels/ base.ts + mattermost.ts + manager.ts cli/ types.ts # CommandHandler type commands.ts # Bootstrap - loads config, registers commands agent.ts # agentCommand() - interactive/single-shot mode gateway.ts # gatewayCommand() - full runtime with Mattermost index.ts templates/ (SOUL.md, AGENTS.md, USER.md, TOOLS.md, HEARTBEAT.md, memory/MEMORY.md) skills/ (copied from Python repo) ```