DEV Community

Cover image for Agents Amplify Whatever Vocabulary They Find
Travis Frisinger
Travis Frisinger

Posted on • Originally published at tddbuddy.com

Agents Amplify Whatever Vocabulary They Find

Originally published at tddbuddy.com.

This is part 2 of the "Vocabulary Is the Product" series. Part 1, The Hidden Output of TDD Was Never Code, argues that the vocabulary a disciplined test suite produces is the compounding asset most teams never account for. Part 3, Product Literacy Is the New Core Engineering Skill, names what that means for how teams hire, review, and grow. Related reading: The Bar for TDD Just Moved and Your Test Suite Is Your API for Agents.

Agents do not bring vocabulary to a codebase. They amplify whatever vocabulary they find.

That single fact reshapes the calculus on every naming decision a team has ever deferred. The disciplined codebase and the undisciplined one both get faster. Only one of them gets better.

Agents Mirror What They Find

An agent composing a new test does not consult a style guide. It does not apply independent domain judgment. It samples the surrounding code, infers the prevalent patterns, and produces more of the same. This is not a limitation to be engineered away. It is how composition-by-example works, and it is the correct behavior given the information available.

Give an agent a test file full of aLoyaltyMember(), aCartReadyForCheckout(), Money, and Address, and it will reach for those same nouns when writing the next test. The output uses the vocabulary because the vocabulary is what's there to use. Reviewers recognize the test immediately. It sounds like the system.

Give an agent a test file full of var customer = new Customer { Type = "premium", YearsActive = 3 } repeated across forty setup blocks, and it will reach for var customer = new Customer { Type = ... } when writing the next one. The output mirrors the surrounding code because the surrounding code is the only signal available. Every PR arrives with slightly novel inline setup that is almost but not quite what the last PR did.

The agent is not malfunctioning in the second case. It is doing exactly what it was designed to do. The codebase is the problem.

This is the mechanism behind what The Bar for TDD Just Moved called "pre-built ontology for the agent." The vocabulary, the grammar, the rails are already present, so the agent does not need to invent them from scratch on each task. Without them, the agent improvises. Improvisation without constraints is not creativity: it is entropy.

The agent also has no intuition about whether two names mean the same thing. It cannot look at aGoldCustomer() and aLoyaltyMember() and ask whether they represent the same domain concept with different names or two genuinely distinct roles. It will treat them as different if they look different. The disambiguation that a senior developer would perform in a three-second mental look-up does not happen. Both names remain, both get used, and the distinction that was never real in the domain hardens into a structural fork in the codebase.

Vocabulary sets the ceiling on what composition can produce. A strong vocabulary produces recognizable contributions. A weak vocabulary produces recognizable-but-drifting ones, and the drift compounds with every generation.

Strong Vocabulary, Strong Output

Consider a codebase that has invested in its test API. The nouns are sharp: aLoyaltyMember(), aPremiumCart(), aCartReadyForCheckout(), Money, Address. The builders compose cleanly. The scenario tests read like sentences in the domain language.

An agent given the task "add a scenario for loyalty members who apply a referral code during checkout" produces this:

[Fact]
public void Loyalty_members_applying_a_referral_code_receive_stacked_discount()
{
    var order = aCartReadyForCheckout()
        .forCustomer(aLoyaltyMember())
        .withReferralCode(aValidReferralCode());

    var receipt = checkout.process(order);

    receipt.discount.Should().Be(loyaltyDiscount().plus(referralDiscount()));
}
Enter fullscreen mode Exit fullscreen mode

Twelve lines. Zero setup noise. Every noun is a domain concept the team has already ratified. The test name pins the behavior in the same vocabulary the product team uses. A reviewer scanning the PR can assess fitness in seconds: does the name describe a real scenario? Do the builders represent it correctly? Does the assertion match what the team agreed on?

The test reads like the rest of the suite because it uses the rest of the suite. The agent didn't decide what aLoyaltyMember() means. The team decided that months ago. The agent just composed it.

That composability is the return on the original investment. The team named things carefully. The team built builders that captured intent. The team treated the test API as a product surface. The agent now generates contributions that stay inside that surface, automatically, on every PR.

Notice what reviewing this PR requires. Not line-by-line scrutiny of setup plumbing. Not a mental translation between the test's naming conventions and the domain's naming conventions. Not a search for the place where "loyal" was last defined. The reviewer's cognitive load is domain-level: does this scenario represent a real case? Does the name capture what matters? Does the assertion verify the right thing?

