<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Software Architecture as a Field]]></title><description><![CDATA[Software Architecture as a Field is a research notebook on software architecture for the age of AI-driven development.

It explores how algebraic ideas can help analyze architectural properties such as boundaries, composition, coupling, invariants, and change propagation.

The goal is to understand how codebases evolve when AI agents accelerate software change, and how architecture can guide that change toward maintainable systems.]]></description><link>https://blog.iroha1203.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/69ff29ebf239332df4a93c3a/3d06bcca-6344-4003-93f5-4e70149cb316.png</url><title>Software Architecture as a Field</title><link>https://blog.iroha1203.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 14 May 2026 17:51:16 GMT</lastBuildDate><atom:link href="https://blog.iroha1203.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Software Architecture as a Field: Asking Better Questions About Software Evolution]]></title><description><![CDATA[AI coding agents are making us faster at writing code. That is a major shift.
But writing code faster is not the same as helping software evolve in a healthy way. The faster we stack up changes, the m]]></description><link>https://blog.iroha1203.dev/software-architecture-as-a-field</link><guid isPermaLink="true">https://blog.iroha1203.dev/software-architecture-as-a-field</guid><category><![CDATA[Computer Science]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[AI coding]]></category><category><![CDATA[software development]]></category><dc:creator><![CDATA[Hiroyuki Nakahata]]></dc:creator><pubDate>Wed, 13 May 2026 15:59:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69ff29ebf239332df4a93c3a/e9c0e75d-e514-4137-bb37-ce4b22b8e229.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>AI coding agents are making us faster at writing code. That is a major shift.</p>
<p>But writing code faster is not the same as helping software evolve in a healthy way. The faster we stack up changes, the more important it becomes to ask what each change leaves behind in the codebase, and how it changes what can be changed next.</p>
<p>Traditional tooling usually looks at things that have already happened. Code state, PR diffs, test results, static dependencies, and runtime errors are all important objects of observation.</p>
<p>But in an era where AI coding agents can generate pull requests one after another, we need to ask one step further.</p>
<ul>
<li><p>What kinds of changes does this change make possible next?</p>
</li>
<li><p>What kinds of pull requests does this PRD make more likely?</p>
</li>
<li><p>What kinds of shortcuts does this review rule make expensive?</p>
</li>
<li><p>What observation axes should this incident leave behind?</p>
</li>
</ul>
<p>At that point, we are no longer interested only in the current state of software. We also want to reason about software evolution itself.</p>
<p>In this article, I introduce AAT, or Algebraic Architecture Theory, and SFT, or Software Field Theory, as theories for asking questions about software evolution. I will start by drawing a rough map of how AAT/SFT tries to look at software that keeps changing.</p>
<h2>Software Keeps Changing</h2>
<p>Software is not something that is completed and then slowly decays. It connects to the real world through use, and as that real world changes, the software continually drifts out of alignment.</p>
<p>Users, business processes, organizations, operations, regulations, libraries, and infrastructure all keep changing. Today, AI coding agents are also changing the development flow itself.</p>
<p>This view is not new. Lehman's laws of software evolution formalized the view that software connected to real-world problems, often called E-type software, requires continual adaptation as long as it is used. They also observed that evolving software tends to become more complex unless explicit work is done to control that complexity. And the process of evolution should not be seen as a mere sequence of changes, but as a multi-layered, multi-loop, multi-actor feedback system.</p>
<p>The important point here is not to jump immediately to a prescription. The answer is not simply "so we should refactor," or "so we should strengthen design review," or "so we should control AI more strictly."</p>
<p>The question after Lehman is a little deeper:</p>
<pre><code class="language-text">If software keeps changing,
what can we ask about that change?
</code></pre>
<p>AAT/SFT starts from this question.</p>
<h2>Decomposing the Question of "Good Design"</h2>
<p>In design discussions, we often encounter questions like these:</p>
<ul>
<li><p>Is this design good?</p>
</li>
<li><p>Is this change safe?</p>
</li>
<li><p>Should we accept this PR?</p>
</li>
<li><p>Is this AI agent's proposal risky?</p>
</li>
<li><p>Should we still repair this system, or should we migrate?</p>
</li>
</ul>
<p>All of these are natural questions. But as they stand, they are too large.</p>
<ul>
<li><p>What does "good" mean we are preserving?</p>
</li>
<li><p>What range of future changes does "safe" refer to?</p>
</li>
<li><p>On which observation axis, and through which kind of failure, is something "risky"?</p>
</li>
<li><p>When we say "we should migrate," which change paths are we closing, and which ones are we opening?</p>
</li>
</ul>
<p>In AAT/SFT, we break the question down a little further.</p>
<ul>
<li><p>What object are we cutting out for analysis?</p>
</li>
<li><p>What do we want to preserve?</p>
</li>
<li><p>Which invariant failed?</p>
</li>
<li><p>How is that failure observed?</p>
</li>
<li><p>What kinds of changes does this change make more natural next?</p>
</li>
</ul>
<p>This is not about turning design review into a checklist. It is about taking the judgments we already make implicitly in practice and reframing them as theoretical objects, so we can reason about changing software.</p>
<h2>AAT (Algebraic Architecture Theory): Reading Changes Locally</h2>
<p>AAT is a theory for reading software architecture locally.</p>
<p>Here, architecture is not treated as the entire codebase all at once. First, we cut out the object needed to answer the question at hand, and call it an <code>ArchitectureObject</code>.</p>
<p>We do not have to handle a huge codebase all at once. We can focus on one boundary, one dependency relation, one runtime interaction, or one semantic contract. What matters is making explicit what we chose as the object.</p>
<p>Next, for that object, we ask which property we want to preserve. This is called an <code>Invariant</code>.</p>
<p>What do we want to preserve? Dependency direction? A boundary? An abstraction? Substitutability? Runtime protection? Consistency of state transitions?</p>
<p>The phrase "good design" mixes together many such invariants. AAT separates them so that we can inspect them one by one.</p>
<p>If a property we wanted to preserve fails, we need evidence explaining that failure. This is called an <code>Obstruction</code>.</p>
<p>An obstruction is not just an error. It is structural evidence, a witness, showing why the property does not hold. Hidden dependencies, boundary crossings, abstraction leaks, mismatched operation orders that ought to commute, and runtime exposure can all be read as obstructions.</p>
<p>Finally, instead of collapsing those observations into a single score, we read them across multiple axes. This is the role of <code>ArchitectureSignature</code>.</p>
<p>In short, the AAT way of reading looks like this:</p>
<ul>
<li><p>Which object is this change operating on?</p>
</li>
<li><p>Which invariant do we want to preserve?</p>
</li>
<li><p>Which obstruction has appeared?</p>
</li>
<li><p>On which signature axis does it appear?</p>
</li>
<li><p>Within what boundary can we make that claim?</p>
</li>
</ul>
<p>AAT does not reduce "architectural quality" to a single number. It decomposes design judgment into object, preserved property, violation, observation axis, and boundary.</p>
<h2>What Does It Mean to See Architecture Algebraically?</h2>
<p>Why call this algebraic?</p>
<p>In AAT, we do not only look at architecture as a static diagram. We treat it as an object that can be operated on. There is an architecture object, and there are operations such as split, replace, abstract, protect, migrate, and repair. We ask what those operations preserve, what they fail to preserve, and how they compose with other operations.</p>
<p>The object of interest has this shape:</p>
<pre><code class="language-text">object
  + operation
  + preservation
  + obstruction
  + composition
</code></pre>
<p>AAT is not merely evaluation. There is an object, there are operations, there is structure that is preserved, there are obstructions where preservation fails, and there are sequences of operations. To study that structure, AAT treats architecture algebraically.</p>
<p>For example, one change may preserve dependency direction. Another change may preserve an abstraction boundary.</p>
<ul>
<li><p>If we apply those two changes in sequence, does their preservation compose?</p>
</li>
<li><p>Does an obstruction appear in the middle?</p>
</li>
<li><p>If two change paths appear to reach the same result, do they have the same signature trajectory?</p>
</li>
</ul>
<p>This is the kind of structure AAT wants to study.</p>
<h2>What Do Design Principles Preserve?</h2>
<p>From this point of view, familiar design principles in software engineering look a little different.</p>
<p>For example, SOLID is not an all-purpose design principle in AAT. It is better read mainly as a family of principles for preserving local contracts.</p>
<ul>
<li><p>SRP tries to preserve responsibility boundaries.</p>
</li>
<li><p>OCP asks for extension without breaking existing contracts.</p>
</li>
<li><p>LSP asks that an abstraction be observable in the same way regardless of which concrete implementation is substituted.</p>
</li>
<li><p>ISP tries to separate unnecessary dependencies from interfaces.</p>
</li>
<li><p>DIP tries to map dependencies on concrete details into dependencies on abstractions.</p>
</li>
</ul>
<p>What SOLID mainly deals with are invariants such as local contracts, abstractions, substitutability, and interface separation.</p>
<p>Layered Architecture deals with a different layer. What Layered Architecture tries to preserve is not so much the responsibility of each individual class, but the dependency direction of the whole system.</p>
<ul>
<li><p>Is there a ranking between upper and lower layers, and do dependencies follow that direction?</p>
</li>
<li><p>Are there dependencies that skip across layers?</p>
</li>
<li><p>Are cycles being introduced?</p>
</li>
<li><p>Is the system kept in a decomposable form?</p>
</li>
</ul>
<p>In AAT terms, SOLID mainly handles invariants in the local contract layer, while Layered Architecture handles invariants in the global structure layer.</p>
<pre><code class="language-text">SOLID
  -&gt; local contract / abstraction / substitutability

Layered Architecture
  -&gt; dependency direction / ranking / acyclicity / decomposability
</code></pre>
<p>The advantage of this classification is that we do not have to treat design principles as competing single answers.</p>
<p>A system can follow SOLID and still fail to decompose cleanly as a whole. Conversely, Layered Architecture can be preserved while individual abstractions fail to be substitutable.</p>
<p>If the invariants they preserve are different, then their failure modes are different, and the signature axes we should observe are different as well.</p>
<ul>
<li><p>For Clean Architecture, we look at boundary preservation, inward dependencies, and abstraction consistency.</p>
</li>
<li><p>For Event Sourcing, we look at replay, projection, and the relation between history and current state.</p>
</li>
<li><p>For Circuit Breaker, we look at runtime protection and failure locality.</p>
</li>
</ul>
<p>AAT does not rank design principles by asking which one is correct. It classifies which invariant family each principle carries, which obstructions it prevents, and which signature axes it appears on.</p>
<h2>The Architecture Zero-Curvature Theorem: Connecting Good Design to Measurement</h2>
<p>AAT takes this idea one step further and expresses it using the vocabulary of curvature.</p>
<p>Here, curvature means the obstruction that remains relative to a selected invariant. If there is a structure we want to preserve, and a witness that violates that structure remains, then there is curvature.</p>
<p>Intuitively, we can read it this way:</p>
<pre><code class="language-text">There is curvature
  = somewhere, the structure we wanted to preserve is violated

Curvature is zero
  = within the selected scope, no required obstruction witness remains
</code></pre>
<p>Up to this point, this is almost definitional. The important part comes next.</p>
<p>AAT is not trying to restate "good design is design without violations." Here, a <em>law</em> should be read as an explicit rule describing a property we want to preserve. The goal is to connect lawfulness with respect to selected laws to finitely observable obstruction witnesses and to the required axes of an <code>ArchitectureSignature</code> being zero.</p>
<pre><code class="language-text">lawfulness for the selected laws
  &lt;-&gt; no required obstruction witness
  &lt;-&gt; required signature axes are zero
</code></pre>
<p>AAT calls this connection the Architecture Zero-Curvature Theorem.</p>
<p>The point is that three layers become connected.</p>
<pre><code class="language-text">semantics:
  lawful under the selected law universe

witness:
  no required obstruction is finitely detected

measurement:
  required signature axes are observed as zero
