Chm.ski Labs

Code Doctrine: The Reusable Version of My AI Coding Constitution

Announcing Code Doctrine: the reusable public version of my AI coding constitution, refined through real use and designed for customization, installation, and sharing across agent environments.

Kamil Chmielewski 11 min read AI
Diagram showing Code Doctrine as a reusable doctrine package installed into coding agent environments like OpenCode and Pi. Diagram showing Code Doctrine as a reusable doctrine package installed into coding agent environments like OpenCode and Pi.

Code Doctrine: The Reusable Version of My AI Coding Constitution

I had already been using this doctrine in real work. Over the following months, I refined it into something more reusable, more customizable, and easier for agents to apply consistently.

A while ago I wrote Your AI Coding Agents Need a Constitution.

That article argued that coding agents need explicit engineering taste.

I still believe that.

A prompt can change what an agent does on one task. A doctrine changes the trade-offs it makes across many tasks, including how it handles complexity, failure, and boundaries.

Today I’m publishing Code Doctrine: a more polished, reusable, customizable version of the AI coding constitution I had already been using in real work.

That is the real story here. Not that I renamed something, but that a working doctrine kept improving once I lived with it across more tasks, more reviews, more refactors, and more failures.

Over those months, I learned a few things:

  • some of the original rules held up almost unchanged
  • some needed to become more operational
  • some needed to become easier for agents to route to at the right moment
  • and the doctrine itself needed a better public shape if it was going to be reused and customized by other people

What Held Up From the Original Article

The original article emphasized a few ideas that still feel completely right to me:

  • prefer simple designs over abstraction theater
  • separate domain failures from operational failures
  • parse at the boundary instead of re-validating forever
  • keep the domain in the center
  • preserve error context

Those still form the spine of the doctrine.

If I removed everything else and kept only those instincts, agents would still make meaningfully better decisions than they do under generic prompting.

What the next few months showed me was how to make that taste more explicit, better structured, and easier for agents to apply consistently.

What Changed After A Few More Months of Using It

When you use a doctrine in real work, you start noticing the difference between:

  • rules that sound good in an article
  • rules that an agent can apply reliably in planning, implementation, and review

That is where most of the evolution happened.

1. The skill needed better structure and routing

A few more months of daily use showed me where the skill could become more structured and easier for agents to navigate.

If an agent is helping with a migration, it should not load a generic blob and improvise. It should go to change safety, database rules, and enforcement.

If it is reviewing an HTTP endpoint, it should think about boundary parsing, action-shaped interfaces, failure modeling, and error context.

If it is touching UI, it should not drag the whole architecture doctrine into the task if a smaller, focused slice is enough.

So the doctrine evolved from one central statement into a compact SKILL.md plus focused references for different concerns:

  • coding foundations
  • database
  • system architecture
  • operability
  • documentation
  • user interface
  • change safety
  • testing
  • enforcement

That change came directly from usage. The article explained the worldview. The refined skill made that worldview easier to route and apply.

2. General principles had to become operational defaults

The article talked about engineering taste in fairly universal terms. That was good for explaining the idea, but daily use exposed the need for more explicit defaults.

For example, “keep the domain in the center” is a good principle. But in practice, I wanted agents to apply it through more concrete rules like:

  • prefer library-over-framework boundaries
  • keep HTTP handlers thin and action-shaped
  • keep async work visible and explicit
  • push infrastructure to the edges
  • expose meaningful command-shaped capabilities

Likewise, “model failure honestly” had to become more specific:

  • do not swallow operational errors
  • preserve original error context when wrapping
  • keep expected domain failures explicit
  • do not invent fake fallback behavior just because it sounds helpful

And “parse at the boundary” had to become a repeated enforcement point rather than a nice design slogan.

Agents do better when doctrine is not just aspirational, but concrete enough to trigger obvious implementation and review decisions.

3. The doctrine had to grow into areas the first article only implied

The original article focused on a handful of architectural and error-handling ideas. That was the right starting point, but real work pushed the doctrine into areas that were only lightly covered before.

The refined doctrine now has more explicit opinions about:

  • database work — SQL-first persistence, optimistic locking, indexes for hot paths, archival before hard delete when it simplifies operations
  • operability — validated config, explicit resource limits, executable operational interfaces, visible background work
  • change safety — migrations, backfills, compatibility windows, rollout safety, and making risky change paths more explicit
  • testing — writing tests where they buy confidence, not as ceremony
  • enforcement — turning values into actual review gates

The doctrine got broader because the work demanded it.

4. Review became a stronger use case than I expected

Using the doctrine over more time made this much clearer.

Generation matters. Review matters more.

A doctrine becomes really valuable when it gives a reviewer agent something sharper than “look for bugs” or “suggest improvements.”

What matters is not asking it a fixed checklist every time. What matters is being able to point the agent at the relevant doctrine for the task and have it review through that lens.

That is when doctrine stops being writing and starts becoming enforcement.

This is also why I added a stronger enforcement layer to the skill. Not because I wanted more process, but because I wanted the doctrine to become usable as a planning and review instrument rather than just background guidance.

5. The doctrine had to become part of the session policy, not just exist as a skill

Another thing that became obvious in practice: if a doctrine is annoying to apply, it will drift.

