Sharing the orchestrator upstream: the line between the template and the project that grew it¶
This project set out to build board games. Somewhere along the way it also built the thing that builds the games: an orchestrator that turns a phase into tickets, schedules them, runs each one through a fixed set of stages, and folds the lessons back in. If you have been reading along, you already know that loop from the inside -- and the more you watch it run, the harder it is to ignore a second question sitting underneath the first one. The games are the product. But what is the orchestrator?
Two earlier posts describe the loop from the builder's seat. Tickets in, a game I can play out: the orchestrated build-and-feedback loop walks the day-to-day cycle -- a ticket goes in, reviewed code comes out, you run the real app and form an opinion, and that opinion becomes the next ticket. The cross-game compounding flywheel: how each game makes the next one cheaper, and the last one better argues the payoff: a catalog built through that loop gets better over time, not just bigger. Both posts treat the machinery as a given -- the apparatus that makes the games possible. This post turns the camera around and points it at the apparatus.
The forward-looking claim is this: the machinery is itself a product. It is a reusable process scaffold that could ship independently of the board-game project that grew it. That reuse only works if someone draws a line between the parts that are generic and the parts that only ever made sense for board games -- and then keeps that line honest as the system keeps changing. This post is about that line: where it falls, why it has to be maintained deliberately rather than discovered by accident, and why "extract your own orchestrator" is an honest direction you can take rather than a finished thing you can download today.
If you are new to building this way, the useful takeaway is not "here is a product to install." It is a way of thinking about your own tooling: the moment a build loop is good enough to lean on, you should start asking which half of it is yours and which half is everyone's.
The orchestrator co-evolves with a reusable scaffold¶
The first thing to notice is that the orchestrator already lives at arm's length from the games. The whole workflow subsystem sits under its own top-level directory, the workflow subsystem, and it is documented as its own vendored thing rather than as board-game code. Its design reference is the orchestrator design docs; its first-run adoption checklist is the bootstrap docs. Neither of those documents mentions a card, a die, or a player's hand. They talk about stages, locks, state files, and notifications -- the vocabulary of a build system, not a game.
That separation is not cosmetic. It is the thing that makes the reuse claim plausible at all. Because the orchestrator is a self-contained subsystem with its own docs, every improvement landed in this repo -- a new lint gate, a smarter scheduler rule, a better field on the run record -- is a candidate contribution back to a generic template, not a one-off local hack welded into the game code. When you fix the way the orchestrator detects a stalled run, you have not improved a board game; you have improved a build system that happens to be building a board game today.
The repo is honest about this relationship in a way that is easy to miss. The bootstrap checklist opens by telling you it is a vendored copy: the canonical upstream of that document lives in a separate scaffold repository, and it is "vendored in" here so the workflow docs resolve and so the file is available as a reference when standing up another project. Read that the right way and it is a small but real admission: this repo is already a consumer of an upstream scaffold, not the sole author of its own process. The orchestrator and the template co-evolve -- the project uses the scaffold, improves it under real load, and those improvements are meant to flow back upstream rather than fossilize in one repo.
The newest expression of that co-evolution is a binary. the Tempo design memo describes tempo, an in-progress Go re-implementation of the same orchestration ideas, built to be a single installable command rather than a folder of bash you copy by hand. It is deliberately early -- its own README says the first scaffold "does not port any ticket, phase, review, ship, status, git, gh, or history-index behavior" yet, and that it exists to prove a clean build-and-toolchain contract before the real verbs land. That honesty matters, and we will come back to it. The point for now is the direction of travel: the generic machinery is being lifted out of "a subsystem inside the board-game repo" and toward "a thing that stands on its own."
Where the line falls: yours, and everyone's¶
So if the machinery can be shared, what exactly gets shared, and what stays behind? The interesting answer is that the boundary is fairly crisp once you look for it, and it splits along a single question: does this rule only make sense because we are building this particular product?
Some of the most load-bearing rules in this repo answer "yes," and those are the ones that stay project-specific:
- Game-shaped lints. This repo forbids gameplay code from calling Python's
randommodule directly; every gameplay draw has to route through aRandomnessService(the randomness service) so it can be seeded, audited, and replayed in a tournament. A dedicated check,lint-no-direct-random, enforces it. That rule is invaluable here and meaningless in a project that is not running games of chance. It belongs to the consumer, not the template. - The device-only, no-personhood rules. This product deliberately has no person accounts -- no chat, no friend requests, no profile photos, no demographic fields. A player profile is a device with a screen name, two-letter initials, a preferred color, and a preset avatar, and nothing else. That is a strong product stance with its own lints and its own canonical design baseline (the overview docs). It exists because of what this thing is. No generic build template should carry it.
Other machinery answers "no" -- it would be just as useful building an invoicing app or a chat server -- and that is the part that is genuinely generic and reusable:
- The ticket-to-stage state machine. The core of the orchestrator is a deterministic pipeline: a unit of work moves through a fixed sequence of stages -- draft, implement, review, ship, merge, cleanup, retro, notify -- and the current stage is written to an on-disk state file before the stage does anything else (the orchestrator design docs). Nothing about that loop is board-game-shaped. It is a generic "turn a ticket into reviewed, merged code, auditably" engine.
- The notification fallback. Every terminal state fires a notification, and the invariant is that no terminal state is ever silent. If the push to the ntfy service fails, the orchestrator does not shrug -- it writes the intended message to a pending-notifications file on disk so the failure itself is visible. That "never lose a terminal signal" design is pure infrastructure.
- The retry and dispatch mechanism. When a stage fails, a diagnostic step proposes a recovery action -- retry, escalate to a stronger model, switch engines, split the work, or hand off to a human -- and the orchestrator acts on that proposal in deterministic code, not in an agent's head. Knowing how to fail, retry, and escalate without a human babysitting every run is generic build-system value.
- The Tempo binary. And then there is
tempoitself, the Go command whose entire reason to exist is to be the generic orchestrator extracted from any one project.
Lay those two lists side by side and the shape of the thing becomes clear. The reusable template is the engine: a state machine that drives tickets through stages, a notification layer that refuses to go quiet, a retry-and-dispatch brain, and a binary to carry it. The project-specific layer is the judgment about this product: which random draws must be auditable, what a profile is allowed to contain, how a game stage must lay out on a phone. The engine is everyone's. The judgment is yours.
Holding the line is deliberate work, not an accident¶
It would be easy to assume that boundary just emerged -- that the generic parts naturally drifted into the workflow subsystem and the Tempo implementation while the game rules accreted in the consuming repo. They did not. The line is held on purpose, by rules whose whole job is to keep generic machinery and project specifics from leaking into each other.
The clearest example is the rule that any change under the workflow subsystem must be mirrored into the corresponding the Tempo implementation surface in the same change. When you improve the generic orchestrator's docs or its lint gates, you are not allowed to improve only the bash copy and let the Go port drift; the two are kept in lockstep, with a parity check as the backstop. That rule exists precisely because the template and the binary are two expressions of the same generic thing, and letting them diverge would quietly destroy the claim that there is one reusable orchestrator rather than two half-maintained ones.
The bootstrap design pushes the same discipline from the other direction. When the generic scaffold is stood up in a fresh project, it ships the build-system contracts -- ticket authoring, run records, batch authoring, data shape -- as vendored starter docs, and it leaves the project-specific decisions as blanks to fill in. the bootstrap docs even formalizes the split with two categories: "live config," the values a project sets in its own the review configuration and that the running system reads at runtime, versus "baked placeholders," the handful of names substituted once when the scaffold is adopted. That distinction is the boundary made mechanical. The scaffold arrives generic and parameterized; the consumer supplies the specifics; nothing about one project's name, branch, or ports is hard-coded into the shared machinery.
So the boundary is not a happy accident of where files landed. It is actively defended -- generic machinery under the workflow subsystem and in the Tempo binary, kept in sync and kept parameterized; project-specific rules and lints in the consuming repo, expressed through that repo's own agent rules, its own the project dev wrapper, and its own review configuration. The same control plane that runs the loop -- the orchestration control plane orchestrator directory and the /dev-* commands that drive phases and tickets -- is itself generic surface, configured per project rather than rewritten per project. The line is a thing people maintain, ticket by ticket, every time a new rule has to be filed on one side of it or the other.
Reframing the orchestrator as a product -- honestly¶
Put all of that together and the reframe is genuinely on the table: the orchestrator is not a one-off scaffold that happens to live in a board-game repo. It is a reusable product in the making -- a state machine, a notification layer, a retry brain, and a binary, with a clean boundary holding back the game-specific judgment so the engine can travel.
But the honest word in that sentence is "making." The template-versus-consumer split is a real, partly-open design question, and the post would be lying if it pretended otherwise. The Tempo binary is early by its own account: it can prove a toolchain and a build contract, but it does not yet run a real ticket through real stages, and its parity report deliberately lists the lifecycle surfaces it does not yet cover so a future cutover expands coverage on purpose instead of mistaking a partial check for a finished one. The adoption checklist is candid about what the scaffold simply does not do -- it generates no CI, installs no dependencies, manages no secrets. None of that is hidden. It is written down, because the people drawing the line would rather be honest about where it is unfinished than oversell a clean extraction that does not exist yet.
There is also a harder kind of incompleteness than "not built yet," and it is the part most worth teaching a newcomer: some rules sit ambiguously on the boundary, and reasonable people could file them on either side. Take this repo's rule that a browser-visible bug fix has to ship a live-repro evidence artifact and a multi-device test against a real running server. Is that a generic build-system rule or a project-specific one? The idea -- "prove the bug reproduced before you claim you fixed it" -- is about as generic as software discipline gets. But the implementation is shaped entirely around this product: it reaches for the dev-server logs this project happens to write and the multi-device browser specs this project happens to run. The principle wants to be upstream. The mechanism is hopelessly downstream. Where does the rule belong? That is not a settled question, and dozens of rules like it sit on the same fence.
That is what "partly-open" actually means in practice. It is not that the line is undrawn; it is that the line keeps having to be re-drawn as new rules show up that do not cleanly belong to either side. Extracting an orchestrator is not a one-time act of sorting files into two folders. It is an ongoing negotiation about what is essential machinery versus what is one product's taste -- and the negotiation never fully closes, because every new lesson the system learns arrives as a new rule that has to be placed.
The honest edges¶
I would be overselling this if I stopped at "and so you can lift out your orchestrator." Here is where the idea is load-bearing only if you keep paying for it.
The boundary decays the instant nobody defends it. Generic machinery and project judgment do not stay sorted on their own; they leak. A game-specific assumption sneaks into a "generic" lint because it was convenient; a genuinely reusable improvement gets stranded in the consuming repo because filing it upstream was extra work. The mirror rule and the bootstrap config split are dams, not laws of nature. Stop maintaining them and within a few cycles you are back to one repo with the machinery and the game rules fused into a ball that cannot be shared with anything.
Not every rule wants to be generic, and forcing the ones that do not is its own failure. The temptation, once you have a template, is to hoist everything into it so the template feels complete. That is how a generic orchestrator ends up carrying one product's quirks -- the build-system equivalent of every project inheriting your game's no-personhood stance whether it has players or not. Part of the skill of holding the line is telling "this is essential machinery" apart from "this is how we happen to do it," and that judgment is exactly the kind of thing the line-drawing work exists to get right, and sometimes gets wrong.
Early is early. Tempo is real and it is in the repo, but it is a scaffold proving a contract, not a shrink-wrapped command that runs your project today. If you take "extract your orchestrator" as a direction -- a way to think about your own tooling, a thing to grow toward -- it is honest and useful. If you take it as a download link, you will be disappointed, and rightly so. The split is being drawn, not shipped.
None of those are reasons not to think this way. They are the shape of the deal. The reward for treating your build loop as two things -- a generic engine and a specific consumer of it -- is that the engine becomes something you can carry to the next project instead of rebuilding from scratch. The price is that you have to keep drawing the line, keep defending it as new rules arrive, and stay honest that the drawing is never quite done.
That is the forward-looking turn the other two posts set up. Tickets in, a game I can play out shows the loop you stand inside; The cross-game compounding flywheel shows why running that loop makes the whole catalog better over time. This post is the next question both of them invite: once the loop is good enough to lean on, the machinery stops being scaffolding for one product and starts being a candidate product of its own -- a generic template with a specific consumer attached, separated by a line that someone has to keep drawing by hand. The line is not finished. That is not a flaw in the idea; it is the work the idea is made of.