</code></pre>
<p>In ordinary design reviews, phrases such as "the boundary is preserved," "responsibilities are separated," or "this is easy to extend" are often used ambiguously. The zero-curvature theorem offers a bridge: relative to which law, which witness, which observation, and which signature are we making that judgment?</p>
<p>Good design in AAT is not merely design that looks elegant. It is design where, within the selected laws, required obstruction witnesses are absent, and this can also be observed as zero on the signature axes.</p>
<p>The value of the theorem is not the slogan "if there are no violations, it is good." Its value is that it lets us move a design judgment between semantics, witnesses, and measurable signature axes.</p>
<h2>SFT (Software Field Theory): Making Software Evolution Computable</h2>
<p>If AAT asks, "What did this change preserve, and what did it fail to preserve?", SFT goes one step further and asks, "What kinds of changes does this change make more natural next?" On top of the local algebra built by AAT, SFT builds a framework for reasoning about software evolution.</p>
<p>AAT looks at the local structure of one change. It asks which object the change operates on, what it preserved, where obstructions remain, what can be said on which observation axes, and where those claims stop.</p>
<p>SFT looks at the field in which that change is placed. It asks how the change guides the next changes, which paths it makes cheaper, which paths it makes harder to see, which feedback remains as memory, and which futures become reachable.</p>
<p>In SFT, this whole context is called a <code>field</code>.</p>
<p>A <code>field</code> is not only the codebase. It includes requirements, design documents, PRDs, issues, review rules, CI, type checkers, runtime feedback, AI agent policies, and everything else that acts on the codebase. It determines which changes look natural, which changes become difficult, what becomes observable, and which feedback remains for the next judgment.</p>
<pre><code class="language-text">field
  = codebase
  + artifacts
  + practices
  + agents
  + governance
  + feedback
</code></pre>
<p>SFT is not a theory of the codebase alone. It is a theory of the whole development organization that writes requirements, designs systems, creates issues, opens PRs, reviews them, runs CI, and receives operational feedback.</p>
<p>Then what is a <code>force</code>?</p>
<p>In this article, I use force for the bundle of candidate updates that artifacts such as PRDs, specs, issues, AI proposals, and incident reports create in the field. A single PRD does not determine exactly one PR. But that PRD changes which issue decomposition feels natural, which PRs are likely to be created, and which architecture regions are likely to be touched.</p>
<pre><code class="language-text">force
  = candidate updates that an artifact gives to the field
  = changes in operation support / observation boundary / selection policy
</code></pre>
<p>Here, operation support means which operations are possible, natural, and low-cost. Observation boundary means what is visible and what is not. Selection policy means which choices are likely to pass through the process.</p>
<p>The field is the state. Force is artifact-mediated change. Future is the range of paths reachable from that field.</p>
<p>Even if two codebases have the same module graph, their next natural changes can differ if their fields differ. Past incidents, old workarounds, implicit ownership boundaries, and local patterns that an AI agent can easily mimic all change future operation support and selection policy.</p>
<h2>What Does "Computation" Mean in SFT?</h2>
<p>Computable here does not mean that we can predict the future of software as one exact outcome. It means that, under an explicit field model, operation support, observation boundary, and horizon, we can treat the range of reachable futures as a bounded problem.</p>
<p>For example, when an artifact enters a field, SFT views questions like the following as computational problems.</p>
<pre><code class="language-text">input:
  current field
  + artifact
  + operation support
  + observation axes
  + horizon

output:
  reachable path classes
  + affected architecture regions
  + changed signature axes
  + obstruction witness candidates
  + missing invariants / boundaries
  + review / CI recommendations
</code></pre>
<p>On the theoretical side, this set of reachable paths is called a <code>ForecastCone</code>. On the practical side, the readable report summarizing it is called a <code>ConsequenceEnvelope</code>.</p>
<p>The important point is that SFT does not claim, "this PRD will necessarily produce this PR." What SFT wants to see is which paths become closer in this field, which paths become farther away, and which obstructions become visible.</p>
<h2>PRDs Shape Future PRs</h2>
<p>The intuition of SFT is easier to grasp if we look at the flow from PRD to PR.</p>
<p>A PRD is not merely a requirements document. It is an artifact that partially shapes the form of future pull requests. Even for the same request, "add this feature," the PRs that become likely differ depending on whether the PRD states boundaries, responsibilities, and properties that should be observed. And in many cases, PRDs are written by people who do not directly write code, such as product managers, designers, and domain experts. Non-engineers also apply force to the codebase through artifacts.</p>
<pre><code class="language-text">PRD
  -&gt; possible issue decomposition
  -&gt; possible PR shapes
  -&gt; possible architecture changes
  -&gt; possible signature changes
</code></pre>
<p>In SFT, we ask what force this PRD applies to the codebase and what kind of <code>ForecastCone</code> it opens. But that cone does not represent one predetermined future. It is closer to the cone of uncertainty in a weather forecast: it represents the range of futures that may become reachable.</p>
<ul>
<li><p>What kinds of PRs are likely to emerge from this PRD?</p>
</li>
<li><p>Which architecture regions are those PRs likely to affect?</p>
</li>
<li><p>Which invariants might they preserve, and which obstructions might they create?</p>
</li>
<li><p>Within the <code>ForecastCone</code>, which future paths become closer and which become farther away?</p>
</li>
</ul>
<p>For this reason, SFT asks questions in the following form:</p>
<ul>
<li><p>Which <code>ForecastCone</code> opens in this field?</p>
</li>
<li><p>Which paths become natural, and which paths move farther away?</p>
</li>
<li><p>What is observable, and what remains unobserved?</p>
</li>
</ul>
<p>In this sense, SFT is a theory that tries to treat software evolution as a computable object.</p>
<h2>Conway's Law: Systems Reflect Organizational Communication Structures</h2>
<p>Conway's Law can be read naturally in the vocabulary of SFT. As is well known, Conway's Law is usually described as the empirical rule that the design of a system reflects the communication structure of the organization that built it.</p>
<p>In SFT, we read this not merely as a metaphor, but as a phenomenon where an organization field shapes the architecture future.</p>
<p>The organizational structure does not directly command the codebase to have a particular architecture. But team boundaries, ownership, review routes, approval flows, on-call boundaries, and issue decomposition all change which modifications look natural, which PRs are low-cost, and which changes are likely to pass review.</p>
<pre><code class="language-text">organization structure
  -&gt; communication paths
  -&gt; ownership boundaries
  -&gt; issue decomposition
  -&gt; PR shape
  -&gt; operation support
  -&gt; architecture future
</code></pre>
<p>Organizational structure changes operation support, and operation support changes day-to-day design changes. Through repetition, that pattern settles into the codebase as architecture.</p>
<p>For example, if an organization is split into Frontend Team, Backend Team, Data Team, and Infra Team, issues and PRs are likely to follow those boundaries. As a result, the architecture is also likely to split along those boundaries. On the other hand, if the organization is structured around product capabilities such as Search, Checkout, and Billing, changes that preserve those capability boundaries become more natural.</p>
<p>In SFT terms, Conway's Law can be read as follows:</p>
<pre><code class="language-text">organization field
  -&gt; recurrent PR shape
  -&gt; recurrent architecture operation
  -&gt; architecture structure
</code></pre>
<p>If we want a desirable architecture to become a natural future, we need to design the organization field so that changes preserving that architecture are low-cost and repeatable.</p>
<p>Conway's Law is not a story about system structure accidentally resembling organization structure. It is a story about organizations shaping the ease of day-to-day changes, and the repetition of those changes settling into architecture.</p>
<h2>ArchSig: A Lens for Observation</h2>
<p>To connect AAT/SFT to real development, we need an observation layer. ArchSig is the concept for that layer.</p>
<ul>
<li><p>AAT turns architecture into a local algebra.</p>
</li>
<li><p>ArchSig makes architecture observable.</p>
</li>
<li><p>SFT makes software evolution computable.</p>
</li>
</ul>
<p>ArchSig is a lens for reading artifacts such as codebases, PRs, issues, reviews, and incident traces. It asks which signature axes changed, which obstructions appeared, and what becomes input to the next field update.</p>
<p>I plan to discuss ArchSig in a separate article. For now, I only want to emphasize that AAT/SFT is meant to connect to observation and tooling.</p>
<h2>Attractor Engineering: Designing Fields Where Good Changes Become Natural</h2>
<p>One especially important idea in SFT is attractor engineering.</p>
<p>Here, an attractor is a direction of change that becomes repeatedly likely within a field. Good design decisions, good abstractions, good tests, good review rules, and good PRDs make it easier for the next good change to follow. Conversely, easy shortcuts, ambiguous responsibilities, broken boundaries, and invisible runtime coupling make the next easy shortcut more likely.</p>
<p>A codebase has a bias in how it is likely to be changed next. SFT treats that bias as a property of the field.</p>
<p>Attractor engineering means designing that bias.</p>
<p>Arrange the field so that good changes are:</p>
<ul>
<li><p>easy to find,</p>
</li>
<li><p>easy to write,</p>
</li>
<li><p>easy to review,</p>
</li>
<li><p>protected by CI,</p>
</li>
<li><p>and updated by operational feedback.</p>
</li>
</ul>
<p>This is somewhat different from the idea of simply placing strong restrictions on AI agents. Of course, prohibitions and guardrails are necessary. But by themselves, they only keep piling rules on top of an undesirable field.</p>
<p>The goal of attractor engineering is to build a field where good paths are naturally selected, and bad shortcuts are expensive, observable, and detectable in review.</p>
<p>This becomes even more important in the age of AI coding agents. AI agents tend to choose the path that looks most natural from the existing codebase and surrounding artifacts. In this sense, a codebase is not only an implementation; it is also a prompt for AI agents. If the field is undesirable, AI agents can amplify bad local patterns quickly. If the field is well designed, AI agents can also amplify good structure quickly.</p>
<p>In SFT terms, attractor engineering means designing future operation support.</p>
<ul>
<li><p>Which changes look natural?</p>
</li>
<li><p>Which changes become low-cost?</p>
</li>
<li><p>Which violations become observable?</p>
</li>
<li><p>Which feedback remains in the next field?</p>
</li>
</ul>
<p>This question is not merely quality control. It is the design of the direction of software evolution.</p>
<h2>What AAT/SFT Is Trying to Do</h2>
<p>AAT/SFT is trying to do three main things.</p>
<p>The first is to change the question.</p>
<pre><code class="language-text">Is this design good?
</code></pre>
<p>Instead of trying to answer that question as-is, we decompose it:</p>
<ul>
<li><p>What object are we talking about?</p>
</li>
<li><p>What do we want to preserve?</p>
</li>
<li><p>Which invariant failed?</p>
</li>
<li><p>What are we failing to observe?</p>
</li>
<li><p>What kinds of changes does this change make more natural next?</p>
</li>
</ul>
<p>This is not meant to make practical design judgment lighter. It is meant to make implicit judgment more tractable.</p>
<p>The second is to formalize the core of the theory in the Lean theorem prover.</p>
<p>We do not need to formalize all of AAT/SFT at once. The first step is to make the foundations of AAT, such as local algebra, invariants, obstructions, signatures, and the zero-curvature theorem, verifiable in Lean.</p>
<pre><code class="language-text">architecture object
  + operation
  + invariant
  + obstruction
  + signature
  + theorem boundary
</code></pre>
<p>Once this core is formalized, we can mechanically check what follows from which assumptions, and where the consequences stop.</p>
<p>The third is to connect the theory to practical tools and methods.</p>
<p>AAT reads design judgment in terms of invariants and obstructions. ArchSig makes those observable from codebases, PRs, issues, reviews, and incident traces. SFT uses those observations to reason about which <code>ForecastCone</code> opens and which paths become natural.</p>
<pre><code class="language-text">AAT
  -&gt; theoretical core

Lean
  -&gt; formalization of the core

ArchSig / tooling
  -&gt; observation of real artifacts

SFT
  -&gt; computation of software evolution
