Skip to main content

Why I Built LocalGPT in 4 Nights

· 11 min read
Yi
Maintainer

I'm a data engineer by day. At night, I've been building side projects — apps, games, the usual fun stuff. I kept running into the same problem: context. Every time I started a new AI chat session, I had to re-explain my projects, my preferences, my decisions. The AI had amnesia.

Then I discovered OpenClaw.

The OpenClaw Pattern

OpenClaw pioneered something elegant: a set of markdown files that give an AI assistant persistent context.

  • SOUL — personality and behavioral guidance
  • MEMORY — long-term knowledge that persists across sessions
  • HEARTBEAT — a task queue the assistant checks autonomously

It's brilliant in its simplicity. No databases to manage. No complex state machines. Just markdown files that both humans and AI can read and write.

But OpenClaw is a full platform — ~460k lines of TypeScript across ~2,500 files, with a gateway, mobile apps, multi-channel integrations, and Docker deployments. I wanted something smaller.

4 Nights in Rust

I wondered: what does this architecture look like as a single Rust binary? So I paired up with Claude and here's what happened.

Night 1 (Feb 1, 2 commits): The foundation. Core agent system with the full module architecture (agent, CLI, daemon, memory, server, heartbeat), streaming responses, and the initial project structure. By midnight: a working skeleton. ~3,000 lines.

Night 2 (Feb 2-3, 34 commits — the marathon): This is where it came alive. Claude CLI as the primary LLM provider, OpenClaw-compatible file structure, SOUL persona support, system prompt with identity and safety, skills system, readline CLI with arrow keys and history, SQLite memory index with FTS5, embeddings and vector search, streaming tool execution, WebSocket API, session persistence, Anthropic streaming, model selection, and memory management — and by 2am, a working end-to-end system. The most productive night by far.

Night 3 (Feb 3, 27 commits): Woke up and kept going. Local embeddings with fastembed for semantic search, multi-session HTTP API with 6+ endpoints, token tracking, file and image attachments, tool approval mode, an embedded web UI served from the binary, daemon mode running heartbeat and server concurrently, configurable memory indexing, session management, and config API endpoints.

By the afternoon of Feb 3, I couldn't resist — I got this running on my work machine, tweaked the configuration, and started integrating it into my actual workflow. I set up heartbeat tasks to autonomously discover and summarize data pipeline improvements for my team. It went from side project to daily tool in two days.

Night 4 (Feb 4-5, 18 commits): Polish and hardening. First-run experience improvements, configurable embedding providers with multilingual model support, heartbeat task tracking with git integration, a daemon logs panel and session viewer in the web UI, date-based rolling logs, prompt injection defenses (a full 381-line sanitization module), cached MemoryManager, GGUF embedding support via llama.cpp, and the egui-based desktop GUI app.

After that, a few days of bug fixes and documentation.

The result: ~15k lines of Rust (+ ~1,400 lines of HTML/CSS/JS for the web UI), a ~27MB binary, and cargo install localgpt just works. No Node.js. No Docker. No Python. (The embedding model cache may require a few hundred MB of disk space, depending on the model you choose.)

For perspective, when this post is written:

# OpenClaw: ~460k lines of TypeScript across ~2,500 files
$ find openclaw/src -name "*.ts" | wc -l
2598
$ find openclaw/src -name "*.ts" | xargs wc -l | tail -1
463649 total

# LocalGPT: ~15k lines of Rust across 43 files
$ find localgpt/src -name "*.rs" | wc -l
43
$ find localgpt/src -name "*.rs" | xargs wc -l | tail -1
15031 total

To be fair, this isn't an apples-to-apples comparison. OpenClaw is a full platform — it includes a gateway, mobile apps, multi-channel integrations, and much more that LocalGPT simply doesn't have now. LocalGPT currently is a focused, single-user tool. The point isn't that less code is better — it's that you can get surprisingly far with a narrow scope and the right language.

How I Actually Use It

LocalGPT runs as a daemon on my machine — localgpt daemon start. Every heartbeat interval, it checks HEARTBEAT for tasks.

Knowledge accumulator — It remembers my project context across sessions. My TODO list, my decisions. I never re-explain.

Research assistant — "What embedding models support multilingual search?" It researches, summarizes, and saves the answer to my knowledge bank for next time.

