Oh-My-Pi's Hash-Anchored Edits: How a Terminal Agent Avoids the \\\\\\\"Replace Entire File\\\\\\\" Trap Oh-My-Pi, a fork of the Pi coding agent by Mario Zechner, introduces hash-anchored edits to prevent coding agents from overwriting entire files during small edits. The terminal-native agent, built with ~80k lines of Rust and a Bun runtime, uses hash anchors to ensure edits apply only if the target lines still match, turning silent corruption into explicit failures. It also integrates 14 LSP operations and 28 DAP operations, enabling agents to leverage language servers and debuggers without parsing code themselves. Most coding agents fail at scale because they replace entire files when making small edits. One misaligned line number or stale context window and the agent clobbers unrelated code. Oh-My-Pi solves this with hash-anchored edits: the agent specifies a hash of the exact lines it wants to replace, and the edit only applies if those lines still match. If the file changed underneath, the edit fails cleanly instead of silently corrupting state. This is a fork of Pi by Mario Zechner, now at 26,497 stars. It ships 60+ provider integrations Ollama, OpenAI, Anthropic, Gemini, DeepSeek , 31 built-in tools, 14 LSP operations, and 28 DAP operations. The core is ~80k lines of Rust with a Bun runtime wrapping TypeScript orchestration. The architecture is terminal-native: no Electron, no web UI, just a TUI that pipes LSP and DAP state directly into the agent's tool harness. Traditional agents send a replace file tool call with new content. If the file changed between the agent's last read and the write, the agent overwrites everything. Hash-anchored edits work differently: This turns silent corruption into an explicit failure mode. The agent sees the error, re-reads the file, and retries with updated context. The cost is one extra hash comparison per edit, which is negligible compared to the token budget of re-reading an entire file after a bad write. The hash anchoring also enables concurrent edits. Multiple agents or a single agent with subagents can propose edits to different parts of the same file. The tool harness serializes the writes and rejects any edit whose anchor hash is stale. This avoids the classic race condition where two agents both read version N, both write version N+1, and the second write silently clobbers the first. Oh-My-Pi exposes 14 LSP operations as agent tools: go-to-definition, find-references, hover, diagnostics, code actions, rename, format, and more. The agent doesn't parse code itself. It calls lsp goto definition and gets back a file path and line number. It calls lsp diagnostics and gets structured error messages with severity, range, and suggested fixes. This creates a clean boundary: the agent reasons about what to do, the LSP server maintains the semantic model of the codebase. The agent never needs to understand TypeScript's module resolution or Rust's borrow checker. It just asks the LSP server where a symbol is defined or what errors exist. The LSP server runs in a separate process. The tool harness communicates over JSON-RPC. If the LSP server crashes, the tool returns an error and the agent can retry or fall back to grep-based search. The agent's prompt includes LSP tool descriptions, so it learns to prefer lsp find references over grep when looking for call sites. The DAP Debug Adapter Protocol integration works the same way: 28 operations for setting breakpoints, stepping, inspecting variables, and evaluating expressions. The agent can start a debugger, set a breakpoint, run to that point, inspect state, and decide what to do next. All without custom debugger logic in the agent itself. The tool harness decides when to execute a tool inline versus spawning a subagent. Inline execution is cheaper no extra prompt, no extra model call but blocks the main agent. Subagent spawning costs tokens but allows parallelism and isolation. The decision logic: Subagents inherit the parent's tool access but get a fresh context window. The parent agent sends a task description, the subagent runs, and the result success or error flows back as a tool result. The parent agent sees the subagent's final output, not the intermediate reasoning steps. This keeps the parent's context window from filling with subagent chatter. Subagents can spawn their own subagents. The harness enforces a depth limit default 3 to prevent runaway recursion. Each subagent gets a token budget. If it exceeds the budget, the harness kills it and returns a truncated result. The core is Rust: file I/O, process spawning, LSP/DAP clients, hash computation, edit application. The Rust code compiles to a native binary that the Bun runtime calls via FFI. The TypeScript layer handles: This split keeps the hot path file edits, LSP queries in Rust while keeping the orchestration logic in TypeScript where it's easier to iterate. The Bun runtime is single-threaded but uses async I/O for model calls and subagent communication. The Rust core uses Tokio for async file I/O and process management. The tool harness is a TypeScript class that wraps the Rust FFI. Each tool is a method that validates arguments, calls Rust, and returns a structured result. The agent sees tools as JSON schemas in the prompt. The model outputs a tool call, the harness executes it, and the result goes back into the context window. Oh-My-Pi supports 60+ providers through a unified interface. Each provider implements: chat messages, tools, options : send a chat completion request. stream messages, tools, options : same, but streaming. embeddings texts : generate embeddings for semantic search.The provider layer handles retries, rate limits, and token counting. The agent doesn't know if it's talking to OpenAI or Ollama. The tool harness tracks token usage per request and per session. If the session exceeds a budget default 200k tokens , the harness truncates old messages and re-summarizes. The token budget is critical for long-running tasks. Without it, the agent's context window fills with old tool results and the model starts hallucinating. The summarization step uses a cheaper model e.g., GPT-3.5 to condense the last N messages into a single summary message. The summary replaces the original messages, freeing up tokens for new tool calls. Common failure modes: | Failure | Cause | Recovery | |---|---|---| | Hash mismatch | File changed between read and write | Agent re-reads file, retries edit | | LSP timeout | Language server crashed or hung | Tool returns error, agent falls back to grep | | Subagent runaway | Subagent exceeds token budget | Harness kills subagent, returns truncated result | | Model refusal | Agent asks for dangerous operation | Tool returns error, agent rephrases or skips | | Context overflow | Session exceeds token budget | Harness summarizes old messages, continues | The tool harness logs every tool call, result, and error to a structured log file JSON lines . The log includes: This log is the primary observability surface. You can replay a session by feeding the log back into the harness. You can analyze which tools the agent uses most, which ones fail most often, and where token budget goes. The TUI shows a live view of the agent's reasoning: the current message, the tool it's calling, and the result. You can pause the agent, inspect the context window, and manually approve or reject tool calls. This is useful for debugging but not practical for production automation. // Simplified tool harness method for hash-anchored edits async applyEdit args: { file: string; startLine: number; endLine: number; anchorHash: string; newContent: string; } : Promise<{ success: boolean; error?: string } { // Call Rust FFI to read current file content const currentLines = await this.rust.readLines args.file, args.startLine, args.endLine ; // Compute hash of current content at the specified range const currentHash = await this.rust.hashLines currentLines ; // Compare hashes if currentHash == args.anchorHash { return { success: false, error: Hash mismatch: file changed. Current hash: ${currentHash} , }; } // Apply edit await this.rust.replaceLines args.file, args.startLine, args.endLine, args.newContent ; return { success: true }; } The Rust side uses blake3 for hashing fast, collision-resistant . The hash is computed over the exact byte range, including newlines. If the file uses CRLF on Windows and LF on Linux, the hash will differ. The tool harness normalizes line endings before hashing to avoid spurious mismatches. Hash-anchored edits make sense when: They don't make sense when: For terminal-native coding agents, hash-anchored edits are a forcing function: they make the agent's assumptions explicit. If the agent thinks line 42 contains function foo but it actually contains function bar , the edit fails and the agent sees the mismatch. This feedback loop is faster and cheaper than waiting for a test suite to catch the corruption. Use Oh-My-Pi when you need a terminal-native coding agent with production-grade LSP/DAP integration and hash-anchored edit safety. The 60+ provider support and 31 built-in tools mean you can start automating without writing custom tool wrappers. The Rust core is fast enough for large codebases tested on repos with 100k+ files . Avoid it if you need a web UI, collaborative editing, or tight integration with a specific IDE VSCode, IntelliJ . The terminal-only interface is a feature for automation but a limitation for interactive use. The subagent spawning logic is optimized for task parallelism, not for fine-grained human oversight. The hash-anchored edit pattern is worth stealing even if you don't use Oh-My-Pi. It's a simple, low-overhead way to turn silent corruption into explicit failure. Pair it with structured logging and you get a reliable foundation for agentic file edits.