</code></pre>
<p>The eventual goal is to have tools for observing, questioning, and improving requirements, design, issues, PRs, review, CI, operational feedback, and the behavior of AI agents on top of the same theory.</p>
<h2>Closing</h2>
<p>Lehman saw that software keeps changing. Software connected to the real world continues to drift from its environment as long as it is used. It is changed to close that gap, and through being changed, it gains complexity and creates new feedback loops.</p>
<p>AAT/SFT sits on the same line of concern.</p>
<p>AAT asks about the local structure of that change. What object are we looking at, what do we preserve, which invariant failed, and on which axes can we observe it?</p>
<p>SFT asks how that change shapes the next change. It asks which futures are pulled closer, and which futures are pushed farther away, by requirements, design, issues, PRs, review, CI, operational feedback, and AI agents.</p>
<p>A good theory does not necessarily give all the answers immediately. But it gives us better questions, and it tells us how far those questions can be answered.</p>
<p>As Lehman showed, software keeps changing. If so, we should not merely accept that change. We should make it something we can ask about.</p>
<p>AAT/SFT is an attempt to define questions for software that keeps changing.</p>
<hr />
<h2>Further Reading</h2>
<p>If you want to read more about the mathematical definitions of AAT/SFT, theorem boundaries, and the connection to SFT, see the following primary documents:</p>
<ul>
<li><p><a href="https://github.com/iroha1203/AlgebraicArchitectureTheoryV2/blob/main/docs/aat/mathematical_theory.md">Algebraic Architecture Theory</a></p>
</li>
<li><p><a href="https://github.com/iroha1203/AlgebraicArchitectureTheoryV2/blob/main/docs/sft/software_field_theory.md">Software Field Theory</a></p>
</li>
<li><p><a href="https://github.com/iroha1203/AlgebraicArchitectureTheoryV2/blob/main/docs/sft/aat_interface.md">AAT / SFT Interface</a></p>
</li>
</ul>
<p>For a practical example of attractor engineering, and as an example of a multi-agent system for automatically maintaining a codebase, you may also want to read the article on Gotanda Style:</p>
<ul>
<li><a href="https://iroha1203.hashnode.dev/ai-agents-don-t-need-meetings-gotanda-style-for-stigmergic-software-maintenance">AI Agents Don't Need Meetings: Gotanda Style for Stigmergic Software Maintenance</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[AI Agents Don't Need Meetings: Gotanda Style for Stigmergic Software Maintenance]]></title><description><![CDATA[Most multi-agent systems make agents talk to each other.
We tried something different: our agents do not talk to each other at all.
They leave traces in a shared environment. Other agents read those t]]></description><link>https://blog.iroha1203.dev/ai-agents-don-t-need-meetings-gotanda-style-for-stigmergic-software-maintenance</link><guid isPermaLink="true">https://blog.iroha1203.dev/ai-agents-don-t-need-meetings-gotanda-style-for-stigmergic-software-maintenance</guid><category><![CDATA[AI]]></category><category><![CDATA[ai agents]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[automation]]></category><category><![CDATA[multi-agent]]></category><dc:creator><![CDATA[Hiroyuki Nakahata]]></dc:creator><pubDate>Mon, 11 May 2026 12:51:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69ff29ebf239332df4a93c3a/3fd46f71-1628-493b-8c5b-d428fa1f5fc1.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most multi-agent systems make agents talk to each other.</p>
<p>We tried something different: our agents do not talk to each other at all.</p>
<p>They leave traces in a shared environment. Other agents read those traces later, combine them with new evidence, turn the right ones into GitHub issues, and sometimes produce pull requests.</p>
<p>This pattern is called <strong>stigmergy</strong>: coordination through changes left in the environment rather than direct communication between individuals. In this article, I will call our version of the pattern <strong>Gotanda Style</strong>.</p>
<p>This is not just a thought experiment. We already use this workflow to maintain a Python repository with roughly 200,000 lines of code. Sentry alerts deposit "pheromones." Aggregated signals become issues. Some of those issues are small and well-scoped enough for an implementation agent to turn into pull requests.</p>
<p>The result is a multi-agent maintenance loop that is asynchronous, leaderless, token-efficient, and built for real software operations rather than demo-friendly agent conversations.</p>
<h2>TL;DR</h2>
<ul>
<li><p>Faster coding agents increase maintenance pressure: production errors, performance regressions, test gaps, and architectural drift all grow with change velocity.</p>
</li>
<li><p>Gotanda Style coordinates agents through a shared pheromone field instead of direct agent-to-agent conversation.</p>
</li>
<li><p>Observer agents deposit structured positive and negative signals; an integrator turns only the right clusters of evidence into issues.</p>
</li>
<li><p>This lets maintenance agents run asynchronously, spend fewer tokens, avoid supervisor bottlenecks, and route only safe, well-scoped work to implementation agents.</p>
</li>
</ul>
<h2>Why maintenance matters more as coding agents get better</h2>
<p>AI coding agents are making it much faster to write code.</p>
<p>That is a real shift. But when code creation gets faster, the maintenance burden grows too. More code reaches production in less time. More changes need monitoring, debugging, refactoring, testing, and design review.</p>
<p>It is like doubling the speed of a car. Higher speed is useful, but only if the tires, brakes, suspension, and safety systems can handle it. Otherwise, speed just turns small failures into bigger ones.</p>
<p>In AI-assisted development, speeding up implementation is not enough. The maintenance system has to scale with the new velocity.</p>
<p>Software work is not just "writing code." Over the long run, a large share of the cost comes from work like this:</p>
<ul>
<li><p>Investigating production errors</p>
</li>
<li><p>Detecting performance regressions</p>
</li>
<li><p>Filling test gaps</p>
</li>
<li><p>Finding architectural drift</p>
</li>
<li><p>Repairing broken boundaries between modules</p>
</li>
<li><p>Converting small improvement opportunities into reviewable pull requests</p>
</li>
</ul>
<p>The faster AI helps us produce code, the more important these loops become.</p>
<p>If we want AI-driven development to scale, we need more than automated code generation. We need agentic maintenance.</p>
<p>Gotanda Style came from that problem. The goal is not to hand all product or architecture decisions to AI. The goal is to use multiple agents to continuously support the parts of software maintenance that are repetitive, observable, and evidence-driven.</p>
<h2>Why conversational multi-agent systems are hard to scale</h2>
<p>When people hear "multi-agent system," they often imagine a group of specialist agents solving a problem by talking to each other.</p>
<p>A typical setup looks like this:</p>
<ul>
<li><p>A planner agent decomposes the task</p>
</li>
<li><p>A research agent investigates context</p>
</li>
<li><p>A coding agent implements the change</p>
</li>
<li><p>A reviewer agent reviews the result</p>
</li>
<li><p>A supervisor agent decides what happens next</p>
</li>
</ul>
<p>This can work well for small tasks. Many current agent frameworks are built around patterns like supervisors, handoffs, routers, and subagents.</p>
<p>But software maintenance is different. It is continuous, asynchronous, broad in scope, and tied to production evidence. For that kind of work, conversation-centered coordination has several problems:</p>
<ol>
<li><p>As the number of agents grows, the communication graph gets harder to manage.</p>
</li>
<li><p>Agents need to read each other's context, which increases token usage.</p>
</li>
<li><p>The supervisor becomes an information bottleneck and a potential single point of failure.</p>
</li>
<li><p>In a large codebase, having every agent read the same context is wasteful.</p>
</li>
<li><p>Temporary opinions and noisy reasoning can stay in the chat history and bias later decisions.</p>
</li>
</ol>
<p>Human organizations have the same failure mode. A team that keeps everyone in every meeting slows down as it grows.</p>
<p>The same thing happens with LLM agents. After a certain point, coordination itself becomes the cost.</p>
<h2>Gotanda Style: coordinate through the environment, not through chat</h2>
<p>The core rule of Gotanda Style is simple:</p>
<blockquote>
<p>Agents do not talk to each other. They leave traces in a shared environment.</p>
</blockquote>
<p>Each agent observes only its own slice of the system. When it finds a signal, it writes that signal into a shared environment.</p>
<p>We call that shared environment the <strong>pheromone field</strong>.</p>
<p>For example:</p>
<ul>
<li><p><strong>Sentry worker</strong>: observes runtime errors</p>
</li>
<li><p><strong>Datadog worker</strong>: observes slow requests, slow SQL, 5xx spikes, and cost spikes</p>
</li>
<li><p><strong>Quality worker</strong>: looks for layering violations, missing exception handling, test gaps, and API contract drift</p>
</li>
<li><p><strong>Refactor worker</strong>: reads the pheromone field, combines related signals, and creates issues</p>
</li>
<li><p><strong>Code worker</strong>: picks up Gotanda-labeled issues and opens pull requests</p>
</li>
</ul>
<p>The important part is that observer workers do not directly create a flood of GitHub issues.</p>
<p>The Sentry worker leaves a trace that says, in effect, "this file is involved in a production error." The Datadog worker leaves a trace that says, "this endpoint is slow." The Quality worker leaves a trace that says, "this function may have an error-handling problem."</p>
<p>They do not negotiate with each other in chat.</p>
<p>Later, the Refactor worker reads the accumulated pheromone field and decides which clusters of evidence are worth turning into issues.</p>
<p>The workflow has three stages:</p>
<ol>
<li><p><strong>Observer</strong>: observes the outside world or the codebase and deposits pheromones</p>
</li>
<li><p><strong>Integrator</strong>: reads the pheromone field, merges related signals, and creates issues</p>
</li>
<li><p><strong>Implementer</strong>: turns safe, well-scoped issues into pull requests</p>
</li>
</ol>
<pre><code class="language-text">Sentry worker   ----+
Datadog worker  ----+--&gt;  Pheromone field  --&gt;  Refactor worker  --&gt;  GitHub Issue  --&gt;  Code worker  --&gt;  Pull Request
Quality worker  ----+
</code></pre>
<h2>What is a pheromone?</h2>
<p>A pheromone is a structured signal that an agent leaves in the shared environment.</p>
<p>A minimal model looks like this:</p>
<pre><code class="language-text">(scope, location, worker, strength, half_life, metadata)
</code></pre>
<p>Each field has a specific role:</p>
<ul>
<li><p><code>scope</code>: the granularity of the signal, such as <code>file</code>, <code>function</code>, <code>endpoint</code>, or <code>sql</code></p>
</li>
<li><p><code>location</code>: the actual target, such as a file path, function name, API route, or SQL fingerprint</p>
</li>
<li><p><code>worker</code>: the agent that deposited the signal</p>
</li>
<li><p><code>strength</code>: how strong the signal is</p>
</li>
<li><p><code>half_life</code>: how quickly the signal decays</p>
</li>
<li><p><code>metadata</code>: supporting details such as error category, environment, evidence, or classification</p>
</li>
</ul>
<p>If the Sentry worker finds a production error, it might deposit a pheromone like this:</p>
<pre><code class="language-json">{
  "scope": "file",
  "location": "app/services/invoices.py",
  "worker": "sentry-worker",
  "strength": 2.0,
  "half_life_days": 14,
  "metadata": {
    "category": "runtime_error",
    "environment": "production",
    "error_type": "IntegrityError"
  }
}
</code></pre>
<p>If the Quality worker finds a test gap in the same file, it might deposit a separate pheromone:</p>
<pre><code class="language-json">{
  "scope": "file",
  "location": "app/services/invoices.py",
  "worker": "quality-worker",
  "strength": 1.0,
  "half_life_days": 21,
  "metadata": {
    "category": "test_gap",
    "severity": "medium"
  }
}
</code></pre>
<p>The Refactor worker does not make decisions from a single deposit in isolation. It reads the aggregated field.</p>
<p>When multiple workers deposit signals around the same location, that location becomes a hotspot worth inspecting.</p>
<h2>Positive and negative pheromones</h2>
<p>In Gotanda Style, pheromones are not always positive.</p>
<p>A positive pheromone is an attraction signal: "look here."</p>
<p>A negative pheromone is an inhibition signal: "we looked at this, and for now we should not pursue it."</p>
<p>For example, if the Refactor worker investigates a candidate and decides that it is an accepted design exception, it can deposit a negative pheromone:</p>
<pre><code class="language-json">{
  "scope": "fingerprint",
  "location": "layering_violation:abc123",
  "worker": "refactor-worker",
  "strength": -1.5,
  "half_life_days": 60,
  "metadata": {
    "reason": "accepted design exception"
  }
}
</code></pre>
<p>This prevents the same candidate from becoming a new issue every time a worker sees it.</p>
<p>But the negative pheromone is not permanent. It decays over time. If Sentry or Datadog later deposits a strong signal in the same area, the candidate can resurface.</p>
<p>That property matters in maintenance work. "Won't fix right now" is not the same thing as "ignore forever."</p>
<h2>Why this scales</h2>
<h3>1. More agents do not create a communication explosion</h3>
<p>In a conversational design, every new agent raises a coordination question: who needs to talk to whom, when, and with how much context?</p>
<p>In Gotanda Style, agents do not need to know about each other. They only need to write signals into the shared environment using a known schema.</p>
<p>Adding a new worker is mostly a contract question: what kind of pheromone does it deposit?</p>
<p>That makes the system plugin-like. If you want a Security worker, it deposits security signals. If you want a Performance worker, it deposits performance signals. The Refactor worker can read both as part of the same field.</p>
<h3>2. Large codebases can be explored more efficiently</h3>
<p>In a large codebase, reading every file on every run is not realistic.</p>
<p>The real question is how to spend a limited exploration budget.</p>
<p>With a pheromone field, exploration is not purely random, and it is not limited to "recently changed files" either. A worker can prioritize:</p>
<ul>
<li><p>Recently changed files</p>
</li>
<li><p>A random sample of files</p>
</li>
<li><p>Hotspots from Sentry or Datadog</p>
</li>
<li><p>Areas with strong negative pheromones, at a lower priority</p>
</li>
<li><p>Locations where multiple workers have deposited signals</p>
</li>
</ul>
<p>The search budget adapts to observed evidence.</p>
<p>That is a good fit for continuous AI maintenance over a large codebase.</p>
<h3>3. It is token-efficient</h3>
<p>Because agents do not have long conversations with each other, they do not need to read each other's full reasoning traces or chat histories.</p>
<p>What gets shared is a small structured signal:</p>
<pre><code class="language-json">{
  "scope": "endpoint",
  "location": "GET /api/reports",
  "worker": "datadog-worker",
  "strength": 1.2,
  "metadata": {
    "category": "slow_request",
    "p95_ms": 1800
  }
}
</code></pre>
<p>That is far cheaper than thousands of tokens of conversation.</p>
<p>Only when the Integrator needs to make a decision does it dig into the code, logs, issues, and previous decisions.</p>
<h3>4. It runs asynchronously</h3>
<p>Workers do not need to run at the same time.</p>
<p>The Sentry worker can run every 10 minutes. The Datadog worker can run once a day. The Quality worker can run overnight. The Code worker can poll for labeled issues every few minutes.</p>
<p>Each worker observes the environment and deposits pheromones at its own pace.</p>
<p>That is useful in production. External systems like GitHub, CI, Sentry, and Datadog all have different rate limits, failure modes, and latency profiles. Independent workers localize failures instead of turning every dependency hiccup into a global coordination problem.</p>
<h3>5. Noise can be handled over time</h3>
<p>LLM agents are noisy. A weak signal from one worker should not always become an issue.</p>
<p>In Gotanda Style, pheromones decay.</p>
<p>A one-off weak signal fades away. Signals that recur, signals that come from multiple workers, and signals tied to production impact remain stronger.</p>
<p>This helps the system prioritize persistent problems over one-time noise.</p>
<h2>A simple sum is not enough</h2>
<p>There is an important catch.</p>
<p>If you simply add pheromones together, you can lose information.</p>
<p>Imagine a location has these two signals:</p>
<pre><code class="language-text">sentry-worker: +2.0
refactor-worker: -2.0
</code></pre>
<p>The simple sum is zero.</p>
<p>But this is not the same as a location where nothing is happening.</p>
<p>It means something closer to: "there is a production error here, but there is also a previous won't-fix decision."</p>
<p>If we treat both cases as zero, we miss an important conflict.</p>
<p>So Gotanda Style tracks positive mass, negative mass, total variation, and conflict separately:</p>
<ul>
<li><p><code>current_strength</code>: net strength</p>
</li>
<li><p><code>positive_strength</code>: total positive signal</p>
</li>
<li><p><code>negative_mass</code>: total negative signal</p>
</li>
<li><p><code>total_variation</code>: total signal without cancellation</p>
</li>
<li><p><code>conflict_ratio</code>: how strongly positive and negative signals disagree</p>
</li>
</ul>
<p>The practical rule is simple:</p>
<p><strong>Distinguish silence from conflict.</strong></p>
<p>This lets the Refactor worker make better decisions:</p>
<ul>
<li><p>Strong positive signal only: possibly safe to turn into an implementation issue</p>
</li>
<li><p>Strong negative signal only: do not pursue right now</p>
</li>
<li><p>Strong positive and negative conflict: likely needs human review</p>
</li>
<li><p>No signal: lower exploration priority</p>
</li>
</ul>
<h2>How issues are created in Gotanda Style</h2>
<p>Observer workers generally should not create issues directly.</p>
<p>They usually do not have enough context at observation time.</p>
<p>The Sentry worker knows about an error, but it may not know whether the fix is local, architectural, already accepted, or intentionally deferred.</p>
<p>The Datadog worker knows about a slow SQL query, but it may not know whether the query is unacceptable, part of a tolerated batch job, or tied to a product requirement.</p>
<p>The Quality worker may find something that looks like a layering violation, but it may be an intentional design exception.</p>
<p>So observer workers deposit pheromones. Issue creation belongs to the Integrator.</p>
<p>The Integrator reads multiple pheromones, the current code, existing issues, and previous won't-fix decisions. Then it classifies the candidate:</p>
<table>
<thead>
<tr>
<th>Class</th>
<th>Meaning</th>
<th>Destination</th>
</tr>
</thead>
<tbody><tr>
<td>A</td>
<td>No issue, or known accepted exception</td>
<td>Do not file</td>
</tr>
<tr>
<td>B1</td>
<td>Safe local fix</td>
<td>Code worker</td>
</tr>
<tr>
<td>B2</td>
<td>Design decision needed</td>
<td>Human</td>
</tr>
<tr>
<td>C</td>
<td>Cause unclear; investigation needed</td>
<td>Human</td>
</tr>
</tbody></table>
<p>Only B1 goes to the Code worker.</p>
<p>That boundary is intentional. Issues sent to an implementation agent should have a clear intent, a limited scope, and enough evidence that a reviewer can trace the pull request back to the original problem.</p>
<p>Large design decisions stay with humans. Once a human decides the direction, the local follow-up work can be split into smaller issues for the Code worker.</p>
<h2>What is already working</h2>
<p>Gotanda Style is not just a research sketch.</p>
<p>We are using this pattern on a Python repository with about 200,000 lines of code.</p>
<p>The current loop works like this:</p>
<ol>
<li><p>The Sentry worker detects a production alert.</p>
</li>
<li><p>It classifies the alert and deposits pheromones for cases that appear to need either a local fix or deeper remediation.</p>
</li>
<li><p>The Refactor worker reads the pheromone field and combines the alert with other observations and previous decisions.</p>
</li>
<li><p>It creates improvement issues at a level of detail that can be implemented automatically.</p>
</li>
<li><p>The Code worker reads the issue, creates a branch, makes the change, and opens a pull request.</p>
</li>
</ol>
<p>Not every alert becomes an automated fix. Anything that needs a design decision, has an unclear cause, or has a large blast radius is routed to a human.</p>
<p>But the closed loop from Sentry alert to pheromone deposit to issue to improvement PR is already running in practice.</p>
<p>That is the key point: Gotanda Style did not come from abstract multi-agent theory. It came from operating and maintaining a real large codebase.</p>
<h2>What is new here?</h2>
<p>Multi-agent systems are not new.</p>
<p>There are many existing patterns: supervisors, handoffs, routers, blackboards, shared memory, and more.</p>
<p>The interesting part of Gotanda Style is the combination:</p>
<ol>
<li><p>It is specialized for software maintenance.</p>
</li>
<li><p>It separates observation, integration, and implementation.</p>
</li>
<li><p>Agents do not talk to each other directly.</p>
</li>
<li><p>Agents deposit positive and negative pheromones into a shared environment.</p>
</li>
<li><p>Pheromones decay over time.</p>
</li>
<li><p>Hotspots and conflicts across observers drive issue creation.</p>
</li>
<li><p>Only safe, automatable issues are passed to the Code worker.</p>
</li>
</ol>
<p>This is not a general-purpose chatty multi-agent system.</p>
<p>It is an asynchronous, leaderless, token-efficient workflow pattern for continuously maintaining a large codebase.</p>
<h2>The hard parts</h2>
<p>This pattern has real challenges.</p>
<p>The biggest one is the quality of the pheromone field.</p>
<p>If the field fills with noise, the whole system follows that noise. If inhibition is too strong, the system misses real problems.</p>
<p>Several parts are especially tricky.</p>
<h3>Normalizing locations</h3>
<p>If workers refer to the same place using different location strings, signals will not aggregate.</p>
<p>For example, these may all refer to the same API:</p>
<pre><code class="language-text">GET /api/users/{id}
/api/users/:id
app/api/users.py:get_user
</code></pre>
<p>Deciding when these should collapse into one location is an important design problem.</p>
<h3>Calibrating strength</h3>
<p>A Sentry production error with strength <code>+2.0</code> should not carry the same meaning as a low-confidence Quality worker concern with strength <code>+0.5</code>.</p>
<p>The system needs ongoing calibration across worker reliability, category severity, environment, and production impact.</p>
<h3>Defining negative pheromone semantics</h3>
<p>Negative pheromones are useful, but they are also dangerous.</p>
<p>"Do not pursue right now" is different from "ignore forever."</p>
<p>Negative pheromones need reasons, fingerprints, half-lives, and resurfacing conditions.</p>
<h3>Auditability</h3>
<p>As automation increases, the system has to explain itself.</p>
<p>Operators need to trace why an issue was created, why a pull request was opened, which worker run contributed which signal, and which previous decisions were considered.</p>
<p>Without that audit trail, the workflow will not be trusted in production.</p>
<h2>What we want to improve next</h2>
<p>Gotanda Style is still evolving.</p>
<p>The next areas we care about most are:</p>
<ul>
<li><p>Reliability weights per worker</p>
</li>
<li><p>Category-specific weights</p>
</li>
<li><p>Location alias normalization</p>
</li>
<li><p>Audit logs tied to <code>run_id</code></p>
</li>
<li><p>Self-stop conditions</p>
</li>
<li><p>Canary operation</p>
</li>
<li><p>Human-in-the-loop boundaries</p>
</li>
<li><p>Reallocating exploration based on the pheromone field</p>
</li>
</ul>
<p>The last point is especially important. The field should not only be something agents read when making issues. It should also shape what agents inspect next.</p>
<p>For example, instead of letting the Quality worker explore completely at random, it can divide its budget like this:</p>
<ul>
<li><p>Recent files: 40%</p>
</li>
<li><p>Random files: 30%</p>
</li>
<li><p>Pheromone hotspots: 20%</p>
</li>
<li><p>Cooling follow-up: 10%</p>
</li>
</ul>
<p>This keeps some randomness while adapting to what the system has already observed.</p>
<h2>Gotanda Style as attractor engineering</h2>
<p>Another way to describe Gotanda Style is practical attractor engineering for a codebase.</p>
<p>By "attractor," I mean the structure or state that a codebase naturally drifts toward as changes accumulate.</p>
<p>In a codebase with clear boundaries, good types, good tests, and good examples, the next change is more likely to fit the same pattern. In a codebase with a giant <code>common</code> module, vague services, overly convenient helpers, and bad nearby examples, changes tend to drift in that direction.</p>
<p>AI coding agents amplify this dynamic.</p>
<p>AI does not write code in a vacuum. It reads existing code, neighboring files, names, tests, previous implementations, and docs. The whole codebase becomes part of the prompt.</p>
<p>If the codebase contains bad local grammar, AI will often reproduce it as the natural answer. When development speeds up, the drift toward bad attractors can speed up too.</p>
<p>Attractor engineering means shaping where future changes are likely to land.</p>
<p>Gotanda Style uses the pheromone field to observe signals like:</p>
<ul>
<li><p>Which files or endpoints accumulate production errors</p>
</li>
<li><p>Where performance regressions show up</p>
</li>
<li><p>Where tests are missing or boundaries are weakening</p>
</li>
<li><p>Which locations are repeatedly flagged by multiple workers</p>
</li>
<li><p>Which candidates were previously marked won't-fix</p>
</li>
<li><p>Where positive and negative signals are in conflict</p>
</li>
</ul>
<p>This is more than alert aggregation.</p>
<p>It is a way to observe where the codebase is drifting, identify areas that are becoming bad basins, and use issues and repair PRs to change the trajectory.</p>
<pre><code class="language-text">codebase field
  -&gt; AI / human PR force
  -&gt; pheromone observation
  -&gt; issue / repair PR
  -&gt; updated codebase field
