Rootstock
by Corewood
A reference architecture for LLM-driven engineering which reliably scales — and a citizen science platform that proves it works.
Open-source architecture. Real production platform. Branch out without uprooting your project.
In horticulture, a rootstock refers to the established root of a fruit plant that can be grafted with limbs from other trees. Because the established root proves hearty and reliable, the branches grow from its steady supply of nutrients and solid grounding.
This is the missing rootstock for LLM-assisted code generation.
It's not a framework. It's not a specific technology. It's the basic way of thinking about a software system that you can graft onto, extend, and evolve for your needs.
Rootstock serves two purposes: a reference architecture for building production software with LLMs, and a citizen science IoT platform that stress-tests every architectural decision under genuinely complex conditions. The architecture is the root. The platform is the proof.
"Most engineering decisions optimize for authoring software — trying to make writing code easier. Corewood optimizes for operations, because over time, you save money."
The question that drives every decision: What kind of system would I want to pay a team to operate?
LLMs Write Code Fast. Making It Scale Is the Hard Part.
You've seen it. Maybe you've lived it.
A vibe-coded project starts fast, works well, then starts toppling under its own weight. Duplicated functions everywhere. Multiple dependencies managing the same job. The LLM gets confused, finds conflicting patterns, and stops helping.
Even disciplined approaches break down. A well-structured Python app with models, a repository pattern, and clean modules still hits the wall — because the context window explodes with every change.
Rootstock exists because this problem is structural, not skill-based. The architecture has to be designed for how LLMs actually work.
Reference Example: Rootstock
Rootstock isn't theoretical. We're building a complete citizen science IoT platform — called Rootstock — to demonstrate every piece of the architecture under real conditions. The full requirements specification follows the Volere template, traced to a Dgraph knowledge graph for auditability.
The Problem
Traditional scientific field data collection is expensive and structurally inadequate. Personnel costs consume 50–80% of grant budgets. Existing biodiversity datasets cover less than 7% of Earth's surface at 5km resolution. 75% of citizen science projects produce zero peer-reviewed publications — because they collect data without a specific research question.
Meanwhile, 250,000+ personal weather stations (Weather Underground), 30,000+ air quality sensors (PurpleAir), and a growing market of water and soil probes are already deployed at massive scale. These are distributed mini-labs sitting idle. PurpleAir data has already been used in peer-reviewed wildfire research — the data quality is there. The coordination isn't.
No platform exists that lets researchers define multi-parameter campaigns and match them to citizen-owned IoT devices.
The Platform
Researchers create data campaigns — structured requests specifying what sensor data they need, where, when, and to what quality standard. The campaign model inverts the typical citizen science flow: instead of "collect data and hope researchers use it," researchers define what they need first.
Scitizens — our term for citizen scientists — discover campaigns relevant to their location and equipment, enroll their IoT devices, and contribute readings. Devices submit data automatically after enrollment, fundamentally changing the retention equation.
Devices are untrusted. Every sensor reading crosses a trust boundary. The platform authenticates via mTLS with a self-operated certificate authority, authorizes via OPA against a device registry, and validates every reading against campaign-defined quality thresholds — schema, range, rate, geolocation, anomaly detection. Quarantined readings are never deleted.
Data is exported with full provenance metadata, quality metrics, and campaign context — meeting FAIR principles so the data is actually publishable. Contributor identity is separated from observation data. Raw contributor locations never appear in public-facing datasets.
What This Exercises
This isn't a demo app. It's a platform with:
- •Multi-tenant hierarchical authorization — institutions contain departments contain labs, roles are org-scoped, OPA enforces everything
- •IoT device lifecycle management — enrollment codes, CSR-based certificate issuance, 90-day cert rotation, automated renewal, bulk revocation
- •mTLS device authentication — two-tier CA hierarchy, device registry as source of truth
- •Campaign-driven data ingestion — schema validation, range checks, cross-device correlation, anomaly flagging
- •Cross-org participation — scitizens have no institutional affiliation with the requesting researcher
- •Privacy architecture — GDPR-compliant consent model, granular per-campaign permissions, configurable spatial resolution
- •Full observability from day one — OpenTelemetry traces, metrics, and structured logs flowing through a collector to Prometheus, Tempo, Loki, and Grafana dashboards
Not a todo app. Not a demo. A platform that solves the last-mile problem between researchers and the data they need.
We Didn't Theorize This. We Shipped It.
Corewood built LandScope with CEO Mitch Rawlyk — a terrain intelligence platform used by land professionals across the world. Enterprise authentication. SSO/SAML integration. OPA-driven permissions. OpenTelemetry from day one. 291,000+ acres mapped and counting.
We also built LLM inference engines, complex Postgres wire protocol interceptions, and a bunch of websites.
Then we asked: what if we open-sourced the architecture that made all of it possible?
Most engineering decisions optimize for authoring software — trying to make writing code easier. Corewood optimizes for operations, because over time, you save money.
Rootstock is the answer. Use it free. Or hire us to build yours.
Two Lessons That Changed Everything
Corewood spent ~11 months learning this the hard way — building production software, yelling at the LLM, and refining the patterns that actually hold up. Rootstock is everything we learned, open-sourced.
1. Manage Context Windows
Context windows are the bottleneck. LLMs effectively work at smaller scale, but as the application grows it buckles under its own weight. The LLMs get confused, find multiple patterns to follow, and ultimately fail to help your project grow.
Rootstock's architecture keeps every task small and self-contained. The LLM never needs to reason about the whole system.
2. Follow Strict Patterns
Every choice you give the LLM is a risk to the stability of the project. Don't give it more choices than absolutely necessary.
Rootstock uses identical file names, identical structure, identical conventions across every module. The LLM copies the nearest neighbor — and it works every time.
Want to apply these lessons to your codebase?
The Three Principles
Use Performant Languages
LLM-generated code won't be perfect. Give it a runtime where imperfect code still performs.
In independent benchmarks, Go executes 3–20x faster than Python and uses 1.5–3x less memory across typical workloads. The performance gap is wide enough that even suboptimal Go code routinely outperforms well-written Python. That headroom is what makes LLM-assisted coding viable at scale.
Definite, Reproducible Patterns
LLMs are pattern completion machines. Give them patterns that repeat identically — same file names, same structure, same conventions across every module. The LLM copies the nearest neighbor. The more consistent the pattern, the more reliable the output.
Keep Context Small Per-Task
The context window is the hard limit. The architecture must ensure that any given task only requires a small slice of the codebase. One module, one layer, two type files. That's all the LLM needs to see.
The Architecture
Four layers. One direction. Every module follows the same shape.
Handler → Auth → Flow → Ops → Repo
Handler
Decouples protocol and policy. Translates wire format to application types. Runs auth before anything else.
Auth
A sub-flow at the edge. Identity and authorization are resolved through globals that delegate to repos. Vendors are implementation details hidden behind interfaces. Unauthorized requests are dropped here — they never reach business logic. Auth-derived data (user ID, roles) is passed explicitly into the flow request. Clean contract. No side-channel dependencies.
Flow
Orchestrates. Calls one or more ops, converts between layer types. Thin by design.
Ops
Does the things. Business rules, validation, decisions. The fat layer.
Repo
Handles integration points. Any external boundary — database, identity provider, object store. Hides provider-specific details behind a clean interface.
The Module Convention
Every module at every layer has exactly two type files:
- receive_types — what comes in
- emit_types — what comes out
That's the contract. That's the entire surface area. If you know the inputs and outputs, you know the module. The LLM knows the module.
Directional Imports
Imports only go inward. Handler imports flow. Flow imports ops. Ops imports repo. Never the reverse.
The innermost layers are the most stable. The outermost layer absorbs all protocol changes without rippling inward. Circular dependencies are structurally impossible.
Globals and Vendor Obfuscation
Cross-cutting concerns — observability, events, auth — live in global/. Each global is a thin singleton accessor that delegates to a repo. The repo wraps the vendor. The global knows nothing about the vendor. Swap OpenTelemetry for Datadog, swap Zitadel for Auth0 — one repo changes, everything else stays the same.
This is volatility-based decomposition: things that change independently are isolated behind separate boundaries. Clean Architecture governs the direction: dependencies always point inward.
See these patterns applied to a real codebase.
The Stack
| Layer | Choice | Why |
|---|---|---|
| Backend | Go | Performance headroom, concurrency, ops-focused ecosystem, CGo for C interop |
| Frontend | Svelte + SvelteKit | Compiles to static assets. No server runtime. |
| API Protocol | ConnectRPC | Typed contracts from protos, codegen both sides, works over HTTP/1.1 |
| Database | PostgreSQL | Handles almost everything. Boring. Proven. |
| Object Store | Minio | S3-compatible, runs as a container, full dev/prod parity |
| Identity | Zitadel | SessionAPI only — isolated behind repo pattern, provider doesn't leak |
| Authorization | OPA | Policies as data, evaluated in-process, recompiled on state changes |
| Observability | OpenTelemetry | Tracing, structured logging, metrics from day one |
| Workflow Engine | DBOS | Durable execution for long-running processes |
| Reverse Proxy | Caddy | Automatic TLS, config-driven routing |
| Containers | Podman Compose | Dev parity with production. Dependencies live in the container, not on your machine. |
| Testing | Playwright | End-to-end stability and automated verifiability |
| Config | koanf | Defaults → YAML → env vars → CLI flags. Resolved once at startup. |
Every choice reduces the number of things the LLM has to guess about.
Every choice reduces the number of things the LLM has to guess about.
Two Ways Forward
Use Rootstock
The architecture, the patterns, the stack — all open source under a BSD 3-Clause license. Clone it, learn from it, graft your own project onto it.
make up starts the full stack: web server with hot reload, PostgreSQL, Zitadel, OPA, OpenTelemetry, Prometheus, Grafana, Caddy. Everything runs in containers. The only prerequisite is Podman.
Rootstock gives you the foundation. You build the branches.
Hire Corewood
You have a real problem. You need software that handles user data, credit cards, enterprise auth, and doesn't fall apart at 2am.
We've built this before. We'll build it with you.
Corewood brings the same architecture, the same patterns, the same production-first mindset — applied to your problem, your domain, your timeline.
Project Log
Building in public. Week by week.
Specification
127 functional requirements written
88 Must / 37 Should / 2 Could
10 business use cases defined
12 business events cataloged
24 spec documents (6,090 lines)
4 constraints with fit criteria
14 adjacent system interfaces
Implementation
16 commits
114 files changed
8,227 lines added
17 test packages
9 repos wired
33 ops built
14 flows composed
7 proto services
Spec Highlights
Volere-template requirements specification — from drivers through functional requirements.
Scope of Work
10 BUCs with dependency chains
14 external interfaces mapped
11 named flows with direction + data elements
12 events (temporal + non-temporal)
Functional Requirements
FR-001 – FR-045: Core BUC coverage (10 BUCs)
FR-046 – FR-127: Gap analysis pass (82 additional FRs)
Cross-cutting: Auth, notifications, admin, dashboards, accessibility, error handling, pagination
MoSCoW: 88 Must / 37 Should / 2 Could / 0 Won't
Constraints
CON-001 Open Source
CON-002 First Principles Design
CON-003 No Shared Context Exists
CON-004 ULID Identifiers
Backend Roadmap
10-phase dependency-ordered build plan (Phase 0–9)
10 of 33 ops reused across phases
FR → handler → flow → op → repo tracing
Architecture: The Grain
Boundaries drawn by volatility decomposition. Dependencies flow inward via clean architecture.
Handlers
Protocol edge. Connect RPC + HTTP.
Flows
Orchestration. No repo access. Pure composition.
Ops
Business logic. 10 of 33 reused across phases.
Repos
SQL, Zitadel, OPA, OTel, MQTT, Dgraph — all behind interfaces.
Infrastructure Stood Up
What's Next
[ ] User registration flow (stubbed, uncommitted)
[ ] Graph repo integration (Dgraph runtime schema, uncommitted)
[ ] TimescaleDB migration for readings (migration 007, uncommitted)
[ ] Notification provider (currently log-only stub)
[ ] UI build-out (Playwright scaffolding in place)
[ ] E2E tests against running stack
127 requirements. 10 business use cases. 9 repos. 33 ops. 14 flows.
Full spec-to-code traceability. Week 1.
This Week
6 commits
153 files changed
15,038 lines added / 536 removed
19 new flows
3 new ops packages (enrollment, graph, scitizen)
3 new repos (enrollment, scitizen, session)
Cumulative
2 new handlers (notification, scitizen)
16 new UI pages
17 new E2E test files
2 new migrations (008, 009)
22 total commits · 267 files · 23,265 lines
Week 1 Checklist
[x] User registration flow
[x] Graph repo integration
[ ] TimescaleDB migration for readings (carried over)
[x] Notification provider
[x] UI build-out
[x] E2E tests against running stack
5 of 6 shipped. TimescaleDB readings migration is the only carryover.
Highlights
Full scitizen vertical in one commit
9,029 insertions across 61 files (commit 431e5fe)
12 new flows, scitizen + enrollment ops & repos
508-line handler, 9 new UI pages, migration 009
8 new E2E specs — database to browser in one shot
Session repo replaced JWT middleware
server/jwt.go and server/jwt_test.go deleted
Session management moved to repo/session/ (clean arch boundary)
Interceptors refactored to use session repo
Auth expanded to full RBAC
authz.rego grew to full matrix: researcher, scitizen, admin
Role guards in Go (auth/roles.go) and UI (lib/auth/roles.ts, guard.ts)
Protobuf surface area tripled
~309 new lines of service definitions (user, scitizen, notification)
~3,400 lines of generated Go code
TypeScript codegen set up (buf.gen.ts.yaml) with Connect RPC client
Infrastructure / Architecture Changes
What's Next
[ ] TimescaleDB migration for readings (carried from Week 1)
[ ] Device provisioning + mTLS enrollment
[ ] MQTT reading ingestion pipeline
[ ] Campaign lifecycle (create, activate, archive)
[ ] Dashboard data visualizations
[ ] CI pipeline for proto + test + build
5 of 6 Week 1 items shipped. Full scitizen vertical landed. Protobuf surface tripled.
15,038 lines. 19 flows. Database to browser. Week 2.
This Week
3 commits
103 files changed
3,999 lines added / 702 removed
1 new flow (score/get_leaderboard)
2 new migrations (reading_values, leaderboard)
4 new E2E specs
2 new UI pages (leaderboard, campaign detail)
Cumulative
9 flow packages (45 files)
13 ops packages
17 repo packages
11 migrations
20 E2E specs · 16 screenshots
25 total commits · 799 files · 49,594 lines
Week 2 Checklist
[x] TimescaleDB migration for readings
[x] MQTT reading ingestion pipeline
[x] Campaign lifecycle (create, publish, activate)
[x] Dashboard data visualizations
[ ] Device provisioning + mTLS enrollment (carried over)
[ ] CI pipeline for proto + test + build (carried over)
4 of 6 shipped. Device provisioning and CI carry to Week 4.
Highlights
E2E test infrastructure
fixtures/db.ts (151 lines) + fixtures/mock-device.ts (168 lines)
full-loop.spec.ts (121 lines) tests researcher→scitizen pipeline end-to-end
Reusable harness for all future E2E specs (commit 5ce9bc2)
Automated screenshot suite
screenshots.spec.ts (222 lines) captures 16 PNGs (commit 560ef1a)
Registration, campaign wizard (5 steps), browsing, device management, dashboards
Reading pipeline overhaul
ingest_reading.go reworked (129-line diff) (commit efad9b6)
Migration 000010_reading_values restructures sensor reading storage
validate_reading.go expanded with new rules (+77 test lines)
Leaderboard — full stack in one commit
flows/score/get_leaderboard.go + ops/score/ops.go + repo/score (+160 lines)
scitizen/leaderboard/+page.svelte (189 lines)
New OPA policy + multi-user-leaderboard.spec.ts (185 lines)
Enriched researcher dashboard
campaign_dashboard.go expanded (+63 lines)
New researcher campaign detail page [id]/+page.svelte (116 lines)
researcher-dashboard-enriched.spec.ts (130 lines)
Infrastructure / Architecture Changes
What's Next
[ ] Device provisioning + mTLS enrollment (carried from Week 2)
[ ] CI pipeline for proto + test + build (carried from Week 2)
[ ] Real-time reading updates (WebSocket / SSE push)
[ ] Campaign archival + data export
[ ] Scitizen profile + contribution history page
[ ] Graph enrichment — link readings to knowledge graph
4 of 6 Week 2 items shipped. Reading pipeline overhauled. Leaderboard full-stack. 16 screenshots captured.
49,594 lines. 45 flows. Test harness to leaderboard. Week 3.


