OmniCore and its Claude Code plugin: how a DDD + CQRS framework turns AI coding from improvisation into engineering
A few years ago I wrote here about Domain-Driven Design with Kotlin — how a language with an expressive type system and DSL support lets the code speak the business language instead of burying it under boilerplate. The argument has not changed. What changed is who writes the code.
We now have agents that produce a working service in an afternoon. What we do not have, by default, is any guarantee that the service produced this afternoon looks anything like the one produced last week. Same prompt, same model, two different architectures. Ask for a second entity and you get a second dialect. Ask for a third and you are reviewing a codebase whose only unifying principle is that a language model was confident at the time.
That is the problem OmniCore and the OmniCore Claude Code plugin were built to solve. Not "make the AI write more code" — the opposite. Make almost all of the code something the AI never has to write.
A microservice is roughly 80% shape and 20% business. The shape is the transaction boundary, the outbox, the audit row, the pagination, the filter vocabulary, the HTTP status codes, the OpenAPI document, the projection, the retry ledger. It is identical in every service you will ever write, and it is also exactly where the expensive bugs live. So freeze it: put it in a framework, enforce it at compile time, and let the agent work only on the remaining 20% — the part that is actually your domain. Then give the agent a spec to work from and a fixed set of procedures to follow, so its output is reproducible instead of inspired.
Framework below, spec above, agent in between with its hands tied to the good path. That is the whole design.
OmniCore is a DDD + CQRS framework for Go microservices. Its most important property is not any single feature — it is that the correct behavior is unavoidable.
The six write verbs land with zero handler code. Insert, update, partial-update, archive, unarchive, delete are generic over your entity. There is no handler to write, so there is no handler to get wrong.
DDD the compiler enforces. Four layers, one direction, sealed domain types: a ValidEntity can only be produced inside the domain package. You cannot accidentally persist an unvalidated entity, because the type does not exist outside the layer that validates it. This is the part a prompt can never guarantee and a type system always can.
Correct-by-construction writes. Domain rows, the outbox row and the audit row commit in a single transaction. Always. Not "if the developer remembered".
One handler, five surfaces. A handler is a pure pipeline.Handler[TReq, TRes] and never learns who invoked it. The same instance serves REST, gRPC, GraphQL, the message broker and CSV/Excel export. Adding a surface is attaching, not rewriting — and the business rules cannot drift between channels, because there is only one copy of them.
A read side that repairs itself. Writes land in PostgreSQL (or MySQL, SQL Server, Oracle — one engine seam, your domain never names a vendor); CDC carries them to the broker; the sync engine projects them into Mongo views with keyset pagination, filter-operator allowlists and versioned, blue-green rebuildable schemas. Failed projections retry, park in a relational ledger and auto-replay.
Every one of those is a decision that a human team debates for a sprint and an AI agent invents differently each time. Here, it is already decided, documented, and checked by the compiler.
Kotlin taught me what an expressive domain model feels like. Go is a different argument, and for this particular job it wins on four points:
The compiler is the referee. Go's build is fast and unforgiving, which matters enormously when the author is an agent. An LLM is a probabilistic writer; go build and go test are deterministic readers. The tighter that loop, the less the probabilistic part can hurt you. Generics (1.18+) gave Go exactly enough type-level expressiveness to make handlers generic over entities without reflection — which is what makes "zero handler code" possible at all.
One dialect. Go's small surface and gofmt mean there is essentially one way to write things. There is no idiom debate for a model to have with itself. Uniform code is code you can review at a glance — and code an agent can pattern-match against reliably.
Operational honesty for microservices. Static binaries, tiny containers, quick starts, first-class concurrency, an ecosystem built by and for infrastructure people. A service here can even run as a single pure-Go binary against SQLite with no Docker at all when you are still exploring.
Explicit errors. Nothing is invisible. For a framework whose promise is "these guarantees always hold", a language that refuses to hide control flow is the right substrate.
Kotlin remains, to me, the more beautiful language for expressing a domain. Go is the more governable one — and governability is what you need when a machine is holding the keyboard.
DDD is usually sold as a communication discipline: ubiquitous language, bounded contexts, aggregates, a model that mirrors the business. All of that still applies. But DDD turns out to have a second life that nobody designed it for.
An agent needs a vocabulary with fixed meanings. "Entity", "aggregate root", "child", "value object", "command", "query", "view", "bounded context" — these are not decorative words in OmniCore, they map to concrete structural obligations. When you tell the agent "Order is an aggregate root with OrderItem children", you have not written prose. You have specified a transaction boundary, a cascade, a migration shape, a DTO set and a projection, all at once, unambiguously.
That is why the spec can be short and the output can be large. DDD is the compression format.
Everyone's first instinct is a CLAUDE.md with the conventions in it. It helps, and then it stops helping: it is advice, and advice degrades under context pressure. What the plugin adds is procedure.
The OmniCore Plugin installs sixteen /omnicore:* skills — not tips, but scripted workflows with approval gates:
scaffold-service · scaffold-system · scaffold-entity
from empty directory, to a whole-system domain map, to a complete entity across every layer
evolve-entity · evolve-view · remove-entity
change and removal done as impact maps you approve — migration, table schema, DTOs, translations, view version bump and OpenAPI moving together
scaffold-view
read models beyond a single entity: composed views, shared-base identities, upstream embeds
implement
wire any framework capability the dedicated skills don't own — and say so honestly when the framework doesn't offer it
qa
derive the framework's promised behaviors and emit an executable contract suite that proves them against the running service
run · doctor · configure · upgrade · gen · omnicore-gen · help
boot it, diagnose it, change its infrastructure posture, move its pin
Three properties matter more than the list itself.
The skills are version-agnostic. They read the framework version from your go.mod and treat that release's bundled docs as the only authority. The agent is not recalling OmniCore from training data — it is reading the pinned manual. Framework evolves, skills don't rot.
Nothing destructive happens without an approved plan. Removing an entity produces an inventory first; evolving one produces an impact map; both block on shared bases, composed views and event consumers until a human decides. The gate is the product.
The skills write down what was decided. Every artifact lands under specs/ in your repository — the approved model, the plan, the generator's entity specs and lock files, the QA plan, the upgrade migration plan. It is committed, never ignored. Your repo root holds the service; specs/ holds the reasoning. Six months later, the "why" is still in version control instead of in a chat transcript nobody kept.
The plugin includes OmniCore-Gen (beta), a spec-driven generator: one YAML file per entity describes fields, modes, uniqueness, children and views; the generator writes the whole entity across every layer in seconds and a fraction of the tokens an agent would spend writing the same files one by one. A per-entity lock.json records which framework release it was last generated against, and omnicore-gen doctor reports drift between the spec, the lock and the files actually on disk.
Two details make this honest rather than magical.
Generated code and hand-written code are physically separated: anything in a *_manual.go file is yours and the generator never touches it. The spec language deliberately does not try to express every business rule — it expresses shape. The rules it cannot express, the agent implements by hand, in files that survive regeneration.
And generation is never forced. Both scaffold-entity and evolve-entity open a gateway where you choose: let the generator do it, or have the agent write file by file. Changing an entity always includes the migration pair, which a regeneration will never write for you.
Benefits
Fewer bugs — because the risky code is framework code, written once and exercised by every service, instead of handler code re-invented per endpoint.
Lower cost — an agent that emits a 400-line YAML spec and a handful of business rules burns a small fraction of the tokens of one that writes a full CRUD stack per entity. Multiply by seven aggregates.
More control — plans and impact maps are approved before anything is written. You review decisions, not diffs.
Standardization — the tenth entity is structurally identical to the first, whoever (or whatever) wrote it.
Assertiveness — the agent is not guessing at conventions. It reads them from the pinned docs and follows a procedure.
Robustness — transactional outbox, audit, aggregate cascades, projection retry, blue-green view rebuilds. Guarantees, not intentions.
Arguments about methodology are cheap. So the method has a worked example that is public and readable end to end: AuthCore, the identity provider of a multi-tenant platform — tenants, users, clients, groups, roles, permissions and claims, 57 management endpoints over seven aggregates, on REST and GraphQL, PostgreSQL as the source of truth with relational* views in front of it.
It was scaffolded, evolved, QA'd and upgraded entirely through the plugin. Most of internal/ was written by omnicore-gen from the specs under specs/omnicore-gen/; the hand-written half lives in *_manual.go; every change went through an /omnicore:* command instead of a hand edit; qa/run.sh is the contract suite the qa skill generated for it.
It also demonstrates the versioning model this whole thing depends on: a service pinned to a published omnicore release, maintained by a pinned release of the plugin, the two moving independently.
*As this is an example, this project was set up in a simplified way. However, you can use the configure skill to turn it into a full CQRS project with Kafka/NATS synchronization and views served by MongoDB.
It is not a no-code tool. Someone still has to know what an aggregate boundary is, when eventual consistency is acceptable, and whether that field really belongs on the root. The framework removes the mechanical decisions so that your attention lands on the domain ones. That is the entire trade.
It is also honest about its stage: omnicore is 0.x and the API may still evolve; OmniCore-Gen is beta and targets one framework line at a time, deliberately — branching templates per version is the largest drift source a generator can have. Both are Apache-2.0.
The industry spent 2023–2025 asking how to make AI write more code. The more useful question is how to make it write less — and make the little it writes the part only a human could have specified.
Spec-driven development is that answer, and it needs three things to be real: a framework strong enough that the shape is not up for debate, a vocabulary precise enough that a short spec is unambiguous, and tooling disciplined enough that the agent follows procedure instead of instinct. OmniCore, DDD, and the Claude Code plugin.
The domain is yours. Everything else is already written.
Explore: OmniCore · Documentation · OmniCore-Plugin · AuthCore — all Apache-2.0.
How to install it in Claude Code?
/plugin marketplace add ClaudioSchirmer/omnicore-plugin
/plugin install omnicore@omnicore
Quick Start:
scaffold-service
scaffold-entity