</code></pre>
<p>In that sense, Gotanda Style is not a system for making AI write more code.</p>
<p>It is an operating model for directing AI's increased change velocity toward a codebase that remains maintainable and observable.</p>
<h2>A mathematical view</h2>
<p>Intuitively, the pheromone field is a set of weighted signals per <code>(scope, location)</code>.</p>
<p>More formally, each worker deposits a signed weight at <code>(worker, scope, location)</code>, and the system aggregates those weights by <code>(scope, location)</code>.</p>
<p>Positive weights attract attention. Negative weights inhibit attention.</p>
<p>Over time, each weight decays exponentially:</p>
<pre><code class="language-text">current = strength * 0.5 ^ (elapsed / half_life)
</code></pre>
<p>A simple sum can hide conflicts because positive and negative signals cancel each other out.</p>
<p>So the system keeps positive mass, negative mass, total variation, and conflict ratio:</p>
<pre><code class="language-text">net = positive - negative
total_variation = positive + negative
conflict_ratio = 1 - abs(net) / total_variation
</code></pre>
<p>These additional values let the system distinguish a quiet location from a contested one.</p>
<h2>Conclusion</h2>
<p>In LLM multi-agent systems, coordination is one of the core design problems.</p>
<p>Conversation-based coordination is easy to understand, but as the number of agents, the size of the codebase, and the duration of operations grow, communication and context-management costs become significant.</p>
<p>Gotanda Style avoids direct agent-to-agent conversation.</p>
<p>Instead, each agent deposits pheromones into a shared environment. Other agents read those pheromones, integrate them with evidence, create issues, and turn only the safe, automatable ones into pull requests.</p>
<p>The pattern has several advantages:</p>
<ul>
<li><p>It is easier to add more agents.</p>
</li>
<li><p>It supports efficient exploration of large codebases.</p>
</li>
<li><p>It reduces inter-agent communication and token usage.</p>
</li>
<li><p>It works asynchronously.</p>
</li>
<li><p>It handles noise through time decay.</p>
</li>
<li><p>It can detect hotspots and conflicts that are hard to see in direct conversation.</p>
</li>
</ul>
<p>We think this can become a useful design pattern for LLM-based software maintenance.</p>
<p>And for us, it is not a future idea. It is already running on a Python repository with about 200,000 lines of code, closing the loop from Sentry alert to improvement pull request.</p>
<p>If AI coding agents increase development velocity, maintenance has to become stronger at the same time. We need agents that observe the codebase, detect anomalies, combine improvement signals, create issues at safe granularity, and turn those issues into pull requests.</p>
<p>As more agents continuously observe, maintain, and improve codebases, coordination models that do not depend on constant conversation will matter more.</p>
<p>Gotanda Style is one experiment in that direction.</p>
]]></content:encoded></item><item><title><![CDATA[Attractor Engineering: Seeing Software Development as Field Dynamics]]></title><description><![CDATA[TL;DR

A codebase can be read as a field that attracts future changes, and a pull request can be read as a force applied to that field.

A good field makes good changes easier to make. A bad field rep]]></description><link>https://blog.iroha1203.dev/attractor-engineering-seeing-software-development-as-field-dynamics</link><guid isPermaLink="true">https://blog.iroha1203.dev/attractor-engineering-seeing-software-development-as-field-dynamics</guid><category><![CDATA[architecture]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[AI]]></category><category><![CDATA[programing]]></category><category><![CDATA[Computer Science]]></category><dc:creator><![CDATA[Hiroyuki Nakahata]]></dc:creator><pubDate>Sat, 09 May 2026 12:47:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/69ff29ebf239332df4a93c3a/0b97ec46-a91c-4220-8302-874616dcceec.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<blockquote>
<p><strong>TL;DR</strong></p>
<ul>
<li><p>A codebase can be read as a field that attracts future changes, and a pull request can be read as a force applied to that field.</p>
</li>
<li><p>A good field makes good changes easier to make. A bad field repeatedly makes bad shortcuts look natural. In an era where AI can produce PRs quickly, this attraction becomes stronger.</p>
</li>
<li><p>I call the practice of designing where future changes are pulled <strong>Attractor Engineering</strong>.</p>
</li>
<li><p>CI/CD, tests, reviews, and harnesses can be read as dissipative systems: they remove unwanted force and shape the trajectory.</p>
</li>
<li><p>ArchSig, or Architecture Signature, is a tool for observing that trajectory along multiple axes.</p>
</li>
</ul>
</blockquote>
<p>The first half of this article is written for practitioners: it explains the intuition in terms of codebases, PRs, review, CI, and AI-assisted development. The second half is more mathematical: it connects the same intuition to AAT, Architecture Signature, Lean formalization, and finite counterexamples.</p>
<h2>The First Discovery</h2>
<p>The starting point was a simple thought experiment.</p>
<p>What if we look at software architecture not only as a set of directories, design rules, or conventions, but as an <strong>algebraic structure</strong>?</p>
<p>From that point of view, everyday changes such as feature additions, refactorings, splits, migrations, repairs, protections, deletions, and integrations become operations acting on the structure we call architecture.</p>
<pre><code class="language-text">current codebase
  + feature addition
  + refactoring
  + review fix
  + migration
  + repair
  -&gt; next codebase
