Files
nanobot-ts/memory-bank/systemPatterns.md
2026-03-13 14:52:51 -06:00

3.6 KiB

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<typeof XSchema>`
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:

interface Tool {
  name: string
  description: string
  parameters: Record<string, unknown>  // JSON Schema object
  execute(args: Record<string, unknown>): Promise<string>
}

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<T>. 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:

// src/cli/agent.ts
export function agentCommand(program: Command, config: Config, workspace: string): void {
  program.command('agent')
    .description('...')
    .option('-m, --message <text>', '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)