That is the review work that actually matters. The strong vocabulary makes it the only review work necessary. Compare that to a PR where the reviewer has to first establish what the code is even trying to say before evaluating whether it says it correctly. The difference is not small. At twenty PRs per week, it is the difference between a team that keeps up with review and a team that starts rubber-stamping.

Weak Vocabulary, Faster Drift

Now consider the same task in a codebase where domain concepts live as primitives, setup is inline, and no consistent naming convention has taken hold.

The agent reads the surrounding code. It sees var customer = new Customer() followed by field-setting. It sees string customerType = "loyal" in one test and bool isLoyalMember = true in another and CustomerCategory.Gold in a third. The concept exists in the codebase three different ways. The agent picks the most recent one it sampled and runs with it.

The resulting test looks like this:

[Fact]
public void ApplyReferralCode_ForLoyalCustomer_StacksDiscounts()
{
    var customer = new Customer
    {
        Id = Guid.NewGuid(),
        Type = "loyal",
        YearsActive = 3,
        IsActive = true,
        Tier = CustomerTier.Standard
    };

    var cart = new Cart
    {
        Id = Guid.NewGuid(),
        CustomerId = customer.Id,
        Items = new List<CartItem>
        {
            new CartItem { ProductId = Guid.NewGuid(), Quantity = 2, UnitPrice = 29.99m }
        },
        Status = CartStatus.Active
    };

    var referralCode = new ReferralCode
    {
        Code = "REF123",
        DiscountPercent = 0.10m,
        IsValid = true
    };

    var service = new CheckoutService(
        new DiscountCalculator(),
        new ReferralCodeValidator(),
        new LoyaltyService()
    );

    var receipt = service.Process(customer, cart, referralCode);

    Assert.True(receipt.DiscountApplied);
    Assert.True(receipt.ReferralApplied);
}
Enter fullscreen mode Exit fullscreen mode

Sixty-three lines. Three new object-construction patterns that slightly diverge from the last test that did something similar. customer.Type = "loyal" sitting alongside CustomerTier.Standard with no explanation of how those two properties interact. A test name in a different casing convention from the two previous tests in the same file. An assertion that checks boolean flags rather than amounts, which means the test does not verify correctness so much as existence.

The agent did not invent dysfunction. It mirrored dysfunction. Every choice it made was defensible given what surrounded it. The problem is the surrounding code, and the surrounding code is now one PR worse.

What makes the weak-vocabulary output damaging is not that it is wrong, exactly. The logic compiles. The boolean assertions will catch a regression. The test adds genuine coverage. But it adds that coverage in a way that is non-composable: the next test that needs a loyal customer will not reach for this one's setup because it is not factored into a reusable noun. It will repeat the same sixty-three lines, with its own small variations. Three tests later, the team has three different inline definitions of what a loyal customer looks like for test purposes, and none of them are authoritative.

The Your Test Suite Is Your API for Agents post observed that a test suite full of test_1, test_2, test_3 is a codebase with no documentation. Primitive-obsessed setup is the structural equivalent: it is a codebase where the domain concepts exist only as local variables, never as named, reusable, reviewable things. The agent cannot build on it because there is nothing to build on.

Speed amplifies the direction already established. A weak vocabulary doesn't get better faster. It gets weaker faster.

The Drift Window Just Collapsed

Humans were susceptible to vocabulary drift too. A developer who joined six months after the codebase was built learns the prevailing patterns through osmosis: reading PRs, pairing, picking up whatever naming conventions seem prevalent. That process is slow enough that most teams treat drift as a background problem rather than an acute one.

A six-month on-boarding arc produces some bad naming choices. The accumulated damage is real but spread thin. A single offending PR per week is a slow leak.

Agents compress throughput. A team using an agentic workflow might generate twenty, thirty, fifty PRs a week that would previously have taken months. The rate of contribution scales; the rate of vocabulary review does not. If the vocabulary has any ambiguity, agents will resolve that ambiguity differently on different tasks. Each resolution looks locally reasonable. The cumulative effect is a codebase where the same concept wears four slightly different names, and no single PR introduced enough drift to trigger a refactoring conversation.

The drift window is the interval between when vocabulary drift starts and when someone notices enough to stop and fix it. That window used to be measured in months because the rate of new contributions was slow enough to accumulate damage gradually. At high agentic throughput, the same amount of drift compresses into weeks.

The time-to-detection has not changed. The amount of damage that accumulates before detection arrives is larger. And the cost of repair grows with every PR that builds on the drifted vocabulary.