Autonomous worker — I use localgpt chat and add tasks to HEARTBEAT and walk away. It organizes my knowledge files, researches topics, drafts content, and reminds me of approaching deadlines.

Memory that compounds — Every session makes the next one better. It builds a structured knowledge bank across domains — finance, legal, tech — all on my personal computer — that grows over time.

The Technical Bits

For those interested in the stack:

  • Tokio async runtime for concurrent operations
  • Axum for the HTTP API
  • SQLite with FTS5 for full-text search and sqlite-vec for semantic search
  • fastembed for local embeddings (no API key needed)
  • eframe for the desktop GUI
  • rusqlite with bundled SQLite (zero system dependencies)

The architecture is intentionally simple. Markdown files are the source of truth. SQLite is the index. The binary is the runtime. That's it.

Open Source

LocalGPT is open source under the Apache 2.0 license. Fork it, extend it, build something better — that's the point.

cargo install localgpt
localgpt config init
localgpt chat

If you're building with AI assistants — whether you're in the OpenClaw community or rolling your own — I'd love to hear what patterns are working for you.

GitHub: https://github.com/localgpt-app/localgpt Website: https://localgpt.app


Appendix: Complete Commit Log

85 commits across 4 nights + follow-up. Every commit listed.

Night 1 — Feb 1 (2 commits)

#HashTimeMessage
155599d312:33first commit
2c7e92ed22:55feat: implement LocalGPT core application

Night 2 — Feb 2 afternoon → Feb 3 ~2am (34 commits)

#HashTimeMessage
3cf9cb6aFeb 2 16:27feat: add thread safety and SSE streaming support
4e143f73Feb 2 18:03feat: add Claude CLI as primary LLM provider
5e549dd2Feb 2 19:29feat: add OpenClaw-compatible file structure for session storage
6a2b8bc8Feb 2 19:31feat: add SOUL.md support for persona/tone guidance
73734aeaFeb 2 19:37feat: add system prompt with identity, safety, and workspace info
89568159Feb 2 19:51feat: add tool call style, time, skills system, and /new command
99520850Feb 2 19:53feat: add OpenClaw config migration support
1074505a0Feb 2 19:53feat: add memory recall guidance to system prompt
112aebfaeFeb 2 19:54docs: update CLAUDE.md with skills, CLI commands, and migration info
126cf4b39Feb 2 20:04feat: add streaming output in CLI chat + clean up dead code
13a22f96eFeb 2 20:17feat: auto-create workspace templates and .gitignore on first run
145779539Feb 2 20:21fix: move daemon.pid from workspace to state directory
15e0fbc7fFeb 2 20:24feat: add line editing with rustyline for arrow keys and history
16dbe9670Feb 2 23:23feat: move SQLite to OpenClaw-compatible location
17e05d050Feb 2 23:37feat: add embeddings and vector search support
18637f611Feb 2 23:39feat: add streaming with tool support
19d18ebd8Feb 2 23:41feat: add session persistence across restarts
2039449afFeb 2 23:42feat: add WebSocket API for real-time chat
2138fdf39Feb 2 23:49fix: reorder schema to create index after migration
223283d17Feb 3 00:01feat(memory): align SQLite schema with OpenClaw
2340de884Feb 3 00:15feat: add OpenClaw workspace compatibility and multi-agent support
24b82c188Feb 3 00:37feat: add Anthropic streaming and fix provider selection
25005d896Feb 3 00:45feat: align model naming with OpenClaw format
265454349Feb 3 00:51fix: correct Anthropic model ID to claude-sonnet-4-20250514
2723dd9c9Feb 3 00:53feat: use Claude Opus 4.5 as default model
28702b7b5Feb 3 00:54fix: use correct Anthropic model IDs from official docs
29fa0354eFeb 3 00:55refactor: only use Claude 4.5 models, remove legacy mappings
30693fd5dFeb 3 01:08fix: pass tools to streaming API to prevent XML tool output
31919a006Feb 3 01:13feat: implement streaming tool execution
328fd048eFeb 3 01:19fix: memory_append now supports MEMORY.md for persistent facts
33bd30e61Feb 3 01:20chore: add user data files to gitignore
34f469cceFeb 3 01:27feat: OpenClaw-style memory management
3566fd879Feb 3 01:44feat: show file paths and details in tool execution display
366ff42acFeb 3 01:48feat: improve memory search UX

