How We Work With AI

AI enables us, as engineers, to design solutions at a higher level of abstraction. It flattens learning curves and allows us to orchestrate the production of new, useful functionality without getting bogged down in the details of any particular framework or language. But if we let AI do the bulk of the grunt work — roughing out code for a first spike, finding bugs, generating inital drafts of documentation — does that mean we are mindlessly channeling AI slop?

No. Why?

Because:

Begin With the End (User) in Mind

Documentation-first is just one aspect of a more general rule we follow: Begin with the End in Mind. We strive to approach things from the end user's perspective, the highest level of abstraction that actually matters.

When you sketch out your desired end state first, you often see early on that some of your ideas lead nowhere, or to a non-intuitive workflow for your end users. You can always abandon those directions before you've sunk real time into building them out. Having AI create that first sketch is a bit like a painter dumping their preferred colors onto a virtual canvas with just a rough idea of what the finished piece might look like. An AI-enabled painter can quickly preview different arrangements of those colors into shapes and concepts, then easily flip, swap, or resize any of those before committing to a particular option. Because the initial drafts are cheap, the artist can quickly shift directions with minimal time spent eliminating the rejected alternative. The same goes for the AI-enabled engineer.

Keep in mind that, while the most important end state you are pursuing is useful, intuitive new functionality, a secondary end state objective is to have code that is easy to understand, modify and obtain -- by future new members of the team, or maybe even 'future you'. It is for the benefit of these future colleagues that you develop the backgrounders we cover below.

Understand Before Using

We never blindly accept what the AI gives us without taking the time to understand it first.

We pay particular attention to reviewing code at the 'seams' of a proposed design -- that is code that exposes any aspect of functionality to some higher layer -- whether that layer is calling code, or an end user. If what we're reviewing is unclear, we instruct the AI to generate more documentation. Upon review, if we find a comment using unfamiliar terminology, or a section using an unfamiliar syntax pattern, we ask for further explanation — and we weigh folding such explanations back into the original comments (or a backgrounder document), so it's there for the next person too. Does that mean we scrutinize every numeric value in, say, some CSS the AI dumped out? Since our attention is limited, probably not — CSS gone wrong is at worst a rendering issue on one page -- not something that would kneecap an app completely. Taking the AI's word for it is relatively low risk in these scenarios.

That's the trade-off in a nutshell: attention is limited, so we can't scrutinize everything equally. What varies is the cost if we miss something — a bad CSS value costs little, a bad migration costs a lot. So we do a quick risk analysis, and we spend eyeball time on sections that would really hose us if they're done wrong, and we 'never sweat the small stuff'.

Teach It Forward

The goal here is to be able to echo back the concept in your own words. This serves two purposes. First, it checks off the mechanical task of cleaning up the wordiness and robotic cadence issues typically found in an AI's first draft. Second, it moves you beyond just reading the AI's response, into active learning — which measurably beats passive reading for retention.

The best way we've found to put this to work is by:

The second point has real science behind it: the interplay of neuroplasticity and associative learning ensures that associating something you just learned with some other thing you know about reinforces the impression of both things. [Neural] "cells that fire together, wire together" (Shatz, 1992). We explore it further below.

Case study: linking backgrounders to code

Here's an example from some Webpack plugin-in code that our founder wrote when he was just starting to learn about webpack:

gas-demodulify-plugin's design doc and its associated backgrounder, docs/plugin-design.md, walks through why the plugin needs to reach into Webpack's internal RuntimeSpec type — and then just links straight down to the file that does it:

We pull out the RuntimeSpec via entry.runtime, which we type initially as 'unknown' (since there is no published guarantee we can rely on), but we then prove to the type checker that it is compatible with our RuntimeSpec type if it passes the guard assertRuntimeSpec

And the code links back. Sitting right on the WebpackRuntimeSpec type in CodeEmitter.ts is a doc comment pointing at the exact section of the backgrounder that explains why it exists:

/**
 * Webpack's internal structure encoding a runtime specification.
 *
 * See:
 * https://github.com/doikayt/gas-demodulify-plugin/blob/main/docs/plugin-design.md#rationale-for-referencing-webpacks-internal-runtimespec
 */
type WebpackRuntimeSpec = Parameters<import("webpack").CodeGenerationResults["get"]>[1];

Notice two things here. First, two different link styles for two different jobs: a plain relative link when pointing at a whole file that isn't going anywhere, and a commit-pinned permalink down to exact line numbers when the claim is precise enough that a refactor could make it stale. Second, the backgrounder doesn't try to explain everything itself — it says outright that the why lives in the doc and the precise invariants live in the source file's own comments. Each document sticks to one level of detail — high-level rationale in the markdown, precise mechanics in the code comments — and they link to each other, so you can start reading from either one and still find your way to the rest.

Other Strategies to More Effectively Leverage AI