TOMWELLS.ORG

Are we making what we intended?

Or: Sloponomics - how much slop debt are we accumulating?

I've been building Kyyn with agents, and a fairly large part of that work has been figuring out how to describe the architecture so that another agent can pick it up and carry on. As the codebase grows, this becomes a very practical problem. What do we want the system to do? What have we actually built? How close are those two things?

I think there are two dimensions worth measuring here.

  1. The quality of the Architecture Decision Records (ADRs) themselves - how clearly and completely they describe what we intend to build, and how much they leave an implementer guessing.
  2. The convergence of those decisions and the source code - how much of that intent we've actually delivered.

Together they give us a much better picture of our engineering progress. A precise architecture can still be mostly unbuilt, and an implementation can faithfully follow a description that leaves important questions unanswered.

ADR clarity and completeness increase to the right; implementation convergence increases upwards. Bottom left: guesswork in progress. Bottom right: good plan, work to do. Top left: false confidence - what did we leave unsaid? Top right: clear intent, delivered.

The false confidence corner is the sneaky one. Convergence is measured against what we specified, so a vague ADR can be easy to satisfy. Important decisions may still have been filled in silently along the way. That's what I mean by slop debt here - assumptions baked into the code that we haven't examined or agreed on, waiting for us to discover them later.

1. How much are we leaving to guesswork?

An implementation can match an ADR and still leave important decisions to whoever happened to write the code. Alongside comparing the two, I want to know how much the ADR leaves unanswered.

For this, I ask agents to read the architecture without seeing the code, and list the questions they'd need answered before they could build it. They read the whole set of ADRs, because something missing from one might be explained perfectly well in another. The interesting questions are the ones the architecture leaves unanswered.

Consider a connector making a payment request. The architecture says the scheduler owns retries and each attempt uses the same idempotency key. But what happens if the payment succeeds and the response never arrives? Passing the same key is only useful if something honours it. If the architecture doesn't explain how duplicate payments are prevented, that's a potentially high-impact question for an agent to raise.

In Kyyn, three agents do this independently, reading the ADRs in different orders. A judging pass brings their questions together, checks whether the answers are already there, and keeps track of questions raised by at least two of the three. That helps separate recurring gaps from one agent going off on its own little adventure. Contradictions between ADRs are recorded too.

We rank the gaps high, medium or low according to what a wrong guess would cost. The unanswered duplicate-payment question above is high-impact. An unspecified error response shape might be medium - visible to callers, but something we can change with some coordination. A harmless default that's cheap to change might be low. The ranking is about the consequence of leaving the decision open.

This gives us a useful view of how much an implementer still has to invent. I can look at an ADR and see the actual unanswered questions, prioritise the expensive ones, and improve the description before another agent quietly fills in the blanks. A low count doesn't prove the architecture is good, and answering one question can expose the next level of detail. But it gives us something concrete to work on alongside the implementation's progress.

2. Are we building what we decided?

My preference is to keep the architectural decisions and the source code independently understandable, then use agents to work out where they have converged. I've built a viewer for Kyyn which does exactly this - you can scrub through the project's history and watch decisions being introduced, revised and implemented. It's very cool to see an architecture taking shape like this!

In the viewer, each ADR has a lane containing the decisions it has carried over time. You can see when a decision was specified, where parts of it were delivered, and where the implementation caught up. When the architecture changes, the earlier decision remains visible in the history. Sometimes the code got there before the documentation did. That's useful to see too.

Example decision history: connector-owned retries are specified, partly delivered and implemented, then superseded by scheduler-owned retries. The replacement decision follows its own path from specified to partly delivered to implemented; the earlier decision remains visible in history.
One architectural concern, two successive decisions. Each dot marks a different point in the git history.

The assessment involves agent judgement. Git supplies the history; agents examine the evidence and curate the correspondence between decisions and implementation. The result is reviewable, including the uncertain calls. I wouldn't treat a green dot as a mathematical proof, but it's a much more useful answer than finding a familiar name with a search tool and declaring victory.

At the time of writing, Kyyn's viewer covers 29 ADRs and around 680 curated decisions. This has gone beyond a tiny demonstration. We can also update the assessment incrementally, PR by PR. Each merge gives us a bounded set of changes to examine against the relevant decisions, while retaining what we already know about the rest of the system. A decision might take several PRs to deliver. One PR might advance several decisions. The history can represent both.

What I really like is being able to look at a particular moment and ask where the implementation still falls short of the architecture we intended. That gives the next agent something concrete to investigate, and gives me a way to inspect progress without reading a pile of completed task lists.

Giving agents something worth measuring

Both assessments depend on giving agents a clear description of the system we intend to have.

