Why a Repository Is Not Enough for an AI Agent: Architecture as a Shared System Representation
AI is already reasonably good at writing individual functions, fixing bugs, and assembling small applications. But as a project grows, a strange paradox becomes more visible: before changing a few lines, an agent may need to read hundreds of files and reconstruct how the entire system works.
We are used to treating source code as the most precise description of a program. For a machine, it is indeed the executable truth. But code does not always answer a different question very well: why is the system designed this way?
Where does intentional architecture end and historically accumulated implementation begin? Which branch runs in parallel? Which timeout is part of a contract, and which one merely happened to end up in a configuration file? Which dependencies are allowed, and which appeared as a temporary workaround three years ago?
A developer gradually learns these things from documentation, discussions, and experience with the system. An AI agent mainly sees code and has to conduct a small architectural investigation every time.
This made me wonder: what if the problem is not only the size of the context window or the quality of the model? Perhaps the repository itself is too low-level an interface for collaboration between humans and AI.
The following is an engineering hypothesis that I am trying to test in practice: for developers and AI to work together effectively, a system needs a separate architectural layer between intent and source code.
One Prompt, Multiple Architectures
Consider an ordinary task: build an order-processing service. It accepts an order, splits it into items, checks inventory in parallel over gRPC, applies a timeout, merges the results, and returns a response.
Ask AI to implement this service several times and you may get several perfectly plausible designs:
- HTTP and goroutines with custom retries;
- gRPC, channels, and a circuit breaker;
- REST and a worker pool with a different error-handling model.
Each solution may look reasonable on its own. The problem is that, together with the business logic, the model is choosing the architecture, concurrency model, component boundaries, and infrastructure patterns all over again.
We can make the prompt more detailed. We can describe parallel branches, timeouts, error paths, retries, metrics, and tracing. But at some point, the prompt begins to resemble an architectural specification—only informal and open to multiple interpretations.