You can have a beautifully written skill, but if every new session still depends on you explicitly loading it as the first step, it stays fragile and local.

That was one of the few real technical issues I ran into. I assumed that having the skill available in the environment would be enough, and that agents would reliably pick it up when relevant. In practice, that was not consistently true. Sometimes they used it. Sometimes they ignored it. Sometimes they drifted back toward generic behavior because nothing explicitly told them that this doctrine should govern the task.

That is what pushed me to install the doctrine into the agent’s policy layer through AGENTS.md.

For readers who do not live in these runtimes every day: AGENTS.md is the startup policy file some agent environments read at session start.

The doctrine now ships with a managed AGENTS section so it can be installed into environments like OpenCode and Pi and become the default doctrine for non-trivial coding work.

That AGENTS block exists because I learned that agents were not always picking up the skill reliably without it. It gives the doctrine an explicit place in the environment instead of hoping the model will discover and apply it on its own.

That block is intentionally small. It does not restate the whole doctrine. It just establishes the policy:

  • load code-doctrine for planning, implementation, debugging, testing, and review
  • let the doctrine’s own routing decide which parts matter
  • keep the cited doctrine concise and relevant
  • apply only the rules that materially affect the task

That separation turned out to be important.

AGENTS should declare when the doctrine applies. The doctrine itself should own the deeper reasoning surface.

Why the Public Version Needed a Better Name

“Engineering constitution” was a useful phrase for the original essay because it signaled seriousness and explicit rules.

As a public package name, though, it felt too heavy. A little too formal. A little too ideological. A little too much like a governing document instead of something a developer might casually adopt.

code-doctrine feels lighter and more approachable. It still says there is a real doctrine behind the tool, but it sounds more like something you can install, reuse, customize, and maybe publish yourself.

That matters because I do not want this to stay a private artifact with legacy naming around it. I want it to be something other developers and teams can actually adopt and shape for themselves.

From One Skill to Doctrine Packages

Once the doctrine became more practical, installable, and structured, it stopped feeling like something that should stay tied to one private setup.

That is where the broader code-doctrine idea came from.

Instead of every developer or team inventing a custom installer and a custom distribution shape, I split the ecosystem into two parts:

1. Plain doctrine packages

A developer doctrine package should stay plain. It should mostly contain:

  • SKILL.md
  • focused doctrine references
  • AGENTS-section.md
  • doctrine.json
  • package metadata

In other words: doctrine, not tooling glue.

2. A shared install client

The shared code-doctrine CLI owns installation and resolution mechanics.

So instead of every doctrine package shipping its own Pi/OpenCode installer, the ecosystem can standardize on a simple flow like:

code-doctrine install kamilchm opencode --project

The resolver first tries npm:

  • @<author>/code-doctrine

Then falls back to GitHub:

  • github:<author>/code-doctrine

That means individual developers can own their actual doctrine, while the shared client owns:

  • package resolution
  • installation into OpenCode or Pi
  • AGENTS integration
  • manifest validation

It also creates a much better local development loop. You can clone someone else's doctrine repo yourself, or fork one into a local directory with the CLI, customize it, and install it directly from local files before publishing anything.

For example:

npx code-doctrine fork kamilchm ./my-doctrine
npx code-doctrine install ./my-doctrine opencode --project

That separation feels much healthier than mixing doctrine with harness plumbing in every repo.

Why I Find This More Interesting Now

The first article argued that AI coding agents need explicit engineering taste.

After using and refining the doctrine further, I think the more interesting claim is this:

Engineering taste should be iterated on, operationalized, installed, versioned, and shared.

That is a stronger statement than the original one.

It suggests doctrine is not just a personal note to your future self or a clever prompt appendix. It can become a real artifact of how a person or team builds software.

That opens up a few useful possibilities.

Teams can make hidden standards visible

Instead of every engineer steering agents with private preferences, a team can point to one doctrine package and say:

  • this is how we think about failure
  • this is how we think about abstraction
  • this is how we treat migrations and rollout risk
  • this is what good generated code should look like here

Individuals can publish engineering taste, not just code

Open source already shares libraries, tools, and frameworks. I think it is increasingly reasonable to also share doctrine.

Not because one doctrine is universal. But because doctrine is one of the highest-leverage inputs you can give a coding agent.

Try Code Doctrine, Then Make It Your Own

That is the part I care about most now.

Code Doctrine is not meant to be a universal doctrine that everyone should copy line for line. It is meant to be something you can try, inspect, customize, and eventually publish in your own shape.

If you want to explore it:

If you want to try it in a project, the intended flow looks like this:

npx code-doctrine install kamilchm opencode --project

Or for Pi:

npx code-doctrine install kamilchm pi --project

If you want to customize it locally first, you can fork it into a working directory and install it from there:

npx code-doctrine fork kamilchm ./my-doctrine
npx code-doctrine install ./my-doctrine opencode --project

If the defaults fit how you want agents to think, use them. If they do not, change them. That is the whole point.

I think more developers should treat engineering doctrine the same way they treat lint rules, CI workflows, or editor config: something worth making explicit, refining over time, and sharing with others.

So try Code Doctrine, customize it, and if it helps, publish your own.

Tags: ai llm coding agents code doctrine engineering culture prompt engineering software architecture open source package manager developer tools review agent workflow

Menu

Settings