One Engine, Ten Flows: Getting Sitecore Out of My Own Pipeline
My pipeline had learned Sitecore so thoroughly it could not build anything else. How we measured that, why we did not split the repo, and what packs turned out to be.
Welcome back to another blog post. This time I did not build another brand site — I went after something in the pipeline itself that had been quietly annoying me for months.
Short version: my agentic pipeline had learned Sitecore so well that it had forgotten how to build anything else.
The thing that finally got on my nerves
I kicked off a Marketplace app run — a Cloud Portal extension, no head app, no editing host, no publish step anywhere in its life — and watched it inherit assumptions that only make sense for a Content SDK site. It did not crash. It just carried baggage it could not use. And a run that was not Sitecore at all was worse: the "generic" pipeline was really the Content SDK pipeline with the Sitecore bits switched off, which is a very different thing.
So I stopped guessing and counted.
How much does the shared engine actually know about Sitecore?
100 references to platform_target across 32 files, inside commands that were
supposed to be platform-agnostic. /implement branched on it. /test branched on
it. Five agent identity files carried a hand-maintained "Framework awareness"
block that had already drifted out of sync with the commands' own lists.
My founding principle for this workspace is agents are identities, commands are tasks — and it turned out to be exactly one axis short. Domain had no home, so it leaked into both.
The obvious answer, and why I killed it
A repo split. One for Content SDK, one for Marketplace, each free to be as opinionated as it likes. I nearly did it, then measured it instead.
Two independent measures — static line count, and the last 200 commits — agreed within two points: a split makes about 85% of framework change land twice, forever. The optimistic case does not save it either. If Marketplace grew to match Content SDK exactly, duplication would fall from 86% to 74%, because a shared engine is platform-agnostic by definition and cannot shrink when platforms diverge.
Then the one that actually settled it. I have a clone flow — point it at a public website and it rebuilds that site to a measured parity threshold. A clone flow cannot be a repo: it has no deploy target of its own. You always clone into something. Under a split it would have to be copied into every platform repo, or live in one and be missing from the others.
A split forecloses the merge. Packs preserve the option to split later. So packs first — and if I ever really want two repos, the pack directory is already the boundary.
The probe that made it buildable
Deciding not to split still leaves the hard question: can platform knowledge actually be pulled out, or is it genuinely tangled into the logic?
So, the probe. Walk the nine heaviest commands, find every place they branch on the platform, and classify each one: is it data, is it an ordered step, or is it a genuine fork — control flow that is structurally different per platform and cannot be expressed as either?
27 branch points. Zero genuine forks. Every one reduced to pack data or a hook at a named anchor. If even three had been real forks, packs would have been the wrong shape and I would have found out after building them.
What we decided
Three declarative dimensions, resolved once at intake, injected everywhere:
| Dimension | The question it answers | Values |
|---|---|---|
platform | What you build on | content-sdk · marketplace · none |
method | Where the spec comes from | greenfield · clone |
audience | Who operates the run | developer · marketer |
The important word is compose, not multiply. Not eleven flows I maintain —
three independent choices over one engine. Three by two by two is twelve
combinations, of which ten are runnable. The twelfth, marketplace × clone, is
declared incompatible and refused by name, because "rebuild this website as a
Cloud Portal extension" is not a coherent request and I would rather it be refused
than discovered at runtime.
Agents are identities. Commands are tasks. Packs are domains.
What that actually looks like
Start with the engine at every default, then turn one dial at a time. The restrictions are more interesting than the additions.
coreThe engine, with every pack at its default
No platform, greenfield, developer. Eight stages, five operator gates and a delivery loop that runs per tranche. This is what is left when the domain has been taken out of it — and proving it still runs clean is the test that the domain really left.
/create-prd/architect/design/task-breakdown/implement/code-review/test/ship+ platform: content-sdkSix stages appear, and the release step changes meaning
The platform is what you build ON, so it owns the beginning and the end: read the content model before designing against it, stand the cloud up before building, and — because authoring happens against preview — put the whole content phase after ship, with one operator-confirmed publish as the only thing that goes live.
/discover-sitecoreairead the real content model first/create-prd/architect/design/task-breakdown/provision-sitecoreaiCM + editing host, idempotent/implement/code-review/test/shipcode only, deployed against preview/discover-componentsa visual map for the author/author-contentcopy and imagery into the tenant/test-contentan independent content audit/publishthe only step that goes live+ method: cloneThe spec stops being written and starts being measured
The method is where the specification comes FROM. Point it at a public site and the crawl replaces the interrogation outright — eleven questions, none of them greenfield. Design has nothing to diverge from, so it collapses into a parity phase, and "done" becomes a score per page per viewport instead of a feature list.
/source-analysisthe crawl becomes the blueprint/create-prdthe greenfield questions are dropped/architect/designthe source site IS the designparity loopbounded, with its own stop reason/task-breakdown/implement/code-review/test+ a 4-criterion parity matrix/ship+ audience: marketerThe same spine, a different conversation
Additive only — this dimension may never override a stage body, and a validator enforces that rather than a convention. Nothing is added or removed from the flow. What changes is what reaches the operator: a brief instead of a PRD, plain-language questions, technical answers derived instead of asked, and five dials preset so none of them ever gets surfaced.
/create-prda brief, and 7 plain questions/architect/design/task-breakdown/implement/code-review/testhuman half + a blocking machine half/shipnothing technical surfaced
+ added · ⇄ replaced · − dropped · everything unmarked is the same stage the engine already ran.
The platform owns the beginning and the end, and nothing in the middle. Discover the model, stand the cloud up, and — because authoring happens against preview — the whole content phase sits after ship, with one operator-confirmed publish as the only thing that ever goes live.
The method is the only dimension that removes anything. Clone drops /design
outright: when you are rebuilding an existing site there is nothing to diverge
from, because the source is the design.
The audience adds and removes nothing at all. Every stage still runs and every
artifact is still produced; what changes is which of them a human ever sees. A
validator enforces that rather than good intentions — an audience pack that could
override a stage body would collide with clone and foreclose marketer × clone,
the single most plausible non-developer scenario there is.
Two constraints were locked before any code, and both held: a pack may never add a gate token (it can carry one of my five existing stops, never invent a sixth), and nothing merges off a branch — this is the engine that builds everything else.
The journey, honestly summarized
Eight tranches, each a complete slice: the contract, the two heaviest commands, intake and planning, the tail, then relocation — six stages and three agents physically moving into pack directories while the validator went from advisory to enforcing — then export, then the guided audience.
The first tranche is the one worth naming. Three packs declaring today's behaviour verbatim, and no file moved. An extraction you can diff against a golden snapshot is an extraction you can trust; if the pack says something different from the command it came from, that is a bug in the pack, not an improvement.
None of that is the interesting part. The interesting part is the question I asked once everything was green — validator, twelve suites, seven export self-tests:
Has any of this ever actually been run?
It had not. Zero of 89 real run manifests carried a pack block. The resolver was exhaustively unit-tested and had never once been persisted by anything.
Running it produced a fistful of defects in an afternoon, and I will spare you the list, because they were all the same defect wearing different clothes. A declaration nothing read. An ordered sequence missing a step that every individual declaration got right. A template whose placeholders never rendered. A safety boundary that checked file paths while the thing it guarded lived in file contents. Each one passed every gate it had.
Structural green is not functional green, and the counter-move is not a cleverer gate. It is embarrassingly simple: run the thing. Most of what I found came from execution; almost none of it from inspection.
There was a coda to that, and it is the bit I would most like back. A transitional
platform_target field survived the whole refactor behind a ratchet — a count
allowed to fall and never rise, which felt responsible and was not. A ratchet made
the debt manageable rather than visible, which is exactly how a temporary
duplicate becomes a permanent one. It finally came out in what became tranche nine
of an eight-tranche plan: roughly eighty-five references down to thirty-five, of
which zero are branches or writes.
A ratchet buys you time, and then quietly spends it.
Then I ran it five more times
Five throwaway runs, one per composition, driven from a single prompt per platform so that in each pair the only variable was the audience. For the first time the thesis had numbers rather than a claim:
| developer | marketer | |
|---|---|---|
| the first question asked | "Current page only, or a site-wide sweep?" | "Who is this for?" |
| the auth model | asked | derived |
| the spec produced | a PRD, fifteen sections | a brief |
| dials preset by the pack | 0 | 5 |
| scope-dial questions reaching the operator | the defaults | zero, by design |
Clone did exactly what it says on the tin: replaced the greenfield question set outright — eleven questions, not one of them greenfield — and presented the skipped design stage with the pack's own reason attached rather than silently omitting it.
So what do I have now
Six stages and three agents live inside packs, and every stage-shaped command resolves a generated brief rather than restating a platform's skill list — which is what makes it drift-proof, because two hand-maintained lists diverge by construction.
My favourite number is this one: the Marketplace pack owns zero stages. Its entire behaviour is data. Adding a platform is writing one file, not editing the engine.
And because I did not want to argue the thesis, I ran it. Two cold agents, identical buggy code, identical symptom, one dependency different. The Content-SDK-composed agent cited the relevant Sitecore rule six times and escalated the work. The core agent cited it zero times and wrote a plain fix. That is the whole point, shown rather than claimed.
Where it stands
Merged, validated, published — and one gap stated rather than hidden: subagent isolation inside an export is untested, because the agent registry resolves against the caller's working directory, so a test from my repo would exercise my own agents and prove nothing.
Two things I would do differently, and they are the same thing twice. Run it earlier — every serious defect came from execution, and execution came last. And do the retirement while it is still small, because a ratchet on something you intend to delete is just a polite way of not deleting it.
Is this the right shape forever? No idea. It is the right shape for the next platform, which is the only horizon I trust myself to design for. Ask me again after the third one.
More to come. As always.
- Agentic
- SitecoreAI
- Content SDK
- Marketplace
