Skip to content
·[architecture]·8 min read

The architecture I drew before any AI agent wrote a line

AI-directed architecture: the strict downward-only package layering I fixed before any agent wrote a line - and why it held when the build's own tooling got refactored.

Before any agent ran, before git init, I drew a layer graph on a whiteboard. Not a Jira ticket. Not a README. A dependency direction - an arrow that said: packages flow downward only, and nothing in a higher tier imports from a tier below it.

That rule was the first and load-bearing decision in a stack of guardrails: agent briefings, the workspace resolver, typecheck, a verifier role, and tests at every boundary. Together they let eight specialized agents stand up a React Native game engine in a focused April 2026 sprint, then keep its structure sound across the month-plus of continuous growth since. The other guardrails matter, and I get to them below. But they all sit on top of one decision made before any of them existed: the AI-directed architecture I fixed before the agents started.

The proof that the layering was the keystone and not just decorative came weeks later, when the enforcement tooling itself was refactored out from under the engine. The dependency graph did not move.

AI-directed architecture starts before the code

My previous post - the React Native game engine gap - ended with a public promise: "the architecture decisions I made before any code, why a strict layering with downward-only dependencies is the load-bearing piece, and how it survives an AI-directed build at all." This is that post.

The engine began as a layer graph on paper, not a repo. Every package had a tier assignment - a number that determined what it could and could not import. The rule was not "try to keep dependencies pointing in the right direction" (vague, unenforceable). It was "any package in tier n may import only from packages in tiers 0 through n-1". A violation fails at pnpm install - no special linter needed, no agent-side discipline required.

An earlier post in this series - how I directed agents to build the engine - established the frame: I wrote the rules the agents had to follow. What that post called "five strict layers" was the architectural model as drawn before the build. This post is the measured, current picture - and explains why the numbers differ without contradicting each other.

The measured 7-tier dependency graph

I drew roughly four or five layers before writing code. The build filled them. Then the engine kept growing - more packages, more modules - and the measured longest-path depth of the dependency graph deepened to 7 tiers.

The depth changed. The rule never did: still downward-only, still zero cycles.

That an engine's layer graph deepens as it grows is normal. That it stayed acyclic through an AI-directed build, a tooling refactor, and continued package growth - that is the point.

Here is the measured 7-tier spine, built from the actual package.json dependency scan run on 2026-05-17. Every arrow points one way. There are 0 cycles.

100%

Drawn as ~5 layers before any code; 7 tiers after the engine grew. Downward-only throughout. 0 cycles confirmed by the 2026-05-17 dependency scan.

The earlier build post described "five strict layers" with scene at Layer 3 - that was accurate for the snapshot when it was written. As the engine gained more packages (including debug, tilemap, the analytics and leaderboard adapters, the React bridge), the measured longest path deepened. scene now sits at tier 4. Same package, one tier deeper, because the graph below it grew. That is normal engine maturation. The downward-only rule is unchanged.

The design-doc taxonomy in roadmap.md uses named conceptual bands (Foundation, Runtime, Systems, Persistence, Features, UI, Integration, Tooling) - about nine of them. Those are human-readability labels, not the structural metric. The structural metric is the measured graph depth: 7.

The contrast pair: ecs at tier 1 vs scene at tier 4

The rule reads plainly in package.json. ecs sits low in the graph - it may only import tier-0 utilities:

// packages/ecs/package.json - tier 1 runtime core
{
  "name": "@flare-engine/ecs",
  "dependencies": {
    "@flare-engine/events": "workspace:*",
    "@flare-engine/math": "workspace:*"
  }
}

events and math are both tier 0. ecs imports nothing above itself. If any agent had tried to introduce a scene import here - scene sits at tier 4 - the resolver would refuse.

scene sits higher because it orchestrates many systems. It may import from tiers 0 through 3:

// packages/scene/package.json - tier 4 (an earlier post called this "Layer 3" by design name; same package, measured tier 4 after the engine grew)
{
  "name": "@flare-engine/scene",
  "dependencies": {
    "@flare-engine/core": "workspace:*",
    "@flare-engine/ecs": "workspace:*",
    "@flare-engine/events": "workspace:*",
    "@flare-engine/math": "workspace:*",
    "@flare-engine/render": "workspace:*"
  }
}

Every one of those dependencies sits in tier 3 or below. The imports are legal. A reverse import - render pulling from scene - would not be.

The earlier post labeled scene "Layer 3"; here it is tier 4. That is not a contradiction - it is the same package one tier deeper, because the engine grew more foundation packages beneath it after that post was written. Following the link back from this post to the earlier one, you are seeing the same structure at two moments in its growth, not a number that was wrong.

Four detection points on one illegal import

The agent does not need discipline; the graph carries it. Here is what happens when an agent tries to introduce an upward import:

Gate 1 - the agent briefing. The current engine-builder agent file states:

// .claude/agents/engine-builder.md - current file; this rule predates the tooling refactor
## Constraints
 
