You Don't Pick the Architecture. You Catch It.
Architectural decisions for solo-operator portfolios are recognition moves, not menu-picks. The right architecture is the one already trying to emerge from your products. Naming it is most of the work.
Late one evening I was reading through Fourth & Two's newsroom code — F&T being one of the products in the portfolio I run, a fantasy-football intelligence app — when I scrolled past a function I had no memory of writing. It was a per-user reading-state model, attached to the editorial entries. Not a feature I'd planned for the newsroom; not in any of the design docs. It had grown there because the newsroom needed it. I'd written it, shipped it, and forgotten I'd done so.
The newsroom design itself had quietly grown into a magazine-shaped surface. Covers. Editorial entries. Per-decision analytical depth. Ideas getting indexed across stories. None of this had been on the original spec — F&T started life as a fantasy-football decision tool, a thing for picking lineups, not for editorializing the picks.
This was strange because Vela — a different product, a different domain entirely (figurative artwork; emotion architecture; developmental theology) — has a magazine called Reincarnation that does the same thing. I built Vela's magazine first. F&T didn't import any of Vela's code; the newsroom was developed independently, by an earlier version of me, focused on what I'd thought was a different problem.
But the shape was the same. Adaptive curation. Per-user pacing. A reader profile underneath. An item set with editorial discipline on top. Two different products, two different domains, two different teams — both me, but at different times, with different concerns. The same architectural pattern.
The thing I noticed that night isn't the substrate decision. It's that the substrate decision was already being made — by me, in two places at once, without my noticing — and what I had to decide was whether to recognize that, or keep re-deriving the pattern by accident every time a new product needed it.
The pattern was already everywhere
Once I started looking, the pattern was everywhere.
PA-site — peopleanalyst.com, the portfolio site you may be reading this on — has a magazine of its own called principal-issues. Magazine, articles, reader pacing. Same structural shape as Vela's Reincarnation engine and F&T's newsroom. Different topic axes; same curation pattern.
PA Platform — a people-analytics platform of insight cards aimed at C-suite consumers — was structurally adaptive curation against an item set, where the items happened to be analytical insights instead of articles or fantasy-football stories. The reader-relevance question was identical: how much does this insight card feel important to me right now?
Performix — a performance-analytics product — was being positioned as the first non-Vela vertical port of the same library-core pattern.
Three more products were imagined but not yet named. Each of them had the same shape as soon as I sketched its scope.
The pattern, named: adaptive curation against an item set, with measurable response signals, per user, over time. Vela's items are figurative artwork. PA-site's items are articles. PA Platform's items are insight cards. F&T's items are decision-tagged stories. Performix's items will be performance-rating instances. The items are different. The kernel is the same.
This isn't a pattern I picked. It's a pattern I had already started building, four or five times in parallel, without intending to. The architecture question isn't what should I build? — it's what do I do now that I've noticed I've already built it?
Item-agnostic kernel; signal-specific surfaces
I'll call this item-agnostic kernel; signal-specific surfaces.
The kernel does the same job everywhere: takes a set of items, knows things about a user, produces a sequence with calibrated pacing. The items vary. The user-modeling shape varies a little. The signals — what counts as engagement, attention, growth, fatigue — vary a lot, because they depend on what the items mean.
The kernel is shareable across products because it doesn't care what the items are. The signal architecture is per-product because what an "engaged" reader of a fantasy-football decision card looks like is not what an "engaged" reader of a developmental-theology essay looks like, and you cannot collapse the two without losing what makes either signal load-bearing.
Once that distinction is named, the architecture question gets much sharper. It isn't: should I have shared substrate? (Yes. Already do; I just have multiple parallel copies of it.) It's: which layer of the substrate is shareable, and which has to be per-product?
That distinction is the load-bearing move. Try to share the surfaces and you fail — properties bleed into each other, brand identity collapses, deployment cadences fight. Try to keep everything per-property and you fail differently — improvements stop compounding, bugs get fixed in one place and not the others, and the substrate quietly drifts into N different versions of itself, each carrying the patches the others don't have.
What works is sharing the kernel and keeping the surfaces per-property. The kernel is item-agnostic. The surfaces — including the signal architecture each product uses to measure response — are signal-specific.
That much I could see in an evening of reading code. The harder question came next. Which shared-substrate architecture? Because there are several, they're not interchangeable, and the wrong one carries costs that compound for years.
Four patterns, and the trap of picking from a menu
There are four obvious patterns:
Multi-tenant. One codebase, one platform, every property is a tenant configured by data. Shopify pattern. Each property looks completely different to its customers, but they all run on the same engine.
API service. The substrate becomes a service that consumers call over HTTP. Stripe pattern. Each property is its own UI consuming substrate-as-a-service.
Versioned shared package. The substrate becomes a package on npm or its private equivalent; consumers pin to versions. Standard industry pattern for teams that don't trust each other to update synchronously.
Monorepo with kernel-as-library. One repo holds all properties plus shared packages. The substrate is a TypeScript library each consumer imports directly. Each property is an app; shared mechanics are packages; each app deploys independently.
Most architectural-decision writeups stop here. "After evaluating the four patterns, we chose pattern X." The reader is supposed to learn from the conclusion.
That's the wrong shape. The conclusion is downstream of the evaluation; the evaluation is downstream of the objectives and constraints specific to your situation. The same four patterns evaluated against different objectives produce different verdicts. What's instructive is the derivation — what did I need from this architecture, what constraints did I have, and how did those reduce four patterns to one?
There's a structural reason this gets imported wrong. The architectural writing that gets read most widely is written from team-scale environments — a hundred engineers at a SaaS company, a dozen at a series-B startup, a few thousand at a hyperscaler. The verdicts that come out of those environments are correct for those environments, and the writing makes that visible only in passing, because the writers and readers share the team-scale assumptions. A solo operator reading the same writing imports the verdict and inherits a constraint set they don't have. Versioned-package discipline at solo scale is a tax on a problem that doesn't exist; multi-tenant elegance at solo scale produces operations friction that team-scale writing didn't have to account for.
Six objectives, in priority order. Conflicts between later objectives and earlier ones are resolved in favor of earlier:
- Improvements compound across all properties without drift. A fix made once propagates. The substrate gets stronger with use; it does not develop a parallel patched version that diverges across consumers.
- Per-property identity is preserved. Vela looks like Vela. PA-site looks like PA-site. To users, these are distinct products with distinct brands, domains, voices, editorial commitments. They are not skinned versions of one platform.
- Solo-operator economics. Cross-property changes must not multiply work proportionally. The architecture has to be ergonomic for one operator carrying the whole context — minimal context-switching cost, no team-coordination overhead, no integration tax that scales with property count.
- Independent shipping cadence. Vela ships when Vela is ready. PA-site ships when PA-site is ready. F&T ships when F&T is ready. Each property has to be able to ship at its own cadence, without forced coupling to the others' release windows.
- Substrate continues to evolve. The kernel is in active design. The architecture has to accommodate that, not freeze the kernel at a snapshot the day the architecture is committed.
- Per-property data attribution preserved. Each property's user signals are attributable to that property. Cross-property data sharing happens only when deliberately bridged — not as an architectural side-effect.
Four constraints:
- One operator. I'm the only engineer. Team-coordination patterns carry overhead that's pure friction at solo scale.
- Vela is live and mature. 1,300+ commits. Live Stripe membership. An active research program. It cannot be retroactively re-architected without disruption to real users.
- The substrate is still in active design. Extracting now means extracting against a moving target. Better to extract after the kernel stabilizes.
- Cost of being wrong is real. Wrong architecture means major refactoring later. Right architecture done late beats wrong architecture done early.
Now the four patterns evaluate cleanly:
Multi-tenant satisfies (1) — drift prevented — and (3) — solo-ergonomic. Breaks (4): multi-tenant means shipping the codebase ships all tenants. Per-property cadence is sacrificed. Partially breaks (2): tenant deployments running on shared infrastructure feel less like distinct products at the operations layer. Wrong fit.
API service satisfies (4): independent shipping. Breaks (3): every consumer needs its own integration code; API contract maintenance is real solo overhead; the integration tax scales with property count. Partially breaks (5): substrate evolution requires API versioning discipline that adds friction. Right pattern eventually for non-magazine consumers — PA Platform analytics integrating insight-card delivery may want this. Not the right primary pattern for the magazine-shaped consumers.
Versioned shared package breaks (1) directly. Version-pinning is exactly what creates drift; pinned consumers fall behind unless actively updated; updating across consumers is team-coordination work disguised as dependency management. This is the standard industry pattern, optimized for teams that don't trust each other to update synchronously. For solo, it's pure friction.
Monorepo with kernel-as-library satisfies all six. Drift prevented (1) — kernel changes propagate atomically across apps via package import; refactors are atomic across consumers. Per-property identity preserved (2) — each app has its own brand, domain, deployment, theme, content. Solo-ergonomic (3) — one repo, one place to look, atomic refactors, no team-coordination overhead. Independent shipping (4) — each app deploys when ready; per-app builds via Turborepo. Substrate evolves locally in packages/ (5); consumers adopt at their next deploy without coordination. Per-property data isolated by default (6); cross-property bridges available when deliberately wanted.
That's the derivation. The pattern named — item-agnostic kernel, signal-specific surfaces — and the architecture chosen — monorepo with kernel-as-library — both come from the situation, not from a menu.
What's instructive is that the same four patterns would produce a different ranking against different objectives. A team of fifty engineers shipping one product would rank differently. A startup needing to deliver an MVP fast would rank differently. A SaaS platform with thousands of tenants would rank differently still. The pattern catalog is the same; the verdict is downstream of what you're building, who you are, and what you're constrained by. Imports of someone else's verdict will mislead you in proportion to how different your situation is from theirs — and most architectural-decision content is written by people who aren't you.
Decided is not done
The decision is the architecture. The staging is the path that gets there without stalling current property work.
This is the part that took the longest to get right.
The tempting move is now we have an architecture, let's go build it. That move is wrong. Vela's kernel is still in active design. F&T is in mid-development. PA-site is younger than both. If I trigger the monorepo move now — extract the kernel into packages/, migrate all four-or-five products into apps/ — I extract against a moving target. The kernel will keep changing; I'll be refactoring extracted packages weekly to track its evolution. Solo cadence collapses. The substrate I extracted to compound improvements is the substrate I'm now spending my time maintaining instead of using.
The alternative move — we have an architecture, but it's not implemented yet; for now, keep building each product in its own repo — has a non-obvious risk that took a session of arguing with myself to surface. The risk is that I'll start optimizing each property's primitives for what would be easy to extract later. That's a trap. I can't predict which abstractions will survive substrate maturity. I'll carry abstraction overhead that probably won't match the eventual substrate shape, and the per-property work will be slower for it. The pre-optimization feels disciplined; in practice it's drag.
A concrete version of the trap, since the abstract version is harder to feel: the PA-site magazine needs per-user reading state. Without the trap, I write that as a Supabase table called reader_state, scoped to PA-site, with the columns PA-site needs. Five days of work, ships clean. With the trap, I notice that Vela's eventual kernel will probably also have a reader_state table, and I start building PA-site's table as a generic schema designed to merge cleanly with Vela's later. Now I'm designing against an unfinished kernel. I add columns Vela might want and PA-site doesn't need. The schema gets harder to evolve because every change has to be evaluated against two consumers, one of which is imaginary. Three weeks instead of five days, and the design is worse for the actual consumer because it's been compromised toward a hypothetical one. Pre-optimization for substrate compatibility is exactly the cost the architecture decision was supposed to defer until the substrate had earned the move.
The discipline that resolves this:
Phase 0 — Now. The architecture is named. No implementation. The decision document is the deliverable. That's the whole phase.
Phase 1 — Continue current property work without forking substrate. Each property continues to ship in its own repo. PA-site magazine builds its surface on simpler primitives. F&T continues its newsroom design. Vela continues kernel work. The trap to avoid: do not optimize current-property work for what would be easy to extract later. Build each property's primitives the way that's right for that property today. Trust that when the monorepo move triggers, refactoring is cheap; pre-emptive extraction is expensive.
Phase 2 — Audit and migrate. Triggered when the kernel is stable enough to extract. Specific gates: the kernel registry frozen, the measurement framework operationalized, the four-failure-mode veto enforced architecturally, no breaking-change items queued. At that point: code-overlap audit, monorepo stand-up, first migration — smallest property first, to test the pattern before tackling Vela's history.
Phase 3 — Vela migration and substrate extraction. The kernel moves into packages/kernel. Other properties consume the package; their pre-Phase-2 primitives are retired or absorbed. Vela goes last because it has the most history to coordinate.
Phase 4 — Substrate maturity. The substrate evolves through cross-property feedback. Five item-types running on one kernel. The substrate gets stronger because it's tested against multiple item-types instead of one. This is also when API-service-ification might become valuable for specific cross-property runtime data sharing — the library architecture accommodates the addition without restructuring.
The non-obvious move is the discipline of Phase 1. The architecture is named, but the implementation is deferred. Decided is not done. That gap — between the architecture committed and the architecture implemented — is the part most architectural-decision writing skips, because most of the writing is in environments where the architecture is the thing being implemented immediately. Solo cadence at portfolio scale demands a different rhythm. Architecture decisions are cheap; architecture implementations against a moving target are expensive. Decide; defer; trigger.
What this implies for analogous work
If you are running multiple products at solo or near-solo scale and you're noticing convergence between them, the move that follows from this isn't pick a substrate pattern. It's something more specific:
One — name the pattern that's already there. Look at what you've actually built. The substrate decision is downstream of the substrate that's already starting to exist. If two of your products share a kernel that you've re-derived in parallel, that's the pattern; you're not deciding whether to have it, you're deciding whether to consolidate it. Most of the architectural argument is recognition; the rest is housekeeping.
Two — derive the architecture from your specific objectives and constraints. Not from a menu of patterns. The same four patterns produce different verdicts in different situations. Solo operator + portfolio of distinct brands + mature flagship product + still-evolving substrate is a specific situation; its architectural answer is specific. Someone else's architectural answer is theirs.
Three — separate the architecture decision from the implementation timeline. Decide which architecture you're heading toward. Then decide when you can actually implement it without stalling current property work. The two decisions are different. The architectural commitment is cheap; the implementation against a moving target can be very expensive.
Four — protect the present from optimization for the future. Don't pre-extract. Don't pre-abstract. Each property's primitives should be right for that property today. Trust that when the substrate move triggers, refactoring will be cheap because the codebase is small enough that one operator carries the whole context. Pre-optimization for future substrate compatibility is the kind of move that feels disciplined and is actually drag. The discipline that looks like discipline is often a tax; the discipline that pays is the one that protects the present from the future.
These are not generic productivity moves. They're specific to a situation: portfolio of distinct branded products, solo or near-solo team, mature anchor property, substrate still evolving. The reason they apply is that solo cadence at portfolio scale is its own scale — the costs and benefits don't behave the way they do at team scale, and the architectural answer reflects that. Pattern catalogs imported from team scale will mislead you here. Pattern catalogs imported from single-product startup scale will also mislead you. Derive against the situation you actually have.
Honest about scope
This pattern fits a specific operator profile. It does not generalize to enterprise platforms; it does not generalize to single-product teams; it does not generalize to teams large enough that "atomic refactor across consumers" is a multi-quarter undertaking.
The architectural answer in those situations is different. Multi-tenant is right for SaaS platforms with thousands of tenants. API services are right for product surfaces that need to integrate across organizational boundaries. Versioned packages are right for teams that ship on independent cadences and need stability guarantees from their dependencies. Each pattern has a context where it's correct.
The instructive part of this piece isn't monorepo with kernel-as-library. It's the derivation discipline. The architectural answer drops out of the situation. Importing someone else's answer because it's the answer you've heard most about is exactly the wrong move — it imports an evaluation done against objectives and constraints that aren't yours.
There's a sharper version of this point that landed only after I'd written the decision down. The four-pattern analysis is correct for the magazine-shaped consumers in this portfolio — articles, insight cards, decision stories, performance-rating items. The corpus substrate, which sits behind the magazine work — long-PDF extraction, passage chunking, embedding, citation tracking — runs on build-time and batch consumers, not on a render hot path. Re-evaluated against its objectives and constraints, an API-service Phase 1 deployment is right for it, converging to the monorepo as a workspace package in Phase 2. Both substrates land in the same monorepo eventually; their Phase 1 deployments differ.
In other words: even within one portfolio, the same four-pattern catalog gets evaluated twice, against two different sub-substrates, and produces two different Phase 1 verdicts. If your situation is rich enough to have two substrates, your architecture will be rich enough to need two derivations. The verdict isn't a property of the catalog. It's a property of what you're building, and what's true about it.
That's the discipline. Derive against the work, not against the menu.
What you catch
The night I noticed F&T's newsroom had drifted into a mini-Vela, I had two options. I could push F&T to abandon the magazine shape and stay narrowly a fantasy-football intelligence app — which would have been cheaper short-term and would have left the portfolio architecturally unaware of itself. Or I could recognize that the pattern was already there, name it, and commit to not stop the products from continuing to converge.
What followed was the architecture decision. But the decision wasn't an act of choice. It was an act of recognition. The products had already started doing the right thing. My job was to notice it and to commit to the discipline that would let the convergence compound rather than dissipate.
That's the move I'd commend to anyone running a portfolio at solo cadence. The architecture you're heading toward is already there in the products. Most of what calls itself architectural decision-making is pattern-recognition with extra steps. Name what's already happening; derive against what you actually need; defer the implementation to when the substrate has earned the move; protect today's work from premature optimization for tomorrow.
You don't pick the architecture. You catch it.