</code></pre>
<p>If we take one more step, we can ask: if these operations are not applied once, but repeated dozens or hundreds of times, can the whole development process be read as a kind of dynamics?</p>
<p>Each individual PR is small.</p>
<p>But after enough PRs, the codebase gradually moves in some direction.</p>
<p>When a good structure already exists, the next change tends to fit into a good place.</p>
<p>When a bad structure exists, a locally natural change tends to take the same bad shortcut again.</p>
<p>Can we treat "where changes tend to go" as something we design?</p>
<p>I decided to call this way of thinking <strong>Attractor Engineering</strong>.</p>
<h2>A Codebase Is a Field, and a PR Is a Force</h2>
<p>The central interpretation is this:</p>
<pre><code class="language-text">codebase = a field that attracts changes
PR       = a force applied to that field
ArchSig  = an observer for the movement
</code></pre>
<p>A codebase is not a neutral space that passively receives future changes.</p>
<p>Existing names, types, responsibility boundaries, tests, directory structure, previous implementation examples, and review culture all shape which next change feels natural.</p>
<p>A PR is a force applied to that field. Each one may be small, but repeated PRs create a trajectory of change.</p>
<pre><code class="language-text">current codebase
  -&gt; a PR is applied
  -&gt; an architectural change is observed
  -&gt; a trajectory of change emerges
  -&gt; this becomes the next codebase
</code></pre>
<p>The important point is that a PR changes the codebase, and the changed codebase then changes what the next PR is likely to look like.</p>
<h2>People and Systems Create the Field</h2>
<p>This field is not created only by engineers.</p>
<p>Product managers, product owners, engineers, reviewers, AI agents, CI, tests, design documents, coding standards, and existing examples all participate in it.</p>
<p>Everyone and everything involved in development affects which changes become likely next.</p>
<table>
<thead>
<tr>
<th>Participant / mechanism</th>
<th>Effect on the field</th>
</tr>
</thead>
<tbody><tr>
<td>Product manager</td>
<td>Decides which values and demands are repeatedly injected into the system.</td>
</tr>
<tr>
<td>Product owner</td>
<td>Shapes PRs through requirement granularity, priorities, and acceptance criteria.</td>
</tr>
<tr>
<td>Engineer / architect</td>
<td>Creates paths for change through boundaries, abstractions, standard patterns, and reference implementations.</td>
</tr>
<tr>
<td>Reviewer</td>
<td>Pushes back bad force and redirects it toward better directions.</td>
</tr>
<tr>
<td>CI / tests / types</td>
<td>Rejects, weakens, and narrows inappropriate force.</td>
</tr>
<tr>
<td>AI agent</td>
<td>Reads the existing field and quickly proposes changes.</td>
</tr>
</tbody></table>
<p>The way requirements are sliced, prioritized, scheduled, and accepted changes how later PRs are produced. Even people who do not write code apply indirect force to the field of the codebase.</p>
<p>This becomes especially important in AI-assisted development. Vague requirements can quickly become vague PRs. If boundaries and non-goals are clear, an AI system is more likely to produce useful changes within those boundaries.</p>
<h2>What Changes in AI-Assisted Development</h2>
<p>The essence of AI-assisted development is not simply that code can be written faster.</p>
<p>The more important change is that <strong>the distribution of selected change operations</strong> changes.</p>
<p>An AI system reads existing code, neighboring files, names, types, tests, previous implementation examples, READMEs, and design documents, and then generates the next proposed change.</p>
<p>In other words, the whole codebase becomes input context for the AI.</p>
<pre><code class="language-text">the codebase becomes input context for AI
  -&gt; AI proposes a change
  -&gt; the proposal becomes a PR
  -&gt; review / CI / merge process handles it
  -&gt; the codebase is updated
  -&gt; the next input context changes
</code></pre>
<p>If a good reference implementation is nearby, the AI is likely to imitate it.</p>
<p>If a bad shortcut already exists, the AI is also likely to treat it as a natural option.</p>
<p>In this sense, AI rapidly reproduces the local style already present in the field. That is why, in the AI era, what matters is not only the capability of an individual AI agent. The design of the field in which the AI participates matters just as much.</p>
<h2>What Is an Attractor?</h2>
<p>When a codebase contains a huge <code>common</code> module, an overly convenient helper, or an ambiguous service, changes tend to be pulled there.</p>
<p>Conversely, when good responsibility boundaries and clear implementation examples exist, the next change tends to follow them.</p>
<p>This destination toward which changes are pulled is what I call an attractor. When something moves repeatedly, it often tends to approach certain places or states.</p>
<p>The surrounding region from which things are likely to fall into that attractor is what I call a basin.</p>
<p>Technical debt can be read as a bad basin.</p>
<p>Once a codebase falls into it, locally natural changes keep adding to the same place, and the cost of refactoring out of it grows higher and higher.</p>
<p>The important point is that attractors can be good or bad.</p>
<p>A good attractor pulls in good changes.</p>
<p>A bad attractor makes bad shortcuts get selected again and again.</p>
<h2>What Is Attractor Engineering?</h2>
<p>Attractor Engineering is the idea that we should deliberately design these attractors.</p>
<p>Its target is not just the codebase.</p>
<p>It includes the whole development organization: product managers, product owners, engineers, reviewers, AI agents, CI/CD, tests, design documents, and coding standards.</p>
<p>The goal is not only to block bad changes from the outside. The goal is to create a field where good changes are naturally easier to select.</p>
<table>
<thead>
<tr>
<th>Part of Attractor Engineering</th>
<th>What it shapes</th>
<th>Examples</th>
</tr>
</thead>
<tbody><tr>
<td>Create the field</td>
<td>Which changes feel natural to propose.</td>
<td>Requirements, priorities, design boundaries, types, APIs, examples, templates.</td>
</tr>
<tr>
<td>Dissipate bad force</td>
<td>Which proposed changes are rejected, weakened, or redirected.</td>
<td>Harnesses, CI, tests, reviews, PR granularity.</td>
</tr>
<tr>
<td>Observe the trajectory</td>
<td>How architectural movement becomes visible over time.</td>
<td>ArchSig, architecture features, drift reports.</td>
</tr>
</tbody></table>
<p>Attractor Engineering is an integrated design theory for the era of AI-assisted development. It treats the entire development organization as part of the system.</p>
<h2>Harness Engineering as a Dissipative System</h2>
<p>We cannot simply take changes produced by AI and put them directly into the codebase.</p>
<p>Harnesses, CI, tests, type checking, static analysis, and review divide proposed changes into "accept", "fix and check again", and "do not merge".</p>
<p>In Attractor Engineering, this behavior can be read as dissipation.</p>
<p>Dissipation is the mechanism that removes unwanted components of the force entering the field.</p>
<p>If dissipation is too weak, the fast change force produced by AI enters the codebase directly. If it is too strong, nothing moves. A good harness weakens the force that increases debt while preserving the force that moves the product forward.</p>
<p>In this view, CI/CD is not merely a checklist. It is more like brakes, rails, signals, and safety equipment that convert fast PR generation into safe productivity.</p>
<p>What matters in AI-assisted development is not only making the engine stronger.</p>
<p>It is also preparing the field and the dissipative system that can receive a stronger engine.</p>
<h2>What ArchSig Observes</h2>
<p>To design the field, we need to observe what is happening.</p>
<p>That is the role of ArchSig.</p>
<p>ArchSig is short for Architecture Signature.</p>
<p>In my <a href="https://github.com/iroha1203/AlgebraicArchitectureTheoryV2">repository</a>, I use it to mean an observation framework for reading changes in a codebase or PR along multiple axes. Dependencies, boundaries, abstractions, runtime exposure, semantic drift, and test observability are not collapsed into a single score. They are treated as multiple features.</p>
<p>ArchSig is an observer for seeing which direction a PR moves the architecture.</p>
<p>For example, we may observe axes like these:</p>
<table>
<thead>
<tr>
<th>Axis</th>
<th>What we want to observe</th>
</tr>
</thead>
<tbody><tr>
<td>Static dependencies</td>
<td>Dependency direction and violations of forbidden dependencies.</td>
</tr>
<tr>
<td>Boundary rules</td>
<td>Connections that cross boundaries or bypass rules.</td>
</tr>
<tr>
<td>Abstraction leakage</td>
<td>Concrete dependencies that jump over abstractions.</td>
</tr>
<tr>
<td>Semantic drift</td>
<td>Whether responsibilities or meanings have shifted away from what was intended.</td>
</tr>
<tr>
<td>Test observability</td>
<td>Whether the change can be observed through tests.</td>
</tr>
<tr>
<td>Per-PR change</td>
<td>How each axis moves in a single PR.</td>
</tr>
</tbody></table>
<p>The important point is that we do not compress good and bad into one score.</p>
<p>What we want to know is which axes got worse, which axes improved, and what kind of force each change applies.</p>
<pre><code class="language-text">PR
  -&gt; observe change with ArchSig
  -&gt; observe the trajectory of change
  -&gt; see whether it is moving toward a good or bad region
