Build the Simplest Thing That Works Developer Bob Belderbos built a minimal CLI CRM using two Python files, two dependencies, and Markdown files, avoiding over-engineering by constraining the design to a single user, local files, and no database. The approach emphasizes shipping the simplest thing that works and growing it based on actual needs. Build the Simplest Thing That Works Shipping fast with AI but don't fully trust the code? I help developers 1:1 turn AI-built apps into something they understand and own. How it works → I needed a CRM the other day. Not Salesforce, not even a web application. I just needed a way to track contacts, notes, reminders, and a small product catalog. Before writing any code, I focused on the constraints first. The final solution ended up being two Python files, two dependencies, and a folder of Markdown. The Temptation to Over-Engineer When we developers hear "CRM," we reach for the full stack: database schema, user auth, web dashboard, API layer, deployment pipeline. Before writing a line of code, we've committed to thousands of lines that could become tomorrow's technical debt. Dave Thomas puts it bluntly in his book Simplicity https://pragprog.com/titles/dtcode/simplicity/ : "Feature is marketing speak for future liability." Every feature, he argues, is code someone will have to support, maintain, extend, and understand long after you wrote it. AI tools can scaffold a lot of code quickly. That's useful, but it also makes it easier to build far more than you actually need. I've seen this pattern repeatedly coaching developers. It's usually justified as a learning exercise, but you learn more by shipping the smallest thing that works and growing it from there. The Constraints That Set Me Free So what's the simplest thing that actually works? In Start with Design https://belderbos.dev/blog/design-over-code/ , I went from a vibe coded Rust back-end to a GitHub Actions workflow that got the job of dripping content done. Infrastructure over code. In my CRM case, doing the scoping exercise first revealed a few important constraints: Single user. It's my CRM. No auth, no permissions, no multi-tenancy. CLI-first. I live in the terminal. A web UI would be overhead I don't need. Local files. If the data is just text, I can read it, edit it, grep it, back it up, using standard Unix tools. No database means no schema, no migrations, no ORMs. Minimal surface. The data never leaves my machine. No network calls, no third party storing my contacts. Notice what happened here. Every constraint removed an entire class of problems: - Single user → no auth - Local only → no hosting - Files → no database - CLI → no frontend Good architecture is often less about what you build and more about what you decide not to build. What I Actually Built: A CLI CRM in Two Files Two Python files. Two dependencies: Typer for the CLI, Rich for table formatting. The repo is here https://github.com/bbelderbos/simple crm . That repo is the minimal MVP. My day-to-day CRM has since grown a few features on top of this same design. That's the whole point: start small, and let the problem pull you toward more. Data lives in a local folder as plain Markdown: crm/ products.md Simple table: code, name, price reminders.md Due date, contact, description contacts/ jd1.md Jane Doe unique ID bs2.md Bob Smith Each contact file is structured markdown I can open in any editor: Jane Doe - some metadata fields - Notes - 2026-05-28 — ... - 2026-05-31 — ... Python for Logic, Unix for Glue The data folder I expose via an env variable so the CLI can find it from any directory: export CRM DATA=/path/to/your/crm/folder And I added some handy shell aliases. Write the core logic in Python, but use Unix to glue it all together. The core trick is piping crm list into fzf to pick a record, then acting on it: uv can run from anywhere alias crm='uv run --project /path/to/crm crm' Interactive contact picker - open in vim active only crme { local code code=$ crm list "$@" | grep '│' | fzf --ansi | awk -F '│' '{gsub / /, "", $2 ; print $2}' -n "$code" && vim "$CRM DATA/contacts/${code}.md" } Same pattern, different verb. Once it clicked I added a small family of them: | Alias | Action | |---|---| crmg | fzf pick → crm get details | crme | fzf pick → open in vim active only | crmc | fzf pick → intake call template + editor + reminder | crms | fzf pick → coaching check-in template + editor + reminder | crma | fzf pick → add reminder | crmr | open reminders.md in vim | I even added this to my .vimrc to quickly add a note with the current date: nnoremap