2ndbrain 2026.1.30 → 2026.1.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +17 -0
- package/LICENSE +21 -0
- package/README.md +1 -1
- package/db/migrations/001_initial_schema.sql +91 -0
- package/doc/SPEC.md +896 -0
- package/hooks/auto-capture.sh +4 -0
- package/hooks/validate-command.sh +374 -0
- package/package.json +34 -20
- package/skills/journal/SKILL.md +112 -0
- package/skills/knowledge/SKILL.md +165 -0
- package/skills/project-manage/SKILL.md +216 -0
- package/skills/recall/SKILL.md +182 -0
- package/skills/system-ops/SKILL.md +161 -0
- package/src/attachments/store.js +167 -0
- package/src/claude/bridge.js +291 -0
- package/src/claude/conversation.js +219 -0
- package/src/config.js +90 -0
- package/src/db/migrate.js +94 -0
- package/src/db/pool.js +33 -0
- package/src/embeddings/engine.js +281 -0
- package/src/embeddings/worker.js +221 -0
- package/src/hooks/lifecycle.js +448 -0
- package/src/index.js +560 -0
- package/src/logging.js +91 -0
- package/src/mcp/config.js +75 -0
- package/src/mcp/embed-server.js +242 -0
- package/src/rate-limiter.js +114 -0
- package/src/telegram/bot.js +546 -0
- package/src/telegram/commands.js +440 -0
- package/src/web/server.js +1119 -0
package/doc/SPEC.md
ADDED
|
@@ -0,0 +1,896 @@
|
|
|
1
|
+
# 2ndbrain Specification
|
|
2
|
+
|
|
3
|
+
**Version:** 0.5.0
|
|
4
|
+
**Status:** RFC
|
|
5
|
+
**Date:** 2026-01-30
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## 1. Overview
|
|
9
|
+
|
|
10
|
+
**2ndbrain** is an always-on Node.js service that bridges Telegram messaging to Claude AI. It runs on a local network device (e.g. Raspberry Pi 5) and provides a single owner with a persistent, private AI assistant accessible via Telegram, with a local web interface for configuration and monitoring.
|
|
11
|
+
|
|
12
|
+
The service spawns `claude-cli` as a subprocess to handle AI interactions, maintains conversation history in PostgreSQL, supports slash commands for system control, and enforces access through independent whitelists for Telegram users, local commands, MCP tools, and admin features.
|
|
13
|
+
|
|
14
|
+
Running `npx 2ndbrain` starts both the background service and the web admin interface, automatically opening the browser. On first run the browser opens to the Settings page for initial configuration; on subsequent runs it opens to the Dashboard.
|
|
15
|
+
|
|
16
|
+
The service includes a personal knowledge platform with a knowledge graph, vector embeddings, journaling, and project management -- all accessible through natural conversation.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## 2. Architecture
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
+------------------+ +------------------+ +------------------+
|
|
23
|
+
| Telegram | <---> | Command Router | ----> | Slash Command |
|
|
24
|
+
| Bot Adapter | | | | Handlers |
|
|
25
|
+
| (long-polling) | +--------+---------+ +------------------+
|
|
26
|
+
+------------------+ |
|
|
27
|
+
| conversational messages
|
|
28
|
+
v
|
|
29
|
+
+--------+---------+
|
|
30
|
+
| Claude Bridge |
|
|
31
|
+
| (claude-cli |
|
|
32
|
+
| subprocess) |
|
|
33
|
+
+--------+---------+
|
|
34
|
+
|
|
|
35
|
+
+-----+-----+
|
|
36
|
+
| |
|
|
37
|
+
v v
|
|
38
|
+
+---------+--+ +-----+---------+
|
|
39
|
+
| MCP Tool | | Conversation |
|
|
40
|
+
| Layer | | Manager |
|
|
41
|
+
| (pg, cmds) | | (history, |
|
|
42
|
+
+-----+------+ | compaction) |
|
|
43
|
+
| +-----+---------+
|
|
44
|
+
| |
|
|
45
|
+
v v
|
|
46
|
+
+-----+---------------+------+
|
|
47
|
+
| PostgreSQL |
|
|
48
|
+
| Database |
|
|
49
|
+
+---+---+---+---+---+---+----+
|
|
50
|
+
| | | | | |
|
|
51
|
+
+---------------------+ | | | | +-------------------+
|
|
52
|
+
| +-----------+ | | +----------+ |
|
|
53
|
+
v v v v v v
|
|
54
|
+
+------+ +---------+ +----------+ +--------+ +-------+ +----------+
|
|
55
|
+
|Attach| |Knowledge| |Embeddings| |Projects| |Journal| |System |
|
|
56
|
+
|ments | |Graph | |Engine | |& Issues| | | |Logs |
|
|
57
|
+
+------+ +---------+ +----------+ +--------+ +-------+ +----------+
|
|
58
|
+
|
|
59
|
+
+------------------+
|
|
60
|
+
| Web Admin |
|
|
61
|
+
| Server (Express) |
|
|
62
|
+
| - Dashboard |
|
|
63
|
+
| - Settings |
|
|
64
|
+
| - Logs |
|
|
65
|
+
+------------------+
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Components
|
|
69
|
+
|
|
70
|
+
| Component | Responsibility |
|
|
71
|
+
|-----------|---------------|
|
|
72
|
+
| **Telegram Bot Adapter** | Long-polling listener via Telegram Bot API. Receives messages and attachments, sends responses, manages "Typing" indicator. |
|
|
73
|
+
| **Command Router** | Parses incoming messages. Routes `/` prefixed messages to slash command handlers; all others to the Claude Bridge. |
|
|
74
|
+
| **Claude Bridge** | Spawns `claude-cli` as a child process with conversation context. Passes MCP configuration and tool whitelist. Captures stdout response. |
|
|
75
|
+
| **MCP Tool Layer** | Locally-registered MCP servers available to Claude. Includes PostgreSQL MCP, `embed_query` tool, and whitelisted local shell commands. |
|
|
76
|
+
| **Conversation Manager** | Persists conversation turns to `conversation_messages`. Handles auto-compaction when history exceeds a threshold. |
|
|
77
|
+
| **Database Layer** | PostgreSQL connection pool. Schema migrations. |
|
|
78
|
+
| **Process Manager** | Startup sequence, graceful shutdown (SIGTERM/SIGINT), restart, health checks, error notification to owner, rate limiting. |
|
|
79
|
+
| **Attachment Store** | Saves received Telegram file attachments to `~/data/attachments/YYYY/MM/DD/{uuid}.{ext}`. Attachments are also included in the conversation context sent to Claude (e.g., images as vision input). |
|
|
80
|
+
| **Web Admin Server** | Express HTTP server for dashboard, settings, and log viewing. Always starts with the service. Auto-opens the default browser on launch (Settings page on first run; Dashboard on subsequent runs). LAN-only binding. |
|
|
81
|
+
| **Knowledge Platform** | Knowledge graph (nodes & edges), journal, project management (projects, specifications, issues), and embeddings engine (pgvector-backed semantic search). |
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
## 3. Telegram Bot Adapter
|
|
85
|
+
|
|
86
|
+
The adapter connects to Telegram via long-polling and handles all message I/O.
|
|
87
|
+
|
|
88
|
+
| Property | Value |
|
|
89
|
+
|----------|-------|
|
|
90
|
+
| Polling method | Long-polling (`getUpdates`) -- no public URL required |
|
|
91
|
+
| Supported inbound types | text, photo, document, audio, video, voice (attachments are stored and forwarded to Claude as conversation context) |
|
|
92
|
+
| Response format | Markdown (Telegram MarkdownV2) |
|
|
93
|
+
| Max message length | 4096 characters; longer responses chunked into multiple messages |
|
|
94
|
+
| Typing indicator | Sent before Claude invocation; refreshed every 4s on long responses |
|
|
95
|
+
| Reply behavior | Bot sends each response as a reply to the triggering user message (`reply_to_message_id`). Provides visual threading in the Telegram UI, but conversation history remains flat (single linear context). |
|
|
96
|
+
|
|
97
|
+
**Access control:** Only whitelisted Telegram user IDs may interact with the bot. Messages from non-whitelisted users are silently dropped and logged (see §14).
|
|
98
|
+
|
|
99
|
+
See also: §4 (Command Router), §8 (Attachment Store), §14 (Security Model), §18 (`TELEGRAM_BOT_TOKEN`, `TELEGRAM_ALLOWED_USERS`)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
## 4. Command Router
|
|
103
|
+
|
|
104
|
+
Parses incoming messages and routes them:
|
|
105
|
+
- Messages prefixed with `/` are dispatched to slash command handlers
|
|
106
|
+
- All other messages are forwarded to the Claude Bridge (§5)
|
|
107
|
+
|
|
108
|
+
### Slash Commands
|
|
109
|
+
|
|
110
|
+
| Command | Args | Description | Response |
|
|
111
|
+
|---------|------|-------------|----------|
|
|
112
|
+
| `/status` | none | System status summary | Uptime, memory usage, message count, last activity |
|
|
113
|
+
| `/health` | none | Health check | OK / degraded / error with component details |
|
|
114
|
+
| `/stop` | none | Graceful shutdown | Confirmation message, then process exit |
|
|
115
|
+
| `/restart` | none | Process restart | Confirmation prompt; on confirm, restart process |
|
|
116
|
+
| `/reboot` | none | OS reboot | Confirmation prompt; on confirm, `sudo reboot` |
|
|
117
|
+
| `/new` | none | Start new conversation session | Confirmation; old session preserved in logs |
|
|
118
|
+
| `/help` | none | List available commands | Command list with descriptions |
|
|
119
|
+
|
|
120
|
+
`/restart` and `/reboot` require explicit confirmation reply before execution (see §14.4). `/new` starts a fresh Claude session; the previous session remains accessible in logs and search.
|
|
121
|
+
|
|
122
|
+
See also: §5 (Claude Bridge), §14.4 (Dangerous Operations)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
## 5. Claude Bridge
|
|
126
|
+
|
|
127
|
+
Spawns `claude -p` (print mode) as a child process for each conversational message. The CLI manages its own conversation context via sessions; 2ndbrain tracks the `session_id` for continuations.
|
|
128
|
+
|
|
129
|
+
### Invocation Pattern
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# First message (new session):
|
|
133
|
+
claude -p \
|
|
134
|
+
--output-format stream-json --verbose \
|
|
135
|
+
--model $CLAUDE_MODEL \
|
|
136
|
+
--system-prompt "$SYSTEM_PROMPT" \
|
|
137
|
+
--mcp-config $MCP_CONFIG_PATH \
|
|
138
|
+
--allowed-tools "$MCP_TOOLS_WHITELIST" \
|
|
139
|
+
[--max-budget-usd $CLAUDE_MAX_BUDGET] \
|
|
140
|
+
"$user_message"
|
|
141
|
+
|
|
142
|
+
# Continuation (existing session):
|
|
143
|
+
claude -p \
|
|
144
|
+
--output-format stream-json --verbose \
|
|
145
|
+
--resume $SESSION_ID \
|
|
146
|
+
"$user_message"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
For long messages or messages with special characters, pipe via stdin:
|
|
150
|
+
```bash
|
|
151
|
+
echo "$user_message" | claude -p --resume $SESSION_ID
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Behavior
|
|
155
|
+
|
|
156
|
+
- **Streaming output:** `stream-json` format emits `{"type":"assistant",...}` chunks during generation (used to refresh the Telegram typing indicator) and a final `{"type":"result",...}` object containing `session_id`, `total_cost_usd`, and token usage.
|
|
157
|
+
- **Session management:** The `session_id` from the result object is stored and passed back via `--resume` on subsequent messages. See §6 for session lifecycle.
|
|
158
|
+
- **System prompt:** Assembled by the `on_pre_claude` lifecycle hook (§13.1). Includes current date/time, user preferences, and skill instructions.
|
|
159
|
+
- **MCP configuration:** `--mcp-config` points to the runtime MCP config file. The CLI also loads MCP servers from `~/.claude/settings.json`.
|
|
160
|
+
- **Tool whitelist:** `--allowed-tools` enforces `MCP_TOOLS_WHITELIST` at the CLI level.
|
|
161
|
+
- **Cost control:** `--max-budget-usd` caps cost per invocation (optional, see `CLAUDE_MAX_BUDGET` in §18).
|
|
162
|
+
- **stderr** monitored for errors.
|
|
163
|
+
- **Process timeout** enforced by killing the subprocess after `CLAUDE_TIMEOUT` ms.
|
|
164
|
+
|
|
165
|
+
See also: §6 (Conversation Manager -- session lifecycle), §7 (MCP Tool Layer), §12 (Claude Skills), §13 (Claude Hooks), §18 (`CLAUDE_MODEL`, `CLAUDE_TIMEOUT`, `CLAUDE_MAX_BUDGET`)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
## 6. Conversation Manager
|
|
169
|
+
|
|
170
|
+
Persists all conversation turns to the `conversation_messages` table, manages Claude CLI sessions, and controls history size through auto-compaction.
|
|
171
|
+
|
|
172
|
+
### Session Management
|
|
173
|
+
|
|
174
|
+
The Claude CLI manages its own conversation context internally via sessions. 2ndbrain tracks the active `session_id` and passes it back to the CLI via `--resume` (see §5).
|
|
175
|
+
|
|
176
|
+
**Session lifecycle:**
|
|
177
|
+
- **New session** is created when: the service starts for the first time, the user sends `/new`, or the previous session encounters an unrecoverable error.
|
|
178
|
+
- **Active session** continues via `--resume $SESSION_ID` on each subsequent message.
|
|
179
|
+
- **Session ID** is captured from the `session_id` field in the CLI's JSON result object and stored in the `conversation_messages` table.
|
|
180
|
+
|
|
181
|
+
### Persistence
|
|
182
|
+
|
|
183
|
+
Every user message and assistant response is stored with role, content, `session_id`, and metadata (tool calls, attachments, `telegram_message_id` for reply threading). The `conversation_messages` table serves as a log and search index; the CLI manages the actual conversation context used for generation.
|
|
184
|
+
|
|
185
|
+
### Auto-Compaction
|
|
186
|
+
|
|
187
|
+
Compaction runs only when no Claude subprocess is active (prevents race conditions).
|
|
188
|
+
|
|
189
|
+
When `conversation_messages` count exceeds `HISTORY_COMPACT_THRESHOLD`:
|
|
190
|
+
|
|
191
|
+
1. Select the oldest N messages (where N = threshold - 20, keeping the 20 most recent verbatim)
|
|
192
|
+
2. Send the old messages to Claude with a summarization prompt
|
|
193
|
+
3. Insert a single `role='summary'` message with the condensed context
|
|
194
|
+
4. Archive or delete the original old messages
|
|
195
|
+
5. Log the compaction event
|
|
196
|
+
|
|
197
|
+
See also: §17.1 (`conversation_messages` table), §18 (`HISTORY_COMPACT_THRESHOLD`)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
## 7. MCP Tool Layer
|
|
201
|
+
|
|
202
|
+
Locally-registered MCP servers and tools available to the Claude subprocess.
|
|
203
|
+
|
|
204
|
+
### PostgreSQL MCP Server
|
|
205
|
+
|
|
206
|
+
Provides the `mcp__pg__query` tool for Claude to execute SQL queries against the database. Used by the knowledge, journal, project-manage, recall, and system-ops skills (§12).
|
|
207
|
+
|
|
208
|
+
### `embed_query` MCP Tool
|
|
209
|
+
|
|
210
|
+
A lightweight MCP tool registered alongside the PostgreSQL MCP server. It accepts `{ text: "search query" }` and returns `{ vector: [0.123, ...], dimensions: 1536 }` by calling the configured embedding provider API. This allows the Claude subprocess to vectorize search queries without direct API access.
|
|
211
|
+
|
|
212
|
+
Available only when `EMBEDDING_PROVIDER` is configured.
|
|
213
|
+
|
|
214
|
+
### Whitelisted Shell Commands
|
|
215
|
+
|
|
216
|
+
Claude can execute shell commands matching patterns in `COMMANDS_WHITELIST`. Command execution is enforced by the `validate-command.sh` subprocess hook (§13.2).
|
|
217
|
+
|
|
218
|
+
### Tool Whitelist
|
|
219
|
+
|
|
220
|
+
`MCP_TOOLS_WHITELIST` controls which MCP tools are available to Claude. Set to `*` for all tools, or a comma-separated list of tool names.
|
|
221
|
+
|
|
222
|
+
See also: §12 (Claude Skills), §13.2 (Command Whitelist Enforcement), §14 (Security Model), §18 (`MCP_CONFIG_PATH`, `COMMANDS_WHITELIST`, `MCP_TOOLS_WHITELIST`)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
## 8. Attachment Store
|
|
226
|
+
|
|
227
|
+
Handles Telegram file attachments received by the bot.
|
|
228
|
+
|
|
229
|
+
### Storage
|
|
230
|
+
|
|
231
|
+
Files are saved to `$DATA_DIR/attachments/YYYY/MM/DD/{uuid}.{ext}`, where `$DATA_DIR` defaults to `~/data`.
|
|
232
|
+
|
|
233
|
+
### Flow
|
|
234
|
+
|
|
235
|
+
1. Telegram adapter receives a message with an attachment
|
|
236
|
+
2. File is downloaded via Telegram Bot API using `telegram_file_id`
|
|
237
|
+
3. File is saved to the date-organized directory structure
|
|
238
|
+
4. A record is inserted into the `attachments` table with file metadata
|
|
239
|
+
5. Attachment data is included in the conversation context sent to Claude (images are sent as vision input)
|
|
240
|
+
|
|
241
|
+
See also: §3 (Telegram Bot Adapter), §17.2 (`attachments` table), §18 (`DATA_DIR`)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
## 9. Web Admin Server
|
|
245
|
+
|
|
246
|
+
Express HTTP server for configuration and monitoring. Always starts alongside the background service as part of `npx 2ndbrain`.
|
|
247
|
+
|
|
248
|
+
### Routes
|
|
249
|
+
|
|
250
|
+
| Route | Method | Description |
|
|
251
|
+
|-------|--------|-------------|
|
|
252
|
+
| `/` | GET | Dashboard: system status overview, recent activity, quick actions |
|
|
253
|
+
| `/settings` | GET/POST | View and update environment configuration (first-run landing page when no configuration exists) |
|
|
254
|
+
| `/logs` | GET | Activity log viewer with filtering |
|
|
255
|
+
| `/health` | GET | JSON health check endpoint |
|
|
256
|
+
|
|
257
|
+
### Browser Auto-Open
|
|
258
|
+
|
|
259
|
+
On launch, auto-opens the default browser to the web UI:
|
|
260
|
+
- **First run** (no configuration file or missing required env vars): opens to `/settings` so the user can configure the service
|
|
261
|
+
- **Subsequent runs** (configuration present): opens to `/` (Dashboard)
|
|
262
|
+
|
|
263
|
+
Set `AUTO_OPEN_BROWSER=false` for headless/systemd deployments.
|
|
264
|
+
|
|
265
|
+
### Access Control
|
|
266
|
+
|
|
267
|
+
- Binds to `WEB_BIND` address (default `127.0.0.1`, LAN-only)
|
|
268
|
+
- No authentication required; access is restricted by network binding
|
|
269
|
+
- Authentication can be added later if remote access is needed
|
|
270
|
+
|
|
271
|
+
See also: §14 (Security Model), §18 (`WEB_PORT`, `WEB_BIND`, `AUTO_OPEN_BROWSER`)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
## 10. Process Manager
|
|
275
|
+
|
|
276
|
+
Manages the service lifecycle: startup, shutdown, restart, health checks, error notification, and rate limiting.
|
|
277
|
+
|
|
278
|
+
### Startup Sequence
|
|
279
|
+
|
|
280
|
+
1. Load environment variables
|
|
281
|
+
2. Validate required config (`TELEGRAM_BOT_TOKEN`, `TELEGRAM_ALLOWED_USERS`, `DATABASE_URL`)
|
|
282
|
+
3. Connect to PostgreSQL, run pending migrations from `db/migrations/*.sql` (numbered files, e.g. `001_initial_schema.sql`; applied migrations tracked in a `schema_migrations` table)
|
|
283
|
+
4. Resolve embedding configuration if `EMBEDDING_PROVIDER` is set (see §11.4)
|
|
284
|
+
5. Verify `claude-cli` is available (`claude --version`)
|
|
285
|
+
6. Start web admin server on `WEB_BIND`:`WEB_PORT`
|
|
286
|
+
7. Start Telegram long-polling
|
|
287
|
+
8. Log startup, set process signal handlers
|
|
288
|
+
9. Auto-open browser (unless `AUTO_OPEN_BROWSER=false` or running under systemd/no-TTY):
|
|
289
|
+
- **First run** (no `.env` file or missing required config): open `http://{WEB_BIND}:{WEB_PORT}/settings`
|
|
290
|
+
- **Subsequent runs**: open `http://{WEB_BIND}:{WEB_PORT}/`
|
|
291
|
+
|
|
292
|
+
### Graceful Shutdown
|
|
293
|
+
|
|
294
|
+
On SIGTERM or SIGINT:
|
|
295
|
+
1. Flush pending outgoing messages
|
|
296
|
+
2. Close database connections
|
|
297
|
+
3. Log shutdown event
|
|
298
|
+
4. Exit process
|
|
299
|
+
|
|
300
|
+
### Error Notification
|
|
301
|
+
|
|
302
|
+
Component errors are pushed to the owner via Telegram message. See §15 for the full error handling matrix.
|
|
303
|
+
|
|
304
|
+
### Rate Limiting
|
|
305
|
+
|
|
306
|
+
Configurable limits prevent runaway API usage:
|
|
307
|
+
- `RATE_LIMIT_CLAUDE`: max Claude calls per minute (default: 10)
|
|
308
|
+
- `RATE_LIMIT_TELEGRAM`: max Telegram sends per minute (default: 30)
|
|
309
|
+
|
|
310
|
+
Requests exceeding limits are queued. Users are notified if queue depth exceeds a threshold.
|
|
311
|
+
|
|
312
|
+
### System Logging
|
|
313
|
+
|
|
314
|
+
Structured operational logs are written to the `system_logs` table with level (`debug`, `info`, `warn`, `error`), source component, and content.
|
|
315
|
+
|
|
316
|
+
See also: §9 (Web Admin Server), §15 (Error Handling), §17.1 (`system_logs` table), §18 (rate limit and log variables)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
## 11. Knowledge Platform
|
|
320
|
+
|
|
321
|
+
2ndbrain's personal knowledge features: a knowledge graph, journal, project management, and vector embeddings for semantic search.
|
|
322
|
+
|
|
323
|
+
### 11.1 Knowledge Graph
|
|
324
|
+
|
|
325
|
+
Entity relationship storage using nodes and edges.
|
|
326
|
+
|
|
327
|
+
- **Nodes** represent concepts, people, places, ideas -- anything worth tracking
|
|
328
|
+
- **Edges** represent named, directed relationships between nodes (e.g., "works at", "related to", "depends on")
|
|
329
|
+
- Unique constraint on `(source_id, target_id, name)` prevents duplicate edges
|
|
330
|
+
- Full-text search across node `name` and `note` fields
|
|
331
|
+
- New nodes are automatically queued for embedding generation when embeddings are enabled
|
|
332
|
+
|
|
333
|
+
See also: §12 (`knowledge` skill), §17.3 (`knowledge_nodes`, `knowledge_edges` tables)
|
|
334
|
+
|
|
335
|
+
### 11.2 Journal
|
|
336
|
+
|
|
337
|
+
Personal note and journal entries for reflection and recall.
|
|
338
|
+
|
|
339
|
+
- Free-form text entries stored with timestamps
|
|
340
|
+
- Searchable by text content and date range
|
|
341
|
+
- New entries are automatically queued for embedding generation when embeddings are enabled
|
|
342
|
+
|
|
343
|
+
See also: §12 (`journal` skill), §17.5 (`journal` table)
|
|
344
|
+
|
|
345
|
+
### 11.3 Project Management
|
|
346
|
+
|
|
347
|
+
Lightweight project tracking with hierarchical issues and specifications.
|
|
348
|
+
|
|
349
|
+
- **Projects** are named containers
|
|
350
|
+
- **Issues** track tasks, bugs, and blockers with completion status. Support `parent_id` for sub-task hierarchies.
|
|
351
|
+
- **Specifications** document requirements, architecture decisions, and API contracts. Support `parent_id` for nested specs.
|
|
352
|
+
|
|
353
|
+
See also: §12 (`project-manage` skill), §17.4 (`projects`, `specifications`, `issues` tables)
|
|
354
|
+
|
|
355
|
+
### 11.4 Embeddings Engine
|
|
356
|
+
|
|
357
|
+
pgvector-backed semantic search over all knowledge platform records.
|
|
358
|
+
|
|
359
|
+
**Activation:** Embeddings are enabled when `EMBEDDING_PROVIDER` is set. When unset, embeddings are disabled (no pgvector required, `embedding_config` and `embeddings` tables are not created). All features gracefully degrade to text-based search.
|
|
360
|
+
|
|
361
|
+
**`embed_query` MCP tool:** Registered alongside the PostgreSQL MCP server (§7). Accepts `{ text: "search query" }` and returns `{ vector: [...], dimensions: N }` by calling the configured embedding provider API.
|
|
362
|
+
|
|
363
|
+
#### Configuration Resolution (runs at startup step 4)
|
|
364
|
+
|
|
365
|
+
1. **Resolve dimensions:**
|
|
366
|
+
- If `EMBEDDING_DIMENSIONS` is set, use that value.
|
|
367
|
+
- Else look up model defaults: `text-embedding-3-small` = 1536, `text-embedding-3-large` = 3072, `text-embedding-ada-002` = 1536.
|
|
368
|
+
- If the model is unknown and `EMBEDDING_DIMENSIONS` is not set, **fail startup** with a clear error.
|
|
369
|
+
|
|
370
|
+
2. **First-time setup** (no `embedding_config` row):
|
|
371
|
+
- `CREATE EXTENSION IF NOT EXISTS vector`
|
|
372
|
+
- Create `embedding_config` and `embeddings` tables with the resolved dimension
|
|
373
|
+
- Create the HNSW index
|
|
374
|
+
- Insert the config row
|
|
375
|
+
|
|
376
|
+
3. **Model switch** (`embedding_config` row exists but provider, model, or dimensions differ):
|
|
377
|
+
- Log warning: *"Embedding model changed from {old} to {new}. All existing embeddings will be dropped and re-generated."*
|
|
378
|
+
- Drop the HNSW index
|
|
379
|
+
- `ALTER TABLE embeddings DROP COLUMN vector`
|
|
380
|
+
- `ALTER TABLE embeddings ADD COLUMN vector VECTOR(${new_dimensions})`
|
|
381
|
+
- Recreate the HNSW index
|
|
382
|
+
- Update the `embedding_config` row
|
|
383
|
+
- Queue all existing rows (`vector IS NULL`) for background re-embedding
|
|
384
|
+
|
|
385
|
+
4. **No change** (config matches): no action needed.
|
|
386
|
+
|
|
387
|
+
The background re-embedding worker processes `NULL`-vector rows progressively after startup. Semantic search returns partial results during re-embedding; text fallback covers the gap.
|
|
388
|
+
|
|
389
|
+
For new entities, embeddings are automatically generated on-save when all embedding environment variables are properly defined (`EMBEDDING_PROVIDER`, `EMBEDDING_API_KEY`, `EMBEDDING_MODEL`). No manual step is required; disabling is implicit when `EMBEDDING_PROVIDER` is unset.
|
|
390
|
+
|
|
391
|
+
See also: §7 (`embed_query` MCP tool), §12 (`recall` skill), §17.6 (`embedding_config`, `embeddings` tables), §18 (embedding variables)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
## 12. Claude Skills
|
|
395
|
+
|
|
396
|
+
2ndbrain configures a set of custom skills for the Claude subprocess via `SKILL.md` files in the runtime working directory. These skills enable the AI assistant to perform structured operations against the PostgreSQL database and host system on behalf of the owner through natural conversation.
|
|
397
|
+
|
|
398
|
+
### 12.1 Skill File Layout
|
|
399
|
+
|
|
400
|
+
```
|
|
401
|
+
$DATA_DIR/claude-runtime/.claude/skills/
|
|
402
|
+
journal/SKILL.md
|
|
403
|
+
knowledge/SKILL.md
|
|
404
|
+
project-manage/SKILL.md
|
|
405
|
+
recall/SKILL.md
|
|
406
|
+
system-ops/SKILL.md
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
The runtime skill directory is passed to `claude-cli` via the `--project` flag (or equivalent working directory configuration).
|
|
410
|
+
|
|
411
|
+
### 12.2 Skill Definitions
|
|
412
|
+
|
|
413
|
+
Each skill is defined as a `SKILL.md` file containing natural-language instructions for Claude. The SQL patterns documented below describe the operations each skill's prompt instructs Claude to perform via `mcp__pg__query`. The `SKILL.md` files are prompts, not executable code.
|
|
414
|
+
|
|
415
|
+
#### `journal` -- Personal Journal
|
|
416
|
+
|
|
417
|
+
| Property | Value |
|
|
418
|
+
|----------|-------|
|
|
419
|
+
| Name | `journal` |
|
|
420
|
+
| Description | Create and search personal journal entries. Use when the user wants to note something, reflect, or recall past entries. |
|
|
421
|
+
| Invocation | User sends message with `/journal` preamble, or automatic (Claude detects intent: "note to self", "I want to remember", "what did I write about X") |
|
|
422
|
+
| Allowed tools | PostgreSQL MCP (`mcp__pg__query`) |
|
|
423
|
+
| Tables | `journal`, `embeddings` |
|
|
424
|
+
|
|
425
|
+
**Behaviors:**
|
|
426
|
+
- **Create entry:** `INSERT INTO journal (note) VALUES ($1)` with the user's note content
|
|
427
|
+
- **Search entries:** `SELECT * FROM journal WHERE note ILIKE '%' || $1 || '%' ORDER BY created_at DESC LIMIT 10`
|
|
428
|
+
- **Date-filtered recall:** `SELECT * FROM journal WHERE created_at >= $1 AND created_at < $2 ORDER BY created_at DESC`
|
|
429
|
+
- **Post-create:** Queue for embedding: `INSERT INTO embeddings (entity_type, entity_id) VALUES ('journal', $1) ON CONFLICT DO NOTHING`. No-op when embeddings are disabled (table does not exist).
|
|
430
|
+
|
|
431
|
+
#### `knowledge` -- Knowledge Graph Operations
|
|
432
|
+
|
|
433
|
+
| Property | Value |
|
|
434
|
+
|----------|-------|
|
|
435
|
+
| Name | `knowledge` |
|
|
436
|
+
| Description | Manage personal knowledge graph. Create nodes (concepts, people, places, ideas), connect them with named relationships, and query the graph. |
|
|
437
|
+
| Invocation | User sends message with `/knowledge` preamble, or automatic (Claude detects entity mentions, relationship descriptions, or "how is X related to Y" queries) |
|
|
438
|
+
| Allowed tools | PostgreSQL MCP (`mcp__pg__query`) |
|
|
439
|
+
| Tables | `knowledge_nodes`, `knowledge_edges`, `embeddings` |
|
|
440
|
+
|
|
441
|
+
**Behaviors:**
|
|
442
|
+
- **Create node:** `INSERT INTO knowledge_nodes (name, note) VALUES ($1, $2) RETURNING id`
|
|
443
|
+
- **Create edge:** `INSERT INTO knowledge_edges (source_id, target_id, name) VALUES ($1, $2, $3) ON CONFLICT (source_id, target_id, name) DO NOTHING`
|
|
444
|
+
- **Query by name:** `SELECT * FROM knowledge_nodes WHERE name ILIKE '%' || $1 || '%'`
|
|
445
|
+
- **Traverse edges:** `SELECT kn.name, ke.name AS relationship FROM knowledge_edges ke JOIN knowledge_nodes kn ON kn.id = ke.target_id WHERE ke.source_id = $1`
|
|
446
|
+
- **Full-text search:** Search across both `name` and `note` fields
|
|
447
|
+
- **Post-create:** Queue for embedding: `INSERT INTO embeddings (entity_type, entity_id) VALUES ('node', $1) ON CONFLICT DO NOTHING`. No-op when embeddings are disabled (table does not exist).
|
|
448
|
+
|
|
449
|
+
#### `project-manage` -- Project and Issue Tracking
|
|
450
|
+
|
|
451
|
+
| Property | Value |
|
|
452
|
+
|----------|-------|
|
|
453
|
+
| Name | `project-manage` |
|
|
454
|
+
| Description | Manage projects, specifications, and issues. Create projects, add tasks, track progress, mark items complete. |
|
|
455
|
+
| Invocation | User sends message with `/project` preamble, or automatic (Claude detects task/project mentions: "I need to do X", "add a task for Y", "what's the status of project Z") |
|
|
456
|
+
| Allowed tools | PostgreSQL MCP (`mcp__pg__query`) |
|
|
457
|
+
| Tables | `projects`, `specifications`, `issues` |
|
|
458
|
+
|
|
459
|
+
**Behaviors:**
|
|
460
|
+
- **Create project:** `INSERT INTO projects (name) VALUES ($1) RETURNING id`
|
|
461
|
+
- **Create issue:** `INSERT INTO issues (project_id, parent_id, note) VALUES ($1, $2, $3) RETURNING id`
|
|
462
|
+
- **Create spec:** `INSERT INTO specifications (project_id, parent_id, note) VALUES ($1, $2, $3) RETURNING id`
|
|
463
|
+
- **Mark complete:** `UPDATE issues SET completed = TRUE, updated_at = NOW() WHERE id = $1`
|
|
464
|
+
- **Status summary:** Aggregate open/closed issue counts per project
|
|
465
|
+
- **Sub-task hierarchy:** Support `parent_id` for nested issues and specifications
|
|
466
|
+
|
|
467
|
+
#### `recall` -- Semantic Memory Search
|
|
468
|
+
|
|
469
|
+
| Property | Value |
|
|
470
|
+
|----------|-------|
|
|
471
|
+
| Name | `recall` |
|
|
472
|
+
| Description | Search across all personal data using natural language. Searches journal entries, knowledge graph, projects, issues, and conversation history. |
|
|
473
|
+
| Invocation | Automatic (Claude detects recall intent: "find", "remember", "what was", "search for") |
|
|
474
|
+
| Allowed tools | PostgreSQL MCP (`mcp__pg__query`), `embed_query` MCP tool |
|
|
475
|
+
| Tables | `embedding_config`, `embeddings` + all entity tables via join |
|
|
476
|
+
|
|
477
|
+
**Behaviors:**
|
|
478
|
+
|
|
479
|
+
1. **Check availability:** `SELECT dimensions FROM embedding_config WHERE id = 1`
|
|
480
|
+
2. **If semantic search is available:**
|
|
481
|
+
- Call the `embed_query` MCP tool with the user's search text to obtain a query vector
|
|
482
|
+
- Execute: `SELECT entity_type, entity_id, 1 - (vector <=> $1::vector) AS similarity FROM embeddings WHERE vector IS NOT NULL ORDER BY vector <=> $1::vector LIMIT 10`
|
|
483
|
+
- Join results back to source tables based on `entity_type` to retrieve full content
|
|
484
|
+
3. **If semantic search is unavailable** (table missing, no config row, or `embed_query` fails):
|
|
485
|
+
- Fall back to `ILIKE` text search across `journal.note`, `knowledge_nodes.name`, `knowledge_nodes.note`, `issues.note`, `specifications.note`
|
|
486
|
+
- Note to user: "Using text search (semantic search unavailable)"
|
|
487
|
+
4. **Source attribution:** Present results with context ("From your journal on Jan 15...", "From project X, issue #3...")
|
|
488
|
+
|
|
489
|
+
**Dependency:** Semantic search requires `EMBEDDING_PROVIDER` to be configured (see §11.4). Text fallback always operates.
|
|
490
|
+
|
|
491
|
+
#### `system-ops` -- System Health Queries
|
|
492
|
+
|
|
493
|
+
| Property | Value |
|
|
494
|
+
|----------|-------|
|
|
495
|
+
| Name | `system-ops` |
|
|
496
|
+
| Description | Check system health, uptime, memory usage, and database status. Read-only system queries. |
|
|
497
|
+
| Invocation | Automatic (Claude detects health queries: "how's the system", "check disk space", "is everything running") |
|
|
498
|
+
| Allowed tools | Bash (`uptime`, `free`, `df`, `pg_isready`) |
|
|
499
|
+
| Tables | `system_logs`, `conversation_messages` (read-only) |
|
|
500
|
+
|
|
501
|
+
**Behaviors:**
|
|
502
|
+
- **Uptime:** `uptime`
|
|
503
|
+
- **Memory:** `free -h`
|
|
504
|
+
- **Disk:** `df -h $DATA_DIR`
|
|
505
|
+
- **Database status:** `pg_isready` and `SELECT COUNT(*) FROM conversation_messages`
|
|
506
|
+
- **Recent errors:** `SELECT * FROM system_logs WHERE level = 'error' ORDER BY created_at DESC LIMIT 5`
|
|
507
|
+
|
|
508
|
+
**Restrictions:** This skill provides read-only system access only. It does NOT support restart, reboot, shutdown, or any mutating operations. Those remain exclusive to slash commands with confirmation prompts (§4).
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
## 13. Claude Hooks
|
|
512
|
+
|
|
513
|
+
2ndbrain uses hooks at two levels: application lifecycle hooks within the Node.js service, and Claude subprocess hooks configured in the runtime `.claude/settings.json`.
|
|
514
|
+
|
|
515
|
+
### 13.1 Application Lifecycle Hooks
|
|
516
|
+
|
|
517
|
+
The 2ndbrain service implements an internal event pipeline. Each hook receives a context object and can modify it or abort the pipeline.
|
|
518
|
+
|
|
519
|
+
| Event | When | Context | Purpose |
|
|
520
|
+
|-------|------|---------|---------|
|
|
521
|
+
| `on_message_received` | Telegram message arrives, before routing | `{ message, telegram_user_id, timestamp }` | Log to `system_logs`, check rate limits, validate whitelist |
|
|
522
|
+
| `on_pre_claude` | Before spawning `claude-cli` subprocess | `{ conversation_history, system_prompt, mcp_config, skills_dir }` | Inject system prompt with current date/time and user preferences; assemble conversation context from `conversation_messages`; set `--mcp-config` path and tool whitelist |
|
|
523
|
+
| `on_post_claude` | After `claude-cli` returns stdout | `{ response, tool_calls, duration_ms }` | Log to `system_logs`; if embeddings enabled, queue response for embedding generation; extract entities for knowledge graph (auto-capture) |
|
|
524
|
+
| `on_pre_send` | Before sending response to Telegram | `{ text, parse_mode }` | Chunk messages exceeding 4096 chars; escape MarkdownV2 special characters; validate formatting |
|
|
525
|
+
| `on_error` | Any component error | `{ error, source, context }` | Notify owner via Telegram (per §15); log to `system_logs`; trigger retry logic if applicable |
|
|
526
|
+
| `on_startup` | Service startup complete | `{ timestamp, config }` | Verify all components healthy; log startup event; notify owner if recovering from a crash |
|
|
527
|
+
| `on_shutdown` | Graceful shutdown initiated | `{ signal, timestamp }` | Flush pending messages; close DB connections; log shutdown event |
|
|
528
|
+
|
|
529
|
+
**Hook registration:** Hooks are registered as async functions in the service's event emitter. Multiple handlers per event are supported and execute in registration order. A handler can abort the pipeline by throwing or returning `{ abort: true, reason: "..." }`.
|
|
530
|
+
|
|
531
|
+
```js
|
|
532
|
+
// Example hook registration pattern
|
|
533
|
+
bot.on('on_pre_claude', async (ctx) => {
|
|
534
|
+
ctx.system_prompt += `\nCurrent time: ${new Date().toISOString()}`;
|
|
535
|
+
return ctx;
|
|
536
|
+
});
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### 13.2 Claude Subprocess Hooks
|
|
540
|
+
|
|
541
|
+
These hooks are configured in the runtime `.claude/settings.json` file that `claude-cli` reads when spawned. They run inside the Claude subprocess context.
|
|
542
|
+
|
|
543
|
+
#### Command Whitelist Enforcement
|
|
544
|
+
|
|
545
|
+
| Property | Value |
|
|
546
|
+
|----------|-------|
|
|
547
|
+
| Event | `PreToolUse` |
|
|
548
|
+
| Matcher | `Bash` |
|
|
549
|
+
| Type | `command` |
|
|
550
|
+
| Script | `$DATA_DIR/claude-runtime/hooks/validate-command.sh` |
|
|
551
|
+
|
|
552
|
+
**Behavior:**
|
|
553
|
+
- Reads `tool_input.command` from stdin JSON
|
|
554
|
+
- Checks against `COMMANDS_WHITELIST` patterns
|
|
555
|
+
- **Blocks** (exit code 2): `sudo` (except whitelisted), `rm -rf`, `shutdown`, `reboot`, `kill`, `pkill`, writing to system directories, network configuration changes, package installation, file writes outside `~` and `FILE_EDIT_PATHS` (see §14.5)
|
|
556
|
+
- **Allows** (exit code 0): commands matching whitelist patterns, read-only system queries from `system-ops` skill, file writes within `~` or `FILE_EDIT_PATHS`
|
|
557
|
+
- **Logging:** All blocked attempts are logged to `system_logs` via a PostgreSQL insert
|
|
558
|
+
|
|
559
|
+
```json
|
|
560
|
+
{
|
|
561
|
+
"hooks": {
|
|
562
|
+
"PreToolUse": [
|
|
563
|
+
{
|
|
564
|
+
"matcher": "Bash",
|
|
565
|
+
"hooks": [
|
|
566
|
+
{
|
|
567
|
+
"type": "command",
|
|
568
|
+
"command": "$DATA_DIR/claude-runtime/hooks/validate-command.sh"
|
|
569
|
+
}
|
|
570
|
+
]
|
|
571
|
+
}
|
|
572
|
+
]
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
#### Auto Knowledge Capture (v2 -- deferred)
|
|
578
|
+
|
|
579
|
+
| Property | Value |
|
|
580
|
+
|----------|-------|
|
|
581
|
+
| Event | `PostToolUse` |
|
|
582
|
+
| Matcher | `Bash\|mcp__pg__.*` |
|
|
583
|
+
| Type | `command` |
|
|
584
|
+
| Script | `$DATA_DIR/claude-runtime/hooks/auto-capture.sh` |
|
|
585
|
+
|
|
586
|
+
**Status:** Deferred to v2. In v1, knowledge graph entries are created explicitly via the `/knowledge` preamble or Claude's automatic intent detection (see §12.2).
|
|
587
|
+
|
|
588
|
+
**Planned behavior (v2):**
|
|
589
|
+
- Checks `EMBEDDING_PROVIDER` env var; if empty, exits 0 immediately (embeddings disabled)
|
|
590
|
+
- Fires after successful tool executions (bash commands and DB queries)
|
|
591
|
+
- Reads `tool_response` from stdin JSON
|
|
592
|
+
- Queues substantive outputs for embedding generation (skips trivial outputs like row counts)
|
|
593
|
+
- Rate-limited: max 10 captures per minute to avoid flooding the embeddings queue
|
|
594
|
+
- Non-blocking: exits 0 regardless of capture success
|
|
595
|
+
|
|
596
|
+
#### Response Length Guard
|
|
597
|
+
|
|
598
|
+
Implemented as a Node.js check in the `on_pre_send` application lifecycle hook (§13.1), not as a Claude subprocess hook.
|
|
599
|
+
|
|
600
|
+
**Behavior:**
|
|
601
|
+
- After receiving the Claude response, the `on_pre_send` hook checks `text.length`
|
|
602
|
+
- If the response exceeds 3500 characters, re-invokes Claude with a condensation prompt: "Condense the following to under 3500 characters, using bullet points and brief sentences."
|
|
603
|
+
- If condensation also exceeds the limit, falls through to the Telegram adapter's chunking logic (§3)
|
|
604
|
+
- This avoids the cost and latency of a prompt-type subprocess hook on every response
|
|
605
|
+
|
|
606
|
+
### 13.3 Runtime Hook File Layout
|
|
607
|
+
|
|
608
|
+
```
|
|
609
|
+
$DATA_DIR/claude-runtime/
|
|
610
|
+
.claude/
|
|
611
|
+
settings.json # Subprocess hooks configuration (§13.2)
|
|
612
|
+
skills/ # Skill definitions (§12.2)
|
|
613
|
+
journal/SKILL.md
|
|
614
|
+
knowledge/SKILL.md
|
|
615
|
+
project-manage/SKILL.md
|
|
616
|
+
recall/SKILL.md
|
|
617
|
+
system-ops/SKILL.md
|
|
618
|
+
hooks/
|
|
619
|
+
validate-command.sh # Command whitelist enforcement
|
|
620
|
+
auto-capture.sh # Post-tool knowledge capture
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
## 14. Security Model
|
|
625
|
+
|
|
626
|
+
### 14.1 Whitelist Architecture
|
|
627
|
+
|
|
628
|
+
2ndbrain uses four independent whitelists, each governing a separate access domain:
|
|
629
|
+
|
|
630
|
+
| Whitelist | Scope | Configuration |
|
|
631
|
+
|-----------|-------|---------------|
|
|
632
|
+
| **Telegram** | Which Telegram user IDs can interact with the bot | `TELEGRAM_ALLOWED_USERS` env var (comma-separated IDs) |
|
|
633
|
+
| **Commands** | Which local shell commands Claude can execute | `COMMANDS_WHITELIST` env var (command patterns) |
|
|
634
|
+
| **MCP Tools** | Which MCP tools are available to Claude | `MCP_TOOLS_WHITELIST` env var (`*` or comma-separated names) |
|
|
635
|
+
| **Admin** | Who can access the web admin interface | Network binding via `WEB_BIND` (default `127.0.0.1`, LAN-only) |
|
|
636
|
+
|
|
637
|
+
### 14.2 Unauthorized Access Behavior
|
|
638
|
+
|
|
639
|
+
- Messages from non-whitelisted Telegram users: **silent drop + log the attempt** (no response sent)
|
|
640
|
+
- Non-whitelisted command execution attempts: **blocked, logged, owner notified**
|
|
641
|
+
|
|
642
|
+
### 14.3 Secrets Management
|
|
643
|
+
|
|
644
|
+
All secrets stored in `.env` file (excluded from version control):
|
|
645
|
+
|
|
646
|
+
| Secret | Description |
|
|
647
|
+
|--------|-------------|
|
|
648
|
+
| `TELEGRAM_BOT_TOKEN` | Telegram Bot API token |
|
|
649
|
+
| `DATABASE_URL` | PostgreSQL connection string |
|
|
650
|
+
| `CLAUDE_API_KEY` | Claude API key (if needed beyond CLI auth) |
|
|
651
|
+
|
|
652
|
+
### 14.4 Dangerous Operations
|
|
653
|
+
|
|
654
|
+
- `/reboot` requires explicit confirmation reply before execution
|
|
655
|
+
- `/restart` requires confirmation reply
|
|
656
|
+
- Local command execution is limited to whitelisted patterns only
|
|
657
|
+
|
|
658
|
+
### 14.5 File System Access
|
|
659
|
+
|
|
660
|
+
Claude may edit files on the host within defined boundaries:
|
|
661
|
+
|
|
662
|
+
| Scope | Access | Enforcement |
|
|
663
|
+
|-------|--------|-------------|
|
|
664
|
+
| **Home directory** (`~`) | Read and write permitted | Always allowed |
|
|
665
|
+
| **Whitelisted paths** | Read and write permitted | Paths listed in `FILE_EDIT_PATHS` env var (comma-separated absolute paths) |
|
|
666
|
+
| **All other paths** | Requires explicit user permission | Claude requests confirmation via Telegram before each write; user must approve |
|
|
667
|
+
| **System directories** (`/etc`, `/usr`, `/boot`, `/sys`, `/proc`) and other users' home directories | **Always blocked** | Rejected by `validate-command.sh` hook (§13.2) regardless of user confirmation |
|
|
668
|
+
|
|
669
|
+
Path enforcement is applied by the `validate-command.sh` subprocess hook (§13.2) which inspects file-write targets in `tool_input.command` before execution.
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
## 15. Error Handling
|
|
673
|
+
|
|
674
|
+
| Failure | Behavior |
|
|
675
|
+
|---------|----------|
|
|
676
|
+
| Claude CLI unreachable / crashes | Retry with exponential backoff (3 attempts). After all retries fail, notify owner via Telegram: "Claude is unavailable." |
|
|
677
|
+
| PostgreSQL down | Buffer incoming messages in memory (bounded queue). Retry DB connection with backoff. Notify owner if DB is down for >60s. |
|
|
678
|
+
| Telegram API unreachable | Buffer outgoing messages. Retry with backoff. Log errors. |
|
|
679
|
+
| Uncaught exception | Log the error, notify owner via Telegram, attempt graceful restart. |
|
|
680
|
+
| Claude timeout | Kill the subprocess after `CLAUDE_TIMEOUT` ms. Respond to user: "Response timed out, please try again." |
|
|
681
|
+
| Rate limit exceeded | Queue the request. Notify user if queue depth exceeds a threshold. |
|
|
682
|
+
| Claude CLI not found at startup | Fail startup with error: "claude not found. Install Claude Code: https://claude.ai/code" |
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
## 16. Deployment
|
|
686
|
+
|
|
687
|
+
### Target Platform
|
|
688
|
+
- Raspberry Pi 5 (ARM64, Debian/Ubuntu)
|
|
689
|
+
- Eventually any Linux host with Node.js
|
|
690
|
+
|
|
691
|
+
### Prerequisites
|
|
692
|
+
- Node.js >= 20
|
|
693
|
+
- PostgreSQL >= 15
|
|
694
|
+
- `claude-cli` installed and authenticated
|
|
695
|
+
- pgvector extension (required when `EMBEDDING_PROVIDER` is set)
|
|
696
|
+
|
|
697
|
+
### Installation
|
|
698
|
+
```bash
|
|
699
|
+
npx 2ndbrain
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
Requires adding a `bin` field to `package.json`:
|
|
703
|
+
```json
|
|
704
|
+
{
|
|
705
|
+
"bin": {
|
|
706
|
+
"2ndbrain": "./src/index.js"
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
### Boot Configuration (Linux)
|
|
712
|
+
|
|
713
|
+
systemd service unit template:
|
|
714
|
+
|
|
715
|
+
```ini
|
|
716
|
+
[Unit]
|
|
717
|
+
Description=2ndbrain Telegram-Claude bridge
|
|
718
|
+
After=network.target postgresql.service
|
|
719
|
+
|
|
720
|
+
[Service]
|
|
721
|
+
Type=simple
|
|
722
|
+
User={SYSTEM_USER}
|
|
723
|
+
EnvironmentFile=/home/{SYSTEM_USER}/.env
|
|
724
|
+
Environment=AUTO_OPEN_BROWSER=false
|
|
725
|
+
ExecStart=/usr/bin/npx 2ndbrain
|
|
726
|
+
Restart=on-failure
|
|
727
|
+
RestartSec=10
|
|
728
|
+
|
|
729
|
+
[Install]
|
|
730
|
+
WantedBy=multi-user.target
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
See §10 for the full startup sequence.
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
## 17. Data Model
|
|
737
|
+
|
|
738
|
+
### 17.1 Conversation & Logging
|
|
739
|
+
|
|
740
|
+
```sql
|
|
741
|
+
CREATE TABLE conversation_messages (
|
|
742
|
+
id SERIAL PRIMARY KEY,
|
|
743
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
744
|
+
session_id TEXT, -- Claude CLI session ID (links to --resume)
|
|
745
|
+
role TEXT NOT NULL, -- 'user', 'assistant', 'system', 'summary'
|
|
746
|
+
content TEXT NOT NULL,
|
|
747
|
+
metadata JSONB -- tool calls, attachments, telegram_message_id (used for reply_to_message_id on responses), cost_usd, etc.
|
|
748
|
+
);
|
|
749
|
+
|
|
750
|
+
CREATE TABLE system_logs (
|
|
751
|
+
id SERIAL PRIMARY KEY,
|
|
752
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
753
|
+
level TEXT NOT NULL DEFAULT 'info', -- 'debug', 'info', 'warn', 'error'
|
|
754
|
+
source TEXT, -- component name (e.g. 'telegram', 'claude', 'process')
|
|
755
|
+
content TEXT NOT NULL
|
|
756
|
+
);
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
### 17.2 Attachments
|
|
760
|
+
|
|
761
|
+
```sql
|
|
762
|
+
CREATE TABLE attachments (
|
|
763
|
+
id SERIAL PRIMARY KEY,
|
|
764
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
765
|
+
message_id INTEGER REFERENCES conversation_messages(id) ON DELETE SET NULL,
|
|
766
|
+
telegram_file_id TEXT,
|
|
767
|
+
mime_type TEXT,
|
|
768
|
+
file_path TEXT NOT NULL, -- relative to ~/data (e.g. attachments/2026/01/30/abc123.jpg)
|
|
769
|
+
file_size INTEGER
|
|
770
|
+
);
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
### 17.3 Knowledge Graph
|
|
774
|
+
|
|
775
|
+
```sql
|
|
776
|
+
CREATE TABLE knowledge_nodes (
|
|
777
|
+
id SERIAL PRIMARY KEY,
|
|
778
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
779
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
780
|
+
name TEXT NOT NULL,
|
|
781
|
+
note TEXT
|
|
782
|
+
);
|
|
783
|
+
|
|
784
|
+
CREATE TABLE knowledge_edges (
|
|
785
|
+
id SERIAL PRIMARY KEY,
|
|
786
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
787
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
788
|
+
source_id INTEGER NOT NULL REFERENCES knowledge_nodes(id) ON DELETE CASCADE,
|
|
789
|
+
target_id INTEGER NOT NULL REFERENCES knowledge_nodes(id) ON DELETE CASCADE,
|
|
790
|
+
name TEXT NOT NULL,
|
|
791
|
+
UNIQUE(source_id, target_id, name)
|
|
792
|
+
);
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
### 17.4 Project Management
|
|
796
|
+
|
|
797
|
+
```sql
|
|
798
|
+
CREATE TABLE projects (
|
|
799
|
+
id SERIAL PRIMARY KEY,
|
|
800
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
801
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
802
|
+
name TEXT NOT NULL
|
|
803
|
+
);
|
|
804
|
+
|
|
805
|
+
CREATE TABLE specifications (
|
|
806
|
+
id SERIAL PRIMARY KEY,
|
|
807
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
808
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
809
|
+
project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
810
|
+
parent_id INTEGER REFERENCES specifications(id) ON DELETE SET NULL,
|
|
811
|
+
note TEXT NOT NULL
|
|
812
|
+
);
|
|
813
|
+
|
|
814
|
+
CREATE TABLE issues (
|
|
815
|
+
id SERIAL PRIMARY KEY,
|
|
816
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
817
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
818
|
+
project_id INTEGER REFERENCES projects(id) ON DELETE SET NULL,
|
|
819
|
+
parent_id INTEGER REFERENCES issues(id) ON DELETE SET NULL,
|
|
820
|
+
note TEXT NOT NULL,
|
|
821
|
+
completed BOOLEAN NOT NULL DEFAULT FALSE
|
|
822
|
+
);
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
### 17.5 Journal
|
|
826
|
+
|
|
827
|
+
```sql
|
|
828
|
+
CREATE TABLE journal (
|
|
829
|
+
id SERIAL PRIMARY KEY,
|
|
830
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
831
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
832
|
+
note TEXT NOT NULL
|
|
833
|
+
);
|
|
834
|
+
```
|
|
835
|
+
|
|
836
|
+
### 17.6 Embeddings
|
|
837
|
+
|
|
838
|
+
```sql
|
|
839
|
+
-- Embedding configuration (single row, tracks active model)
|
|
840
|
+
CREATE TABLE embedding_config (
|
|
841
|
+
id INTEGER PRIMARY KEY DEFAULT 1 CHECK (id = 1),
|
|
842
|
+
provider TEXT NOT NULL, -- 'openai' (extensible to any OpenAI-compatible API)
|
|
843
|
+
model TEXT NOT NULL, -- e.g. 'text-embedding-3-small'
|
|
844
|
+
dimensions INTEGER NOT NULL, -- e.g. 1536
|
|
845
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
846
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
847
|
+
);
|
|
848
|
+
|
|
849
|
+
-- Vector embeddings (requires pgvector extension)
|
|
850
|
+
-- NOTE: The VECTOR dimension is set dynamically at migration time based on
|
|
851
|
+
-- EMBEDDING_DIMENSIONS env var or the model's default. Example shown with 1536.
|
|
852
|
+
CREATE TABLE embeddings (
|
|
853
|
+
id SERIAL PRIMARY KEY,
|
|
854
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
855
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
856
|
+
entity_type TEXT NOT NULL, -- 'message', 'node', 'journal', 'issue', etc.
|
|
857
|
+
entity_id INTEGER NOT NULL,
|
|
858
|
+
vector VECTOR(${dimensions}), -- dynamic: 1536, 3072, etc. depending on model
|
|
859
|
+
UNIQUE(entity_type, entity_id)
|
|
860
|
+
);
|
|
861
|
+
|
|
862
|
+
-- HNSW index for fast approximate nearest neighbor search (cosine distance)
|
|
863
|
+
CREATE INDEX idx_embeddings_vector
|
|
864
|
+
ON embeddings USING hnsw (vector vector_cosine_ops);
|
|
865
|
+
```
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
## 18. Configuration Reference
|
|
869
|
+
|
|
870
|
+
| Variable | Required | Default | Component | Description |
|
|
871
|
+
|----------|----------|---------|-----------|-------------|
|
|
872
|
+
| `TELEGRAM_BOT_TOKEN` | yes | -- | §3 Telegram | Telegram Bot API token |
|
|
873
|
+
| `TELEGRAM_ALLOWED_USERS` | yes | -- | §3 Telegram | Comma-separated Telegram user IDs |
|
|
874
|
+
| `DATABASE_URL` | yes | -- | §17 Database | PostgreSQL connection string |
|
|
875
|
+
| `CLAUDE_MODEL` | no | `claude-sonnet-4-20250514` | §5 Claude Bridge | Claude model for `claude-cli` |
|
|
876
|
+
| `CLAUDE_THINKING` | no | `true` | §5 Claude Bridge | Enable extended thinking |
|
|
877
|
+
| `CLAUDE_TIMEOUT` | no | `120000` | §5 Claude Bridge | Claude subprocess timeout (ms) |
|
|
878
|
+
| `CLAUDE_MAX_BUDGET` | no | *(none)* | §5 Claude Bridge | Max cost per Claude invocation in USD (e.g. `0.50`) |
|
|
879
|
+
| `DATA_DIR` | no | `~/data` | §8 Attachments | Attachment and runtime storage root directory |
|
|
880
|
+
| `COMMANDS_WHITELIST` | no | *(none)* | §7 MCP Tools | Allowed shell command patterns (comma-separated) |
|
|
881
|
+
| `MCP_TOOLS_WHITELIST` | no | `*` | §7 MCP Tools | Allowed MCP tool names (`*` = all) |
|
|
882
|
+
| `FILE_EDIT_PATHS` | no | *(empty)* | §14 Security | Additional directories Claude may edit (comma-separated absolute paths). Home directory is always allowed. |
|
|
883
|
+
| `MCP_CONFIG_PATH` | no | `~/.claude/mcp.json` | §7 MCP Tools | Path to MCP server configuration |
|
|
884
|
+
| `RATE_LIMIT_CLAUDE` | no | `10` | §10 Process | Max Claude calls per minute |
|
|
885
|
+
| `RATE_LIMIT_TELEGRAM` | no | `30` | §10 Process | Max Telegram sends per minute |
|
|
886
|
+
| `HISTORY_COMPACT_THRESHOLD` | no | `100` | §6 Conversation | Message count before auto-compaction |
|
|
887
|
+
| `LOG_LEVEL` | no | `info` | §10 Process | Minimum log level (debug/info/warn/error) |
|
|
888
|
+
| `WEB_PORT` | no | `3000` | §9 Web Admin | Web admin server port |
|
|
889
|
+
| `WEB_BIND` | no | `127.0.0.1` | §9 Web Admin | Web admin bind address |
|
|
890
|
+
|
|
891
|
+
| `AUTO_OPEN_BROWSER` | no | `true` | §9 Web Admin | Auto-open browser to web UI on launch. Set to `false` for headless/systemd deployments. |
|
|
892
|
+
| `EMBEDDING_PROVIDER` | no | *(empty)* | §11 Knowledge | Embedding provider: `"openai"` or empty to disable. Extensible to any OpenAI-compatible API via `EMBEDDING_BASE_URL`. |
|
|
893
|
+
| `EMBEDDING_API_KEY` | no | *(empty)* | §11 Knowledge | API key for the embedding provider |
|
|
894
|
+
| `EMBEDDING_MODEL` | no | `text-embedding-3-small` | §11 Knowledge | Model name passed to the provider API |
|
|
895
|
+
| `EMBEDDING_DIMENSIONS` | no | *(empty)* | §11 Knowledge | Override output dimensions (empty = model default). OpenAI v3 models support truncation. |
|
|
896
|
+
| `EMBEDDING_BASE_URL` | no | *(empty)* | §11 Knowledge | Override API base URL (empty = `https://api.openai.com/v1`). Use for proxies or compatible providers. |
|