</code></pre>
<p>ArchSig becomes an observer for the AI PR era.</p>
<p>Are AI-generated changes moving toward good attractors?</p>
<p>Are they falling into a pool of technical debt?</p>
<p>Is the harness dissipating enough bad force?</p>
<p>ArchSig gives us shared language for discussing those questions.</p>
<h2>PRs Become More Important, Not Less</h2>
<p>When AI reduces the cost of generating code, it may look as if PRs become less important.</p>
<p>From a dynamical systems viewpoint, the opposite is true.</p>
<p>A PR is not just a work unit.</p>
<p>A PR has the following roles:</p>
<ul>
<li><p>It cuts continuous change into observable units.</p>
</li>
<li><p>It lets us separate the directions in which a change acts.</p>
</li>
<li><p>It embeds the dissipative process of review, CI, and approval.</p>
</li>
<li><p>It creates a boundary for rollback and reversibility.</p>
</li>
<li><p>It creates a unit for decision-making and discussion.</p>
</li>
</ul>
<p>What AI lowers is mainly the cost of producing a PR.</p>
<p>But the value of PRs as units of observation, decomposition, dissipation, reversibility, and decision-making increases. In the AI era, PRs do not become unnecessary. They become more important as units for observing and controlling architectural movement.</p>
<h2>Future Development Organizations</h2>
<p>In future development organizations, the central problem will not be only how fast we can write code. It will be how to design a field that can safely receive that speed.</p>
<p>A fast train cannot run safely just because it has a powerful motor.</p>
<p>It needs rails, brakes, signals, safety equipment, and operations control.</p>
<p>Software development is similar. AI is a powerful motor, but by itself it can create semantic drift, responsibility drift, degradation of design properties we wanted to preserve, merge confusion, and flow toward technical debt.</p>
<p>What we need is a field with properties like these:</p>
<ul>
<li><p>Small and observable PRs.</p>
</li>
<li><p>Fast feedback.</p>
</li>
<li><p>Reliable CI.</p>
</li>
<li><p>A useful type system.</p>
</li>
<li><p>Architecture tests.</p>
</li>
<li><p>Carefully selected reference implementations.</p>
</li>
<li><p>Isolation of legacy code.</p>
</li>
<li><p>Clear demands, requirements, and design boundaries.</p>
</li>
<li><p>Human-designed boundaries for value and acceptance criteria.</p>
</li>
</ul>
<p>The safest AI coding environment is not the one with the strongest external harness. It is the one where good changes are natural, easy to imitate, observable, and less likely to fall into bad shortcuts.</p>
<h2>Summary So Far</h2>
<p>The success or failure of AI-assisted development is not determined only by how fast AI can write code.</p>
<p>The direction of the next PR changes depending on the field created by the codebase, requirements, design boundaries, reference implementations, review, CI/CD, and ArchSig.</p>
<p>A good field attracts good changes. A bad field repeatedly makes bad changes look natural. That is why architecture design in the AI era becomes the design of where future changes are attracted.</p>
<p>So far, this has been Attractor Engineering in practical engineering language. In the second half, I translate the same intuition into the language of AAT, Algebraic Architecture Theory, and dynamical systems.</p>
<h2>From Here, in Mathematical Language</h2>
<p>The rest of this article uses more mathematical formulation. If you only want the practical view first, it is fine to skip to the <a href="#conclusion">conclusion</a>.</p>
<p>Around AI development, there are many heuristics: "this prompt worked", "this workflow made us faster", and so on. These heuristics are valuable. But by themselves, they make it hard to separate why something worked, how far it generalizes, and under what conditions it breaks.</p>
<p>So I decompose the flow: a change is selected, becomes a PR, passes review / CI, is merged, and then the updated codebase changes the distribution of future changes. By separating state, operation, observation, invariant, obstruction witness, and proof obligation, we can turn heuristics into something easier to test.</p>
<h2>A Short Introduction to AAT</h2>
<p>The background theory for this discussion is AAT, or Algebraic Architecture Theory. Here I introduce only the minimal vocabulary needed for the rest of the article.</p>
<p>The Lean snippets below are excerpts from implemented APIs, adjusted for readability. Namespaces, imports, and some proof bodies are omitted.</p>
<p>AAT treats software development not merely as a sequence of code changes, but as a theory of architectural extension, decomposition, repair, and composition.</p>
<p>Its central proposition does not appear in Lean as one large equation. It appears as packages that combine operations and proof obligations.</p>
<pre><code class="language-lean">structure OperationProofObligation (State : Type u) (Witness : Type v) where
  kind : ArchitectureOperationKind
  obligation : ProofObligation State Witness
  precondition : Prop
  nonConclusion : Prop
</code></pre>
<p>Operations are first classified as an operation catalog on the Lean side.</p>
<pre><code class="language-lean">inductive ArchitectureOperationKind where
  | compose
  | refine
  | abstract
  | replace
  | split
  | merge
  | isolate
  | protect
  | migrate
  | reverse
  | contract
  | repair
  | synthesize
  deriving DecidableEq, Repr
</code></pre>
<p>The important point is that names such as <code>split</code> or <code>repair</code> prove nothing by themselves. An operation kind is a classification for theorem packages. Claims about preservation, improvement, or repair must be stated separately as proof obligations.</p>
<p>From this viewpoint, a design review is not only the question "is this design good or bad?"</p>
<pre><code class="language-text">- Is the existing structure embedded after extension?
- Can the new feature be split out from the existing structure?
- Do interactions pass through declared interfaces?
- Which invariants are preserved, and which invariants were broken?
- If a split is not possible, which obstruction witness blocks it?
</code></pre>
<p>The smallest object in AAT is <code>ArchitectureCore</code>.</p>
<pre><code class="language-lean">structure ArchitectureCore (C : Type u) (A : Type v)
    (StaticObs : Type w) (SemanticExpr : Type q)
    (SemanticObs : Type r) where
  flatness :
    ArchitectureFlatnessModel C A StaticObs SemanticExpr SemanticObs
  staticUniverse : ComponentUniverse flatness.static
  componentDecidableEq : DecidableEq C
  staticEdgeDecidable : DecidableRel flatness.static.edge
  runtimeEdgeDecidable : DecidableRel flatness.runtime.edge
  boundaryPolicyDecidable : DecidableRel flatness.boundaryAllowed
  abstractionPolicyDecidable : DecidableRel flatness.abstractionAllowed
  runtimeRole : C -&gt; C -&gt; RuntimeDependencyRole
  semanticRequiredDecidable :
    ∀ d : RequiredDiagram SemanticExpr,
      Decidable (flatness.requiredSemantic d)
</code></pre>
<p>Here it is important that <code>ArchitectureCore</code> is not the whole real-world codebase itself. It is a finite or bounded object extracted from code, specifications, reviews, and operational observations so that the theory can handle it.</p>
<p>Feature addition is read as an operation that extends an existing architecture into a larger one while preserving the existing architecture.</p>
<pre><code class="language-text">ExistingCore X
  -&gt; ExtendedArchitecture X'
  -&gt; FeatureView F
</code></pre>
<p>In a good feature extension, the existing core is preserved inside the extension, the feature view can be extracted in an explainable way, and interactions from the feature to the core pass through declared interfaces. Conversely, hidden dependencies, boundary policy violations, abstraction leakage, runtime exposure, and semantic mismatch are treated not as impressions, but as <code>ObstructionWitness</code> values.</p>
<p>To count, remove, preserve, or explicitly decline to conclude about these obstructions, AAT makes <code>ProofObligation</code> and <code>Certificate</code> explicit.</p>
<pre><code class="language-lean">structure ProofObligation (State : Type u) (Witness : Type v) where
  formalUniverse : Prop
  requiredLaws : Prop
  invariantFamily : State -&gt; Prop
  witnessUniverse : Witness -&gt; Prop
  coverageAssumptions : Prop
  exactnessAssumptions : Prop
  operationPreconditions : Prop
  conclusion : Prop
  nonConclusions : Prop
</code></pre>
<p>An obligation is not discharged merely because it exists. It is discharged only when the visible assumptions imply the conclusion.</p>
<pre><code class="language-lean">def AssumptionsHold (P : ProofObligation State Witness) : Prop :=
  P.formalUniverse ∧
  P.requiredLaws ∧
  P.coverageAssumptions ∧
  P.exactnessAssumptions ∧
  P.operationPreconditions

def Discharged (P : ProofObligation State Witness) : Prop :=
  AssumptionsHold P -&gt; P.conclusion
</code></pre>
<p>The same is true for certificates. For example, in repair synthesis, if we say "there is no solution", solver failure alone is not enough. Only when a valid certificate exists do we use soundness to conclude that no satisfying architecture exists.</p>
<pre><code class="language-lean">structure NoSolutionCertificate
    {State : Type u} {Constraint : Type c} (Certificate : Type v)
    (C : SynthesisConstraintSystem State Constraint)
    (cert : Certificate) where
  valid : Prop
  sound : valid -&gt; NoArchitectureSatisfies C
  coverageAssumptions : Prop
  exactnessAssumptions : Prop
  nonConclusions : Prop

theorem sound_of_valid
    (pkg : NoSolutionCertificate Certificate C cert)
    (hValid : ValidNoSolutionCertificate pkg) :
    NoArchitectureSatisfies C
</code></pre>
<p><code>nonConclusions</code> is not decoration. Even if a static split is proved, runtime flatness or semantic flatness does not automatically follow. Even if no obstruction is found in one observation universe, that does not imply there is no obstruction in every universe. Making this boundary explicit is necessary if we want to treat the theory as testable rather than as a collection of engineering anecdotes.</p>
<p><code>ArchitectureSignature</code> is also not intended to collapse architecture quality into a single score. It is a multi-axis diagnosis for reading multiple invariant and obstruction families axis by axis.</p>
<pre><code class="language-lean">structure ArchitectureSignatureV1 where
  core : ArchitectureSignatureV1Core
  weightedSccRisk : Option Nat
  projectionSoundnessViolation : Option Nat
  lspViolationCount : Option Nat
  nilpotencyIndex : Option Nat
  runtimePropagation : Option Nat
  relationComplexity : Option Nat
  empiricalChangeCost : Option Nat
  deriving DecidableEq, Repr
</code></pre>
<p>Some axes are <code>Option Nat</code> because an unmeasured value must not be treated as zero. <code>none</code> does not mean "no problem". It means "not measured in this universe / extractor / bridge".</p>
<pre><code class="language-lean">theorem not_axisAvailableAndZero_of_axisValue_none
    {sig : ArchitectureSignatureV1} {axis : ArchitectureSignatureV1Axis}
    (hNone : axisValue sig axis = none) :
    ¬ axisAvailableAndZero sig axis
</code></pre>
<p>From this perspective, a signature is not a convenient bag of metrics. It is a multi-axis invariant relative to a law universe. For selected required law axes, there are also bridge theorems connecting lawfulness and zero signature axes.</p>
<pre><code class="language-lean">theorem architectureLawful_iff_requiredSignatureAxesZero
    {C : Type u} {A : Type v} {Obs : Type w}
    (X : ArchitectureLawModel C A Obs)
    [DecidableEq C] [DecidableEq A] [DecidableEq Obs]
    [DecidableRel X.G.edge] [DecidableRel X.GA.edge]
    [DecidableRel X.boundaryAllowed]
    [DecidableRel X.abstractionAllowed] :
    ArchitectureLawful X ↔
      RequiredSignatureAxesZero (ArchitectureLawModel.signatureOf X)
</code></pre>
<p>AAT does not treat every claim at the same level. It separates definitions, proved theorem packages, bounded bridge theorems, tooling-side evidence, and empirical hypotheses. It also records which universe, observation, coverage, and exactness assumptions each claim is relative to.</p>
<p>The dynamics part below follows the same discipline. The important point is that AAT is not using mathematical vocabulary for atmosphere. It is trying to make the assumptions, conclusions, and non-conclusions part of the type-level structure.</p>
<p>So far, AAT gives us vocabulary for a single architectural state and operations acting on it.</p>
<p>But in AI-assisted development, the central issue is not only a single operation. Requirements, existing code, review, CI, and AI agents change which operation is likely to be selected next, and this selection is repeated many times.</p>
<p>So we need to view AAT operations not only as one-time proof targets, but also as transformations repeatedly selected over time. This is where a chaos-game-like reading enters.</p>
<h2>Formalizing Attractor Dynamics</h2>
<p>From here, I use AAT vocabulary to rewrite "field", "force", "dissipation", and "observation" in a more mathematical form.</p>
<p>This is not merely a metaphor that says "AI development is kind of like a chaos game". It is an attempt to place PR force, operation support, trajectory, and basin candidates on top of the AAT vocabulary of state, operation, invariant, obstruction, proof obligation, certificate, and signature.</p>
<p>At this stage, I should be careful: this is not a finished theorem of real-world software development. It is a way to organize phenomena that practitioners can feel, in a structure that may eventually support measurement and verification.</p>
<p>The minimal loop of the dynamics can be read as follows:</p>
<pre><code class="language-text">architecture field
  -&gt; operation distribution
  -&gt; accepted / rejected transitions
  -&gt; signature trajectory
  -&gt; updated architecture field
