How I built Mind Palace, my personal memory storage for coding conversations A developer built Mind Palace, a local-first desktop memory workspace that captures and retrieves coding-agent conversations so past decisions and context can be found again. The app pairs an Angular 22.2.1 interface with a Tauri 2.12.1/Rust native layer that supervises bundled Python 3.12.10 executables for transcript validation, vault writes and keyword-based local search, storing sessions as Markdown files, JSON metadata and original sources rather than the SQLite/FTS5 setup explored during research. Ask returns stored passages by keyword instead of generating LLM answers, avoiding API keys and keeping conversations on-device. Updated 10 October 2026. This describes the current Windows development build. When I work with a coding agent, the conversation holds more than the code. It holds the reasons behind the code. Why a stack was chosen. What we tried. What did not work. What should happen next. I wanted a place to keep that context and find it again when I need it. That is what I am building with Mind Palace : a personal memory workspace that stores coding conversations on my device. The larger idea includes decisions, preferences and handoffs. The working flow today is more focused: capture Codex conversations, store the original source, read sessions and ask questions that return passages from those sessions. In this article, I will walk through the stack, the user workflow, the work happening behind the UI and the challenges that shaped the implementation. The code snippets are excerpts from the project, rather than a separate demo implementation. Take a question like “Why did we choose SQLite?” The answer might already be somewhere in an earlier coding conversation. I want to find that message and open the conversation around it. I also want to know whether it was a suggestion or something I actually agreed to. That led to two rules for the storage: For now, Ask finds stored passages using keywords. It does not generate a new answer with an LLM. This gives me a usable capture-and-retrieval path without needing an API key or sending my conversations to another service. The main stack is Angular, Tauri and Python . Each layer has a specific job. Angular handles Connections, the session Library and Ask. I wanted one place for the screens and the state that connects them. The project uses Angular 22.2.1 and TypeScript. The interface tracks whether the vault is connected, whether an operation is running, and what error or result should be shown. For example, these are actual state declarations from local-vault.ts : readonly connected = signal false ; readonly busy = signal false ; readonly error = signal '' ; readonly notice = signal '' ; These values let the UI reflect the current operation. An open vault, an import in progress and a failed sync are different states. I do not want the screen to hide that difference. Angular's signals documentation https://angular.dev/guide/signals explains the reactive state mechanism used here. This needs to work with local files and local processes, so I chose a desktop application. Tauri connects the Angular interface to a Rust layer. The project pins Tauri 2.12.1 . Rust owns the worker processes and exposes specific commands such as opening the vault, importing captures and reading a session. The interface does not get a general command for running arbitrary executables. There is a cost to this choice: I now have boundaries between TypeScript, Rust and Python to maintain. The project uses generated contracts and response validation so those boundaries are checked instead of relying only on matching field names. Tauri documents embedding external binaries https://v2.tauri.app/develop/sidecar/ . In this project, the native layer supervises prepared Python executables through private process transport; it does not expose the documentation's generic shell example to the frontend. Python handles transcript validation, the capture inbox, vault writes and local search. The development runtime recorded for this project is Python 3.12.10 , with a frozen executable used in native checks. This keeps the file and text processing together. Bundling it also means the prepared native workflow does not depend on a user manually running a Python service. The current authoritative vault uses Markdown session files, JSON metadata and original source files. SQLite and FTS5 were explored during runtime research, but they are not the storage or search engine behind this current flow. The SQLite question in the demo is a fictional conversation topic. That distinction matters when explaining the stack. A technology mentioned inside a saved conversation is not necessarily a technology used by the app. Here is how I expect someone to use the current desktop build. This is the complete path from setup to finding a stored memory. Older history has a separate confirmation step. 1. Create or open the local vault. The current app uses one default vault under the operating system's local-data folder, at dev.mindpalace.local/vault . Custom vault-folder selection is still pending. Once created, the same vault can be reopened. 2. Set the Codex capture scope. In Connections, select Codex and enter the absolute folder containing its session transcripts. The tested default layout is %USERPROFILE%/.codex/sessions ; a custom CODEX HOME changes that location. Enable hooks and save the scope. The app needs explicit access to that source folder. It does not silently import all older conversations. 3. Install and review the hook. Mind Palace merges its handlers into the user-level hook file, preserves other handlers and keeps a backup. Then open /hooks in Codex and review the exact commands before trusting them. Installing the file and approving the handler are separate steps. The app's setup screen can show installation state, but it does not claim that the current Codex runtime has approved it. The Connections checklist in the native development build. This screenshot uses an isolated test setup; an installed hook does not prove runtime approval or delivery. 4. Work with Codex. After approval, start or resume a session. The lifecycle hooks invoke the local receiver. Mind Palace does not have to remain open for this capture step. 5. Open Mind Palace and sync. When the vault opens, the app checks available captures and imports verified sessions. There is also a Sync action, and Ask checks for new captures before searching. 6. Read the session or ask a question. In Sessions, select a conversation and read its messages. Use “Show complete source” when you need the complete stored record. In Ask, use words from the conversation. A matching result includes the stored passage and a source reference. For existing history, there is a separate preview-and-confirm import flow. That lets the user check the source, count and size before importing older sessions. The Library keeps the saved session and its original messages together. This is a real native screenshot with fictional conversation content. I did not want capture to depend on the desktop UI being open. The receiver and the vault worker therefore have different responsibilities. The hook receiver validates the event, reads the allowed transcript and writes a unique spool record to the local inbox. Later, the app indexes those records and imports them into the vault. The receiver can run while Mind Palace is closed. Indexing and vault import happen later, when the app checks captures. This excerpt from session capture.py shows how the receiver identifies the transcript revision and the session: sha = hashlib.sha256 data .hexdigest key = hashlib.sha256 config "provider" + "\0" + session .encode .hexdigest sha identifies the captured bytes. key identifies the provider and session together. These are different identities: one conversation can have several source revisions as it grows. The receiver writes a unique temporary file, flushes it and publishes the spool record. It avoids the app's shared operation lock. Indexing and deduplication happen later, outside the short synchronous hook path. The capture inbox is separate from the vault, under %LOCALAPPDATA%/MindPalace/capture . That separation lets capture continue while the vault is closed. There are two parts here: the supervised vault worker and the operations triggered by the interface. When the vault opens, the native layer starts the local worker. The worker serves validated requests for storage and retrieval. Opening the vault also checks the recovery journal for interrupted writes. Closing the vault or exiting the app stops the owned vault worker. The current sync is triggered by actions , rather than a timer that constantly polls. The local-vault service checks captures when opening the vault, refreshing a connected vault, using Sync, or submitting an Ask question. The worker serves requests while the vault is open. Sync runs through these stages when triggered; it does not continuously repeat them. The stages inside a sync are: | Stage | What happens | |---|---| | Check configuration | Confirm whether Codex capture is configured. | | Index the inbox | Validate spool deliveries, deduplicate them and publish source manifests. This also performs approved-root recovery for known sessions. | | Import pages | Create or update canonical sessions through the vault writer. | | Acknowledge and clean up | After import acknowledgment, evaluate redundant prefixes for safe retention cleanup. | | Refresh the Library | Load the updated session list. | The import loop in local-vault.ts makes the page boundary explicit: js const result = await this.checked