botholomew 0.16.4 → 0.18.0

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.
Files changed (98) hide show
  1. package/README.md +46 -41
  2. package/package.json +4 -9
  3. package/src/chat/agent.ts +37 -40
  4. package/src/chat/session.ts +10 -10
  5. package/src/cli.ts +0 -2
  6. package/src/commands/capabilities.ts +35 -33
  7. package/src/commands/context.ts +133 -221
  8. package/src/commands/init.ts +22 -1
  9. package/src/commands/mcpx.ts +21 -8
  10. package/src/commands/nuke.ts +52 -15
  11. package/src/commands/prepare.ts +16 -13
  12. package/src/config/loader.ts +1 -8
  13. package/src/config/schemas.ts +6 -0
  14. package/src/constants.ts +16 -32
  15. package/src/init/index.ts +52 -27
  16. package/src/mcpx/client.ts +21 -5
  17. package/src/mem/client.ts +33 -0
  18. package/src/{context → prompts}/capabilities.ts +11 -7
  19. package/src/schedules/store.ts +1 -1
  20. package/src/tasks/store.ts +1 -1
  21. package/src/threads/store.ts +1 -1
  22. package/src/tools/capabilities/refresh.ts +1 -1
  23. package/src/tools/membot/adapter.ts +111 -0
  24. package/src/tools/membot/copy.ts +59 -0
  25. package/src/tools/membot/count_lines.ts +53 -0
  26. package/src/tools/membot/edit.ts +72 -0
  27. package/src/tools/membot/exists.ts +54 -0
  28. package/src/tools/membot/index.ts +26 -0
  29. package/src/tools/{context → membot}/pipe.ts +34 -32
  30. package/src/tools/registry.ts +6 -37
  31. package/src/tools/tool.ts +6 -8
  32. package/src/tui/App.tsx +3 -4
  33. package/src/tui/components/ContextPanel.tsx +109 -226
  34. package/src/tui/components/HelpPanel.tsx +2 -2
  35. package/src/tui/components/StatusBar.tsx +0 -6
  36. package/src/tui/components/ThreadPanel.tsx +8 -7
  37. package/src/tui/wrapDetail.ts +11 -0
  38. package/src/worker/heartbeat.ts +0 -20
  39. package/src/worker/index.ts +13 -13
  40. package/src/worker/llm.ts +7 -9
  41. package/src/worker/prompt.ts +25 -13
  42. package/src/worker/spawn.ts +1 -1
  43. package/src/worker/tick.ts +10 -9
  44. package/src/commands/db.ts +0 -119
  45. package/src/commands/with-db.ts +0 -22
  46. package/src/context/chunker.ts +0 -275
  47. package/src/context/embedder-impl.ts +0 -100
  48. package/src/context/embedder.ts +0 -9
  49. package/src/context/fetcher-errors.ts +0 -8
  50. package/src/context/fetcher.ts +0 -515
  51. package/src/context/locks.ts +0 -146
  52. package/src/context/markdown-converter.ts +0 -186
  53. package/src/context/reindex.ts +0 -198
  54. package/src/context/store.ts +0 -841
  55. package/src/context/url-utils.ts +0 -25
  56. package/src/db/connection.ts +0 -255
  57. package/src/db/doctor.ts +0 -235
  58. package/src/db/embeddings.ts +0 -317
  59. package/src/db/query.ts +0 -56
  60. package/src/db/schema.ts +0 -93
  61. package/src/db/sql/1-core_tables.sql +0 -53
  62. package/src/db/sql/10-dedupe_context_items.sql +0 -26
  63. package/src/db/sql/11-rebuild_hnsw.sql +0 -8
  64. package/src/db/sql/12-workers.sql +0 -66
  65. package/src/db/sql/13-drive-paths.sql +0 -47
  66. package/src/db/sql/14-drop_hnsw_index.sql +0 -8
  67. package/src/db/sql/15-fts_index.sql +0 -8
  68. package/src/db/sql/16-source_url.sql +0 -7
  69. package/src/db/sql/17-worker_log_path.sql +0 -3
  70. package/src/db/sql/18-reset_embeddings_for_local.sql +0 -39
  71. package/src/db/sql/19-disk_backed_index.sql +0 -36
  72. package/src/db/sql/2-logging_tables.sql +0 -24
  73. package/src/db/sql/20-drop_db_tables_for_files.sql +0 -19
  74. package/src/db/sql/3-daemon_state.sql +0 -5
  75. package/src/db/sql/4-unique_context_path.sql +0 -1
  76. package/src/db/sql/5-reset_embeddings_for_openai.sql +0 -1
  77. package/src/db/sql/6-vss_index.sql +0 -7
  78. package/src/db/sql/7-drop_embeddings_fk.sql +0 -23
  79. package/src/db/sql/8-task_output.sql +0 -1
  80. package/src/db/sql/9-source-type.sql +0 -1
  81. package/src/tools/context/read-large-result.ts +0 -33
  82. package/src/tools/dir/create.ts +0 -47
  83. package/src/tools/dir/size.ts +0 -77
  84. package/src/tools/dir/tree.ts +0 -124
  85. package/src/tools/file/copy.ts +0 -73
  86. package/src/tools/file/count-lines.ts +0 -54
  87. package/src/tools/file/delete.ts +0 -83
  88. package/src/tools/file/edit.ts +0 -76
  89. package/src/tools/file/exists.ts +0 -33
  90. package/src/tools/file/info.ts +0 -66
  91. package/src/tools/file/move.ts +0 -66
  92. package/src/tools/file/read.ts +0 -67
  93. package/src/tools/file/write.ts +0 -58
  94. package/src/tools/search/fuse.ts +0 -96
  95. package/src/tools/search/index.ts +0 -127
  96. package/src/tools/search/regexp.ts +0 -82
  97. package/src/tools/search/semantic.ts +0 -167
  98. /package/src/{db → utils}/uuid.ts +0 -0