</code></pre>
<p>The position is that architecture quality is not only a property of a snapshot. It is also a property of the future operation distribution and the signature trajectory.</p>
<h3>1. State, Operation, Observation</h3>
<p>Let the architectural state be <code>X_t</code>.</p>
<p>Feature addition, repair, split, protection, migration, and refactoring are operations acting on that state.</p>
<pre><code class="language-text">X_{t+1} = op_t(X_t)
</code></pre>
<p>The operation <code>op_t</code> is not selected uniformly at random.</p>
<p>The current codebase, requirements, prompt, review policy, CI, design boundaries, and organizational judgment all change which operations are likely to be selected.</p>
<pre><code class="language-text">op_t ~ P(operation | X_t, control_t)
</code></pre>
<p>This probability expression is notation for the practical reading. The formal core of AAT is not a probability distribution. It is finite or bounded operation support, bounded scripts, accepted transition predicates, and explicit preservation assumptions.</p>
<p>In Lean, for example, operation support is represented not as a probability distribution, but as a finite list of candidate operations for each state.</p>
<pre><code class="language-lean">structure FiniteOperationKernel
    (State : Type u) (OperationId : Type w) where
  support : State -&gt; List OperationId
  coverageAssumptions : Prop
  weightSourceBoundary : Prop
  normalizationBoundary : Prop
  nonConclusions : Prop
</code></pre>
<p>Weights, normalization, and completeness of AI-generated proposals are not mixed into the theorem here. Boundaries such as <code>weightSourceBoundary</code> and <code>normalizationBoundary</code> record what is outside the formal claim, and the probabilistic reading remains outside that core.</p>
<p>Likewise, repeated operation sequences are first treated as bounded scripts.</p>
<pre><code class="language-lean">structure BoundedOperationScript (OperationId : Type w) where
  operations : List OperationId
  operationFamily : OperationId -&gt; Prop
  operationsInFamily :
    ∀ op, op ∈ operations -&gt; operationFamily op
  coverageAssumptions : Prop
  nonConclusions : Prop
</code></pre>
<p>This boundary helps avoid confusing probabilistic interpretation with preservation claims over finite support.</p>
<p>Instead of observing the entire state directly, we map it into signature space through an observation function <code>Obs</code>.</p>
<pre><code class="language-text">sigma_t = Obs(X_t)
</code></pre>
<p>This <code>sigma_t</code> is the Architecture Signature.</p>
<p>It contains multi-axis observations such as dependency direction, boundaries, abstraction, runtime exposure, and semantic mismatch.</p>
<p>In Lean, even the observation itself is packaged. The package contains not only the observation function, but also coverage and non-conclusion boundaries.</p>
<pre><code class="language-lean">structure SignatureObservation (State : Type u) (Sig : Type v) where
  observe : State -&gt; Sig
  coverageAssumptions : Prop
  nonConclusions : Prop
</code></pre>
<p>When architectural evolution is mapped into observation space, we get a signature trajectory.</p>
<pre><code class="language-lean">def SignatureTrajectory (O : SignatureObservation State Sig) :
    {X Y : State} -&gt; ArchitectureEvolution State X Y -&gt; List Sig
  | X, _, ArchitecturePath.nil _ =&gt; [O.observe X]
  | X, _, ArchitecturePath.cons _step rest =&gt;
      O.observe X :: SignatureTrajectory O rest
</code></pre>
<p>A change moves the signature.</p>
<pre><code class="language-text">Delta_t = sigma_{t+1} - sigma_t
</code></pre>
<p>This <code>Delta_t</code> can be read as the force applied by the PR or operation.</p>
<p>However, not every axis admits simple subtraction. For numeric axes, we may read it as a signed delta. For other axes, we read it as a before / after comparison, the appearance of a witness, or a change in state classification.</p>
<h3>2. PR Force Model</h3>
<p>With this structure, a PR becomes more than a diff.</p>
<p>A PR can be read as a force applied to the codebase in the selected Architecture Signature space.</p>
<pre><code class="language-text">PRForce(PR) = sigma(after(PR)) - sigma(before(PR))
</code></pre>
<p>The word force here does not mean physical force. It means an observed change in signature, relative to which axes are observed and which differences are defined.</p>
<p>Delta sequences, like trajectories, are relative to finite paths in Lean.</p>
<pre><code class="language-lean">def SignatureDeltaSequence
    (O : SignatureObservation State Sig) (D : SignatureDelta Sig Delta) :
    {X Y : State} -&gt; ArchitectureEvolution State X Y -&gt; List Delta
  | _, _, ArchitecturePath.nil _ =&gt; []
  | X, _, ArchitecturePath.cons (Y := Y) _step rest =&gt;
      D.between (O.observe X) (O.observe Y) ::
        SignatureDeltaSequence O D rest
</code></pre>
<p>For selected additive deltas, the sum of step deltas agrees with the endpoint delta. This is proved as a theorem.</p>
<pre><code class="language-lean">theorem netSignatureDelta_telescopes [Zero Delta] [Add Delta]
    (O : SignatureObservation State Sig) (D : SignatureDelta Sig Delta)
    (law : AdditiveSignatureDeltaLaw D) :
    {X Y : State} -&gt; (plan : ArchitectureEvolution State X Y) -&gt;
      NetSignatureDelta (SignatureDeltaSequence O D plan) =
        EndpointSignatureDelta O D plan
</code></pre>
<p>But this theorem is finite path calculus under the assumption that the selected delta satisfies an additive law. It does not claim that unobserved axes, incident risk, review quality, or actual PR outcomes can all be added this way.</p>
<p>The force has multiple components.</p>
<table>
<thead>
<tr>
<th>Force component</th>
<th>Meaning</th>
</tr>
</thead>
<tbody><tr>
<td>Feature force</td>
<td>The force that moves product functionality forward.</td>
</tr>
<tr>
<td>Repair force</td>
<td>The force that repairs existing breakage.</td>
</tr>
<tr>
<td>Coupling force</td>
<td>The force that increases or decreases coupling.</td>
</tr>
<tr>
<td>Boundary force</td>
<td>The force that preserves or violates boundaries.</td>
</tr>
<tr>
<td>Type force</td>
<td>The force that adds type information or creates type holes.</td>
</tr>
<tr>
<td>Test force</td>
<td>The force that increases or decreases observability through tests.</td>
</tr>
<tr>
<td>Debt force</td>
<td>The force that pushes the system toward a bad basin.</td>
</tr>
<tr>
<td>Refactor force</td>
<td>The force that helps it escape a bad basin.</td>
</tr>
</tbody></table>
<p>A good PR has not only feature force, but also stabilizing force.</p>
<pre><code class="language-text">v_PR = v_feature + v_stabilize
</code></pre>
<p>A risky PR moves the feature forward locally while quietly adding small debt force.</p>
<pre><code class="language-text">v_PR = v_feature + v_debt
</code></pre>
<p>In AI-generated PRs, the especially important case is one where tests pass and the specification is satisfied, but small <code>v_debt</code>, <code>v_coupling</code>, <code>v_type_hole</code>, or <code>v_entropy</code> accumulates repeatedly. It may be hard to see in a single PR. But as a trajectory, the system is moving toward a bad basin. I call this the Local Correctness Trap.</p>
<h3>3. Three Classes of Force</h3>
<p>The earlier discussion about product managers, product owners, review, and CI/CD becomes clearer if we separate force by observability.</p>
<p>Force can be divided into three classes.</p>
<table>
<thead>
<tr>
<th>Force class</th>
<th>Meaning</th>
<th>Main evidence</th>
</tr>
</thead>
<tbody><tr>
<td>ObservedForce</td>
<td>Before / after signature delta of PRs that were actually merged.</td>
<td>PRs, ArchSig reports, drift ledger.</td>
</tr>
<tr>
<td>LatentForce</td>
<td>Invisible force by which requirements, design, prompts, and local code style shape which PRs are proposed.</td>
<td>Requirements, prompts, proposal logs, case studies.</td>
</tr>
<tr>
<td>DissipatedForce</td>
<td>Raw force that was rejected, corrected, or weakened by review / CI / types / policy.</td>
<td>CI failures, requested changes in review, rejected proposals.</td>
</tr>
</tbody></table>
<p>This classification makes AI-assisted development more concrete.</p>
<p>If we look only at merged PRs, we see only ObservedForce.</p>
<p>But in an era where AI can generate many proposals, the force that was not merged also matters. Force removed by review, rejected by CI, or reshaped before merge matters.</p>
<p>To understand how well a dissipative system is working, we need DissipatedForce.</p>
<p>To understand what kind of PR distribution is created by upstream requirements or prompts, we need LatentForce.</p>
<p>In Lean, the separation between accepted and rejected changes appears as a damping / control schema.</p>
<pre><code class="language-lean">structure DampingControlSchema (State : Type u) (Sig : Type v) where
  observation : SignatureObservation State Sig
  invariant : SafeRegion Sig
  accepted :
    {X Y : State} -&gt; ArchitectureTransition State X Y -&gt; Prop
  rejected :
    {X Y : State} -&gt; ArchitectureTransition State X Y -&gt; Prop
  acceptedPreservesInvariant :
    ∀ {X Y : State} (t : ArchitectureTransition State X Y),
      accepted t -&gt; StepPreservesSafeRegion observation invariant t
  coverageAssumptions : Prop
  nonConclusions : Prop
</code></pre>
<p>What this proves is limited: transitions classified as <code>accepted</code> preserve the explicitly stated <code>invariant</code>. The existence of rejected changes does not prove that the whole future of the codebase is safe.</p>
<p>On top of this schema, it is proved that accepted finite evolutions create trajectories inside the selected invariant.</p>
<pre><code class="language-lean">theorem acceptedEvolution_preserves_selectedInvariant
    (control : DampingControlSchema State Sig) :
    {X Y : State} -&gt; (plan : ArchitectureEvolution State X Y) -&gt;
      StateInSafeRegion control.observation control.invariant X -&gt;
      control.AcceptedEvolution plan -&gt;
        SignatureTrajectoryInSafeRegion
          control.invariant (SignatureTrajectory control.observation plan)
</code></pre>
<h3>4. A Chaos-Game-Like Correspondence</h3>
<p>This is where the chaos-game-like reading appears.</p>
<p>The similarity is that we have multiple transformations, one of them is selected at each step, and a state trajectory is produced.</p>
<p>The difference is that, in software development, neither the set of transformations nor their likelihood is fixed. Requirements, review, CI, design boundaries, existing examples, and AI agent behavior all affect which operation is likely to be selected next.</p>
<p>So the goal is not to claim that software development literally is the classical chaos game. The goal is to use AAT vocabulary to handle the structure where multiple operations are repeatedly selected and the resulting trajectory tends to move toward certain regions.</p>
<p>The correspondence is:</p>
<table>
<thead>
<tr>
<th>Chaos-game side</th>
<th>AAT / development side</th>
</tr>
</thead>
<tbody><tr>
<td>State <code>X_t</code></td>
<td>Architecture state / codebase field.</td>
</tr>
<tr>
<td>Transformation <code>f_i</code></td>
<td>Architecture operation / PR / patch.</td>
</tr>
<tr>
<td>Transformation selection</td>
<td>Operation selection by developer / AI / requirement / review.</td>
</tr>
<tr>
<td>Trajectory</td>
<td>Architecture Signature trajectory.</td>
</tr>
<tr>
<td>Attractor</td>
<td>Signature region where the system tends to stay.</td>
</tr>
<tr>
<td>Basin</td>
<td>Initial or surrounding states likely to fall into that attractor.</td>
</tr>
<tr>
<td>Control input</td>
<td>Prompt, review policy, CI, type checker, architecture rule.</td>
</tr>
</tbody></table>
<p>As a formula:</p>
<pre><code class="language-text">X_{t+1} = f_{i_t}(X_t)
i_t ~ P(. | X_t, control_t)
Y_t = sigma(X_t)
</code></pre>
<p>The important point is not to assert probability or attractors as strong theorems too early.</p>
<p>What we should handle in practice is first an attractor candidate or basin candidate relative to a finite observation window, bounded horizon, selected signature axes, and selected operation support.</p>
<pre><code class="language-text">finite observed trajectory
  + selected signature region
  + bounded operation support
  -&gt; attractor / basin candidate
