---
title: "Locality of context over separation of concerns"
subtitle: "Code should keep its explanation close"
description: "Coding agents strengthen the case for locality: organize code so the context needed to change it is nearby, rather than scattered across architectural layers."
author: "Pau Friedman"
kind: "essay"
published_at: "2025-09-19"
url: "https://pau.fm/blog/2025/09/19/locality-of-context-over-separation-of-concerns"
markdown_url: "https://pau.fm/blog/2025/09/19/locality-of-context-over-separation-of-concerns.md"
canonical_url: "https://pau.fm/blog/2025/09/19/locality-of-context-over-separation-of-concerns"
---

# Locality of context over separation of concerns

> Code should keep its explanation close

htmx has a useful idea called
[“locality of behavior”](https://htmx.org/essays/locality-of-behaviour/): the behavior of a piece of
code should be obvious from looking at that piece of code. A button that says `hx-get="/clicked"`
tells you what clicking it does. You do not need to find the event listener, trace a dispatch into a
store, inspect some middleware, and discover that a plugin registered three directories away is what
actually makes the request.

This is usually framed as a principle for humans. Local behavior is easier to understand because
less of the program has to fit in your head at once. But coding agents make the idea more
interesting, not less. An agent does not begin a task with a mature mental model of the repository.
It constructs one by searching, opening files, and deciding which clues deserve another search.
Every jump is both a context cost and another chance to miss the mechanism entirely.

Separation of concerns is often implemented as separation by technical category: components over
here, state over there, network requests in a service, validation in a schema folder, and side
effects in middleware. Each directory looks tidy while a single product behavior is smeared across
all of them. The architecture has separated the code without separating the concern. A concern is
usually closer to “cancel a subscription” than to “all the functions which issue HTTP requests.”

Locality does not require inlining every implementation. The htmx essay makes the useful distinction
between inlining behavior and surfacing its invocation. A function can hide complicated machinery
while its call site still makes the dependency obvious. The same is true of authorization, database
access, or a background job: centralize the policy, but make its participation visible where the
behavior is defined. Abstraction should hide machinery, not causality.

Agents sharpen the economics of that distinction because context is finite. Anthropic describes
effective context engineering as finding the
[smallest set of high-signal tokens](https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents)
that produces the desired result. A codebase is therefore not just a program; it is a context
retrieval system. Co-located tests, nearby comments, explicit dependencies, and feature-oriented
folders help an agent retrieve the right evidence cheaply. Registries, implicit conventions, and
spooky action at a distance force it to reconstruct an invisible graph before it can safely change a
line.

There are still good reasons to put some things far away. A security invariant should not be copied
into every endpoint merely to improve locality; it should be encoded once in a boundary, type, or
check that cannot be bypassed. Shared implementation belongs behind a stable interface. The test is
not whether all relevant code occupies one file, but whether the local code reveals which larger
rules govern it and gives the reader an obvious path to them.

Separation of concerns remains a means, not a goal. The more useful default for a world of coding
agents is **locality of context**: code that changes together should be understandable together.
Every architectural hop consumes attention; good architecture compresses the explanation of a change
into the same neighborhood as the change itself.

