allanlotta
~/blog/agent-native-software

Agent-native software

We built frameworks because writing code was expensive. We built SaaS infrastructure because operating servers was expensive. AI is making both dramatically cheaper.

So why do we still design software as if every line and every server operation had to be performed by hand?

Maybe the AI-native stack is not more abstraction. Maybe it is less.

We spent a decade optimizing developer experience. The next decade is about agent experience. Agent-native does not mean software written with AI. It means software whose architecture, infrastructure, security and operations were deliberately designed so an agent can understand and run the whole system with little ambiguity and little human dependency.

Context is the scarce resource

For a human, writing code is expensive. For an agent, generating code is cheap. What is expensive now is the context needed to change the system correctly.

Take two applications that do the same thing. The first one is:

HTML
CSS
TypeScript
Bun
Postgres

The second one is:

Next.js       Prisma      Clerk
React         tRPC        Supabase
React Query   Zod         Vercel
Zustand       Tailwind    Sentry
shadcn                    PostHog

The second probably has less code written by the company. It also asks an agent to understand many more concepts before it can change anything safely.

Lines of code stopped being a good measure of complexity.

Semantic surface area

Call it the amount of knowledge an agent has to load to make a change correctly: files, dependencies, framework APIs, conventions, configuration, jumps between abstractions, external services, implicit rules.

A feature that requires understanding

page → component → hook → provider → service
     → repository → ORM → external service

has a much larger surface than

customers.ts → db.ts

even when the second has more lines. The goal of an agent-native architecture is to minimize that surface without giving up correctness, security or real product requirements.

So: minimize concepts, not code. Fifty explicit lines can beat fifteen lines that depend on five abstractions. In an AI-native world, code can be cheaper than abstraction.

Locality, and no magic

A change should touch few places. One feature, one to three files. Files are context boundaries, and the names should reveal the architecture:

src/
  app.ts        api.ts       state.ts     types.ts
  pages/        dashboard.ts customers.ts reports.ts
  components/   table.ts     modal.ts
server/
  server.ts     db.ts        auth.ts
AGENTS.md

Agents work best when cause and effect are close. Prefer

router.get("/customers", getCustomers);

to behavior you can only discover by chasing decorators, plugins, metaprogramming or conventions scattered across the project. Magic saves typing and spends understanding.

Then reduce the number of ways to do the same thing. Not useState and Redux and Context and Zustand and signals and React Query and custom stores, all at once. One decision, written down:

HTTP          → api.ts
Shared state  → state.ts
Pages         → pages/
Reusable UI   → components/
Database      → db.ts

Less architectural freedom can produce more agent autonomy.

Platform first, dependencies second

Before installing anything, ask whether the platform already solves it. HTML, CSS, DOM APIs, ES modules, fetch, URL, FormData, WebSocket, JSON, HTTP. On the server: files, processes, SQL, cron, logs. On infrastructure: Linux, SSH, systemd, Docker, Caddy, PostgreSQL.

These have a property that matters enormously for agents: they are stable, textual, predictable and universally understood.

Every dependency adds a new language to the system — an API, configuration, versions, docs, breaking changes, advisories, edge cases, implicit behavior. So the rule is not "every project needs this". The rule is: a dependency must remove more complexity than it introduces.

This is not an argument that React, Next, Supabase or Vercel are bad. They solve real problems. The change is that they stop being defaults and start having to justify themselves. If we removed this framework, would the system actually get harder for an agent to operate? If not, we probably do not need it.

The same goes in reverse: zero dependencies is also a dogma. A mature crypto library is safer than your own. A good charting library saves weeks. Use dependencies deliberately, not culturally.

Source code is an interface for the agent

Never minify source to save tokens. This

export async function getCustomer(id: string) {
  const response = await fetch(`/api/customers/${id}`);

  if (!response.ok) {
    throw new Error("Failed to load customer");
  }

  return response.json();
}

costs more tokens than a one-line minified version and is worth every one of them, because it is less ambiguous. Well named, formatted, typed, explicit. Minification belongs to the production artifact, not to the code the agent has to read. Formatting is semantic information.

Documentation follows the same logic: a map, not a book. One AGENTS.md, fifty to a hundred lines, saying what the architecture is, where things live, what the rules are and which commands verify the work. It is effectively the repository's system prompt.

Tests are the agent's senses

For autonomous development, tests stop being only regression protection. They become the sensory system. The loop is:

understand → modify → typecheck → test → run
           → inspect → fix → repeat

A project has to let the agent find out on its own that it was wrong. That means simple commands

bun typecheck
bun test
bun run security
bun run build

and equally simple results.

TYPECHECK PASS
TESTS PASS
SECURITY PASS
BUILD PASS

Autonomy is not the absence of constraints. Autonomy comes from reliable feedback.

Own the simple. Rent the hard.

Modern engineering did not only add frameworks. It turned infrastructure into subscriptions. A small application is often born with eight vendors before it has any real scale — and with them eight accounts, eight SDKs, eight auth models, eight docs sets, eight pricing pages, eight failure modes.

The complexity did not disappear. It moved outside your repository.

External dependency is an architectural decision with four costs: money, context the agent has to carry, coupling to proprietary APIs, and another company inside your critical path. An agent-native stack minimizes economic dependency surface too.

So: own the simple, rent the hard. Or more precisely, rent complexity, not convenience.