package/README.md CHANGED
@@ -14,12 +14,14 @@ documents, researching topics, organizing notes, and maintaining context
14
14
  over time — while you sleep, work, or chat with it.
15
15
 
16
16
  Botholomew has **no shell and no access to your real filesystem**. The
17
- agent's world is a sandboxed `context/` tree inside the project: it can
18
- read, write, edit, and grep files there, but cannot escape upward,
19
- follow symlinks, or touch anything outside. Local files and URLs are
20
- brought in through `botholomew context add`. External capabilities
21
- (email, Slack, the web, and hundreds of other services) are granted
22
- deliberately, per project, through MCP servers wired up via
17
+ agent's world is a per-project knowledge store managed by
18
+ [`membot`](https://github.com/evantahler/membot) every read, write,
19
+ search, and delete is addressed by `logical_path` (a DB key, not a
20
+ filesystem path), so a prompt-injected attempt to reach `~/.ssh/id_rsa`
21
+ has nowhere to land. Local files and URLs are brought in through
22
+ `botholomew context add`. External capabilities (email, Slack, the web,
23
+ and hundreds of other services) are granted deliberately, per project,
24
+ through MCP servers wired up via
23
25
  [MCPX](https://github.com/evantahler/mcpx).
24
26
 
25
27
  ---
@@ -32,12 +34,13 @@ deliberately, per project, through MCP servers wired up via
32
34
  - **Portable.** A project is just a directory of files — markdown for
33
35
  prompts, tasks, schedules, and context; CSVs for conversation history.
34
36
  Copy it, share it, `git diff` it, check it in (or `.gitignore` it).
35
- - **Your data, your disk.** Tasks, schedules, threads, and the agent's
36
- context tree are all real files you can `vim`, `grep`, and `git`.
37
- DuckDB is demoted to a single search-index sidecar (`index.duckdb`)
38
- that's fully derivable from disk and safe to delete. Model calls go
39
- direct to Anthropic; any further reach is scoped to the MCP servers
40
- you add.
37
+ - **Your data, your disk.** Tasks, schedules, threads, prompts, and
38
+ skills are all real files you can `vim`, `grep`, and `git`. The
39
+ knowledge store is a single local DuckDB file (`index.duckdb`,
40
+ managed by [`membot`](https://github.com/evantahler/membot)) append-only,
41
+ versioned, queryable with the DuckDB CLI if you ever want to. Model
42
+ calls go direct to Anthropic; any further reach is scoped to the MCP
43
+ servers you add.
41
44
  - **Extensible.** External tools come from MCP servers via
42
45
  [MCPX](https://github.com/evantahler/mcpx) — run them locally (Gmail,
43
46
  Slack, GitHub) or connect through an MCP gateway like
@@ -46,9 +49,12 @@ deliberately, per project, through MCP servers wired up via
46
49
  Reusable workflows are defined as markdown "skills" (slash commands)
47
50
  that the chat agent can also create, edit, and search at runtime.
48
51
  - **Safe by default.** The agent has no shell and no direct filesystem
49
- access. Every path-taking tool is sandboxed to the project's `context/`
50
- tree (NFC normalization + lstat-walk to reject symlinks at any level);
51
- every external capability is an MCP server you explicitly add.
52
+ access. The knowledge store is keyed by `logical_path` (an opaque DB
53
+ string, not a filesystem path); every external capability is an MCP
54
+ server you explicitly add. The remaining file-system paths the agent
55
+ touches (`tasks/`, `schedules/`, `prompts/`, `skills/`) all route
56
+ through a single sandbox helper (NFC normalization + lstat-walk to
57
+ reject symlinks at any level).
52
58
  - **Concurrent.** Many workers can run at once. Each writes a pidfile
53
59
  and heartbeats; tasks and schedules are claimed via `O_EXCL` lockfiles
54
60
  and crashed workers get reaped automatically.
@@ -133,8 +139,8 @@ my-project/
133
139
  standup.md
134
140
  capabilities.md
135
141
  mcpx/servers.json # external MCP servers (Gmail, Slack, …)
136
- models/ # local embedding model cache
137
- context/ # agent-writable knowledge tree
142
+ index.duckdb # knowledge store (managed by membot)
143
+ config.json # membot config (separate from config/config.json)
138
144
  tasks/ # one markdown file per task
139
145
  <id>.md # status & metadata in frontmatter
140
146
  .locks/<id>.lock # O_EXCL claim file (held by a worker)
@@ -144,12 +150,14 @@ my-project/
144
150
  threads/<YYYY-MM-DD>/<id>.csv # full conversation history
145
151
  workers/<id>.json # worker pidfile + heartbeat
146
152
  logs/<YYYY-MM-DD>/<id>.log # per-worker logs
147
- index.duckdb # search index sidecar (rebuildable; safe to delete)
148
153
  ```
149
154
 
150
- `index.duckdb` is the only opaque file; everything else is plain text.
151
- Delete the index any time and `botholomew context reindex` rebuilds it
152
- from `context/`.
155
+ Tasks, schedules, threads, prompts, and skills are plain text `vim`,
156
+ `grep`, and `git` work without ceremony. The agent's knowledge store
157
+ lives in `index.duckdb`, managed end-to-end by the
158
+ [`membot`](https://github.com/evantahler/membot) library: ingestion
159
+ (PDF/DOCX/HTML → markdown), local WASM embeddings, hybrid BM25 +
160
+ semantic search, append-only versioning, and URL refresh all live there.
153
161
 
154
162
  ---
155
163
 
@@ -157,8 +165,8 @@ from `context/`.
157
165
 
158
166
  ![CLI walkthrough: task list, task add, schedule list, context list](docs/assets/cli-tour.gif)
159
167
 
160
- Pulling a remote document straight into `context/` via an LLM-driven
161
- fetcher (`mcp_search` → `mcp_exec` → save):
168
+ Pulling a remote document straight into the knowledge store via an
169
+ LLM-driven fetcher (`mcp_search` → `mcp_exec` → `membot_pipe`):
162
170
 
163
171
  ![Importing a Google Doc into context](docs/assets/context-import-gdoc.gif)
164
172
 
@@ -170,14 +178,14 @@ fetcher (`mcp_search` → `mcp_exec` → save):
170
178
  | `botholomew chat` | Interactive Ink/React TUI |
171
179
  | `botholomew task list\|add\|view\|update\|reset\|delete` | Manage the task queue (markdown files in `tasks/`) |
172
180
  | `botholomew schedule list\|add\|view\|enable\|disable\|trigger\|delete` | Recurring work (markdown files in `schedules/`) |
173
- | `botholomew context add\|import\|tree\|stats\|reindex\|search\|read\|write\|edit\|move\|delete\|…` | Bring files/URLs into `context/`; rebuild the search index; expose the agent's file/dir tools as CLI subcommands |
181
+ | `botholomew context add\|ls\|tree\|read\|write\|search\|info\|versions\|diff\|refresh\|…` | Knowledge-store passthrough to [`membot`](https://github.com/evantahler/membot) `--config` is set to the project dir automatically |
182
+ | `botholomew context import-global` | Seed the project from `~/.membot` (copies `index.duckdb` + `config.json` in) |
174
183
  | `botholomew capabilities` | Rescan built-in + MCPX tools and rewrite `prompts/capabilities.md` |
175
184
  | `botholomew prompts list\|show\|create\|edit\|delete\|validate` | CRUD over the markdown files in `prompts/` (with strict frontmatter validation) |
176
185
  | `botholomew mcpx servers\|list\|add\|remove\|info\|search\|exec\|ping\|auth\|deauth\|import-global\|…` | Configure external MCP servers (passthrough to `mcpx`) |
177
186
  | `botholomew skill list\|show\|create\|validate` | Manage slash-command skills |
178
187
  | `botholomew thread list\|view` | Browse the agent's conversation history (CSVs in `threads/`) |
179
- | `botholomew nuke context\|tasks\|schedules\|threads\|all` | Bulk-erase project state |
180
- | `botholomew db doctor [--repair]` | Probe the search-index DB; rebuild via EXPORT/IMPORT |
188
+ | `botholomew nuke knowledge\|tasks\|schedules\|threads\|all` | Bulk-erase project state |
181
189
  | `botholomew upgrade` | Self-update |
182
190
 
183
191
  All `list` subcommands support `-l, --limit <n>` and `-o, --offset <n>` for pagination.
@@ -206,8 +214,9 @@ All `list` subcommands support `-l, --limit <n>` and `-o, --offset <n>` for pagi
206
214
  │ schedules/<id>.md │
207
215
  │ threads/<date>/<id>.csv │
208
216
  │ workers/<id>.json │
209
- context/ ─► index.duckdb
210
- (search sidecar)
217
+ prompts/, skills/, mcpx/
218
+ index.duckdb ◄─ membot
219
+ │ (knowledge store) │
211
220
  └──────────────────┬────────────────────┘
212
221
 
213
222
 
@@ -232,12 +241,11 @@ Topics worth understanding in detail:
232
241
  - **[The TUI](docs/tui.md)** — the `botholomew chat` Ink/React terminal UI:
233
242
  eight tabs, slash-command autocomplete, message queue, tool-call
234
243
  visualization, and a live workers panel.
235
- - **[Files & the sandbox](docs/files.md)** — the agent's `context/`
236
- tree, the path sandbox (NFC + lstat-walk), and how
237
- `context_read`/`context_write`/`context_edit` work.
238
- - **[Context & hybrid search](docs/context-and-search.md)** — LLM-driven
239
- chunking, local embeddings, and DuckDB BM25 + linear-scan vector
240
- search merged with reciprocal rank fusion.
244
+ - **[Files & the knowledge store](docs/files.md)** — the membot store,
245
+ the path sandbox (NFC + lstat-walk) for non-knowledge files, and how
246
+ `membot_read`/`membot_write`/`membot_edit` work.
247
+ - **[Context & search](docs/context-and-search.md)** — pointer to
248
+ membot for ingestion, chunking, embeddings, and hybrid search.
241
249
  - **[Tasks & schedules](docs/tasks-and-schedules.md)** — markdown
242
250
  frontmatter as the source of truth, lockfile-based claim, DAG
243
251
  validation, and natural-language recurring schedules.
@@ -260,15 +268,12 @@ Topics worth understanding in detail:
260
268
  ## Tech stack
261
269
 
262
270
  - **[Bun](https://bun.sh)** + TypeScript
263
- - **[DuckDB](https://duckdb.org)** via `@duckdb/node-api` drives the
264
- search-index sidecar only. `array_cosine_distance()` (core DuckDB) for
265
- vector search, plus the built-in FTS extension for BM25 keyword
266
- search; the index is rebuildable from `context/` at any time
271
+ - **[`membot`](https://github.com/evantahler/membot)** — owns the
272
+ knowledge store: ingestion (PDF/DOCX/HTML markdown), local WASM
273
+ embeddings, hybrid BM25 + semantic search over DuckDB, append-only
274
+ versioning, URL refresh. Botholomew consumes it as an SDK.
267
275
  - **[Anthropic SDK](https://docs.anthropic.com/en/api/client-sdks)** for
268
276
  Claude — the reasoning model
269
- - **[`@huggingface/transformers`](https://huggingface.co/docs/transformers.js)**
270
- for local embeddings (default `Xenova/bge-small-en-v1.5`, 384-dim) —
271
- no API key, weights cached on first run
272
277
  - **[MCPX](https://github.com/evantahler/mcpx)** for external tools
273
278
  - **[Ink 6](https://github.com/vadimdemedes/ink)** + **React 19** for the
274
279
  terminal UI
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botholomew",
3
- "version": "0.16.4",
3
+ "version": "0.18.0",
4
4
  "description": "An autonomous AI agent for knowledge work — works your task queue while you sleep.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -28,9 +28,7 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@anthropic-ai/sdk": "^0.92.0",
31
- "@duckdb/node-api": "^1.5.2-r.1",
32
- "@evantahler/mcpx": "0.21.3",
33
- "@huggingface/transformers": "^4.2.0",
31
+ "@evantahler/mcpx": "0.21.6",
34
32
  "ansis": "^4.2.0",
35
33
  "commander": "^14.0.0",
36
34
  "gray-matter": "^4.0.3",
@@ -38,8 +36,8 @@
38
36
  "ink-spinner": "^5.0.0",
39
37
  "ink-text-input": "^6.0.0",
40
38
  "istextorbinary": "^9.5.0",
39
+ "membot": "^0.14.0",
41
40
  "nanospinner": "^1.2.2",
42
- "onnxruntime-web": "1.26.0-dev.20260416-b7804b056c",
43
41
  "react": "^19.2.0",
44
42
  "uuid": "^14.0.0",
45
43
  "wrap-ansi": "^10.0.0",
@@ -57,8 +55,5 @@
57
55
  },
58
56
  "trustedDependencies": [
59
57
  "protobufjs"
60
- ],
61
- "patchedDependencies": {
62
- "@huggingface/transformers@4.2.0": "patches/@huggingface%2Ftransformers@4.2.0.patch"
63
- }
58
+ ]
64
59
  }
package/src/chat/agent.ts CHANGED
@@ -7,8 +7,8 @@ import type {
7
7
  ToolUseBlock,
8
8
  } from "@anthropic-ai/sdk/resources/messages";
9
9
  import type { McpxClient } from "@evantahler/mcpx";
10
+ import type { MembotClient } from "membot";
10
11
  import type { BotholomewConfig } from "../config/schemas.ts";
11
- import { withDb } from "../db/connection.ts";
12
12
  import { logInteraction } from "../threads/store.ts";
13
13
  import { registerAllTools } from "../tools/registry.ts";
14
14
  import {
@@ -24,6 +24,7 @@ import {
24
24
  buildMetaHeader,
25
25
  extractKeywords,
26
26
  loadPersistentContext,
27
+ MEMBOT_PROMPT_SECTION,
27
28
  STYLE_RULES,
28
29
  } from "../worker/prompt.ts";
29
30
  import type { ChatSession } from "./session.ts";
@@ -35,19 +36,29 @@ import {
35
36
 
36
37
  registerAllTools();
37
38
 
38
- /** Tools available in chat mode — no worker terminal tools (complete/fail/wait), no bulk-destructive file tools (copy/move, dir ops) */
39
+ /** Tools available in chat mode — no worker terminal tools (complete/fail/wait), and the destructive `membot_prune` is omitted (chat shouldn't permanently GC history). */
39
40
  const CHAT_TOOL_NAMES = new Set([
40
41
  "create_task",
41
42
  "list_tasks",
42
43
  "view_task",
43
44
  "update_task",
44
45
  "delete_task",
45
- "context_info",
46
- "context_tree",
47
- "context_read",
48
- "context_write",
49
- "context_edit",
50
- "search",
46
+ "membot_add",
47
+ "membot_list",
48
+ "membot_tree",
49
+ "membot_read",
50
+ "membot_write",
51
+ "membot_edit",
52
+ "membot_search",
53
+ "membot_info",
54
+ "membot_stats",
55
+ "membot_versions",
56
+ "membot_diff",
57
+ "membot_refresh",
58
+ "membot_exists",
59
+ "membot_count_lines",
60
+ "membot_copy",
61
+ "membot_pipe",
51
62
  "list_threads",
52
63
  "view_thread",
53
64
  "search_threads",
@@ -62,8 +73,6 @@ const CHAT_TOOL_NAMES = new Set([
62
73
  "mcp_search",
63
74
  "mcp_info",
64
75
  "mcp_exec",
65
- "read_large_result",
66
- "pipe_to_context",
67
76
  "spawn_worker",
68
77
  "skill_list",
69
78
  "skill_read",
@@ -84,7 +93,6 @@ export async function buildChatSystemPrompt(
84
93
  projectDir: string,
85
94
  options?: {
86
95
  keywordSource?: string;
87
- dbPath?: string;
88
96
  config?: Required<BotholomewConfig>;
89
97
  hasMcpTools?: boolean;
90
98
  },
@@ -97,9 +105,9 @@ export async function buildChatSystemPrompt(
97
105
  prompt += await loadPersistentContext(projectDir, taskKeywords);
98
106
 
99
107
  prompt += `## Instructions
100
- You are Botholomew, an AI agent personified by a wise owl. This is your interactive chat interface. Help the user manage tasks, review results from background worker activity, search context, and answer questions.
108
+ You are Botholomew, an AI agent personified by a wise owl. This is your interactive chat interface. Help the user manage tasks, review results from background worker activity, search the knowledge store, and answer questions.
101
109
  You do NOT execute long-running work directly — enqueue tasks for a background worker instead using create_task, and spawn a worker via spawn_worker when the user wants the task run now.
102
- Use the available tools to look up tasks, threads, schedules, and context when the user asks about them. Files the agent can read and write live under \`context/\` as project-relative paths (e.g. \`notes/foo.md\`). Use \`context_tree\` to see what's there, \`search\` (hybrid regexp + semantic) to find content, then \`context_read\` / \`context_info\` to drill in.
110
+ Use the available tools to look up tasks, threads, schedules, and the knowledge store when the user asks about them. The agent's knowledge lives in the membot store, keyed by \`logical_path\` (e.g. \`notes/foo.md\`). Use \`membot_tree\` to see what's there, \`membot_search\` (hybrid semantic + BM25) to find content, then \`membot_read\` / \`membot_info\` to drill in. Every write creates a new version — use \`membot_versions\` / \`membot_diff\` to inspect history.
103
111
  Past conversations live in CSV files under \`threads/\`; use \`list_threads\`, \`search_threads\`, and \`view_thread\` to find and page through them.
104
112
  When multiple tool calls are independent of each other (i.e., one does not depend on the result of another), call them all in a single response. They will be executed in parallel, which is faster than calling them one at a time.
105
113
  You can manage the agent's prompt files (always-on or keyword-loaded notes the agent sees in every turn) under \`prompts/\` via \`prompt_list\`, \`prompt_read\`, \`prompt_create\`, \`prompt_edit\` (git-style line-range patches), and \`prompt_delete\`. Files marked \`agent-modification: false\` are read-only — \`prompt_edit\` and \`prompt_delete\` will refuse them.
@@ -107,26 +115,28 @@ You can author and refine slash-command skills (reusable prompt templates stored
107
115
  Format your responses using Markdown. Use headings, bold, italic, lists, and code blocks to make your responses clear and well-structured.
108
116
  `;
109
117
 
118
+ prompt += `\n${MEMBOT_PROMPT_SECTION}`;
119
+
110
120
  if (options?.hasMcpTools) {
111
121
  prompt += `
112
122
  ## External Tools (MCP)
113
123
 
114
- ### Local context first
124
+ ### Local knowledge store first
115
125
 
116
- **Before any MCP read, search local context.** Files in \`context/\` (Gmail dumps, GitHub fetches, URL ingests, prior agent outputs) are usually already there — refetching is slower, costs tokens, and risks rate limits.
126
+ **Before any MCP read, search the membot knowledge store.** Prior ingests (Gmail dumps, GitHub fetches, URL captures, prior agent outputs) are usually already there — refetching is slower, costs tokens, and risks rate limits.
117
127
 
118
128
  Workflow for any "look up / find / read" intent:
119
129
 
120
- 1. \`search\` (hybrid regexp + semantic) over \`context/\`, then \`context_read\` / \`context_tree\` to drill in.
121
- 2. If freshness matters, call \`context_info\` and check the file's mtime. To re-pull stale content, write fresh into \`context/\` (\`pipe_to_context\` from an \`mcp_exec\` call is the typical path) rather than going to MCP for the whole document on every question.
130
+ 1. \`membot_search\` (hybrid semantic + BM25) over the store, then \`membot_read\` / \`membot_tree\` to drill in.
131
+ 2. If freshness matters, call \`membot_info\` and check the source mtime / refresh status. To re-pull stale content, call \`membot_refresh\` for URL-backed entries, or \`membot_pipe\` an \`mcp_exec\` call to capture a fresh snapshot.
122
132
  3. Only call \`mcp_exec\` for reads when the data is genuinely missing locally **or** must be real-time (e.g., "what's on my calendar right now").
123
133
 
124
- Writes always go through MCP — sending an email, creating an issue, posting to Slack. Don't search context first for those.
134
+ Writes to external systems always go through MCP — sending an email, creating an issue, posting to Slack. Don't search membot first for those.
125
135
 
126
136
  Examples:
127
- - "What does doc X say?" → \`search\` first.
128
- - "Any new emails from Y?" → \`search\` for the sender under \`context/gmail/\` (or wherever you've been ingesting mail) before hitting Gmail MCP.
129
- - "Send an email to Y" → MCP write directly; no context lookup.
137
+ - "What does doc X say?" → \`membot_search\` first.
138
+ - "Any new emails from Y?" → \`membot_search\` for the sender's name before hitting Gmail MCP.
139
+ - "Send an email to Y" → MCP write directly; no membot lookup.
130
140
 
131
141
  ### Calling MCP tools
132
142
 
@@ -201,7 +211,7 @@ export async function runChatTurn(input: {
201
211
  messages: MessageParam[];
202
212
  projectDir: string;
203
213
  config: Required<BotholomewConfig>;
204
- dbPath: string;
214
+ mem: MembotClient;
205
215
  threadId: string;
206
216
  mcpxClient: McpxClient | null;
207
217
  callbacks: ChatTurnCallbacks;
@@ -218,7 +228,7 @@ export async function runChatTurn(input: {
218
228
  messages,
219
229
  projectDir,
220
230
  config,
221
- dbPath,
231
+ mem,
222
232
  threadId,
223
233
  mcpxClient,
224
234
  callbacks,
@@ -259,7 +269,6 @@ export async function runChatTurn(input: {
259
269
  const keywordSource = findLastUserText(messages);
260
270
  const systemPrompt = await buildChatSystemPrompt(projectDir, {
261
271
  keywordSource,
262
- dbPath,
263
272
  config,
264
273
  hasMcpTools: mcpxClient != null,
265
274
  });
@@ -410,7 +419,7 @@ export async function runChatTurn(input: {
410
419
  toolUseBlocks.map(async (toolUse) => {
411
420
  const start = Date.now();
412
421
  const result = await executeChatToolCall(toolUse, {
413
- dbPath,
422
+ mem,
414
423
  projectDir,
415
424
  config,
416
425
  mcpxClient,
@@ -461,7 +470,7 @@ export async function runChatTurn(input: {
461
470
  }
462
471
 
463
472
  interface ChatToolCallCtx {
464
- dbPath: string;
473
+ mem: MembotClient;
465
474
  projectDir: string;
466
475
  config: Required<BotholomewConfig>;
467
476
  mcpxClient: McpxClient | null;
@@ -490,20 +499,8 @@ async function executeChatToolCall(
490
499
  }
491
500
 
492
501
  try {
493
- // `sleep` deliberately yields for up to an hour; opening a DuckDB
494
- // connection for that whole window would hold the instance-level file
495
- // lock and block any worker that also wants the DB. Run it without a
496
- // connection — the tool doesn't touch the DB.
497
- const runWithoutDb = tool.name === "sleep";
498
- const result = runWithoutDb
499
- ? await tool.execute(parsed.data, {
500
- ...baseCtx,
501
- conn: undefined as unknown as ToolContext["conn"],
502
- })
503
- : await withDb(baseCtx.dbPath, (conn) => {
504
- const ctx: ToolContext = { ...baseCtx, conn };
505
- return tool.execute(parsed.data, ctx);
506
- });
502
+ const ctx: ToolContext = baseCtx;
503
+ const result = await tool.execute(parsed.data, ctx);
507
504
  const isError =
508
505
  typeof result === "object" && result !== null && "is_error" in result
509
506
  ? (result as { is_error: boolean }).is_error
@@ -1,11 +1,10 @@
1
1
  import type { MessageStream } from "@anthropic-ai/sdk/lib/MessageStream";
2
2
  import type { MessageParam } from "@anthropic-ai/sdk/resources/messages";
3
+ import type { MembotClient } from "membot";
3
4
  import { loadConfig } from "../config/loader.ts";
4
5
  import type { BotholomewConfig } from "../config/schemas.ts";
5
- import { getDbPath } from "../constants.ts";
6
- import { withDb } from "../db/connection.ts";
7
- import { migrate } from "../db/schema.ts";
8
- import { createMcpxClient } from "../mcpx/client.ts";
6
+ import { createMcpxClient, resolveMcpxDir } from "../mcpx/client.ts";
7
+ import { openMembot, resolveMembotDir } from "../mem/client.ts";
9
8
  import { loadSkills } from "../skills/loader.ts";
10
9
  import type { SkillDefinition } from "../skills/parser.ts";
11
10
  import {
@@ -20,7 +19,7 @@ import { generateThreadTitle } from "../utils/title.ts";
20
19
  import { type ChatTurnCallbacks, runChatTurn } from "./agent.ts";
21
20
 
22
21
  export interface ChatSession {
23
- dbPath: string;
22
+ mem: MembotClient;
24
23
  threadId: string;
25
24
  projectDir: string;
26
25
  config: Required<BotholomewConfig>;
@@ -61,8 +60,8 @@ export async function startChatSession(
61
60
  );
62
61
  }
63
62
 
64
- const dbPath = getDbPath(projectDir);
65
- await withDb(dbPath, (conn) => migrate(conn));
63
+ const mem = openMembot(resolveMembotDir(projectDir, config));
64
+ await mem.connect();
66
65
  await ensureThreadsDir(projectDir);
67
66
 
68
67
  let threadId: string;
@@ -102,15 +101,16 @@ export async function startChatSession(
102
101
  );
103
102
  }
104
103
 
105
- const mcpxClient = await createMcpxClient(projectDir);
104
+ const mcpxClient = await createMcpxClient(resolveMcpxDir(projectDir, config));
106
105
  const skills = await loadSkills(projectDir);
107
106
 
108
107
  const cleanup = async () => {
109
108
  await mcpxClient?.close();
109
+ await mem.close();
110
110
  };
111
111
 
112
112
  return {
113
- dbPath,
113
+ mem,
114
114
  threadId,
115
115
  projectDir,
116
116
  config,
@@ -158,7 +158,7 @@ export async function sendMessage(
158
158
  messages: session.messages,
159
159
  projectDir: session.projectDir,
160
160
  config: session.config,
161
- dbPath: session.dbPath,
161
+ mem: session.mem,
162
162
  threadId: session.threadId,
163
163
  mcpxClient: session.mcpxClient,
164
164
  callbacks,
package/src/cli.ts CHANGED
@@ -6,7 +6,6 @@ import { registerCapabilitiesCommand } from "./commands/capabilities.ts";
6
6
  import { registerChatCommand } from "./commands/chat.ts";
7
7
  import { registerCheckUpdateCommand } from "./commands/check-update.ts";
8
8
  import { registerContextCommand } from "./commands/context.ts";
9
- import { registerDbCommand } from "./commands/db.ts";
10
9
  import { registerInitCommand } from "./commands/init.ts";
11
10
  import { registerMcpxCommand } from "./commands/mcpx.ts";
12
11
  import { registerNukeCommand } from "./commands/nuke.ts";
@@ -42,7 +41,6 @@ registerThreadCommand(program);
42
41
  registerScheduleCommand(program);
43
42
  registerChatCommand(program);
44
43
  registerContextCommand(program);
45
- registerDbCommand(program);
46
44
  registerCapabilitiesCommand(program);
47
45
  registerPromptsCommand(program);
48
46
  registerMcpxCommand(program);
@@ -1,9 +1,9 @@
1
1
  import type { Command } from "commander";
2
2
  import { createSpinner } from "nanospinner";
3
3
  import { loadConfig } from "../config/loader.ts";
4
- import { writeCapabilitiesFile } from "../context/capabilities.ts";
5
- import { createMcpxClient } from "../mcpx/client.ts";
6
- import { withDb } from "./with-db.ts";
4
+ import { createMcpxClient, resolveMcpxDir } from "../mcpx/client.ts";
5
+ import { writeCapabilitiesFile } from "../prompts/capabilities.ts";
6
+ import { registerAllTools } from "../tools/registry.ts";
7
7
 
8
8
  export function registerCapabilitiesCommand(program: Command) {
9
9
  program
@@ -12,35 +12,37 @@ export function registerCapabilitiesCommand(program: Command) {
12
12
  "Regenerate prompts/capabilities.md by scanning built-in tools and MCPX tools",
13
13
  )
14
14
  .option("--no-mcp", "Skip MCPX tool enumeration (built-in tools only)")
15
- .action((opts: { mcp?: boolean }) =>
16
- withDb(program, async (_conn, dir) => {
17
- const includeMcp = opts.mcp !== false;
18
- const spinner = createSpinner("Loading config").start();
19
- const config = await loadConfig(dir);
20
- spinner.update({ text: "Connecting to MCPX servers" });
21
- const mcpxClient = includeMcp ? await createMcpxClient(dir) : null;
22
- try {
23
- const result = await writeCapabilitiesFile(
24
- dir,
25
- mcpxClient,
26
- config,
27
- (phase) => spinner.update({ text: phase }),
28
- );
29
- const bits = [
30
- `${result.counts.internal} built-in`,
31
- `${result.counts.mcp} MCPX`,
32
- ];
33
- if (!includeMcp) bits.push("MCPX skipped");
34
- spinner.success({
35
- text: `Wrote ${result.path} (${bits.join(", ")})`,
36
- });
37
- } catch (err) {
38
- spinner.error({ text: `Failed: ${(err as Error).message}` });
39
- await mcpxClient?.close();
40
- process.exit(1);
41
- }
15
+ .action(async (opts: { mcp?: boolean }) => {
16
+ const dir = program.opts().dir as string;
17
+ const includeMcp = opts.mcp !== false;
18
+ registerAllTools();
19
+ const spinner = createSpinner("Loading config").start();
20
+ const config = await loadConfig(dir);
21
+ spinner.update({ text: "Connecting to MCPX servers" });
22
+ const mcpxClient = includeMcp
23
+ ? await createMcpxClient(resolveMcpxDir(dir, config))
24
+ : null;
25
+ try {
26
+ const result = await writeCapabilitiesFile(
27
+ dir,
28
+ mcpxClient,
29
+ config,
30
+ (phase) => spinner.update({ text: phase }),
31
+ );
32
+ const bits = [
33
+ `${result.counts.internal} built-in`,
34
+ `${result.counts.mcp} MCPX`,
35
+ ];
36
+ if (!includeMcp) bits.push("MCPX skipped");
37
+ spinner.success({
38
+ text: `Wrote ${result.path} (${bits.join(", ")})`,
39
+ });
40
+ } catch (err) {
41
+ spinner.error({ text: `Failed: ${(err as Error).message}` });
42
42
  await mcpxClient?.close();
43
- process.exit(0);
44
- }),
45
- );
43
+ process.exit(1);
44
+ }
45
+ await mcpxClient?.close();
46
+ process.exit(0);
47
+ });
46
48
  }