← Writing

Agents Don't Read Between the Lines

Nobody could tell me how our authentication worked, and an agent with a closed context is in the same spot with tribal knowledge it never inherited.

4 min readai · software-design

Nobody could tell me how our authentication worked.

I had to migrate it to a third-party identity provider. It was an important client requirement.

The code was a mess. The middleware had no tests, only a few request tests that covered one sign-in path and nothing else. When I asked around, it was “look into Confluence,” “look into the code,” or “ask some other team.” Confluence had nothing on auth. The only document was an old PRD that described something very different from what was built.

Everybody knew how it worked, but no one really understood it. Even QA worked around it:

“I was provided with this Postman collection and I use it always.”

Their automated tests bypassed auth just to run.

It lived in a middleware: five or six classes doing mostly the same thing, tightly coupled instead of working like a stack. Clients authenticated with a session token, and some used a JWT. Nobody knew how that tied to the v3 and v4 endpoints, that Devise sat behind everything, or that there were paths to authenticate without a session. There were four different ways of doing the same thing.

Some teams didn’t even know we had auth flows that “would have been useful in their implementations.”

I dug in, fixed tests, refactored, made short, mid, and long-term plans, and documented everything.

Later, other engineers were assigned related work.

“But then how are we going to authenticate? The idea is to drop the old system of calling the admin with a user and password and just use the new identity provider.”

That was the goal at the top of the document I had written, almost word for word: the identity provider would issue a JWT, and we would use it to validate whether a call had access. They didn’t know what came next, or how to build “new” things I had already covered.

I’m pretty sure this is a common scenario. For me, the worst part was that the question came from another engineer.

Not from QA, not from the project manager, not from the product owner. From another ENGINEER, with the code, the docs, and the READMEs one click away.

Writing it down is only half of the transfer. The other half is someone reading it.

Even when the work is in good shape, knowledge is hard to transfer.

Now imagine an AI with a closed context.

Agents do not inherit tribal knowledge

I wrote about the old principles behind this in The Goal Is Reducing Uncertainty. This is about what those principles were quietly replacing: the person who just knew.

When humans were the main consumers, teams often hid the cost of unclear interfaces in tribal knowledge. Someone knew which method was dangerous. Someone remembered the undocumented flag. Someone warned new developers not to call the service twice. And when that person wasn’t around, you could still read the code, trace the request, or ask a colleague.

An agent does not know that update_customer also invalidates a subscription, or that find_account searches archived records unless told. It does not get suspicious because three tools have nearly identical names.

A developer can compensate for a weak contract with experience.

An agent converts the weak contract into probabilities.

One tool vs four

Consider a tool called manage_user. It accepts a user ID, an optional email and status, and a boolean named notify.

Technically flexible, but operationally a small guessing game.

Should it retrieve or change? What happens when only status is supplied? Does notify: false suppress every message or only the confirmation? Is it idempotent?

You can open the implementation and find out. An agent has to infer intent from the name, the fields, the description, and whatever context surrounds the call.

Now compare:

  • get_user_profile
  • change_user_email
  • suspend_user_access
  • restore_user_access

Each one can state its preconditions, side effects, and expected result. The second design has more tools, each with one job, and less uncertainty.

Always measure a tool by how much its consumer has to infer, not by how little it contains.

You know, the principle of least astonishment: it should behave the way you expect it to behave.

Put it in the contract

Whatever matters has to be in the contract, the context, or the controls around it. Flexible interfaces can still work, but their cost is paid in inference.

When an MCP tool needs paragraphs of explanation and several examples before an agent can use it reliably, look past the prompt. The interface may be carrying design ambiguity nobody resolved.

  • Before adding another example, rename the operation.
  • Before expanding the description, separate the command from the query.
  • Before telling the agent not to trigger a side effect, ask why the side effect is hidden.