Probably own            Probably rent
────────────────        ────────────────
application server      payments
database                DDoS mitigation
cache                   KYC
cron jobs               SMS
background workers      email reputation
static files            bank integrations
basic monitoring        enterprise identity

Stripe is the good example. You are not paying for POST /payment. You are paying for card networks, fraud prevention, PCI, chargebacks, compliance and global payment rails. That is real complexity. Cloudflare in front of the app is the same argument: an edge network and DDoS mitigation you should never build. Rent adversarial or specialized complexity.

What is left can be boring, and boring is the point:

Internet → Cloudflare → Caddy → Bun app → PostgreSQL

On a single VPS. Maybe with Docker. Maybe without, depending on the system.

Security cannot live in the prompt

This is the part that is not negotiable. Agent-native does not mean giving root to a model and asking it to be careful.

Never use prompts as security boundaries.

Security has to exist below the agent, in the infrastructure: only 443 public, PostgreSQL not exposed, SSH with keys and no root login, the app running as its own user, a database role that is not superuser, migrations under a separate role, secrets outside the repository.

And it should be executable. A project can have bun run security that checks what a machine can check:

✓ application is not running as root
✓ PostgreSQL is not publicly exposed
✓ no secrets committed
✓ security headers present
✓ authorization and authentication tests pass
✓ SQL injection tests pass
✓ backup exists and restores

Then security stops being a documentation page and becomes machine-verifiable behavior. The same applies to backups: an unverified backup is hope. An agent can dump production, restore into a temporary instance, run integrity tests and report RESTORE PASS, on a schedule. What a human does occasionally becomes continuous.

Agents are attack surfaces

This is the genuinely new risk. An attacker sends an HTTP request, the request lands in a log, the agent reads the log. Same for tickets, emails, documents, issues, web pages, user input. Content that looks like instructions arrives through data.

Untrusted data must never become trusted authority.

Which leads to the sentence I would keep if I could keep only one: the agent makes decisions, the infrastructure defines authority. A monitoring agent gets read-only. A deploy agent gets deploy, restart, rollback. A migration agent gets migration permissions. A backup agent gets read, write backup, restore into an isolated database. No single agent needs root, DNS, production superuser, GitHub owner and every secret.

The more autonomy we give, the more least privilege, isolation, rollback, immutable backups, audit logs and approval gates matter. The goal is not to limit productivity. It is to limit the damage of one mistake. Autonomous does not mean omnipotent.

The two tests

Before adding a library, framework or service:

  1. What concrete problem does this solve?
  2. Is there a platform primitive that solves it well enough?
  3. How many new concepts does it add?
  4. How much operational surface — configs, tokens, dashboards?
  5. What does it cost today, at 10x, at 100x?
  6. How hard would it be to remove?
  7. Could an agent reliably operate the simpler alternative?
  8. Are we renting real security or just convenience?
  9. What happens when the vendor is down?
  10. Does it remove more complexity than it introduces?

And before adding an abstraction, one question: will this reduce the amount of context a future agent needs in order to change this system correctly? If not, we are probably just moving complexity around.

Refactoring toward this

An agent asked to make an existing project agent-native looks for single-use abstractions to inline, wrappers around wrappers to flatten, trivial dependencies to replace with platform APIs, competing state systems to consolidate, implicit behavior to make explicit, deep directory trees to flatten, SaaS used for convenience to evaluate for self-hosting, fragile tests to make deterministic, long documentation to compress into an operational map.

With one constraint above all others: preserve behavior first. Agent-native is not a license to rewrite.

The measurement this needs

To stop being an opinion, the manifesto needs a number. Something like agent modification cost:

tokens consumed + files inspected + files modified
+ dependencies consulted + tool calls
+ test iterations + time to verified result

Run the same tasks against a Next-plus-managed-services architecture and against an agent-native one, and compare. I do not have that dataset yet. I think it is the most interesting benchmark nobody is publishing.

The directive

The operational half of this fits in a repository. This is the block I would drop into AGENTS.md:

# Agent-Native Software Directive

This repository is designed primarily for autonomous AI
development and operation.

The goal is NOT to minimize lines of code. The goal is to
minimize the context, concepts, dependencies and external
knowledge required to safely understand, modify, test,
deploy and operate the system.

Principles
- Minimize semantic surface area.
- Prefer platform-native primitives.
- Prefer explicit behavior over implicit behavior.
- Prefer locality over distributed abstractions.
- Prefer one obvious implementation path.
- No abstraction used once without strong reason.
- Every dependency must remove more complexity than
  it introduces.
- Make architecture discoverable from filenames.
- Use tests as feedback for autonomous agents.
- Make validation deterministic.
- Keep documentation short and operational.
- Optimize source for comprehension, not size.
- Do not adopt frameworks because they are defaults.
- Own simple infrastructure. Rent specialized,
  adversarial or regulatory complexity.
- Never use prompts as security boundaries.
- The infrastructure, not the model, defines authority.

Validation
Every meaningful change ends with typecheck, test,
security and build. A change is not complete because
code was generated. It is complete when behavior has
been verified.

Five sentences

Code is becoming cheap. Context is becoming expensive.

Own the simple. Rent the hard.

Every abstraction must reduce more complexity than it introduces.

The agent makes decisions. The infrastructure defines authority.

Build systems small enough for an agent to understand, operate, test and repair end to end.