With an existing project, the process runs in reverse. The specification is missing, so the agent tries to reconstruct it from the code. This creates a loop:
- a human expresses intent in natural language;
- AI turns it into many files;
- when the next change arrives, another agent reads those files;
- the agent tries to recover the original intent;
- only then does it modify the code again.
We use the most detailed representation of the system as the primary way to communicate its design.
Why a Mountain of Specifications Does Not Solve the Problem
There is a lot of discussion today about Spec-Driven Development, or SDD: a human and an AI first formulate a specification, and only then does the agent write the code. For an individual function or a small service, this can indeed make the outcome more predictable. But I suspect that, in its pure form, this approach will not scale well to large systems.
If every module, scenario, and architectural decision has its own textual document, a mountain of specifications gradually grows next to the mountain of code. Those specifications also need to be connected, versioned, kept up to date, and mapped back to the implementation. Before making a change, an agent now has to analyze two large bodies of information and determine which specification is still valid, how it relates to the others, and whether it has diverged from the code.
A more realistic direction may be a move toward higher-level DSLs and formal architectural models. Such a model is not another description layered on top of the code. It becomes a source from which we can derive the implementation, validation rules, and a local specification for a particular agent task.
This does not eliminate human-written requirements, business rules, or explanations of intent. But the technical part of the context—types, allowed dependencies, change boundaries, success paths, and error paths—does not need to be restated manually every time. The agent receives a compact projection of the model relevant to the selected component or subgraph rather than an entire mountain of documentation.
Code Is Precise, but It Operates at the Wrong Level
It would be easy to draw an overly strong conclusion here: "We no longer need code; we will draw everything with a mouse." I do not believe that.
Code remains the best tool for algorithms, unusual logic, profiling, low-level optimization, and debugging. IDEs are not going away either. The question is not whether code should be replaced, but whether it should remain the only source for understanding a system.
Compilers have long used an intermediate representation, or IR. A source program is translated into a structure suitable for analysis, optimization, and machine-code generation. Users rarely work with the IR directly, but it separates the meaning of a program from the specifics of a particular platform.
A similar layer may be useful at the architectural level:
human / AI
↓
architectural representation
↓
validation and policies
↓
implementation generation
↓
runtime and observabilityThis representation should be visual enough for humans and formal enough for machines. One example is a typed graph in which nodes describe operations, edges describe data flow and invocation semantics, and separate outputs represent success and error scenarios.
This raises a fair question: do languages for describing architecture not already exist? UML, C4, ArchiMate, and various model-driven approaches have long been used to capture system structure. Some tools can also generate code from models, so generating code from a diagram is not a new idea.
In practice, however, these models often become reflections of an existing implementation. They are updated after the code changes, used as documentation, and gradually cease to be treated as a source of truth. The key distinction is not so much the notation as the lifecycle of the model. It should participate in validation and builds, generate infrastructure code and local tasks for AI agents, and connect to runtime observability. A mismatch between the model and the implementation should be detected by tooling, not by a reader looking at an outdated diagram.
In this case, the graph is not a diagram drawn after the implementation. It is the original architectural description from which a repeatable implementation can be produced under fixed generation rules.
Predictability Does Not Come from Forbidding AI
We usually try to make AI more predictable by providing more detailed instructions: a system prompt, project rules, code examples, and a list of prohibited actions. This helps, but it does not change the underlying process. The agent still has to understand the repository and decide which parts to touch.
In a large system, it is especially important not only to give the agent more context, but to quickly define the boundaries within which it should search for a solution. For example: modify only one component, preserve the input and output contracts, introduce no new dependencies, use the existing port for the external call, and add an explicit path for handling a new error.
A constraint here is not a ban on useful initiative. It is a way to reduce the space of possible solutions. The more precisely the task boundary is defined, the less the agent has to guess and the easier the result is for a human to verify.
These constraints do not replace access controls, sandboxing, tests, or code review. They do not provide security on their own, but they substantially reduce the architectural search space the agent has to explore.
The boundaries must also be quick to define. If preparing a task requires a human to inspect dozens of packages and manually enumerate every architectural invariant, much of the benefit of using AI is lost. With a formal representation of the system, a boundary can be defined by selecting a subgraph or domain component, while types, allowed connections, and dependencies are already part of the contract.
The architectural representation therefore changes not only the amount of context, but also the scale of the task itself.
Instead of "build an order-processing service," the agent can receive a much narrower assignment:
Task: ProcessOrder
Input: Order
Output: OrderState
File: internal/functions/processorder.go
- implement the function body
- do not create a new context
- complete the result context after the response
- run the testsThe architecture, types, connections, and infrastructure dependencies have already been defined. The agent only needs to implement a local business function.
This does more than constrain the model. It also saves work. The agent does not need to reread the transport layer, find where the handler is registered, reconstruct the tracing setup, or analyze dozens of neighboring packages. The necessary context already exists in the graph and in the generated task contract.
An Architectural Diff Instead of Hoping Code Review Will Catch It
There is another consequence that may be more important than generation itself.
Today, a human usually reviews an AI proposal after the fact, as a diff spread across many files. To understand the change, the reviewer has to repeat the same work: recover its architectural meaning from technical implementation details.
With a formal model, the agent can first propose the change at the architectural level:
+ added a fallback branch
~ timeout changed from 25 to 50 ms
+ introduced a separate PartialResult stream
~ replaced InventorySink with ReserveInventoryThis change can be reviewed before any code is generated. Once it is approved, the generator can create the technical implementation according to known rules.
This creates a clearer division of responsibility:
- a human makes the architectural decision;
- a validator checks structural correctness;
- a generator creates a repeatable infrastructure implementation;
- AI writes business logic within explicit boundaries;
- ordinary compilers and tests verify the result.
The change may still be proposed by AI. But AI does not define the rules of correctness.

Why a Graph, and Why Streaming?
An executable graph needs a consistent execution model. In my Open Service Architect experiment, I use typed streams of messages.
A node performs a small transformation, while an edge passes the result forward. Split creates multiple branches, FlatMap expands one value into a sequence of elements, Sink calls an external system, and Merge combines results. Parallelism and fan-out/fan-in are expressed through structure rather than a hand-built combination of goroutines, channels, and WaitGroup.
A streaming model does not mean that every service must look like a complex data pipeline. An ordinary CRUD service fits as well: each API method may contain a single business node. The graph is almost trivial in this case, but transport, configuration, metrics, and tracing are still generated according to the same rules.
Streaming is not the goal. It is a mechanism that makes the architecture executable.
Long-running processes involving sagas, distributed transactions, and compensations require a different executor, such as Temporal. The architectural layer does not need to compete with it: it can start a workflow through an output port and receive its result through an input port. One tool describes the integration topology; the other guarantees durable process execution.
One Model for Design and Observability
Architectural diagrams have a familiar problem: they begin to go stale as soon as the meeting ends. The code changes, while the picture remains in Confluence and gradually turns into a historical document.
An executable representation changes this. If the runtime is actually built from the graph, the same graph can become a tracing map. The path of a message in production is displayed on the structure that the service really executes.
This produces an interesting alignment between several representations:
architecture = runtime topology = trace mapOf course, the equality is not literal. A trace contains timing data, concrete calls, and failures, while the architecture describes possible paths. But the shared topology is no longer an approximate illustration.