Consider what the repair looks like six months into unchecked agentic throughput on a vocabulary-weak codebase. The team wants to rename "loyalty member" to "rewards member" after a product rebrand. In a strong-vocabulary codebase, that rename touches one builder, propagates via the compiler to every test that uses it, and lands in a single PR. In a vocabulary-weak codebase, "loyalty" appears as Type = "loyal", as IsLoyalMember, as CustomerCategory.Gold, as aGoldCustomer(), as aPremiumMember(), as aLoyaltyHolder(), and as a dozen inline strings scattered across test setup. The rename is not a refactor. It is an archaeological expedition. The team decides to do it "later." Later never comes. The old name and the new name coexist indefinitely, and neither one is canonical.

That is the cost the drift window collapse brings forward. The debt was always there. The compressed timeline means the bill arrives before the team has finished accumulating it.

The drift was always the risk. The timeline just collapsed.

The Drift-Detection Audit

Three sprints. Three tickets. Three developers (or three agent sessions) working in the same corner of the codebase without a vocabulary checkpoint.

The result:

// Sprint 1: Added by the payments team
var customer = aGoldCustomer();

// Sprint 2: Added during the loyalty rewrite
var member = aPremiumMember();

// Sprint 3: Added for the referral campaign
var holder = aLoyaltyHolder();
Enter fullscreen mode Exit fullscreen mode

Three names. One concept. None of them wrong in isolation. All of them wrong together.

A new agent session asked to "add a scenario for high-tier members receiving bonus points" will sample all three, pick one, and introduce a fourth variant, or it will attempt to use one consistently and confuse reviewers who expected the others. Either way, the domain model has fractured. The fracture is invisible from any single PR. It only becomes visible when you step back and read the suite as a vocabulary.

The vocabulary audit is not a sophisticated process. Walk a recent agent-generated PR. Count the new nouns it introduced. For each new noun, search the existing test suite for other names that cover the same concept. If there are any, that is a refactoring item. If there are none, verify the new noun was intentional and record the decision somewhere the next agent session can read it.

A useful heuristic: if a PR introduces a builder with a name that contains "Customer," and the codebase already has three builders with "Customer" in their names, read all four together before merging. Do they represent distinct domain concepts? Can any of them be eliminated? Could the new one use an existing one as a base? Those questions take five minutes to answer at PR time. They take five hours to answer when the five PRs that built on the new builder are already merged.

A team running this audit after every sprint will catch drift early, when fixing it means renaming one builder. A team running this audit after six months of agentic throughput will catch drift when fixing it means a cross-cutting rename across three hundred tests and the domain types they depend on.

The audit is not optional. At high agentic throughput, it is the mechanism that keeps the vocabulary from fragmenting under its own speed. Treat audit findings as a refactoring backlog item with the same priority as a broken test. A fragmented vocabulary is a broken specification. It just does not fail a build.

A codebase is not just code. It is a vocabulary. Audit the vocabulary or lose it.

Vocabulary Is Now Leverage

Here is the shift that makes this consequential for teams beyond the purely aesthetic argument about naming things well.

Before agents, investing in vocabulary was a craft choice. Some teams built expressive builder APIs. Some named things carefully. Some enforced the ubiquitous language across layers. Those teams had better codebases, but the advantage was difficult to quantify. Good naming reduced onboarding time. Good builders made tests faster to write. Good domain types caught category errors the compiler could flag. The benefits were real and diffuse.

With agents, each vocabulary improvement compounds across every future agent-generated contribution. Fix aGoldCustomer() to consistently represent what the domain calls a loyalty member, and every future agent session that writes a loyalty-related test uses the correct noun automatically. The fix happened once. The benefit applies to every PR the agent opens from that point forward.

The leverage is not linear. A codebase that generates twenty agent-driven PRs per week gets twenty multiplications of whatever vocabulary state it is in. Twenty PRs built on a strong vocabulary are twenty recognizable, composable contributions. Twenty PRs built on a fragmented vocabulary are twenty vectors of further fragmentation.

Vocabulary is leverage in exactly the financial sense: a small investment amplifies returns on subsequent activity. Unlike financial leverage, it does not amplify losses when used correctly. The tighter the vocabulary, the cleaner the compounding.

This reframes the economics of refactoring work that used to look optional. Renaming a builder method is not an aesthetic cleanup. It is an infrastructure investment that changes the quality distribution of every contribution the agent makes going forward. Deleting a redundant noun eliminates a branching point where the agent would otherwise make an arbitrary choice. Adding a domain type where a primitive was carrying semantic weight gives the agent a rail it will use correctly every time.