An ADR should describe a decision as it stands at a particular point in time. When that decision changes, update it. Remove the obsolete guidance (Kyyn used to carry status fields too). The resulting set of ADRs should make sense together without needing dates to tell you which instruction wins.

Imagine an ADR saying that connectors own their retry behaviour, followed further down by a dated update saying that retries now belong to the scheduler. Both statements are sitting there, waiting to be read. We can probably teach an agent to work out which one applies, but why are we giving it this little archaeology exercise in the first place?

Rewrite the decision so that retry ownership is clear. Git already remembers the previous version. We can still inspect the old reasoning and the discussion that led to the change - we don't need to carry obsolete instructions into every fresh reading of the architecture.

The same goes for implementation checklists. "Move retries into the scheduler" is work to do. "The scheduler owns retries" describes the architecture we intend to have. Putting them together makes it harder to tell whether a sentence describes a requirement, a temporary situation, or something somebody ticked off three weeks ago.

A checklist belongs to the change we're making. Once that work is done, it becomes history. The ADR should still be useful to somebody arriving fresh, without having to read through how we organised the work.

OpenSpec is an interesting comparison. Its documented workflow keeps current specifications alongside per-change proposals, designs, tasks and archived changes. We're addressing much the same need for agents to have clear intent to work from. My preference is to keep fewer kinds of durable project documents, and let the existing development tools carry the temporary work.

"Use a flexible, extensible connector architecture" gives an implementer almost nothing to work with. An ADR can describe the connector interface, the data types crossing that boundary, relevant function signatures, who owns retries, and what happens when a connector fails halfway through an operation. Enough substance for an agent to understand the decision and recognise an implementation of it.

For the retry scenario above, a small ADR might look like this. Notice the data type and function signature in the highlighted block - this is the level of detail that lets an agent recognise the intended interface in the code.

Markdown
# The scheduler owns retries

## Context

If connectors retried internally, the scheduler
could not see or limit attempts. Retry policy
needs one owner so we can control backoff
and attempt limits.

## Decision

A connector performs one attempt and returns
an outcome. It must not schedule its own retry.

The scheduler owns backoff and attempt limits.
Every attempt for the same operation receives
the same idempotency key.

## Connector interface

```haskell
data AttemptResult a
  = Succeeded a
  | Retryable RetryReason
  | Failed FailureReason

attempt
  :: IdempotencyKey
  -> Operation
  -> IO (AttemptResult Receipt)
```

Succeeded completes the operation.
Retryable allows the scheduler to retry within
its configured attempt limit.
Failed ends the operation without retrying.

## Consequences

Connectors classify failures; the scheduler
applies retry policy consistently across them.
Changing that policy does not require changing
each connector.

## Verification

A connector test should show that a timeout
returns Retryable without a second request.
A scheduler test should show that retries
reuse the original idempotency key.

An agent now has something concrete to look for: connectors returning retryable outcomes, scheduling happening outside the connector, and the same key surviving across attempts. It also leaves our earlier question open: what prevents a duplicate if the remote operation succeeds but the response is lost? We can assess what has converged while still identifying what the ADR leaves unanswered.

I don't want to solve the relationship between those documents and the code by scattering ADR numbers through source files, or maintaining lists of source paths in every ADR. A link tells us where somebody thought the implementation lived. It doesn't tell us whether that implementation satisfies the decision.

Instead, an agent can read both. It can follow the types and interfaces into the implementation, inspect the behaviour and tests, and assess whether the architectural idea has actually been delivered. Finding a matching function signature is a useful starting point. Establishing that the scheduler really owns retries takes a bit more reading!

Have a play with the viewer - expand a few lanes and scrub backwards. The places where the plan and the code took different routes are probably the most interesting bits.

