Relieve your context anxiety with modular thinking

Doug Slater
Doug ·

Like electric vehicle range anxiety, context anxiety is that dread you feel when your LLM is almost out of context memory. Modules are your best tool for conserving LLM context.

A photo of a puzzle piece fitting into its place

A photo of a puzzle piece fitting into its place. Source1

In my post AI: Accelerated Incompetence, my last sentence was:

continue to invest in the same fundamental engineering skills that were deemed valuable in 2019

Modular thinking is one of those timeless engineering skills.

Why do modules matter?

A screenshot of Claude Code's warning 'Context left until auto-compact: 12%'

I feel anxious when I see this message from Claude Code.

At the heart of OOP, FP, SOLID, DDD, SOA, CQRS, N-tier, bounded contexts, ports-and-adapters, onion, clean, event-driven, and all the other browbeaten software architecture jargon you're tired of hearing about, is the hope to decompose software into pieces which the human mind can comprehend.

Smart humans can remember about 7 things at a time2 3. (For me, it's closer to 3.) Meanwhile, computer programs can be staggeringly complex. The only way for humans to work on big programs is to think about small pieces of them at a time. If the human mind had infinite working memory and attention span, we would not need any structure in our programs. Our children would write 1,000,000 line monolithic god-functions, and we would smile at their cute simplistic work.

Machines, like humans, have finite capacity.

Today, 1M tokens is considered a cavernous context window, and most people settle for 200k, which is the default for Claude Code. Yet 1M tokens will only fit a small codebase, up to about 75k lines of code. For perspective, that accomodates about half of SQLite and 0.2% of the Linux kernel.

ProjectLines of CodeTokens
SQLite~150K~2M
Linux kernel~40M~500M

In this sense, what's good for humans is good for LLMs. If you're human and want to work on a large codebase, you have to be choosy about what to bring into your working memory. If you want to use an LLM to work on a large codebase, you have to be choosy what to bring into its context.

So that's why modules matter. But what is a module, exactly?

What is a module?

In programming, modules take many physical forms: a function, class, shared library, program, or a group of programs, an HTTP endpoint, or yet other forms.

What makes any of these a module is:

  • It exposes an interface that conceals the secrets of its implementation
  • It operates without using the secrets of other modules.

What makes a good module?

  • A good module is deep. Its interface is smaller than its implementation4. Reading its interface is a much lower cognitive burden than reading its implementation.
  • The preconditions of a good module are well-defined. It is clear what the module assumes to be true about the world in order to run.
  • The postconditions of a good module are well-defined. It is clear what the module guarantees about its output and effects on the world.
  • A good module relies only the guarantees of other modules. Code that relies on facts that other code does not guarantee may work today but can break in the future.
  • The above facts make a module composable. Module A depends on Module B, and Module B depends on Module C. Programs can be understood as fractal structures: even simple programs have a deep dependency tree. My single page web app depends on my chosen web framework, which depends on the browser's JavaScript runtime and rendering engine, which depend on the operating system's system call APIs, which depend on device drivers, which depend on device firmware. Thankfully, my app doesn't have to know about TCP packet retry. All it knows is to call fetch() so I can watch cat videos in peace.

For modules with enough users, it's inevitable5 that someone will eventually break modularity by depending on what happens to work right now (observable behavior) instead of what is guaranteed to work (contractual behavior). Don't do that.

See these references4 6 for more detail and examples on what a module is.

What do modules have to do with LLMs?

Program Correctness

Modules let you and and LLMs reason about program correctness without thinking about the entire program. You or your LLM can change a part of the program, and as long as you don't break that module's public contract, the rest of the program will remain correct if it only depends on that contract.

You can be confident that your changes don't break some other part of the program. This is a powerful capability. The payoff for strictness about contracts, preconditions, and postconditions is confidence to make changes.

LLM Performance

The chart below shows the well-known fact that LLMs as a rule perform worse as they ingest more data. If future LLMs perform better, the problem doesn't go away. The horizontal axis just gets longer.

LLM Performance vs Context Length

LLM Performance vs Context Length

Conversely, an LLM will perform better if you constrain how much information it ingests. The type signature of a function is usually smaller than the function's implementation. If your LLM has to look at the implementation of a function to determine how to use it, that will use more context.

Subagents

Right now, subagents and agent orchestration are hot topics. Your main LLM agent, the orchestrator, hands off work to "subagents", each with an independent context. This conserves the context of the main agent, which improves task adhesion and alignment to your intentions.

You need something to hand off to the subagents. If your code is not modular, each subagent has to read more code to do its task. LLMs can do a pretty good job of making tool calls to e.g. grep a codebase and pick out the relevant bits, but your design choices can make it easier or harder for them. Without modular code, tasks will take longer, incur greater token usage, and fill up contexts more quickly.

Moreover, you want each subagent to work independently. Without modularity, parallel agents can make conflicting changes to the same files.

Chats

If you're copy-pasting code into an LLM chat window, a modular codebase will still help. In this case, you're playing the role of main agent. You have to curate what information the chat bot receives, so you're managing both your working memory and the LLM context.

Summary

It's easy to make software that works right today. It's very hard to make software that keeps working in a changing environment. You can do that by depending only on what your code's environment guarantees. That's modular thinking.

Look for words like interface, specification, contract, assumption, guarantee, precondition, postcondition, and invariant. Those are signals you're dealing with a module.

Modules are humanity's best hope to manage complex software.

References

  1. CC0 Pixnio
  2. Code that Fits in Your Head
  3. The Programmer's Brain
  4. A Philosophy of Software Design (book)
  5. Hyrum's Law
  6. Modules Matter Most for the Masses

Subscribe for More

I'll tell you about new posts. I take your privacy seriously.

Conversation

Loading...