The teams that treated vocabulary as craft will find, at agentic throughput, that the craft was capital. The teams that deferred vocabulary cleanup will find that the deferral was a debt, now compounding at a higher rate than it used to.

The New Economics of Naming

There is a specific class of work that looks expensive before agents and becomes clearly worth doing after them.

Deleting a near-synonym. Two builders that mean almost the same thing have always been a cost. A developer who encounters both has to make a judgment call about which one to use. The judgment call takes time and produces inconsistency. With agents, that cost multiplies. Every agent session that touches the related domain area will make the same inconsistent choice, randomly, across hundreds of PRs. The work of collapsing two near-synonyms to one takes an afternoon. The damage from not doing it arrives one confused PR at a time, indefinitely.

Extracting a domain type. decimal carrying the weight of a monetary amount is a primitive pretending to be a concept. Agents working in the vicinity will produce new methods that take decimal, return decimal, and document nothing about the semantic constraints. Extracting a Money type makes those constraints visible to the compiler and to the agent. Every method the agent writes from that point will use Money where the surrounding code uses Money, because it mirrors what it finds.

Naming a builder for what it represents. aCustomerWithLoyaltyStatusAndThreeYearsActive() is not a good name. It describes construction parameters. aLoyaltyMember() is a good name. It describes a domain concept. An agent given aLoyaltyMember() will name the concept correctly in every test it generates. An agent given the verbose constructor name will approximate it differently each time. The naming work is an investment in every future test.

There is a fourth category worth naming separately: consolidating the collaboration vocabulary. Production code that has two interfaces for the same seam, IDiscountCalculator and IDiscountProvider, will produce agents that randomly choose one when writing new tests or adding new collaborators. The inconsistency is invisible in any single test. It surfaces when someone tries to understand the dependency graph of OrderService and finds it depends on both, with no clear distinction between them.

Collapsing that to one interface takes an hour and a refactor. Not doing it costs a small confusion on every agent-generated PR that touches discounts. At agentic throughput, "small confusion per PR" adds up.

This is not new advice. Good naming has always been the advice. What changed is that the return on that advice is now computable in concrete terms: every hour spent sharpening the vocabulary is an hour that applies across every PR the agent opens this quarter.

The argument for naming things well just got quantifiable.

The Vocabulary Gap Becomes the Quality Gap

The long-run implication follows directly.

Teams with strong vocabularies will find that agent-generated contributions stay inside the domain model, use the established nouns, and arrive in a shape that is reviewable in minutes. The review burden decreases as throughput increases. The codebase coheres under the load. The team's energy goes into evaluating whether the new scenario is the right scenario, not into decoding what the scenario even says.

Teams with weak vocabularies will find that agent-generated contributions introduce novel nouns on every PR, require line-by-line scrutiny to catch mismatches with the existing model, and produce a codebase that becomes harder to reason about as throughput increases. The review burden increases with throughput. The codebase fragments under the load. Eventually the team throttles the agent to manage the review queue, which defeats the purpose of using one.

The two trajectories diverge quickly and are difficult to reverse. A team on the fragmented trajectory that decides to fix it faces a codebase where the vocabulary drift is now embedded in hundreds of tests, dozens of builders, and the names of production types. The repair is a major effort, not a sprint. The team on the strong-vocabulary trajectory continues to accumulate leverage with every PR.

The gap is not visible in velocity dashboards. Both teams are shipping. Both teams are opening PRs. The divergence shows up in the shape of the work that remains: one team's PRs are getting easier to review and extend; the other team's are getting harder. By the time that shows up as a measurable slowdown, months of fragmentation have already been baked in.

This is the argument the previous post in this series was setting up. The hidden output of disciplined TDD was never test coverage or even design pressure. It was vocabulary: a shared, precise, stable language for describing what the system does. That vocabulary was always the compounding asset. Most teams never thought to account for it because the compounding was slow enough to be invisible.

At agentic throughput, the compounding is no longer slow. The gap between teams with strong vocabularies and teams without them will be measurable in quarters, not years.

Agents amplify whatever vocabulary they find. The teams that built good vocabulary did not know they were building leverage. Now they do. The teams that deferred it did not know they were accumulating a compounding debt. Now they do.

The vocabulary you maintained for craft reasons just became the infrastructure on which agentic speed either compounds or corrodes.

Top comments (0)