About
Practice Ledger is a local, encrypted practice-management workspace for a solo accountant. Clients, leads, service agreements, engagements, tasks, deadlines, projects, and earned revenue live in one file on one workstation. AI assistants connect through an MCP server to read and propose. They never write accepted state.
It is for the solo CPA, EA, bookkeeper, or advisor: one firm, one accountable human, many agents.
- Relationship Hub. Leads and clients on one directory identity, with contacts, tags, and an append-only interaction timeline.
- Services and workflow templates as immutable, content-addressed versions, with each service agreement bound to one exact version.
- Recurring engagements materialized by month, quarter, or year with month-end anchors preserved and zero duplicates on replay.
- Execution. A firm board with dependency-locked tasks, blockers, evidence, and status history. Only the human can move work from ready-for-review to complete.
- Assistant Review. Agent proposals, including meeting handoffs from NTLedger, land in an inbox where a human accepts, reassigns, or rejects in one audited transaction.
Status: foundation stage. The storage core and the work-management slice are done; time and budgets, generic proposals, and signing are not. It runs on my machine with my firm’s real file, unsigned, with no installer, no website, and no user but me. The next milestone’s exit criterion is another practitioner relying on it for a real month.
Where it fits. LedgerClaw already has practice management inside a hosted firm OS. Practice Ledger exists because a cloud suite conflicts with the local-first posture of the rest of the desktop library. It links to the other desktop tools without copying their records. It is the suite index, not a suite database.
How I built it
Thirty-six commits in seventeen days, 2026-08-16 to 2026-09-02, with twenty-four logged decisions. Same shape as the other desktop tools, so most of the hard choices were already made.
- Renderer, main, sidecar, file. A React renderer with no filesystem access talks through a typed preload allowlist to Electron main, which drives a Python core over newline-delimited JSON, which owns a SQLCipher file. Zero app-initiated network calls.
- Supervised has a definition. The MCP ceiling is propose. Grants are off by default, per firm, held in the OS vault, and re-read on every call. Tools answer only while the desktop is unlocked. Proposals bind entity versions and fail as stale if the state moved.
- No SDK on the boundary. The MCP adapter is hand-written over stdio and shimmed to an owner-only Unix socket, never TCP.
- Nothing sensitive that does not need to be there. No SSNs, EINs, account numbers, or document bytes. Logs carry event codes only.
- Local transport is not local inference. The docs say it out loud: an agent reading through this server may still be a hosted model. That choice belongs to the practitioner.
