Building an Ada Compiler with an Autonomous AI Development Loop

I have recently been working on adac, an Ada compiler written in Ada.

The compiler currently supports only a small subset of Ada 2022. It can compile a minimal procedure through a separated frontend, abstract syntax tree, semantic analysis, custom intermediate representation, and native backend. The native backend currently produces programs for x86-64 Linux and FreeBSD.

That description is technically accurate, but it misses the part of the project that interests me most.

For me, adac is not only an attempt to build another Ada compiler. It is also an experiment in autonomous AI-assisted software development.

The central question is this:

Can an AI agent make coherent, long-term progress on a systems project when the repository itself defines the architecture, engineering rules, validation requirements, work-selection policy, and stopping conditions?

This is different from asking an AI to generate a compiler from a large prompt. It is also different from casually accepting generated code until something appears to work.

The goal is not to remove engineering discipline. The goal is to encode enough of that discipline into the repository that an AI agent can operate within it.

The Repository as an Operating Contract

Most software repositories contain source code, tests, and perhaps a roadmap. In adac, the repository also defines how development work must proceed.

The project documentation describes architectural boundaries, ownership rules, failure categories, supported targets, validation requirements, and milestone completion criteria.

The AGENTS.md file defines an autonomous development loop. A general implementation instruction can start a sequence roughly like this:

  1. Inspect the working tree, recent commits, roadmap, and relevant documentation.
  2. Run the complete repository checks to establish a known baseline.
  3. Find the earliest unmet roadmap prerequisite.
  4. Select the smallest complete work item that advances that prerequisite.
  5. Define the contracts, success criteria, affected boundaries, and required refactoring.
  6. Update documentation before implementation when a contract changes.
  7. Implement the change as a complete vertical slice.
  8. Add positive and negative tests.
  9. Run focused tests and then the full validation suite.
  10. Review the complete diff and commit only a coherent, passing change.
  11. Reassess the roadmap and continue with the next work item.

The agent is not supposed to stop after producing a plan when implementation has already been authorized. It continues until it reaches a documented stopping condition.

Those stopping conditions are also explicit. Work should stop when, for example, an architectural decision cannot be derived safely from existing contracts, a destructive operation requires approval, user changes overlap with the selected task, or the environment cannot validate the result.

This matters because autonomous development without stopping rules is not really autonomous engineering. It is merely unsupervised modification.

Not “Generate a Compiler”

A compiler is a useful subject for this experiment because it resists shallow implementation.

A parser accepting some syntax is not enough. A language feature may need to pass through several distinct stages:

source
  -> lexer
  -> parser
  -> AST
  -> semantic analysis
  -> custom IR
  -> backend
  -> executable

A feature is incomplete if it exists only in the parser.

It must also be represented correctly, validated, lowered, emitted, tested, and rejected properly when the source program is invalid. Internal compiler errors must remain distinguishable from ordinary source diagnostics. Unsupported targets must fail before producing misleading output. Partial build products must not be published as successful results.

These requirements make it harder for an agent to create the illusion of progress through disconnected code.

The repository explicitly requires vertical feature slices. Each implemented feature must cross every compiler stage it needs, together with diagnostics and tests.

That rule is intended to prevent a common failure mode of AI-generated systems code: many plausible-looking components that do not form a reliable system.

Recent Work: Infrastructure Before Features

The visible Ada subset remains intentionally small.

At the moment, adac can compile a minimal procedure containing simple statements such as null and return. This is nowhere near enough for practical Ada programs, and I do not present it as a usable general-purpose compiler.

Recent development has instead concentrated on the infrastructure required for safer expansion.

That work includes:

  • explicit compilation contexts;
  • context-owned diagnostic state;
  • stable identifiers for syntax nodes, symbols, and semantic entities;
  • source spans attached to syntax objects;
  • validation at compiler-stage boundaries;
  • bounded source input;
  • bounded AST node creation;
  • bounded interned-symbol growth;
  • controlled rejection of malformed or excessive input;
  • deterministic tests;
  • rules for recovering interrupted autonomous work.

These are not exciting user-facing features. They do not make an impressive demonstration program.

However, they address the kinds of problems that become expensive later: unclear ownership, process-global state, unbounded memory growth, invalid internal representations, accidental partial output, and development sessions that cannot be resumed safely after interruption.

For an autonomously developed system, this foundation may be more important than rapidly increasing syntax coverage.

