Explicit Seams as Agent Affordances

tacoda2 pts0 comments

Explicit Seams as Agent Affordances | by Ian Johnson | Jun, 2026 | MediumSitemapOpen in appSign up<br>Sign in

Medium Logo

Get app<br>Write

Search

Sign up<br>Sign in

Explicit Seams as Agent Affordances

Ian Johnson

6 min read·<br>Just now

Listen

Share

The agent’s diff was clean. Three files, all tests green, nothing obviously wrong in the PR. The problem was one of those three files: a service class with no interface in front of it, called from six places, and the agent had quietly changed the semantics of a method in a way that broke a workflow nobody had a test for. The agent didn’t know the workflow existed. The codebase didn’t tell it. The agent saw a function, read its body, and rewrote it.<br>A week later I rewrote the same code with a port in front of it. Same task, same agent, same scope. The diff came back smaller and visibly safer. The seam was the entire difference.<br>The usual argument for hexagonal architecture is about testability; ports let you exercise units in isolation. That argument is true and well-rehearsed. The agent angle is its own thing. Explicit seams are what make an agent safe to delegate to.<br>The agent reads what is there<br>The agent builds its mental model from what it can see in the file. An explicit port — an interface, a dependency injection point, a named boundary — reads as a contract. A bare function call reads as implementation detail you can rewrite.<br>The difference isn’t about agent intelligence. It’s about what the codebase signals. A function call says this is mine; change it if you need to. A port says this is shared; other callers may depend on the contract.<br>The agent respects signals it can see. It does not respect signals it can’t. A workflow depending on an implicit contract is a workflow the agent will break, because the contract is invisible to anything reading the file.<br>Name the boundary<br>The most useful idea I took from the hex-architecture work wasn’t the architecture itself. It was the discipline of giving the boundary a name.<br>A port is a named thing. It has a file, a name, and a list of methods. The name says what the boundary is for.<br>UserRepository is a port. db.query(…) is not. Same plumbing, different signal. The agent reading code that uses UserRepository knows it’s calling across a boundary. The agent reading code that uses db.query(…) thinks it’s calling a utility.<br>The naming move is the cheap part. You don’t have to commit to full hexagonal architecture to get the agent benefit. You name the boundaries that matter: the ports that protect cross-cutting concerns like auth, persistence, external APIs, notifications. The agent reads the names and treats them as load-bearing, because the name is the signal that they are.<br>The other half: name the port for the use case, not the technology. UserRepository is good. PostgresUserStore is worse, because it leaks the implementation into the name. The agent reading PostgresUserStore will reason about it as Postgres code with Postgres-specific assumptions. The agent reading UserRepository will reason about it as a contract. That’s the difference you want.<br>Where seams pay back on agent work<br>A few places where the seam visibly changes the agent’s behavior:<br>Refactors across the boundary. Without a seam, a refactor of an internal helper becomes a refactor of every call site, because the agent doesn’t know the call sites are downstream of an invisible contract. With a seam, the refactor stays on one side. The contract holds; the implementation moves; the call sites don’t change.<br>Test setup. The agent writing tests for business logic that lives behind a port can mock the port and test the logic in isolation. Without a port, the agent has to stand up the whole dependency chain, often by mocking the database driver directly, with all the brittleness that implies. The first set of tests is honest. The second is mostly mocking glue.<br>Replacing implementations. “Swap the email provider” is a one-day task in a codebase where the email port is explicit. It’s a multi-day task in a codebase where every place that sends email talks to the SDK directly. The agent does both jobs, but the second one is much more likely to introduce subtle behavior changes, because every call site becomes its own micro-rewrite.<br>Onboarding the agent to a new area. When the agent is working in a module it hasn’t touched before, the ports give it a map. The agent reads UserRepository, EventBus, BillingClient and understands within a couple of minutes that this module talks to persistence, events, and billing. Without the ports, it reads function bodies and reconstructs the same map by tracing call chains. The seam saves ten minutes per task, every task, forever.<br>The discipline that isn’t architecture<br>The valuable shift was realizing that “explicit seams” isn’t the same as “hexagonal architecture.” The architecture is one way to get the seams. The discipline is portable to codebases that aren’t hex-shaped.<br>Three habits cover most of the benefit:<br>Get Ian Johnson’s...

agent port name explicit seams architecture

Related Articles