Night 3 — Feb 3 morning → midnight (27 commits)

#HashTimeMessage
378706ca509:47feat: add local embeddings with fastembed for semantic search
38bd302cf14:02chore: change server port to 31327 and fix license to Apache-2.0
39db86b8a14:05feat: default to claude-cli/opus for zero-config startup
402143e4315:35fix: use correct db_path in MemoryWatcher
41dc11dc116:52feat: add configurable memory index paths with glob support
42cb2d27e17:09feat: add multi-session HTTP API, token tracking, and daemon improvements
43c0d813417:16feat: add configurable tools and agent parameters
44718d10b17:19test: add tests for token tracking and LLM response handling
4588b978317:21docs: update /help to mention API token usage in /status
46b7948f117:41feat: use config chunk_size and chunk_overlap in memory index
472f1d6a617:52feat: add session management endpoints and /model command
4868545bf18:08feat: enhance CLI status and add memory reindex API
4945bdd2318:09feat: add session management API endpoints
50f190a6218:10feat: add config and saved sessions API endpoints
51b2c3b8018:14chore: fix clippy warnings and formatting
52327ea1818:16feat: add context usage and session export commands
53041d07018:21feat: add session search across all saved sessions
5403595d918:23feat: add file attachments to chat messages
5575405cc18:36feat: add tool approval mode for dangerous operations
560bf0f7a18:41feat: add persistent HTTP sessions
57f05723218:50feat: add image attachment support for multimodal LLMs
58294c9d720:23feat: add embedded Web UI for browser-based chat
594299ed920:35fix: daemonize before starting Tokio runtime on macOS
602d7771f21:00fix: cleanup deleted files from memory index during reindex
61d01e61c22:08feat: run heartbeat and server concurrently in daemon
62c8bf9c222:13chore: add commented active_hours example to config templates
639afdd2223:53feat: add OpenClaw compatibility for sessions, skills, and workspace files

Night 4 — Feb 4 afternoon → Feb 5 ~1am (18 commits)

#HashTimeMessage
64530bc13Feb 4 17:10feat: improve first-run experience and Claude CLI provider
65fca8290Feb 4 18:49feat: add configurable embedding provider for memory search
662525330Feb 4 19:06feat(heartbeat): improve task tracking and git integration
67e746275Feb 4 19:07fix: correct indentation in MemoryManager constructor
680773d78Feb 4 19:22fix: remove session memory truncation, add configurable limits
69b4b0532Feb 4 19:47feat(ui): add daemon logs panel and session viewer
70beb82d2Feb 4 21:23fix(ui): sessions panel reads from correct agent directory
714b23b3eFeb 4 21:59feat(security): add prompt injection defenses
724f5718dFeb 4 22:02fix(heartbeat): cache MemoryManager to avoid reinitializing embedding provider
73fe36213Feb 4 22:08fix(daemon): use date-based rolling logs with configurable retention
7434ce89cFeb 4 22:20fix(server): update logs endpoint to use date-based log path
757eb8156Feb 4 22:27fix(server): share MemoryManager to avoid reinitializing embedding provider
76a7fc787Feb 4 22:28fix(daemon): disable ANSI color codes in log files
77cbc2f67Feb 4 23:01chore: suppress unused function warning for test helper
78ff86421Feb 4 23:16feat(memory): add multilingual embedding models
79a3d440fFeb 4 23:35feat(memory): add configurable embedding model cache directory
8077486d3Feb 5 00:22feat(memory): add GGUF embedding support via llama.cpp
81c313178Feb 5 00:40feat(desktop): add egui-based desktop GUI app

Follow-up — Feb 6-7 (4 commits)

#HashTimeMessage
82f573039Feb 6 00:40feat: add concurrency protections for workspace files
836bd21a3Feb 6 02:10feat: add streaming tool details and slash commands to egui/web UIs
849c91931Feb 7 15:36fix: resolve UTF-8 boundary panics in memory search snippets and simplify indexing
856f4ea95Feb 7 21:06docs: rewrite README and update crate metadata for v0.1.2