The Naming Contract That Stopped Agents Disagreeing on Field Names

πŸ“ Computer

The AI Software House pipeline turns a requirement into code through several agents. The architect designs it, QA writes tests, the engineer implements it, and reviewers check the result.

At least, that is how it is meant to work.

One run went differently. The architect wrote username in the design. QA wrote tests for user_name. The engineer implemented username, the tests failed, and the repair loop spent fifteen attempts trying to reconcile two names that had never been agreed in the first place.

The code was not the original problem. The missing contract was.


The contract gap

Each agent in the pipeline reads the PRD and the design document. Both are prose. Prose is ambiguous.

An architect can mention β€œthe username field” in one paragraph and user_name in another without noticing. A human understands the intent. Two downstream agents may each choose a different spelling.

The QA engineer picks up the design and writes tests. It picks a name. The engineer picks a different name. They are both correct β€” from the perspective of their input. The input was ambiguous.

I stopped asking the agents to infer the names from prose and made the architect write them down exactly.


naming_contract.yaml

The architect now produces a naming_contract.yaml alongside the design document.

version: 1
endpoints:
  - path: /api/users
    method: POST
    auth: required
    request_fields: [username, email, password]
    response_fields: [id, username, email, created_at]
enums:
  UserRole: [admin, member, guest]
service_signatures:
  - fn: user_service.create_user
    args: [CreateUserRequest]

This file is meant to be obeyed, not merely read. Field names, enum values, and service signatures are exact.

Every downstream agent reads it before writing code.

The QA engineer reads it before writing tests. The engineer reads it before writing schemas. They both work from the same names. The mismatch disappears.


ContractValidatorAgent

Of course, giving an agent a contract does not guarantee that it will follow it.

So we added a validation step.

After the QA engineer writes tests, the ContractValidatorAgent runs. It takes the naming_contract.yaml and each test file and asks a simple question: do the field names in the tests match the field names in the contract?

The output is structured:

{
  "passed": false,
  "skipped": false,
  "divergences": [
    {
      "file": "tests/test_users.py",
      "field": "user_name",
      "issue": "Test uses 'user_name' but contract expects 'username'",
      "suggestion": "Rename 'user_name' to 'username'"
    }
  ]
}

Any mismatch now appears before implementation begins, when correcting a test name is still cheap.

The contract validator runs silently when it passes. It is only loud when something is wrong.


The SHA conflict problem

There was a second class of failures, unrelated to naming.

When the pipeline runs a revision β€” the --mode revise path where it reads PR feedback and updates the code β€” it commits multiple files to GitHub. Each commit requires knowing the current file SHA. The pipeline fetches the SHA before it begins.

During a long revision with many files, the cached SHA goes stale. Another commit happened between the fetch and the write. GitHub returns 409.

The original code raised an error. The pipeline failed. The three files that hadn't been committed yet were lost.

The eventual fix was small: on a 409, fetch the current SHA and retry once.

try:
    return self._request("PUT", f"/repos/{self.repo}/contents/{path}", json=payload)
except RuntimeError as exc:
    if _sha_retry and "409" in str(exc):
        log.warning("[github_client] 409 SHA conflict on %s β€” retrying with fresh SHA", path)
        return self.commit_file(path, content, message, branch, _sha_retry=False)
    raise

One flag. One retry. The pipeline no longer fails on the commit that worked fine last time.


Prompt hardening

The naming contract and the SHA retry are structural fixes. There is a third category: prompt fixes for recurring patterns.

Two FastAPI testing patterns were causing consistent failures in generated tests.

The dependency override pattern. FastAPI uses app.dependency_overrides to replace injected dependencies in tests. Generated tests were patching get_current_user directly instead. Direct patches don't work with FastAPI's dependency injection β€” the override is never applied. The QA engineer role prompt now has an explicit rule:

Use dependency_overrides for auth mocking, never patch get_current_user directly:
    app.dependency_overrides[get_current_user] = lambda: mock_user

Async mock configuration. Generated tests also needed clearer rules for async mocks and dependency overrides. A workaround recorded in the QA prompt used side_effect=lambda *a, **kw: user. That should not be read as a Python 3.13 API change: the Python documentation specifies that awaiting AsyncMock(return_value=user)() returns user. The useful rule is to check the mock configuration and the dependency override together, rather than assume the language changed.

These rules are now encoded. They give generated tests explicit guidance, which still needs to be checked by running the tests.


RAG test indexing

There is one more addition: after the QA engineer writes test files, they are indexed into the RAG collection.

When the engineer implements against the tests, it can search for how the tests use a given field name. The tests become a searchable specification. The engineer does not need to read every test file β€” it can query "how does the test expect the user object to be structured?" and get a direct answer.

The contract enforces the names. The RAG index surfaces the usage. Both point the engineer in the same direction.


Fewer repair loops

The pipeline has grown by adding correction loops. Test-fix loop. Revision loop. PRD review loop.

This update went in the opposite direction. Instead of adding another correction loop, it removed several reasons for entering one.

A naming contract means the test-fix loop does not need to reconcile field name mismatches β€” there are none. The SHA retry means the revision does not fail halfway through. Prompt hardening means the QA engineer does not generate tests with broken mock patterns.

The pipeline still needs feedback loops, but it should not spend fifteen rounds repairing a spelling decision that could have been fixed before coding started.


Part of the AI Software House series.