A lightweight, modular AI assistant framework built in TypeScript — inspired by HKUDS/nanobot.
This is a personal learning project, built incrementally to understand how AI agent systems work from the ground up.
The project was built in two rounds of incremental phases. The first round (plan.md) built the core from scratch. The second round (plan2.md) hardened and extended the architecture.
Core concept: TypeScript basics + LLM API
- TypeScript project setup with npm, async/await
- Azure OpenAI SDK integration — simplest working CLI chatbot
Core concept: State management + async iterators
- Session class with message history for multi-turn conversations
- Token-by-token streaming responses for better UX
Core concept: The agent loop pattern
- Tool interface +
ToolRegistryfor registering capabilities AgentRunnerimplementing the core loop: LLM → tool calls → execute → feed results → LLM responds- First tools:
get_current_timeandweb_search
Core concept: Prompt engineering + config management
- Configurable persona and model parameters via
data/config.json - Slash commands (
/help,/clear,/persona,/config) - Context builder for system prompt assembly
Core concept: Adapter pattern + multi-channel
- Abstract
BaseChannelinterface - Telegram bot via Telegraf, sharing the same agent core as CLI
Core concept: Persistence + memory
- Two-tier memory system: LLM-consolidated facts (
memory.md) + session JSONL files - Session persistence across restarts
Core concept: Tool system + context management
- Formal
ToolRegistrywith schema validation and type casting - Token-aware context management — trimming old messages and capping tool results to stay within budget
SessionManagerwith consolidation trackingHISTORY.mdsearchable chronological log alongsideMEMORY.md
Core concept: Decoupling I/O from reasoning
- Async
MessageBuswith inbound/outbound queues — channels never talk to the agent directly AgentLoopas central orchestrator with per-session locks and a global concurrency semaphore- Channels refactored into thin I/O adapters that just publish/consume messages
ChannelManagerhandles lifecycle and routes outbound messages to the correct channel
Core concept: Abstracting LLM providers
ProviderRegistrywith auto-detection from environment variables- Providers for Azure OpenAI and any OpenAI-compatible API (OpenAI, Groq, Ollama, etc.)
- Config-driven provider selection with automatic retry and backoff
Core concept: Giving the agent capabilities
- Filesystem tools: read, write, edit, list directories
- Shell execution with a deny-list for dangerous commands
- Web fetch and search tools
- Concurrent execution of safe tools in parallel
Core concept: Lifecycle observability and prompt engineering
AgentHookinterface with callbacks for each stage of the reasoning loop- Template-based system prompt assembly from modular markdown files (SOUL.md, AGENTS.md, TOOLS.md, USER.md)
ContextBuildercomposes the final system prompt from templates + memory
Core concept: Progressive capability loading
- Skills defined as markdown files with YAML frontmatter
- Two loading strategies: always-on (injected every prompt) vs. on-demand (agent discovers and loads as needed)
- Workspace skill overrides for project-specific behaviors
Core concept: Autonomy and scheduling
CronServicefor interval, cron, and one-shot scheduled tasksHeartbeatServicefor lightweight autonomous monitoring (two-phase: cheap check first, full run only if needed)SubagentManagerfor spawning background worker agents with restricted tool access
User Input
│
▼
Channel (CLI / Telegram)
│
▼
MessageBus ──────────────────────────────┐
│ │
▼ │
AgentLoop (per-session lock) │
│ │
▼ │
ContextBuilder ← Templates + Memory │
│ │
▼ │
AgentRunner (LLM + tool loop) │
│ │
├──→ Provider (Azure / OpenAI / ...) │
├──→ ToolRegistry (execute tools) │
└──→ Response ──────────────────────→┘
│
▼
Channel.send()
Background services (CronService, HeartbeatService, SubagentManager) inject messages into the bus independently, enabling autonomous behavior.
# Install dependencies
npm install
# Configure (copy and fill in your API keys)
cp .env.example .env
# Run in CLI mode
npm start
# Run in Telegram mode
npm start -- --channel telegram
# Run both CLI and Telegram
npm start -- --channel all
# Development mode (auto-reload)
npm run dev- TypeScript + Node.js
- OpenAI SDK — works with any OpenAI-compatible API
- Telegraf — Telegram bot framework
- Zod — runtime schema validation
Phases 1–13 are complete. Here's what's next:
Connect to external MCP servers and dynamically wrap their tools.
- MCP client supporting stdio, SSE, and streamable HTTP transports
MCPToolWrapperto convert MCP server tools into nanobot tools at runtime- Config-driven MCP server connections with lazy loading
- Tool namespacing to avoid collisions across servers
Protect against misuse and dangerous operations.
- SSRF protection — validate URLs against private IP ranges
- Workspace restriction — filesystem tools confined to configured directory
- Shell deny-list hardening
- Per-channel user/group permission allowlists
Inspired by HKUDS/nanobot — a minimal yet capable AI agent that demonstrated how much you can achieve with a clean, focused architecture.
MIT