So far, I have described capabilities that I am already testing in Open Service Architect. Domain components remain a direction for future development. Multiple execution languages have since become a working experiment: the same architectural model can now generate Go, Python, C++, and Rust services. I describe what remained shared—and what deliberately stayed native—in a follow-up engineering case study.
The Next Level Is Business Meaning, Not Operators
The graph approach has an obvious risk: replacing textual code with visual code. When there are six nodes, the diagram is immediately readable. When there are two hundred, it becomes visual spaghetti that is no better than a large source file.
Operators alone are therefore not enough. We need composition, hierarchy, and reusable domain components.
For example, a ReserveInventory operation may internally consist of input validation, an inventory lookup, reservation, and response mapping. From the outside, however, it should look like a single component with explicit ports:
- a reservation command;
- the successful result
Reserved; - the business error
Unavailable; - the technical error
InventoryFailure.
At the next level, operations like these form PlaceOrder: validate the order, reserve inventory, calculate the price, authorize payment, and persist the result.
Generated code and the runtime still form the foundation of this hierarchy. Above them are operators as executable primitives, then the stream graph, domain components, and finally the application topology. Each successive layer hides technical details without becoming detached from the actual implementation.

The benefit for AI becomes especially clear here. Names such as Map and FlatMap describe a mechanism but say almost nothing about intent. ReserveInventory and AuthorizePayment give the agent domain context before it reads any implementation.
The stream becomes the mechanism; the domain component becomes the unit of architecture.
Architecture Should Not Depend on the Execution Language
If the graph truly describes the architecture, the next question is: why should it be tied to one language?
The same operation might run:
- in Go inside an ordinary backend service;
- in Python next to an ML model or research pipeline;
- in C++ when microseconds, codecs, or native libraries matter.
- in Rust when memory safety and predictable performance matter.
This does not mean that an implementation can be moved mechanically between languages while preserving identical operational characteristics. Each runtime has its own libraries, concurrency model, memory management, and diagnostic tools.
But the architectural contracts, topology, validation rules, and observability model can remain shared. The language is then chosen to fit the problem rather than being dictated by the way the system is described.

Where This Approach May Fail
It would be dishonest to end with a beautiful diagram. An architectural IR raises difficult questions, and some of them do not yet have complete answers.
How do we avoid visual complexity? We need subgraphs, domain components, collapsible detail, and sound boundary rules. Without them, a graph quickly stops being useful.
Where is the boundary between generated and handwritten code? If regeneration overwrites a developer's changes, people stop trusting the tool. The ownership zones must be explicit: infrastructure code belongs to the generator; business functions and extensions belong to the developer.
How do we work with existing systems? A full migration is rarely realistic. A more practical approach is to describe one integration boundary as a graph and gradually wrap existing pieces in typed components.
Will the DSL become another form of vendor lock-in? The more standard the output remains—ordinary code, an ordinary binary, OpenTelemetry, standard protocols—the lower the risk. But it never disappears completely: the architectural model itself becomes a dependency.
Can it express everything? Probably not, and that is fine. The system must retain an escape hatch into ordinary code. The value of a constrained language lies precisely in the fact that it does not try to describe every possible construct.
A Final Thought
I believe software development is moving away from manually editing every file and toward working at a higher level. AI accelerates this transition, but it also exposes a limitation in the current model: an agent can write code faster than it can understand a large system.
Larger context windows will help, but they will not eliminate the need to reconstruct architectural intent from implementation every time.
This is why I am interested in a shared architectural representation—a layer between intent and code that both humans and AI can use. A human sees the structure and makes decisions. An agent receives compact context and clear boundaries. A validator checks invariants. A generator creates a repeatable implementation. The runtime connects the same model to observability.
Open Service Architect is my attempt to test this idea in practice with typed streams and generated services for Go, Python, C++, and Rust.
The MVP I use to explore these ideas is available in gorundebug/servicelib. It implements only part of the approach described here, but the typed graph already serves as an executable architectural description: it generates the service together with small, local SDD tasks for an AI agent, each with a ready-made contract and the boundaries of a specific change.
But the question is broader than any particular tool:
If AI becomes a first-class participant in software development, should source code remain the only language we use to explain the system to it?