Article
  { slug: "are-we-making-what-we-intended"
  , originalUrl: Nothing
  , title: "Are we making what we intended?"
  , date: constructDate 2026 11 1
  , keywords: ["ADRs","architecture","agents","Kyyn"]
  , cover: { alt: "Code", caption: Nothing, src: "/images/covers/code.png" }
  , summary: "Using agents to assess how clearly our decisions describe the intended system, and how far the code has converged."
  , author: { bio: "Towards an AI-first, hyper-scaled, functional reactive universe!", email: "tom@tomwells.org", name: "Tom Wells" }
  , body:
      [ FlowRichParagraph [ InlineEmphasis "Or: Sloponomics - how much slop debt are we accumulating?" ]
      , FlowParagraph "I've been building Kyyn with agents, and a fairly large part of that work has been figuring out how to describe the architecture so that another agent can pick it up and carry on. As the codebase grows, this becomes a very practical problem. What do we want the system to do? What have we actually built? How close are those two things?"
      , FlowNumbered { points: ["The quality of the Architecture Decision Records (ADRs) themselves - how clearly and completely they describe what we intend to build, and how much they leave an implementer guessing.","The convergence of those decisions and the source code - how much of that intent we've actually delivered."], start: "I think there are two dimensions worth measuring here." }
      , FlowParagraph "Together they give us a much better picture of our engineering progress. A precise architecture can still be mostly unbuilt, and an implementation can faithfully follow a description that leaves important questions unanswered."
      , FlowImage { alt: "ADR clarity and completeness increase to the right; implementation convergence increases upwards. Bottom left: guesswork in progress. Bottom right: good plan, work to do. Top left: false confidence - what did we leave unsaid? Top right: clear intent, delivered.", caption: Nothing, src: "/images/articles/let-the-architecture-stand-on-its-own/engineering-quadrants.svg" }
      , FlowParagraph "The false confidence corner is the sneaky one. Convergence is measured against what we specified, so a vague ADR can be easy to satisfy. Important decisions may still have been filled in silently along the way. That's what I mean by slop debt here - assumptions baked into the code that we haven't examined or agreed on, waiting for us to discover them later."
      , FlowSection "1. How much are we leaving to guesswork?"
      , FlowParagraph "An implementation can match an ADR and still leave important decisions to whoever happened to write the code. Alongside comparing the two, I want to know how much the ADR leaves unanswered."
      , FlowParagraph "For this, I ask agents to read the architecture without seeing the code, and list the questions they'd need answered before they could build it. They read the whole set of ADRs, because something missing from one might be explained perfectly well in another. The interesting questions are the ones the architecture leaves unanswered."
      , FlowParagraph "Consider a connector making a payment request. The architecture says the scheduler owns retries and each attempt uses the same idempotency key. But what happens if the payment succeeds and the response never arrives? Passing the same key is only useful if something honours it. If the architecture doesn't explain how duplicate payments are prevented, that's a potentially high-impact question for an agent to raise."
      , FlowParagraph "In Kyyn, three agents do this independently, reading the ADRs in different orders. A judging pass brings their questions together, checks whether the answers are already there, and keeps track of questions raised by at least two of the three. That helps separate recurring gaps from one agent going off on its own little adventure. Contradictions between ADRs are recorded too."
      , FlowParagraph "We rank the gaps high, medium or low according to what a wrong guess would cost. The unanswered duplicate-payment question above is high-impact. An unspecified error response shape might be medium - visible to callers, but something we can change with some coordination. A harmless default that's cheap to change might be low. The ranking is about the consequence of leaving the decision open."
      , FlowParagraph "This gives us a useful view of how much an implementer still has to invent. I can look at an ADR and see the actual unanswered questions, prioritise the expensive ones, and improve the description before another agent quietly fills in the blanks. A low count doesn't prove the architecture is good, and answering one question can expose the next level of detail. But it gives us something concrete to work on alongside the implementation's progress."
      , FlowSection "2. Are we building what we decided?"
      , FlowRichParagraph [ InlineText "My preference is to keep the architectural decisions and the source code independently understandable, then use agents to work out where they have converged. I've built a ", InlineLink { link: "https://drshade.github.io/kyyn-v2/", title: "viewer for Kyyn" }, InlineText " which does exactly this - you can scrub through the project's history and watch decisions being introduced, revised and implemented. It's very cool to see an architecture taking shape like this!" ]
      , FlowParagraph "In the viewer, each ADR has a lane containing the decisions it has carried over time. You can see when a decision was specified, where parts of it were delivered, and where the implementation caught up. When the architecture changes, the earlier decision remains visible in the history. Sometimes the code got there before the documentation did. That's useful to see too."
      , FlowImage { alt: "Example decision history: connector-owned retries are specified, partly delivered and implemented, then superseded by scheduler-owned retries. The replacement decision follows its own path from specified to partly delivered to implemented; the earlier decision remains visible in history.", caption: (Just "One architectural concern, two successive decisions. Each dot marks a different point in the git history."), src: "/images/articles/let-the-architecture-stand-on-its-own/decision-history.svg" }
      , FlowParagraph "The assessment involves agent judgement. Git supplies the history; agents examine the evidence and curate the correspondence between decisions and implementation. The result is reviewable, including the uncertain calls. I wouldn't treat a green dot as a mathematical proof, but it's a much more useful answer than finding a familiar name with a search tool and declaring victory."
      , FlowParagraph "At the time of writing, Kyyn's viewer covers 29 ADRs and around 680 curated decisions. This has gone beyond a tiny demonstration. We can also update the assessment incrementally, PR by PR. Each merge gives us a bounded set of changes to examine against the relevant decisions, while retaining what we already know about the rest of the system. A decision might take several PRs to deliver. One PR might advance several decisions. The history can represent both."
      , FlowParagraph "What I really like is being able to look at a particular moment and ask where the implementation still falls short of the architecture we intended. That gives the next agent something concrete to investigate, and gives me a way to inspect progress without reading a pile of completed task lists."
      , FlowSection "Giving agents something worth measuring"
      , FlowParagraph "Both assessments depend on giving agents a clear description of the system we intend to have."
      , FlowParagraph "An ADR should describe a decision as it stands at a particular point in time. When that decision changes, update it. Remove the obsolete guidance (Kyyn used to carry status fields too). The resulting set of ADRs should make sense together without needing dates to tell you which instruction wins."
      , FlowParagraph "Imagine an ADR saying that connectors own their retry behaviour, followed further down by a dated update saying that retries now belong to the scheduler. Both statements are sitting there, waiting to be read. We can probably teach an agent to work out which one applies, but why are we giving it this little archaeology exercise in the first place?"
      , FlowParagraph "Rewrite the decision so that retry ownership is clear. Git already remembers the previous version. We can still inspect the old reasoning and the discussion that led to the change - we don't need to carry obsolete instructions into every fresh reading of the architecture."
      , FlowParagraph "The same goes for implementation checklists. \"Move retries into the scheduler\" is work to do. \"The scheduler owns retries\" describes the architecture we intend to have. Putting them together makes it harder to tell whether a sentence describes a requirement, a temporary situation, or something somebody ticked off three weeks ago."
      , FlowParagraph "A checklist belongs to the change we're making. Once that work is done, it becomes history. The ADR should still be useful to somebody arriving fresh, without having to read through how we organised the work."
      , FlowRichParagraph [ InlineLink { link: "https://github.com/Fission-AI/OpenSpec#see-it-in-action", title: "OpenSpec" }, InlineText " is an interesting comparison. Its documented workflow keeps current specifications alongside per-change proposals, designs, tasks and archived changes. We're addressing much the same need for agents to have clear intent to work from. My preference is to keep fewer kinds of durable project documents, and let the existing development tools carry the temporary work." ]
      , FlowParagraph "\"Use a flexible, extensible connector architecture\" gives an implementer almost nothing to work with. An ADR can describe the connector interface, the data types crossing that boundary, relevant function signatures, who owns retries, and what happens when a connector fails halfway through an operation. Enough substance for an agent to understand the decision and recognise an implementation of it."
      , FlowRichParagraph [ InlineText "For the retry scenario above, a small ADR might look like this. ", InlineStrong "Notice the data type and function signature", InlineText " in the highlighted block - this is the level of detail that lets an agent recognise the intended interface in the code." ]
      , FlowSourceCode { lang: Markdown, body: "# The scheduler owns retries\n\n## Context\n\nIf connectors retried internally, the scheduler\ncould not see or limit attempts. Retry policy\nneeds one owner so we can control backoff\nand attempt limits.\n\n## Decision\n\nA connector performs one attempt and returns\nan outcome. It must not schedule its own retry.\n\nThe scheduler owns backoff and attempt limits.\nEvery attempt for the same operation receives\nthe same idempotency key.\n\n## Connector interface\n\n```haskell\ndata AttemptResult a\n  = Succeeded a\n  | Retryable RetryReason\n  | Failed FailureReason\n\nattempt\n  :: IdempotencyKey\n  -> Operation\n  -> IO (AttemptResult Receipt)\n```\n\nSucceeded completes the operation.\nRetryable allows the scheduler to retry within\nits configured attempt limit.\nFailed ends the operation without retrying.\n\n## Consequences\n\nConnectors classify failures; the scheduler\napplies retry policy consistently across them.\nChanging that policy does not require changing\neach connector.\n\n## Verification\n\nA connector test should show that a timeout\nreturns Retryable without a second request.\nA scheduler test should show that retries\nreuse the original idempotency key." }
      , FlowParagraph "An agent now has something concrete to look for: connectors returning retryable outcomes, scheduling happening outside the connector, and the same key surviving across attempts. It also leaves our earlier question open: what prevents a duplicate if the remote operation succeeds but the response is lost? We can assess what has converged while still identifying what the ADR leaves unanswered."
      , FlowParagraph "I don't want to solve the relationship between those documents and the code by scattering ADR numbers through source files, or maintaining lists of source paths in every ADR. A link tells us where somebody thought the implementation lived. It doesn't tell us whether that implementation satisfies the decision."
      , FlowParagraph "Instead, an agent can read both. It can follow the types and interfaces into the implementation, inspect the behaviour and tests, and assess whether the architectural idea has actually been delivered. Finding a matching function signature is a useful starting point. Establishing that the scheduler really owns retries takes a bit more reading!"
      , FlowParagraph "Have a play with the viewer - expand a few lanes and scrub backwards. The places where the plan and the code took different routes are probably the most interesting bits."
      ]
  }