✓ Human-authored analysis; AI used for formatting and proofreading.
I'm one developer building an open-source cloud security reasoning engine called Stave. One person directing AI coding agents to generate and maintain the entire codebase.
The conventional wisdom says cognitive debt is a team problem — fragmented understanding across multiple developers who can't keep up with AI-generated code. But I'm one person. I still accumulated cognitive debt that nearly paralyzed the project.
That experience is the strongest evidence I have that cognitive debt is structural, not organizational. It's not caused by poor communication between developers. It's caused by AI generating code faster than any human — including the human who prompted it can assimilate.
The 10,000-line peak
By the time I'd been building Stave for several months, the codebase had grown past 10,000 lines of Go. The AI had generated the evaluation kernel (CEL predicates against configuration snapshots), a control catalog (2,650+ controls at the time), 109 JSON Schema sub-schemas for asset validation, and the bloated conflict detection analyzer.
The conflict analyzer was supposed to detect contradictions in the control catalog: cases where one control's predicate logically conflicted with another's. It sounded architecturally elegant. It took several rounds of AI-generated code to get close to working. Each round fixed edge cases the previous one missed. Each round added complexity. Each round produced code I reviewed but never fully internalized.
That's the dynamic the cognitive debt articles describe: the AI delivers the solution, and the developer skips the cognitive struggle necessary to build a robust mental model. I approved every change. I tested every edge case. And by the end, I couldn't confidently explain what the analyzer did.
Because I never fully knew. The AI generated the logic. I evaluated the output. The output looked correct. But the theory of the system — what Peter Naur calls the program as a theory that lives in the developer's mind was never fully formed. The AI bypassed the construction process that would have built it.
The conflict analyzer was 5,726 lines. It had its own internal dependency graph, its own test infrastructure, its own failure modes. I could tell you what it was supposed to do (detect control conflicts). I could tell you that the tests passed. I could not tell you how it handled the interaction between deny-override semantics and compound predicate decomposition in the presence of implicit asset-type constraints. The AI could tell you. I could not. That's cognitive debt.
The moment I realized the problem wasn't the analyzer's correctness but the analyzer's existence, I deleted it. One commit. 5,726 lines removed. Two design documents survived: catalog-authoring-as-service.md (the feature belongs in a separate service, not the kernel) and analyzer-construction-lessons.md (what building it taught about predicate analysis). The documents captured the intent. The AI-generated code was disposable.
The system got sharper, not smaller. And I could explain every remaining line's purpose. Because the remaining system was small enough to hold as a coherent theory.
The three mechanisms
The conflict detection deletion was the first move in a pattern that eventually reduced the codebase by 90%. The pattern has three mechanisms.
Mechanism 1: Delete what isn't the core
The conflict analyzer wasn't the core. The core is: evaluate a configuration snapshot against a catalog of CEL predicates and produce typed findings. Everything else is a candidate for deletion.
The question is "does this feature serve the essence?" The conflict analyzer served catalog-authoring quality, not security evaluation. It belonged in a separate service, not in the kernel.
When the AI generates code that works and passes tests, deletion requires overriding the sunk-cost instinct. Round after round of AI-generated code, each reviewed, debugged, producing a working improvement and the right move was to delete all of it. The AI made it easy to produce. That same ease made it hard to let go. The code existed, it worked, it felt like progress. But code that doesn't serve the essence is negative progress: it's cognitive load without cognitive value.
Mechanism 2: Delegate to systems that do it better
For every capability the kernel needs but shouldn't implement, find the system that already does it better and export to it.
The most consequential delegation was the predicate engine itself. Early versions of Stave used a custom-built predicate evaluator. AI-generated, functional, and a constant source of edge cases. Replacing it with Google's Common Expression Language (CEL) library eliminated thousands of lines of custom evaluation logic and replaced them with a production-grade, well-tested expression engine maintained by Google. CEL gave Stave a predicate language that cloud engineers can read, that's fast enough for CI, and that's expressive enough for both atomic and compound safety invariants. The entire specification layer rests on this library. The kernel would not be 1,030 lines without it.
CEL also proves something broader about the delegation mechanism: a module that solves a problem domain-independently, validated at Google scale for Kubernetes admission control and Firebase security rules, transfers directly to cloud security invariants without modification. CEL doesn't know about S3 buckets or IAM roles. It evaluates expressions against structured data. The domain independence is the reusability. Google validated the expression engine at planetary scale. Stave inherits that validation for free by reusing the module rather than reimplementing the capability. Every line of custom predicate evaluation code the AI generated was an inferior, unvalidated reimplementation of a problem Google already solved.
This isn't a new insight. David Parnas established in 1972 that the right criterion for decomposing systems into modules is information hiding. You don't care about the implementation inside the module, you care about the interface. Unix proved it at scale: small tools, standard interfaces (pipes, text streams, everything-is-a-file), composable without understanding each other's internals. grep doesn't know about sort. sort doesn't know about uniq. They compose through stdin/stdout — the interface, not the implementation.
Stave's architecture is Parnas applied directly. CEL is the predicate interface. Stave doesn't care how CEL evaluates expressions internally. JSONL, SMT-LIB, SARIF are the output interfaces. Downstream tools don't care how Stave produces findings. The kernel talks to modules through interfaces. The modules are replaceable. The interfaces are stable.
What AI-era development adds to Parnas: the implementations behind the interfaces are now AI-generated, which makes information hiding more important. When a human writes the module internals, they at least understand what they built. When AI generates the internals, nobody fully understands them. Parnas says that's fine, as long as the interface contract holds. The AI era doesn't invalidate the 1972 paper. It makes the paper's argument urgent. The interface is the understanding. Everything behind it is a detail you delegate — to Google, to Steampipe, to Z3, or to the AI.
Data collection? Steampipe has 140+ cloud plugins, maintained by dedicated teams. Stave doesn't collect cloud data. It imports snapshots. Zero collection code in the kernel.
Formal reasoning? Z3, Soufflé, Clingo, Prolog, and PRISM are mature reasoning engines with decades of research. Stave doesn't implement a solver. It exports standardized facts and lets external engines reason over them. Zero solver code in the kernel.
Graph analysis? Neo4j, NetworkX, igraph handle graph algorithms at scale. Stave exports RDF/JSON-LD for graph analysis. Zero graph-algorithm code in the kernel.
Each delegation removed capabilities the AI could have generated and generated to a functional level. That's the counterintuitive part: the AI is capable of producing a graph traversal algorithm or a solver integration that works well enough for experimentation. The problem is that "smart intern level" code becomes cognitive debt the moment it lands in the production codebase. Every AI-generated module is a module I must understand, harden, and reason about. Delegating to external systems eliminates that debt entirely. The capability exists at production quality, I use it via standard formats, and zero lines of intern-level solver code live in my codebase.
Mechanism 3: Standardize outputs to eliminate adapter code
Custom integration code is the silent codebase killer. Every tool you integrate with wants a custom adapter. The AI can generate adapters. A Slack adapter, a Neo4j adapter, a Confluence adapter, a Jira adapter. Each one is 200-400 lines and correct. Each one is cognitive debt: code I didn't write, don't fully understand, and must maintain when the target API changes.
The alternative: standardize the output formats so tools integrate with you.
Custom approach: Standardized approach:
Slack adapter (200 lines) Export JSONL → Slack reads JSONL
Neo4j adapter (300 lines) Export RDF → Neo4j imports RDF
Jira adapter (350 lines) Export SARIF → Jira reads SARIF
Confluence adapter (400 lines) Export JSON → Confluence reads JSON
= 1,250 lines of AI-generated glue = 0 lines of adapter code
Stave exports JSONL, SMT-LIB, SARIF, JSON, and plain text. Each is a standard format with existing tooling. Zero custom adapters. The integration burden shifts from Stave to the ecosystem and the ecosystem already reads these formats.
The 1,030-line kernel
After all three mechanisms:
Conflict detection: DELETED (-5,726 lines — not the core)
Data collection: DELEGATED (to Steampipe)
Formal reasoning: DELEGATED (to Z3/Soufflé/Clingo/Prolog/PRISM)
Graph analysis: DELEGATED (to Neo4j/NetworkX via RDF/JSON-LD)
Tool integration: STANDARDIZED (JSONL/SARIF/RDF/SMT-LIB)
What remained:
The kernel: ~1,030 lines of Go
The catalog: 3,854 controls (scope: atomic|compound)
The schemas: 109 asset-type definitions
The verdict format: out.v0.1.json (the platform contract)
The capability is larger than ever. 3,854 controls. Five reasoning engines. Unlimited tool integrations via standard formats. CIA-derived intensional detection via Soufflé Datalog. The code is 90% smaller. And critically: I can explain every remaining line's purpose. The cognitive debt is near zero. Because 9,000 of them no longer exist in my codebase.
The specification layer I didn't plan to build
When I wrote the cognitive debt TRIZ analysis and the Su-Field model for the next development platform, I realized I'd been building the architecture the analysis prescribes — driven by practical necessity, not by theory.
The CEL predicates are the specification layer. Each predicate is a typed, structured declaration of a safety property: "no S3 bucket with PII classification may have public access." I author these deliberately. The AI generates the evaluation machinery around them. The specification is human-authored intent. The code is AI-generated implementation. They live in different artifacts, change at different speeds, and are reviewed by different processes.
The out.v0.1.json platform contract is the operating envelope. Every downstream consumer — Steampipe plugin, Powerpipe mod, Flowpipe pipeline, reasoning engine — reads the same versioned, schema-validated output. The contract is stable. The implementation behind it changes freely.
The reasoning specs are the agent-native contract. A YAML file packages the security question, the reasoning methodology, and the expected output. A blind AI agent with no source code access can execute the spec and produce correct security reasoning. We validated this across five reasoning paradigms — Z3, Soufflé, Clingo, Prolog, PRISM — with blind-agent trials where fresh agents independently produced correct results.
In Su-Field terms, the complete chain exists:
F1: Spec Authoring (human) F2: Evaluation (mechanical)
▼ ▼
S1: Developer → S3: CEL Predicate → S2: Cloud Configuration
│
Also: reasoning specs, platform contract,
JSON Schema definitions
F1 is slow, deliberate, human. I decide what must be true. F2 is fast, mechanical and automated. The kernel checks whether it's true. The cognitive debt lives only in S3 (the specification layer), which is small, stable, and entirely within one person's comprehension.
The agentic workflow
The discipline: one concern per prompt, separate sessions for design-by-contract, idiomatic Go, and architectural concerns. Each session receives the intent (what must be true) and produces implementation (how to make it true).
The insight: the AI generates code I don't fully understand, and THAT'S FINE as long as the code is verified against specifications I DO fully understand.
I don't need to understand the AI-generated evaluation machinery's internal implementation. I need to understand the CEL predicate that defines what the machinery must check. The predicate is 3-10 lines. The machinery is hundreds of lines. My understanding scales with the predicates, not with the machinery.
The reasoning specs made this even sharper. Before reasoning specs, extending Stave's analytical capability meant prompting the AI to generate Go code. Code I would review, not fully internalize, try to refactor and accumulate cognitive debt on. After reasoning specs, extending the analysis means writing a YAML file: the question, the reasoning steps, the expected output. The AI generates nothing. I author the spec entirely. The external reasoning engine executes it.
The cognitive debt from AI-generated Go code was real and accumulating. The cognitive debt from human-authored YAML specs is near zero. Because the spec is the understanding. Writing the spec is the struggle that builds the mental model. The TRIZ Article 1 calls this "redirecting the mental struggle from implementation to specification." It works.
Stage 4 by design
Looking at Stave through the TRIZ Feedback & Control Trend, the architecture has been feed-forward from the beginning. That wasn't accidental. It was the result of an extended research phase that deliberately explored how far to push the verification boundary before writing production code.
The research phase produced extensive notes on where the limits of each approach lay: what CEL could express and where it ran out, which problem classes needed SMT solvers versus Datalog, how far snapshot-based analysis could go before requiring runtime observation. The feed-forward mechanism — evaluating invariants against proposed state before it reaches production emerged from that experimentation. It took a lot of trial and error to learn that the specification must be the gate, not the report. That lesson is now in the core. Because the alternatives were tried and found insufficient.
CEL predicates are evaluated against configuration snapshots in CI before changes to cloud infrastructure ship. A proposed configuration that violates an invariant is blocked with the specific violated predicate surfaced. The developer doesn't need to understand every resource's configuration. The specification reviewed it — mechanically, exhaustively, before execution. That's Stage 4: predict the effect before execution, block what violates the envelope.
The CIA-derived Datalog queries push toward Stage 5. "Does any unauthorized path exist from any public principal to any sensitive resource?" is an intensional property query. It defines what's unsafe and the engine finds all violations, including novel ones. The system discovers unsafe states without being told what to look for.
What's still missing: the specification coverage report. I know the catalog has 3,854 controls and 180 are compound. I don't yet have the metric that says "these cloud resources have zero specification coverage." That's the cognitive-debt-visible instrument — the one that makes the unmediated gaps visible. This article was written many months before publishing it. The gaps are now found by using HAZOP. That is a topic for a separate article.
Code generation and cognitive load
AI-generated code that functions, passes basic tests, and solves the immediate problem can still be the wrong thing to have in your codebase.
The conflict analyzer worked — mostly. The Slack adapter worked. The Neo4j integration worked. Each one was functional, tested against the happy path, and good enough to demo. None was production quality. AI-generated code operates at the level of a smart intern: it gets the structure right, handles the common cases, and misses the edge cases that matter in production. But "good enough for fast experimentation" is why cognitive debt accumulates — you move fast, the code functions, and you defer the understanding you'd need to harden it.
The three-mechanism reduction wasn't about quality. It was about cognitive load. The AI generates functional code trivially — that's its value for experimentation. But every module of functional-but-not-fully-understood code is cognitive debt. The question changed from "does this code work?" to "do I need this code to exist in my codebase at all?" The AI makes it trivially easy to generate code that works well enough. That ease makes cognitive debt accumulate. Because the barrier to adding code dropped to near zero while the cost of understanding code stayed constant.
The fix isn't to generate less code. It's to be ruthless about what code needs to exist. Delete what isn't the core. Delegate what other systems do better. Standardize what remains. Then the 1,030 lines that survive are the lines that matter. They're few enough that one person can hold the theory of the entire system.
The design is the work. The reduction is the discipline. The code is just the artifact.
Stave is an open-source cloud security reasoning engine. Apache 2.0. The 1,030-line kernel evaluates CEL predicates against JSON Schema-validated configuration snapshots and exports standardized facts to five reasoning engines.
This article is a companion to the cognitive debt TRIZ series: "Six Contradictions Behind Cognitive Debt", "Six Industries That Already Solved Cognitive Debt", and "The Next Platform Won't Track Code. It'll Track Intent."
Top comments (0)