İçeriğe geç

Keeping Specs and Shared Understanding Aligned at AI Speed

With AI-assisted engineering, we engineers are moving faster and changes are getting shipped faster. This is also true for my own daily work. But I must say, the work surrounding those changes doesn’t really get smaller. Part of it simply moves somewhere else.. I have always believed that complexity never goes away, it just moves to a different place… Because of this speed, we can end up with more pull requests waiting for review, changes that take longer to understand and a domain that keeps evolving faster than we can keep up with.

I think this also raises a question that many organizations will have to answer sooner or later. How do engineers stay up to date when things change this frequently?

In this article, I want to share a small practice I am trying to follow, at least for myself and my team, to keep our OpenSpec specifications up to date as a source of truth and help the team absorb changes without having to read every merged PR.

Where the SDD Article Left Off

In my previous article, I shared how Spec-Driven Development (SDD) with OpenSpec changed my workflow and how the specifications started becoming the shared context for both engineers and agents. Once a change is archived, its delta becomes part of the main specs. So at least in theory, the specs always describe what the system does and developers can keep their eyes on them to stay up to date.

In practice, not every change goes through the SDD ritual. As I also mentioned in that article, a tiny and obvious fix doesn’t need a proposal, a design and tasks. Hotfixes are done under time pressure. Sometimes an agent implements something slightly differently than the spec and the review doesn’t catch it.

Each of these is fine on its own. But over time, the specs start describing a system that doesn’t fully exist anymore.

I think this matters more in the AI era for a couple of reasons. The team relies on these specs to understand the domain and the agents rely on them too. In my case, I also have reusable agent skills that use the accepted specs as their reference context when they help other teams build on top of the platform. So a stale spec doesn’t only mislead a developer, it also misleads every agent built on top of it.

And this goes beyond the specs. When code changes faster than people’s mental models, teams slowly lose track of what the system actually does. Nobody notices it on a single PR, it just builds up over time as a gap in the team’s shared understanding.

Keeping Specs in Sync at the PR

In my experience, the main mechanism should be a domain guardrail check on every PR. It is backed by the archived specs, so it knows what each capability is supposed to do. When a change touches a capability that has a spec, the agent checks whether the change affects the behavior described in that spec and whether a spec delta comes with it. If not, it comments on the PR with the evidence, which requirement is affected and why. What actually blocks the merge stays a clear, deterministic rule.

This also touches the other aspect I mentioned. Since a spec delta has to come with the change, the reviewers who are already in the PR read the intended behavior change in plain words, not only in the code. But I would say, that visibility is limited to the people involved in the PR. The rest of the team still never sees it.
 
This is where I think an additional mechanism might help, although it is still more of a hypothesis than something I have experienced. Change digests are a familiar practice, so I think a weekly one for behavior changes could work. Instead of expecting everyone to track updates as they happen, a short summary built from the specs archived that week could be posted in the team’s channel, explaining what changed and, more importantly, what the system does differently as a result.

Because the specs remain the source of truth, people wouldn’t have to wait for those updates either. If a question comes up, they could go straight to the spec or ask an agent that relies on the same specifications. The digest simply makes those changes easier to discover.
 
Margaret Storey describes this broader challenge as cognitive debt. The code can stay clean and the builds can stay green, while the team’s shared understanding of the system quietly fades. Keeping specifications synchronized with implementation becomes more than a documentation exercise. It becomes a way of preserving organizational knowledge.

That said, a digest can’t replace real understanding, and self-service only works when the underlying specs are accurate. That’s exactly why I think the PR check is so important.

 

So, Let’s Build a Small PoC

To keep things simple, the PoC is a GitHub Actions workflow that runs on every PR in a single repository. I also didn’t want to build a new sample project just for this, so I will reuse the product search agent from my previous article, where I built a PoC with Microsoft Agent Framework.

The domain guardrail check needs something to compare the code with, so to demonstrate the idea, I placed a baseline OpenSpec spec in that repository. It describes the current business behavior of the search agent, like how it searches the catalog and how many products it returns.
 
For the model, I am using GPT-6 Luna hosted on Microsoft Foundry. A smaller reasoning model is enough for this job, since the task is quite narrow. The model only sees the PR diff and the handful of specs relevant to it, then evaluates whether the change still agrees with them. That also lets the check run efficiently on every PR.
 
The workflow authenticates with GitHub OIDC through a federated credential in Microsoft Entra ID and calls the model through Azure API Management as an AI gateway which gives me a single place for authentication and token limits. I won’t go into the details of this setup here. Instead, I included the whole setup as Bicep templates in the Infrastructure folder of the same repository, so you can run and experiment with it locally.
 
Now that we have the idea and the setup, let’s see what actually happens when a PR is opened. I tried to keep the design simple. It is basically three steps: collect the facts, let the model judge, then verify the result and decide.
 
