status:alpha
version:v0.0.1
devstation engineering

Designed for extensibility: the technical decisions behind DevStation

Hexagonal, tactical DDD, CQRS, internal events and JSON-RPC contracts. Why a set that can sound like overengineering became the central bet — and what the evidence on AI and complex code has to say about it.

Jul 05, 2026 André Spineli
PT

A CLI that touches real infrastructure accumulates responsibilities fast. DevStation models clusters, nodes, VMs, images, sizes, vault, provisioning, stations and blueprints — and each of those carries its own rules. The list of features that fit a project like this is, in practice, unbounded. So the question that guided the design was not “how do we ship today’s feature” but “how do we accommodate the next one without compromising what already exists”. The throughline is extensibility.

Hexagonal: the domain isolated from the world

Hexagonal architecture — ports & adapters, proposed by Alistair Cockburn in 2005 — exists precisely to isolate business logic from infrastructure and let it be tested independently. In DevStation, Proxmox, the TUI, the file system and OpenTofu live at the edge; the core sees them only as ports. Architecture tests validate that boundary on every commit: the domain imports no infrastructure, and the inbound side never imports the outbound. The practical effect is direct — adding a provider or swapping the interface does not touch the business rules.

The layout matches what Herberto Graça catalogued as Explicit Architecture — his compilation of hexagonal, onion and clean into a single map, drawn from what was already practiced: an App Core — the Domain wrapped by the Application layer — with ports as the boundary and every dependency pointing inward. The naming here is inbound (driving) and outbound (driven), in place of primary/secondary.

Tactical DDD + CQRS: small contexts that grow without friction

The resources are organized into isolated bounded contextscluster, vault, station, service, images, size, blueprint, auth — each with its own aggregates, commands and events. Reads and writes are separated following CQRS. As Martin Fowler describes the pattern, it is about using different models to read and to write; it is worth being precise that CQRS requires neither a separate database nor event sourcing. Here it is single-store CQRS, where the queries project records directly, bypassing the aggregates.

The payoff shows up in maintenance. Extracting images into its own context was replaying a pattern, not inventing one. Renaming definition to size across the whole stack was mechanical because the boundary was explicit. And renaming deploy → install and destroy → uninstall end to end — contract, domain, events, persistence and UI — stayed a disciplined operation, precisely because each context knows what is its own.

Internal events between contexts

DevStation is a single binary, not a distributed system. Even so, the contexts integrate through domain events: a policy reacts to another context’s event and dispatches a command of its own. The project’s context map records this as a rule — cross-context communication is never direct. When a station finishes an install, the cluster context records the service projection on the VM and the vault stores the published secrets, both through policies, with no direct coupling between the contexts.

In a single binary this can sound excessive. The justification is one of timing: separating the contexts from the start, with eventual communication, keeps the door open to extracting a backend later — a shared service, a PaaS — without rewriting rules. It is cheap to do now and expensive to do later.

JSON-RPC contracts: the interface is detachable

The engine exposes a JSON-RPC boundary over stdio, and the contracts (OpenRPC, with code generated from them) are the real edge. The migration to that boundary was deliberately incremental: one context first, with no dual path, and a formal review before expanding to the rest — because mistakes in the envelope and contract modeling are costly to reverse once spread around. The result is that the React Ink TUI is just a client. Another TUI, a desktop app, even a web UI are, from the engine’s point of view, the same contract with a different screen. The contract is what matters; the interface is a detail.

Complexity, AI, and the role of the harness

It is worth acknowledging the trade-off: structure has a cost, and it has become measurable. METR measured experienced developers 19% slower using AI on mature repositories they already knew well, while estimating themselves to be 20% faster. Recent benchmarks point the same way: agent resolve rates fall off a cliff when a change crosses many files, and models degrade as context grows. Hence a critique that has been gaining ground: every layer is one more file to read, every indirection one more hop to trace, every purposeless abstraction noise competing for attention. Humans never paid per token to understand code; agents do.

The critique is right where it aims at ceremony. An interface with a single implementation forever, a use case that merely organizes, a mapper between identical DTOs: if the honest answer to “what real problem does this layer solve” is “organization”, the cost is not justified — for people or for models. No pattern in DevStation is exempt from that question.

The disagreement is in the generalization. Here, every layer answers the question that opened this article. The ports exist because the provider is swappable by design decision, and because the TUI and MCP are already two clients of the same contract. The bounded contexts exist because the domain genuinely accumulates rules — and a well-delimited context is precisely what shrinks the set of files an agent needs to load to touch a feature. That is not ceremony; it is context optimization. Mental frameworks and organization models were invented to fit complex domains into human heads, and models trained on human code inherit the benefit. In a domain with too many rules and unbounded room for extension, systematic convention beats naive software: it is what turns the next feature into replaying a pattern instead of re-deciding everything.

What changed the economics is the harness plus verification. The bottleneck the studies describe — scattered context, implicit patterns, boundaries that exist only in the team’s heads — is what a good harness attacks: rules always loaded, skills on demand, and architecture tests that break the build when a boundary is crossed. The evidence points the same way: agents improve when the repository’s structure is made explicit and verifiable — dependency graphs and interface specs lift resolve rates on multi-file codebases — while generic context files, on their own, do not help. Keeping the discipline was always the expensive part of these architectures. With an agent that follows the rules by construction and a suite that flags every violation, the discipline became the cheap part; what is left is the payoff.

Extensibility, in DevStation, is not architectural decoration. It is the decision that makes everything after it cheaper — which is why it came first.

References