</code></pre>
<p>This is not an escape into weak claims. It is a boundary that makes measurement and falsification possible.</p>
<h3>5. Support Shaping</h3>
<p>The mathematical core of Attractor Engineering is support shaping.</p>
<p>This is not just about "blocking bad PRs". It is about changing the set of operations that can naturally be selected next, and changing how likely they are to be selected.</p>
<p>In short, Attractor Engineering is a theory of designing the geometry of the operation distribution.</p>
<pre><code class="language-lean">def Supports
    (kernel : FiniteOperationKernel State OperationId)
    (X : State) (op : OperationId) : Prop :=
  op ∈ kernel.support X
</code></pre>
<p>For the current architecture state <code>X</code>, the set of operations that can naturally be selected is called operation support.</p>
<p>Good design changes this support.</p>
<pre><code class="language-lean">def SupportOperationsPreserveSafeRegion
    (kernel : FiniteOperationKernel State OperationId)
    (sem : OperationTransitionSemantics State OperationId)
    (O : SignatureObservation State Sig) (R : SafeRegion Sig) : Prop :=
  ∀ {X : State} (op : OperationId),
    kernel.Supports X op -&gt; sem.OperationPreservesSafeRegion O R op
</code></pre>
<p>The strong form of support shaping says: operations that remain in support preserve the selected safe region. In practical terms, we reduce bad operations, increase good operations, make correct paths easier to choose, and make dangerous shortcuts less convenient.</p>
<p>This idea is packaged on the Attractor Engineering side as follows:</p>
<pre><code class="language-lean">structure AttractorEngineeringSupportPackage
    (State : Type u) (Sig : Type v) (OperationId : Type w) where
  observation : SignatureObservation State Sig
  kernel : FiniteOperationKernel State OperationId
  semantics : OperationTransitionSemantics State OperationId
  targetRegion : SafeRegion Sig
  supportPreserves :
    kernel.SupportOperationsPreserveSafeRegion semantics observation targetRegion
  coverageAssumptions : Prop
  measurementBoundary : Prop
  nonConclusions : Prop
</code></pre>
<p>With this package, if a bounded script uses only operations from finite support, and the starting point is in the target region, then the observed trajectory remains in the target region. This is stated as a theorem.</p>
<pre><code class="language-lean">theorem supportPackage_preserves_targetTrajectory
    (package : AttractorEngineeringSupportPackage State Sig OperationId)
    (script : BoundedOperationScript OperationId)
    {X Y : State} (plan : ArchitectureEvolution State X Y)
    (hStart :
      StateInSafeRegion package.observation package.targetRegion X)
    (hRealizes : script.RealizesEvolution package.semantics plan)
    (hSupport :
      package.kernel.ScriptUsesSupport script.operations plan) :
    SignatureTrajectoryInSafeRegion package.targetRegion
      (package.TargetTrajectory plan)
</code></pre>
<p>For example, good APIs, good examples, clear ownership, narrow modules, and domain states represented in types increase the local discoverability of good operations.</p>
<p>Conversely, a huge common module, implicit global context, ambiguous services, and overly convenient helpers increase the local convenience of bad operations.</p>
<p>From this viewpoint, refactoring is not only cleaning up the current structure. It is also a basin-reshaping operation that changes the future operation distribution.</p>
<p>In the measurement layer, one tooling-side metric candidate to watch here is <code>SupportRiskMass</code>.</p>
<pre><code class="language-text">SupportRiskMass(C) =
  sum over op in support(C) of weight(op) * risk(op, C)
</code></pre>
<p>Here again, it is important not to reduce <code>risk</code> to a simple 0 / 1.</p>
<p>In AAT terms, we should distinguish at least:</p>
<pre><code class="language-text">safe-preserving proved
safe-preserving measured
safe-preserving estimated
unsafe witness measured
unmeasured
unavailable
private
notComparable
outOfScope
</code></pre>
<p>Unmeasured must not be read as zero. This is a central principle in both ArchSig and AAT.</p>
<h3>6. The Same Signature Does Not Imply the Same Future</h3>
<p>An important point is that two states can have the same current Architecture Signature but different future operation distributions.</p>
<p>For example, two modules might show the same number of dependency violations, the same test coverage, and the same complexity. But one may have a good canonical example nearby, while the other may contain many bad shortcuts.</p>
<p>Even if the current observation values are the same, future PRs may be attracted in different directions.</p>
<pre><code class="language-text">Obs(X) = Obs(Y)
  does not imply
OperationSupport(X) = OperationSupport(Y)
</code></pre>
<p>This is why architecture quality should not be judged by snapshot metrics alone. The current value can be the same while the future force field is different. Attractor Engineering treats this future force field as a design object.</p>
<h3>7. Accepted Preservation and Support Preservation Are Different</h3>
<p>There is another important separation.</p>
<p>Suppose review and CI ensure that merged PRs preserve a safe region.</p>
<p>Even then, unsafe operations may still remain inside operation support.</p>
<p>This separation is not just a warning. It appears on the Lean side as a finite counterexample. Accepted-step invariant preservation can hold, and an accepted safe step can exist, while operations that do not preserve the safe region remain in support.</p>
<pre><code class="language-lean">theorem acceptedPreservation_not_supportPreservation_counterexample :
    (∃ (t : ArchitectureTransition ExampleState safeState safeState),
      control.AcceptedStep t ∧
        StepPreservesSafeRegion control.observation control.invariant t) ∧
    (∀ {X Y : ExampleState} (t : ArchitectureTransition ExampleState X Y),
      control.AcceptedStep t -&gt;
        StepPreservesSafeRegion control.observation control.invariant t) ∧
    (∃ X op,
      kernel.Supports X op ∧
        ¬ semantics.OperationPreservesSafeRegion control.observation
          control.invariant op)
</code></pre>
<p>This is the difference between guardrails and attractor engineering.</p>
<p>Strong guardrails may stop bad PRs.</p>
<p>But a field where bad PRs are produced in large numbers every time is still not a good field.</p>
<p>A good field is one where bad operations are less likely to appear in the first place, and good operations are natural, easy to imitate, observable, and low-friction.</p>
<h3>8. PRs Are Non-Commutative</h3>
<p>PRs are generally non-commutative.</p>
<pre><code class="language-text">PR_2 o PR_1 != PR_1 o PR_2
</code></pre>
<p>Of course, this only makes sense when both orders can be applied. Even then, the same two PRs can produce different final signatures depending on merge order.</p>
<p>This matters in an era where AI agents can generate multiple PRs in parallel.</p>
<p>Even when individual PRs are locally correct, their order can change boundaries, types, tests, and semantic alignment.</p>
<p>I call this merge order sensitivity.</p>
<pre><code class="language-text">MergeOrderSensitivity(a, b, X) =
  distance(
    sigma(b(a(X))),
    sigma(a(b(X)))
  )
</code></pre>
<p>This is not merely a merge conflict issue. It is the non-commutativity of operation algebra branching the signature trajectory. We will need this viewpoint when evaluating teams of AI agents as well.</p>
<h3>9. Observe the Shape of the Trajectory</h3>
<p>Architecture Signature should be read not only as a current value, but also as a trajectory.</p>
<p>Even if the endpoint is safe, the path may have passed through a bad region.</p>
<p>Even if net delta is zero, there may have been large churn inside the path.</p>
<pre><code class="language-text">endpoint safe
  does not imply path safe

net force zero
  does not imply no excursion
</code></pre>
<p>This is also proved in Lean as a small finite counterexample. In a trajectory such as <code>0 -&gt; 2 -&gt; 0</code>, both endpoints are the same safe state. The endpoint delta and net delta can both appear to be zero, while the path passed through an unsafe region.</p>
<pre><code class="language-lean">theorem netSignatureDelta_eq_zero :
    NetSignatureDelta (SignatureDeltaSequence observation signedNatDelta
      excursionPlan) = 0

theorem endpointSafe_and_zeroDelta_but_not_pathSafe :
    EndpointSignatureDelta observation signedNatDelta excursionPlan = 0 ∧
      StateInSafeRegion observation safeRegion 0 ∧
      StateInSafeRegion observation safeRegion 0 ∧
      ¬ SignatureTrajectoryInSafeRegion safeRegion
          (SignatureTrajectory observation excursionPlan)
</code></pre>
<p>Trajectories have shapes.</p>
<table>
<thead>
<tr>
<th>Trajectory type</th>
<th>Meaning</th>
</tr>
</thead>
<tbody><tr>
<td>Stable Orbit</td>
<td>The system returns to a safe region after small changes.</td>
</tr>
<tr>
<td>Drift</td>
<td>The system slowly shifts toward a bad region.</td>
</tr>
<tr>
<td>Spiral Debt</td>
<td>It appears to return, but over the long run moves closer to a bad basin.</td>
</tr>
<tr>
<td>Sudden Phase Shift</td>
<td>A signature changes sharply after a particular PR.</td>
</tr>
<tr>
<td>Oscillation</td>
<td>Feature additions and refactorings alternate between good and bad.</td>
</tr>
<tr>
<td>Basin Capture</td>
<td>After some point, the system gets captured by a bad structure.</td>
</tr>
</tbody></table>
<p>What ArchSig really wants to observe is this trajectory.</p>
<p>Not just the evaluation of one PR, but the resulting motion produced by a group of PRs.</p>
<h3>10. Expanding Observation Can Suddenly Reveal Badness</h3>
<p>With coarse observation, a codebase may look safe.</p>
<p>But if we add more observation axes, a hidden obstruction may suddenly appear as nonzero.</p>
<pre><code class="language-text">coarse observation:
  safe

refined observation:
  hidden obstruction appears
</code></pre>
<p>We can call this an observability expansion shock.</p>
<p>The important point is that this does not necessarily mean the architecture got worse. It may simply mean that an axis that used to be invisible has become visible.</p>
<p>That is why ArchSig must distinguish <code>unmeasured</code> from <code>zero</code>. When something that was not measured becomes visible, we must separate "the architecture got worse" from "the observation became better".</p>
<h3>About the Lean Formalization</h3>
<p>The structure above is not built only from metaphor. Some of the core vocabulary of AAT has been implemented as Lean definitions and theorems under <code>Formal/Arch</code>.</p>
<p>The repository is <a href="https://github.com/iroha1203/AlgebraicArchitectureTheoryV2">AlgebraicArchitectureTheoryV2</a>. The proved API is summarized in the <a href="https://github.com/iroha1203/AlgebraicArchitectureTheoryV2/blob/main/docs/aat/lean_theorem_index.md">Lean definition and theorem index</a>.</p>
<p>The vocabulary used in the second half of this article mainly corresponds to <code>Formal/Arch/Evolution/SignatureDynamics.lean</code> and <code>Formal/Arch/Evolution/AttractorEngineering.lean</code>.</p>
<p>The role of Lean formalization is not to give this theory an aura of correctness. Its role is to record, with boundaries, what can be said under which universe, observation, coverage, and exactness assumptions.</p>
<p>It is important that counterexamples live in the same place as proved theorems.</p>
<pre><code class="language-text">proved:
  accepted evolution preserves selected invariant
  bounded sampled support-preserving script stays in target region
  selected additive delta telescopes over finite path

proved counterexamples:
  endpoint safe + zero delta does not imply path safe
  accepted preservation does not imply support preservation
  unmeasured axis is not available-zero evidence
</code></pre>
<p>Conversely, the fact that something is proved in Lean does not mean that a real-world code extractor is complete, or that every runtime / semantic obstruction has already been observed. With this boundary, AAT can separate what is formally known, what depends on measurement, and what remains an empirical research question.</p>
<h2>Conclusion</h2>
<p>The discovery of Attractor Engineering changes how I see software architecture: from a static blueprint to a field that guides future changes.</p>
<p>If software architecture is read as an algebraic structure, feature additions and refactorings become operations.</p>
<p>When those operations are repeated, the architecture state draws a trajectory.</p>
<p>We can then ask where that trajectory tends to go, and where it tends to stay. This is where attractors and basins enter the picture.</p>
<p>Architecture design in the era of AI-assisted development can be described as creating a field where future changes gather in good places and can escape bad places.</p>
<p>Harness engineering becomes the engineering of receiving AI's change force, dissipating unwanted components, and guiding the system toward good attractors.</p>
<p>ArchSig is the tool for observing that trajectory.</p>
<p>The essence of AI-assisted development is not only producing PRs faster.</p>
<p>It is deciding where fast force should converge.</p>
<p>A codebase is a field.</p>
<p>A PR is a force.</p>
<p>CI/CD is a dissipative system.</p>
<p>Product managers, product owners, engineers, reviewers, and AI agents are participants in the field.</p>
<p>ArchSig is an observer of the trajectory.</p>
<p>With this framing, development in the AI era is no longer just automation. It becomes field design.</p>
<p>As a design theory for that purpose, Attractor Engineering may be a useful direction for both practice and research.</p>
]]></content:encoded></item></channel></rss>