Compare commits
1 Commits
345cfef425
...
f0252e663e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0252e663e |
@@ -54,6 +54,7 @@ I maintain two distinct layers of documentation. I must keep them in sync but ne
|
|||||||
- **PRD.md**: Core features, target audience, and business logic.
|
- **PRD.md**: Core features, target audience, and business logic.
|
||||||
- **Architecture.md**: Tech stack, folder structure, and data flow diagrams.
|
- **Architecture.md**: Tech stack, folder structure, and data flow diagrams.
|
||||||
- **API.md**: Endpoint definitions, request/response schemas.
|
- **API.md**: Endpoint definitions, request/response schemas.
|
||||||
|
- **Schema.md**: Database tables, relationships, and types.
|
||||||
- **Discoveries.md**: Things learned empirically that future sessions should know.
|
- **Discoveries.md**: Things learned empirically that future sessions should know.
|
||||||
|
|
||||||
### Layer 2: The Memory Bank, or mb (/memory-bank)
|
### Layer 2: The Memory Bank, or mb (/memory-bank)
|
||||||
|
|||||||
233
docs/API.md
233
docs/API.md
@@ -1,233 +0,0 @@
|
|||||||
# API Reference
|
|
||||||
|
|
||||||
This document describes the tool interface exposed to the LLM and the internal APIs for extending nanobot.
|
|
||||||
|
|
||||||
## Tool Interface
|
|
||||||
|
|
||||||
All tools implement the `Tool` interface from `src/agent/tools/base.ts`:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface Tool {
|
|
||||||
name: string; // Tool identifier
|
|
||||||
description: string; // LLM-readable description
|
|
||||||
parameters: Record<string, unknown>; // JSON Schema object
|
|
||||||
execute(args: Record<string, unknown>): Promise<string>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Built-in Tools
|
|
||||||
|
|
||||||
### read_file
|
|
||||||
|
|
||||||
Read a file from the filesystem.
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
|-----------|------|----------|-------------|
|
|
||||||
| path | string | yes | Absolute or relative file path |
|
|
||||||
| offset | number | no | Line number to start from (1-indexed) |
|
|
||||||
| limit | number | no | Maximum number of lines to read |
|
|
||||||
|
|
||||||
**Returns**: Line-numbered content (e.g., `1: first line\n2: second line`)
|
|
||||||
|
|
||||||
### write_file
|
|
||||||
|
|
||||||
Write content to a file, creating parent directories as needed.
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
|-----------|------|----------|-------------|
|
|
||||||
| path | string | yes | File path to write |
|
|
||||||
| content | string | yes | Content to write |
|
|
||||||
|
|
||||||
**Returns**: Success message or error
|
|
||||||
|
|
||||||
### edit_file
|
|
||||||
|
|
||||||
Replace an exact string in a file.
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
|-----------|------|----------|-------------|
|
|
||||||
| path | string | yes | File path to edit |
|
|
||||||
| oldString | string | yes | Exact string to replace |
|
|
||||||
| newString | string | yes | Replacement string |
|
|
||||||
| replaceAll | boolean | no | Replace all occurrences |
|
|
||||||
|
|
||||||
**Returns**: Success message or error if oldString not found
|
|
||||||
|
|
||||||
### list_dir
|
|
||||||
|
|
||||||
List files in a directory.
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
|-----------|------|----------|-------------|
|
|
||||||
| path | string | yes | Directory path |
|
|
||||||
| recursive | boolean | no | List recursively |
|
|
||||||
|
|
||||||
**Returns**: One file/directory per line, directories suffixed with `/`
|
|
||||||
|
|
||||||
### exec
|
|
||||||
|
|
||||||
Execute a shell command.
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
|-----------|------|----------|-------------|
|
|
||||||
| command | string | yes | Shell command to execute |
|
|
||||||
| timeout | number | no | Timeout in seconds (default: 120) |
|
|
||||||
| workdir | string | no | Working directory override |
|
|
||||||
|
|
||||||
**Returns**: Combined stdout + stderr
|
|
||||||
|
|
||||||
### web_search
|
|
||||||
|
|
||||||
Search the web using Brave Search API.
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
|-----------|------|----------|-------------|
|
|
||||||
| query | string | yes | Search query |
|
|
||||||
| count | number | no | Number of results (default: 10) |
|
|
||||||
|
|
||||||
**Returns**: JSON array of `{ title, url, snippet }` objects
|
|
||||||
|
|
||||||
### web_fetch
|
|
||||||
|
|
||||||
Fetch and parse a URL.
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
|-----------|------|----------|-------------|
|
|
||||||
| url | string | yes | URL to fetch |
|
|
||||||
| mode | string | no | `markdown` (default), `raw`, or `html` |
|
|
||||||
|
|
||||||
**Returns**:
|
|
||||||
- HTML pages: extracted readable text (via Readability)
|
|
||||||
- JSON: pretty-printed JSON
|
|
||||||
- Other: raw text
|
|
||||||
|
|
||||||
### message
|
|
||||||
|
|
||||||
Send a message to the current chat channel.
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
|-----------|------|----------|-------------|
|
|
||||||
| content | string | yes | Message content |
|
|
||||||
|
|
||||||
**Returns**: Success confirmation
|
|
||||||
|
|
||||||
### spawn
|
|
||||||
|
|
||||||
Spawn a background subagent for long-running tasks.
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
|-----------|------|----------|-------------|
|
|
||||||
| task | string | yes | Task description for the subagent |
|
|
||||||
|
|
||||||
**Returns**: Spawn confirmation with subagent ID
|
|
||||||
|
|
||||||
### cron
|
|
||||||
|
|
||||||
Manage scheduled tasks.
|
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
|
||||||
|-----------|------|----------|-------------|
|
|
||||||
| action | string | yes | `list`, `add`, `remove`, `enable`, `disable`, `run`, `status` |
|
|
||||||
| id | string | conditional | Job ID (for remove/enable/disable/run) |
|
|
||||||
| name | string | conditional | Job name (for add) |
|
|
||||||
| message | string | conditional | Task message (for add) |
|
|
||||||
| schedule | string | conditional | Schedule expression (for add) |
|
|
||||||
| deleteAfterRun | boolean | no | Delete after one execution |
|
|
||||||
|
|
||||||
**Schedule formats**:
|
|
||||||
- `every Ns/m/h/d` — e.g., `every 30m`
|
|
||||||
- `at YYYY-MM-DD HH:MM` — one-time
|
|
||||||
- Cron expression — e.g., `0 9 * * 1-5`
|
|
||||||
|
|
||||||
**Returns**: Action-specific response (job list, confirmation, status)
|
|
||||||
|
|
||||||
## Internal APIs
|
|
||||||
|
|
||||||
### BaseChannel
|
|
||||||
|
|
||||||
Extend to create new channel types:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
abstract class BaseChannel {
|
|
||||||
_bus: MessageBus;
|
|
||||||
abstract start(): Promise<void>;
|
|
||||||
abstract stop(): void;
|
|
||||||
abstract send(chatId: string, content: string, metadata?: Record<string, unknown>): Promise<void>;
|
|
||||||
isAllowed(senderId: string, allowFrom: string[]): boolean;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### MessageBus
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
class MessageBus {
|
|
||||||
publishInbound(msg: InboundMessage): void;
|
|
||||||
consumeInbound(): Promise<InboundMessage>;
|
|
||||||
publishOutbound(msg: OutboundMessage): void;
|
|
||||||
consumeOutbound(): Promise<OutboundMessage>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### InboundMessage
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
type InboundMessage = {
|
|
||||||
channel: string; // 'mattermost', 'cli', 'system'
|
|
||||||
senderId: string; // User identifier
|
|
||||||
chatId: string; // Conversation identifier
|
|
||||||
content: string; // Message text
|
|
||||||
metadata: Record<string, unknown>;
|
|
||||||
media?: string[]; // Optional media URLs
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### OutboundMessage
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
type OutboundMessage = {
|
|
||||||
channel: string;
|
|
||||||
chatId: string;
|
|
||||||
content: string | null;
|
|
||||||
metadata: Record<string, unknown>;
|
|
||||||
media?: string[];
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### LLMProvider
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
class LLMProvider {
|
|
||||||
defaultModel: string;
|
|
||||||
chat(opts: ChatOptions): Promise<{ response: LLMResponse; responseMessages: ModelMessage[] }>;
|
|
||||||
chatWithRetry(opts: ChatOptions): Promise<{ response: LLMResponse; responseMessages: ModelMessage[] }>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Session
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
class Session {
|
|
||||||
key: string;
|
|
||||||
messages: SessionMessage[];
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
lastConsolidated: number;
|
|
||||||
getHistory(maxMessages?: number): SessionMessage[];
|
|
||||||
clear(): void;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### CronService
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
class CronService {
|
|
||||||
listJobs(): CronJob[];
|
|
||||||
addJob(job: Omit<CronJob, 'state' | 'createdAtMs' | 'updatedAtMs'>): CronJob;
|
|
||||||
removeJob(id: string): boolean;
|
|
||||||
enableJob(id: string, enabled: boolean): boolean;
|
|
||||||
runJob(id: string): Promise<string>;
|
|
||||||
status(): string;
|
|
||||||
start(): void;
|
|
||||||
stop(): void;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
# Architecture
|
|
||||||
|
|
||||||
## Tech Stack
|
|
||||||
|
|
||||||
| Layer | Technology |
|
|
||||||
|-------|------------|
|
|
||||||
| Runtime | Bun (v1.0+) |
|
|
||||||
| Language | TypeScript (strict mode) |
|
|
||||||
| LLM Abstraction | Vercel AI SDK v6 |
|
|
||||||
| Validation | Zod v4 |
|
|
||||||
| CLI | Commander |
|
|
||||||
| Colors | picocolors |
|
|
||||||
| Formatting | oxfmt (single quotes) |
|
|
||||||
| Linting | oxlint |
|
|
||||||
|
|
||||||
## Folder Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
nanobot-ts/
|
|
||||||
├── index.ts # Entry point
|
|
||||||
├── src/
|
|
||||||
│ ├── agent/
|
|
||||||
│ │ ├── loop.ts # AgentLoop: LLM ↔ tool execution loop
|
|
||||||
│ │ ├── context.ts # ContextBuilder: system prompt assembly
|
|
||||||
│ │ ├── memory.ts # MemoryConsolidator: token management
|
|
||||||
│ │ ├── skills.ts # Skill loader from workspace
|
|
||||||
│ │ ├── subagent.ts # SubagentManager: background tasks
|
|
||||||
│ │ └── tools/
|
|
||||||
│ │ ├── base.ts # Tool interface + ToolRegistry
|
|
||||||
│ │ ├── filesystem.ts # read_file, write_file, edit_file, list_dir
|
|
||||||
│ │ ├── shell.ts # exec
|
|
||||||
│ │ ├── web.ts # web_search, web_fetch
|
|
||||||
│ │ ├── message.ts # message
|
|
||||||
│ │ ├── spawn.ts # spawn
|
|
||||||
│ │ └── cron.ts # cron
|
|
||||||
│ ├── channels/
|
|
||||||
│ │ ├── base.ts # BaseChannel abstract class
|
|
||||||
│ │ ├── mattermost.ts # Mattermost WebSocket + REST
|
|
||||||
│ │ └── manager.ts # ChannelManager lifecycle
|
|
||||||
│ ├── bus/
|
|
||||||
│ │ ├── types.ts # InboundMessage, OutboundMessage schemas
|
|
||||||
│ │ └── queue.ts # AsyncQueue, MessageBus
|
|
||||||
│ ├── provider/
|
|
||||||
│ │ ├── types.ts # LLMResponse, ToolCall, ChatOptions
|
|
||||||
│ │ └── index.ts # LLMProvider (AI SDK wrapper)
|
|
||||||
│ ├── session/
|
|
||||||
│ │ ├── types.ts # SessionMessage, SessionMeta schemas
|
|
||||||
│ │ └── manager.ts # Session persistence (JSONL)
|
|
||||||
│ ├── cron/
|
|
||||||
│ │ ├── types.ts # CronJob, CronSchedule schemas
|
|
||||||
│ │ └── service.ts # CronService
|
|
||||||
│ ├── heartbeat/
|
|
||||||
│ │ └── service.ts # HeartbeatService
|
|
||||||
│ ├── config/
|
|
||||||
│ │ ├── types.ts # Zod config schemas
|
|
||||||
│ │ └── loader.ts # loadConfig, env overrides
|
|
||||||
│ └── cli/
|
|
||||||
│ └── commands.ts # gateway + agent commands
|
|
||||||
├── templates/ # Default workspace files
|
|
||||||
│ ├── SOUL.md # Agent personality
|
|
||||||
│ ├── USER.md # User preferences
|
|
||||||
│ ├── TOOLS.md # Tool documentation
|
|
||||||
│ ├── AGENTS.md # Agent behavior rules
|
|
||||||
│ ├── HEARTBEAT.md # Periodic tasks
|
|
||||||
│ └── memory/MEMORY.md # Long-term memory
|
|
||||||
└── skills/ # Bundled skills
|
|
||||||
```
|
|
||||||
|
|
||||||
## Data Flow
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
|
||||||
│ Gateway Mode │
|
|
||||||
├─────────────────────────────────────────────────────────────────┤
|
|
||||||
│ │
|
|
||||||
│ Mattermost ──► BaseChannel ──► MessageBus ──► AgentLoop │
|
|
||||||
│ ▲ │ │ │
|
|
||||||
│ │ ▼ ▼ │
|
|
||||||
│ │ OutboundQueue LLMProvider │
|
|
||||||
│ │ │ │ │
|
|
||||||
│ └───────────────────────────────┘ ▼ │
|
|
||||||
│ ToolRegistry │
|
|
||||||
│ │ │
|
|
||||||
│ ▼ │
|
|
||||||
│ Tool.execute() │
|
|
||||||
│ │
|
|
||||||
└─────────────────────────────────────────────────────────────────┘
|
|
||||||
|
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
|
||||||
│ Agent Mode │
|
|
||||||
├─────────────────────────────────────────────────────────────────┤
|
|
||||||
│ │
|
|
||||||
│ CLI stdin ──► processDirect() ──► AgentLoop ──► Response │
|
|
||||||
│ │
|
|
||||||
└─────────────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## Key Components
|
|
||||||
|
|
||||||
### AgentLoop
|
|
||||||
The core orchestrator. Consumes inbound messages, runs the LLM tool-calling loop, and publishes responses.
|
|
||||||
|
|
||||||
1. Receives `InboundMessage` from bus
|
|
||||||
2. Loads/creates session by key
|
|
||||||
3. Builds context (system prompt + history)
|
|
||||||
4. Calls LLM with tools
|
|
||||||
5. Executes tool calls, appends results
|
|
||||||
6. Repeats until no tool calls or max iterations
|
|
||||||
7. Saves session, publishes response
|
|
||||||
|
|
||||||
### MessageBus
|
|
||||||
An async queue system for decoupling channels from the agent loop.
|
|
||||||
|
|
||||||
- `publishInbound()` / `consumeInbound()`: messages from channels to agent
|
|
||||||
- `publishOutbound()` / `consumeOutbound()`: responses from agent to channels
|
|
||||||
|
|
||||||
### LLMProvider
|
|
||||||
Wraps Vercel AI SDK `generateText()` with:
|
|
||||||
|
|
||||||
- Model string resolution (e.g., `openrouter/anthropic/claude-sonnet-4-5`)
|
|
||||||
- Retry logic (3 attempts, exponential backoff)
|
|
||||||
- Malformed JSON repair
|
|
||||||
- Normalized `LLMResponse` type
|
|
||||||
|
|
||||||
### SessionManager
|
|
||||||
Persists conversation history to JSONL files in `~/.nanobot/sessions/`.
|
|
||||||
|
|
||||||
- Key format: `{channel}:{chatId}` (e.g., `mattermost:abc123`)
|
|
||||||
- Supports history truncation for context window limits
|
|
||||||
|
|
||||||
### ToolRegistry
|
|
||||||
Stores tools by name, provides OpenAI-compatible function definitions to the LLM.
|
|
||||||
|
|
||||||
### MemoryConsolidator
|
|
||||||
When session history exceeds token limits, summarizes old messages and archives to `memory/MEMORY.md`.
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
- File: `~/.nanobot/config.json`
|
|
||||||
- Validation: Zod schemas in `src/config/types.ts`
|
|
||||||
- Env overrides: `NANOBOT_MODEL`, `NANOBOT_WORKSPACE`, `NANOBOT_CONFIG`
|
|
||||||
|
|
||||||
## Session Key Convention
|
|
||||||
|
|
||||||
| Channel | Key Format | Example |
|
|
||||||
|---------|-----------|----------|
|
|
||||||
| Mattermost | `mattermost:{channelId}` | `mattermost:abc123` |
|
|
||||||
| Mattermost (thread) | `mattermost:{channelId}:{rootId}` | `mattermost:abc:def456` |
|
|
||||||
| CLI | `cli:{chatId}` | `cli:interactive` |
|
|
||||||
| System | `system:{source}` | `system:heartbeat` |
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
# Discoveries
|
|
||||||
|
|
||||||
Empirical learnings from implementation that future sessions should know.
|
|
||||||
|
|
||||||
## Zod v4 Specifics
|
|
||||||
|
|
||||||
### `.default()` on Nested Objects
|
|
||||||
|
|
||||||
Zod v4 requires factory functions for nested object defaults, and the factory must return the **full output type** (not just `{}`):
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// ❌ Wrong - empty object won't match the schema
|
|
||||||
const Config = z.object({
|
|
||||||
nested: NestedSchema.default({}),
|
|
||||||
});
|
|
||||||
|
|
||||||
// ✅ Correct - factory returning full type
|
|
||||||
const Config = z.object({
|
|
||||||
nested: NestedSchema.default(() => ({ field: value, ... })),
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### `z.record()` Requires Two Arguments
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// ❌ Wrong
|
|
||||||
z.record(z.string())
|
|
||||||
|
|
||||||
// ✅ Correct
|
|
||||||
z.record(z.string(), z.unknown())
|
|
||||||
```
|
|
||||||
|
|
||||||
## AI SDK v6 Changes
|
|
||||||
|
|
||||||
| v4/v5 | v6 |
|
|
||||||
|-------|-----|
|
|
||||||
| `LanguageModelV2` | `LanguageModel` |
|
|
||||||
| `maxTokens` | `maxOutputTokens` |
|
|
||||||
| `maxSteps` | `stopWhen: stepCountIs(n)` |
|
|
||||||
| `usage.promptTokens` | `usage.inputTokens` |
|
|
||||||
| `usage.completionTokens` | `usage.outputTokens` |
|
|
||||||
|
|
||||||
## ollama-ai-provider Compatibility
|
|
||||||
|
|
||||||
`ollama-ai-provider` v1.2.0 returns `LanguageModelV1`, not the expected `LanguageModel` (v2/v3). Cast at call site:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { ollama } from 'ollama-ai-provider';
|
|
||||||
import type { LanguageModel } from 'ai';
|
|
||||||
|
|
||||||
const model = ollama('llama3.2') as unknown as LanguageModel;
|
|
||||||
```
|
|
||||||
|
|
||||||
## js-tiktoken API
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// ❌ Wrong (Python-style)
|
|
||||||
import { get_encoding } from 'js-tiktoken';
|
|
||||||
|
|
||||||
// ✅ Correct
|
|
||||||
import { getEncoding } from 'js-tiktoken';
|
|
||||||
```
|
|
||||||
|
|
||||||
## Bun/Node Globals
|
|
||||||
|
|
||||||
`Document` is not available as a global in Bun/Node. For DOM-like operations:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// ❌ Wrong
|
|
||||||
function makeDocument(): Document { ... }
|
|
||||||
|
|
||||||
// ✅ Correct
|
|
||||||
function makePseudoDocument(): Record<string, unknown> { ... }
|
|
||||||
// Cast at call site if needed
|
|
||||||
```
|
|
||||||
|
|
||||||
## WebSocket Error Types
|
|
||||||
|
|
||||||
WebSocket `onerror` handler receives an `Event`, not an `Error`:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
socket.onerror = (err: Event) => {
|
|
||||||
console.error(`WebSocket error: ${err.type}`);
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## Template Literals with Unknown Types
|
|
||||||
|
|
||||||
When interpolating `unknown` types in template literals, explicitly convert to string:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// ❌ Risky - may throw
|
|
||||||
console.log(`Error: ${err}`);
|
|
||||||
|
|
||||||
// ✅ Safe
|
|
||||||
console.log(`Error: ${String(err)}`);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Helper: strArg
|
|
||||||
|
|
||||||
For safely extracting string arguments from `Record<string, unknown>`:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// src/agent/tools/base.ts
|
|
||||||
export function strArg(args: Record<string, unknown>, key: string, fallback = ''): string {
|
|
||||||
const val = args[key];
|
|
||||||
return typeof val === 'string' ? val : fallback;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// ❌ Verbose
|
|
||||||
const path = String(args['path'] ?? '');
|
|
||||||
|
|
||||||
// ✅ Cleaner
|
|
||||||
const path = strArg(args, 'path');
|
|
||||||
const timeout = parseInt(strArg(args, 'timeout', '30'), 10);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Mattermost WebSocket
|
|
||||||
|
|
||||||
- Uses raw `WebSocket` + `fetch` (no mattermostdriver library)
|
|
||||||
- Auth via hello message with token
|
|
||||||
- Event types: `posted`, `post_edited`, `reaction_added`, etc.
|
|
||||||
- Group channel policy: `mention` (default), `open`, `allowlist`
|
|
||||||
|
|
||||||
## Session Persistence
|
|
||||||
|
|
||||||
- Format: JSONL (one JSON object per line)
|
|
||||||
- Location: `~/.nanobot/sessions/{sessionKey}.jsonl`
|
|
||||||
- Tool results truncated at 16,000 characters
|
|
||||||
- Memory consolidation triggered when approaching context window limit
|
|
||||||
|
|
||||||
## Retry Logic
|
|
||||||
|
|
||||||
`LLMProvider.chatWithRetry()` retries on:
|
|
||||||
- HTTP 429 (rate limit)
|
|
||||||
- HTTP 5xx (server errors)
|
|
||||||
- Timeouts
|
|
||||||
- Network errors
|
|
||||||
|
|
||||||
Max 3 attempts with exponential backoff.
|
|
||||||
|
|
||||||
## Config Precedence
|
|
||||||
|
|
||||||
1. CLI flags (`-c`, `-m`, `-w`, `-M`)
|
|
||||||
2. Environment variables (`NANOBOT_CONFIG`, `NANOBOT_MODEL`, `NANOBOT_WORKSPACE`)
|
|
||||||
3. Config file (`~/.nanobot/config.json`)
|
|
||||||
4. Zod schema defaults
|
|
||||||
64
docs/PRD.md
64
docs/PRD.md
@@ -1,64 +0,0 @@
|
|||||||
# Product Requirements Document (PRD)
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
nanobot is an ultra-lightweight personal AI assistant framework. It provides a chat-controlled bot that can execute tasks through natural language commands, with pluggable "channels" for different messaging platforms.
|
|
||||||
|
|
||||||
## Target Audience
|
|
||||||
|
|
||||||
- Individual developers and power users who want a personal AI assistant
|
|
||||||
- Users who prefer self-hosted, privacy-respecting AI tools
|
|
||||||
- Teams using Mattermost who want an integrated AI assistant
|
|
||||||
- Users who need AI assistance with file operations, shell commands, and web searches
|
|
||||||
|
|
||||||
## Core Features
|
|
||||||
|
|
||||||
### 1. Agent Loop
|
|
||||||
- Conversational AI powered by LLMs (Anthropic, OpenAI, Google, OpenRouter, Ollama)
|
|
||||||
- Tool execution with iterative refinement
|
|
||||||
- Session management with persistent conversation history
|
|
||||||
- Memory consolidation to manage context window limits
|
|
||||||
|
|
||||||
### 2. Tool System
|
|
||||||
- **Filesystem**: read, write, edit, list files
|
|
||||||
- **Shell**: execute arbitrary commands with configurable security constraints
|
|
||||||
- **Web**: search (Brave), fetch and parse URLs
|
|
||||||
- **Message**: send intermediate updates to chat channels
|
|
||||||
- **Spawn**: delegate long-running tasks to background subagents
|
|
||||||
- **Cron**: schedule recurring tasks
|
|
||||||
|
|
||||||
### 3. Channel System
|
|
||||||
- **Mattermost**: WebSocket-based real-time messaging with REST API for posts
|
|
||||||
- **CLI**: local interactive terminal or single-shot mode
|
|
||||||
- Extensible channel interface for future platforms
|
|
||||||
|
|
||||||
### 4. Scheduling
|
|
||||||
- **Cron Service**: schedule tasks with cron expressions, intervals, or one-time execution
|
|
||||||
- **Heartbeat**: periodic wake-up to check for tasks (e.g., HEARTBEAT.md)
|
|
||||||
|
|
||||||
### 5. Memory & Skills
|
|
||||||
- Long-term memory with consolidation
|
|
||||||
- Skill loading from workspace
|
|
||||||
- System prompt construction from templates (SOUL.md, USER.md, TOOLS.md)
|
|
||||||
|
|
||||||
## Non-Goals (Out of Scope)
|
|
||||||
|
|
||||||
- Non-Mattermost channels (Telegram, Discord, Slack, etc.)
|
|
||||||
- MCP (Model Context Protocol) client support
|
|
||||||
- Extended thinking/reasoning token handling
|
|
||||||
- Onboard configuration wizard
|
|
||||||
- Multi-tenancy or user authentication
|
|
||||||
|
|
||||||
## User Stories
|
|
||||||
|
|
||||||
1. As a developer, I want to ask the AI to read and modify files in my workspace so I can work faster.
|
|
||||||
2. As a team lead, I want the bot to respond in Mattermost channels when mentioned so my team can get AI help without leaving chat.
|
|
||||||
3. As a power user, I want to schedule recurring tasks so the AI can check things automatically.
|
|
||||||
4. As a privacy-conscious user, I want to run the bot locally with Ollama so my data stays on my machine.
|
|
||||||
|
|
||||||
## Success Metrics
|
|
||||||
|
|
||||||
- Zero external dependencies for core functionality beyond LLM providers
|
|
||||||
- Sub-second response time for tool execution
|
|
||||||
- Graceful degradation on LLM errors
|
|
||||||
- Clear error messages for configuration issues
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# Active Context
|
# Active Context
|
||||||
|
|
||||||
## Current Focus
|
## Current Focus
|
||||||
Docs directory created with 4 files (PRD.md, Architecture.md, API.md, Discoveries.md). All source files previously written and verified — typecheck and lint are both clean.
|
All source files written and verified — typecheck and lint are both clean.
|
||||||
|
|
||||||
## Session State (as of this writing)
|
## Session State (as of this writing)
|
||||||
- All source files complete and passing `tsc --noEmit` (0 errors) and `oxlint` (0 errors, 0 warnings)
|
- All source files complete and passing `tsc --noEmit` (0 errors) and `oxlint` (0 errors, 0 warnings)
|
||||||
|
|||||||
@@ -34,7 +34,6 @@
|
|||||||
- **Full typecheck pass**: `tsc --noEmit` → 0 errors
|
- **Full typecheck pass**: `tsc --noEmit` → 0 errors
|
||||||
- **Full lint pass**: `oxlint` → 0 errors, 0 warnings
|
- **Full lint pass**: `oxlint` → 0 errors, 0 warnings
|
||||||
- `package.json` scripts added: `start`, `dev`, `typecheck`
|
- `package.json` scripts added: `start`, `dev`, `typecheck`
|
||||||
- **Docs created**: `/docs/PRD.md`, `Architecture.md`, `API.md`, `Discoveries.md`
|
|
||||||
|
|
||||||
### 🔄 In Progress
|
### 🔄 In Progress
|
||||||
- Nothing
|
- Nothing
|
||||||
|
|||||||
Reference in New Issue
Block a user