Let’s have a look at what the workflow looks like.
name: spec-guardrail

on:
  pull_request:
    types: [opened, synchronize, reopened, labeled, unlabeled]
    paths: ["Program.cs", "Executors/**", "Tools/**", "Prompts/**", "Models/**", "Data/**"]

permissions:
  id-token: write
  contents: read
  pull-requests: write
  statuses: write

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ github.event.pull_request.head.sha }}
          fetch-depth: 0

      - uses: azure/login@v3
        with:
          client-id: ${{ vars.AZURE_CLIENT_ID }}
          tenant-id: ${{ vars.AZURE_TENANT_ID }}
          allow-no-subscriptions: true

      - name: Get a token for the AI gateway
        env:
          APIM_AUDIENCE: ${{ vars.APIM_AUDIENCE }}
        run: |
          TOKEN=$(az account get-access-token --resource "$APIM_AUDIENCE" --query accessToken -o tsv)
          echo "::add-mask::$TOKEN"
          echo "APIM_TOKEN=$TOKEN" >> "$GITHUB_ENV"

      - name: Run the domain guardrail check
        run: python3 .github/spec-guardrail/check.py
        env:
          BASE_SHA: ${{ github.event.pull_request.base.sha }}
          HEAD_SHA: ${{ github.event.pull_request.head.sha }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          REPO: ${{ github.repository }}
          GH_TOKEN: ${{ github.token }}
          APIM_ENDPOINT: ${{ vars.APIM_ENDPOINT }}
          MODEL: ${{ vars.SPEC_GUARDRAIL_MODEL || 'gpt-6-luna' }}
          SRC_PATHS: "Program.cs Executors Tools Prompts Models Data"
The workflow only runs when a PR touches the paths where behavior lives. The “labeled” and “unlabeled” types make the check run again when the override label changes, which we will get to later.
 
The job checks out the PR, signs in with the federated identity from the infrastructure part, gets a token for the AI gateway and runs a small Python script, where I simply encoded the logic of the domain guardrail check.
 
The script does a few things in order. It diffs the base and head commits for the watched paths and looks for a spec delta in the PR. It then asks the model, through the AI gateway, whether the change still agrees with the relevant specs. To do that, it uses two small prompts stored in the prompts folder: one to identify the relevant specs and another to compare the code against them. The model can only return one of four outcomes: nothing changed, the code contradicts a requirement, it introduces behavior not covered by a spec, or it cannot determine the impact.
 
It then verifies that every finding comes with evidence from the diff and turns the verdict into a commit status and a PR comment. The model never becomes the source of truth. The specs remain the source of truth, while the model acts as an automated reviewer that helps enforce them.
 
I won’t spend much time on the implementation details here. The script is intentionally small and straightforward, and you can find the full source in the repository.
 
So, let’s have a look at the workflow in action.
 

Seeing It In Action

 
I prepared two PRs. The first one is a plain refactor. I only renamed a variable and didn’t touch any spec. Nothing observable changes, so the check passes and leaves a short comment saying “Spec guardrail check passed.” I wanted a comment even on a pass, so the team can see that the check actually ran and why it passed.
The second one changes behavior. I changed the number of products the search returns, from “Size = 5” to “Size = 10“. The baseline spec has a requirement called “Bounded search results“, which says each search returns “at most 5 products“. As we can see, the status fails. It points to the requirement, quotes the changed line from the diff, and explains why the implementation no longer matches the spec.
At this point, the author has a clear choice. If the new limit is intended, they add a spec delta through the usual SDD flow, and the check then judges the code against that delta. If it was a mistake, they fix the code. If reviewers believe the finding is incorrect, they can explicitly override it with the label.
 

Wrapping Up

 
Going back to the question I started with, how do engineers stay up to date when things change this frequently? To be honest, I am still figuring that out myself. As AI continues to accelerate implementation, keeping up with the resulting changes increasingly feels like a team problem rather than an individual one. Reading every PR may be possible today, but it doesn’t feel like a strategy that scales.
 
What seems more sustainable is investing in mechanisms that preserve shared understanding. In my case, that starts with treating specs as a living source of truth and using automation to help keep them aligned with the code.
 
Of course, the check has its limits. It only sees the diff of the watched paths and the specs that are relevant to it, and the model can be wrong, so reviewers are still very much needed. But keeping specs aligned with implementation feels like a good place to start.
 
 

References

https://www.thoughtworks.com/radar/techniques/codebase-cognitive-debt
https://margaretstorey.com/blog/2026/02/09/cognitive-debt/
Kategori:AI AgentsArchitecturalAzure

İlk Yorumu Siz Yapın

    Bir yanıt yazın

    E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

    Bu site istenmeyenleri azaltmak için Akismet kullanır. Yorum verilerinizin nasıl işlendiğini öğrenin.