An AI agent can add features quickly. The harder problem is ensuring that each addition leaves the repository in a state that another agent invocation can inspect, understand, validate, and continue.

Resource Limits Are Part of Correctness

One recent development area has been explicit resource limits.

The compiler now has bounded policies for source characters, AST nodes, and distinct interned symbols. These limits belong to the compilation context rather than being hidden global behavior.

This may sound like premature hardening for such a small compiler, but it serves several purposes.

First, source code must be treated as untrusted input. A malformed or adversarial source file should not be able to grow internal structures without limit.

Second, resource exhaustion needs a defined failure path. It should not become an allocator crash, an invalid compiler state, or a partially published output file.

Third, explicit limits make behavior testable. Tests can use deliberately small budgets and verify that the compiler fails at the correct boundary without corrupting its internal stores.

Finally, resource contracts give the autonomous agent a clearer framework. Instead of adding vague defensive checks wherever a failure seems possible, it must preserve a documented policy with defined ownership and observable behavior.

The Human Is Still Responsible

Calling this autonomous development does not mean that the human disappears.

I still define the long-term direction. I decide which product goals matter. I review architectural choices, adjust repository contracts, and stop or redirect the work when necessary.

The experiment is not whether an AI can independently decide what software should exist.

The experiment is whether an AI can perform a meaningful portion of the engineering loop without requiring a human to select and supervise every individual edit.

A useful autonomous agent should be able to:

  • inspect the current state rather than relying on stale conversational context;
  • choose work from documented prerequisites;
  • recognize when refactoring is structurally necessary;
  • update contracts and implementation together;
  • validate the complete result;
  • commit only coherent changes;
  • recover after interruption;
  • stop when it lacks authority to make a decision.

This is a narrower claim than “AI can build software by itself,” but it is also more concrete and testable.

Why Ada?

Ada is an interesting language for this project for several reasons.

It encourages explicit interfaces, strong typing, constrained representations, and careful reasoning about correctness. Those properties are useful in compiler implementation, but they are also useful when trying to constrain AI-generated changes.

Ada also has a mature language specification and a community that tends to care about precise semantics, portability, safety, and long-term maintainability.

That makes it difficult to hide behind a successful toy example. If adac grows into a useful compiler, it will need more than syntax recognition. It will need dependable semantic analysis, validated intermediate representations, correct runtime behavior, defined ABI boundaries, and disciplined support for multiple targets.

The language therefore fits the development experiment unusually well.

Long-Term Direction

The long-term goal is a self-hosting, cross-platform Ada compiler with its own target-independent intermediate representation.

The roadmap includes:

  • broader Ada 2022 frontend coverage;
  • scalar types, expressions, control flow, and subprogram calls;
  • typed and control-flow intermediate representations;
  • dependable native backends;
  • packages and separate compilation;
  • composite types and storage models;
  • exceptions, generics, and tagged types;
  • staged self-hosting;
  • additional targets, including WebAssembly;
  • an optional LLVM backend;
  • tooling such as a style checker and formatter.

The roadmap is capability-based rather than date-based. A milestone is complete only when its contracts, implementation, validators, diagnostics, and tests satisfy the documented exit criteria.

That distinction is important. Autonomous development should not optimize for producing a large number of commits or checking off superficial feature names. It should optimize for verified capability.

What I Am Actually Testing

It is too early to claim that this method works.

The compiler remains small, and many of the most difficult language features are still ahead. Name resolution, overload resolution, packages, generics, exceptions, storage management, separate compilation, ABI details, and self-hosting will put much greater pressure on both the architecture and the development process.

The interesting failures may not be obvious code-generation mistakes.

They may appear as:

  • architectural drift across many individually reasonable commits;
  • documentation that no longer matches implementation;
  • tests that validate examples rather than contracts;
  • local fixes that damage long-term maintainability;
  • over-refactoring without enough evidence;
  • under-refactoring to avoid large diffs;
  • autonomous loops that continue when they should stop;
  • agents that cannot distinguish unfinished work from completed work.

Those are exactly the problems I want the repository-defined process to expose.

adac is therefore two projects at once.

One is an Ada compiler.

The other is an attempt to discover how much engineering judgment can be expressed as repository state, executable validation, and explicit operating rules for an AI agent.

The compiler will be the visible result.

The development history may be the more interesting artifact.

The source code is available on GitHub:

github.com/hodong-kim/adac