- No features beyond the API spec.
- No `any`, `as any`, `@ts-ignore`.
- No default exports.
- No allocations in hot paths (game loop, render, physics tick, particle update).
- No imports from higher layers.
// ... (git-commit guard bullet omitted)

This is the earliest gate. The agent reads the briefing before generating any code. The constraint is explicit, not implicit.

Gate 2 - the workspace resolver. pnpm enforces workspace boundaries. A package that declares no dependency on another workspace package cannot import from it. An upward import would require adding the reverse dependency to package.json, which the verifier would catch in review.

Gate 3 - the TypeScript typecheck. Even if the package.json dependency somehow appeared, TypeScript's project references plus noUncheckedIndexedAccess would surface the violation at typecheck time. The CI gate does not pass without a clean typecheck.

Gate 4 - the verifier review. The verifier agent (one of the eight roles that ran the original April 2026 build sprint) reviewed every diff for structural violations before merge. The graph was the checklist; the verifier's job was to confirm the diff did not break it.

Four detection points, each independent. The rule does not require any single gate to be perfect - they overlap. That redundancy is what made it safe to run eight concurrent agents against the same monorepo.

The test the build did not plan for

Around mid-May 2026, the agent and skill tooling used during the build was refactored in two steps. The .github/ directory - which held the eight-role agent set (architect, engine-builder, engine-planner, engine-researcher, game-builder, game-planner, game-researcher, verifier) plus 38 skill files - was collapsed. The replacement is a much smaller .claude/ set: 2 agents (engine-builder, game-builder) and 3 skills (create-flare-app, flare-game-dev, flare-tier2-stubs).

The enforcement scaffolding shrank from 38 skills and 8 agents to 3 skills and 2 agents. The layer rule is still present - the current engine-builder.md still says "No imports from higher layers" - but the infrastructure around it is a fraction of what ran the build.

The dependency graph did not move.

No packages changed tier. No cycles appeared. The 7-tier graph is identical before and after the refactor.

That is the natural experiment. If the architecture had been held together by the tooling - by the specific combination of agent roles and skill files - it would have shifted when the tooling changed. It did not. The architecture was the load-bearing piece. The tooling was scaffolding.

Limits

n=1, single operator. This is one build, one engineer directing it. There is no team study, no A/B control. The METR 2025 randomized controlled trial found experienced open-source maintainers were ~19% slower with AI assistance on their own repos, despite feeling faster. The Stack Overflow 2025 Developer Survey found 46% of developers actively distrust the accuracy of AI tools. I am not claiming this generalizes; I am claiming it worked here and explaining why I believe the architecture was the reason.

The graph catches structural debt, not logic bugs. An acyclic dependency graph proves nothing about whether a system works. It proves only that the import topology is legal. A tier-1 ECS package can still be algorithmically wrong. The ~2,468 tests in the engine exist for that second job - they are not replaced by the layer graph, only complemented by it.

Period-scoped claims. "Eight specialized agents" describes the roster during the original April 2026 build sprint. That is the number the earlier build post published; it is accurate for that period. Today the .claude/ set is 2 agents and 3 skills. Likewise the "12-day build" is that original sprint (first commit 2026-04-17, phase-11 close 2026-04-28); the engine has been in continuous development since and, as of this writing, is over a month old and still growing - which is exactly why the dependency graph deepened from five to seven tiers. Twelve days is the origin, not the lifetime. Both are true at their own moments; reading the two posts together is reading two snapshots, not a correction.

The engine is not open source yet. The open-source release is targeted for October 2026. Readers cannot inspect the full package.json graph themselves yet - the snippets and diagram here are the public record. The plan is to open-source at that milestone; at that point the graph will be inspectable.

The diagram is a simplification. It shows tier-to-tier flow, not every intra-tier or peer-dependency edge. It represents the spine of the graph; the full graph has more edges than the diagram shows.

The rule pays off earlier than you think. You do not need 36 packages for this to matter. The downward-only constraint starts enforcing useful structure at around six or eight packages in a monorepo. An RN team building a data-heavy dashboard - not a game - with a few shared packages (ui, api, auth, analytics, store, app) gets the same enforceability from the same package.json discipline. The layer names change; the rule does not.

Close

The one constraint you can codify in your own monorepo this week: assign every package a tier number. Put it in the package name, a comment, or a flare.tier field in package.json. Then check, in CI or in code review, that no package imports from a tier above it.

That is the whole rule. It does not require a framework, a special tool, or an agent. It requires deciding the direction before you start, and encoding the decision where the resolver will enforce it.

The first post in this series describes what the agents built. The second describes why that gap in the React Native ecosystem needed filling at all. This post describes the constraint that made both possible.

The next post in the series covers the rest of that guard stack: how the agents themselves were structured - the skill files, the briefing pattern, the verifier role, and the instruction set that let eight roles coordinate without stepping on each other's work. The layer rule was the keystone; the next post is what's built on it.

Related