I built ledger to solve a specific problem: I was losing the "why" behind architectural decisions the moment I moved to the next project. Not the decisions themselves — those live in the code. The reasoning. Why SQLite over PostgreSQL. Why keyword overlap before embeddings. Why the MCP server is read-only. That reasoning was living nowhere, and six months later it was gone.
The tool I built has two layers. Layer 1 is the deterministic core: capture, store, list,
filter, show, supersede. Zero runtime dependencies. No network. No API key. If you
pip install ledger
without any extras, you get a fully functional decision store backed by SQLite — which ships
with Python. Layer 2 is optional AI retrieval: natural-language recall, project re-orientation
briefs, synthesis calls to Anthropic Haiku. Install the
[ai]
extra and Layer 2 turns on. Don't install it and Layer 1 keeps working — the retrieval
degrades to structured search and tells you so.
pip install -e ".[ai]"
The boundary is clear in the design. The problem is that design boundaries are easy to describe and easy to violate. An accidental import in a utility function. A refactor that moved a helper into the wrong module. A transitive dependency that pulls in the AI layer without anyone noticing. The boundary exists in the README until it doesn't, and the only way you find out it was crossed is when a user installs without the AI extra and something breaks.
The solution in ledger is a single test that isn't about behavior at all:
This test doesn't verify what the code does. It verifies what the code is —
specifically, what it refuses to depend on. Import the two core modules. Check that
the Anthropic SDK never made it into
sys.modules.
If the boundary was crossed at any point in either import chain, the test fails.
Red. At pytest.
Before it reaches anyone else's machine.
The test is the architecture, enforced. Not described — enforced.
This is the same principle I apply to AI system guardrails: if the behavior genuinely cannot happen, you don't need a prompt telling the model not to do it. Build the impossibility into the system. A system whose safety depends on correctly applying a "never" instruction at inference time doesn't have a guardrail — it has a suggestion. The same logic applies to your own codebase's structural constraints. A comment is a suggestion. A test is a constraint.
The directory structure became load-bearing. The retrieval module had to live at
ledger/retrieval/,
not ledger/core/.
Not because of a naming convention — because the test made the wrong location a failing build.
The directory structure now encodes the layer boundary. You can read the architecture from
the file tree without opening a single file.
The test also defined what "degradation" means concretely. Before writing it, "Layer 1 works without AI" was a claim. After writing it, it became a property that the test suite verifies on every run. The claim became a guarantee. That's a different category of statement.
One more thing I noticed after writing it: every other test in the file tests behavior. Round-trip capture and retrieval. Supersession chain linking. Filter correctness by project and status. All of those tests answer "does the code do what it's supposed to do?"
test_core_imports_no_anthropic
answers a different question: "is the code what it's supposed to be?" Behavioral tests
verify functionality. Structural tests verify architecture. Both are necessary.
Most projects only write the first kind.
The architecture of ledger is a claim about separation. The test is what makes that claim true.