Your first runnable line: clone it, run it, play it, file a ticket, watch it merge¶
The earlier on-ramp posts explain the shape of this project: write the intent down, let an AI worker build from a ticket, and keep the result reviewable. That is useful, but it still leaves a practical question unanswered.
What do you type first?
For this repo, the first pass is not abstract. It is a short sequence:
bash scripts/dev.sh setup
bash scripts/dev.sh dev-up
play the app on the devices it targets
write one backlog ticket
bash workflow/scripts/dev.sh ticket <ticket-stem>
That sequence matters because it turns the build loop from a claim into something you have seen run. You have a local app, a real product problem, a scoped ticket, a worker clone, review gates, and a run record. After that, posts like Tickets in, a game I can play out are describing a loop you have touched, not a workflow diagram.
Step 1: make the checkout runnable¶
From the repo root:
This command lives in scripts/dev.sh, the project-side wrapper. Its setup verb creates ./venv when needed, installs the Python requirements, hydrates client/node_modules, and wires the repo's git hooks. It is idempotent: on a checkout that is already prepared, it skips the backend dependency install and keeps going.
The point of starting here is repeatability. You do not assemble the backend and frontend by hand from separate guesses. The repo owns one setup entry point, and the same wrapper is what ticket agents are told to run when their environment is missing dependencies.
Step 2: start the app through the wrapper¶
Then start the local stack:
dev-up starts the FastAPI backend and the Vite client. In the authoring/chat clone it uses backend port 8001 and client port 3001; in worker clones the wrapper derives per-clone ports so parallel ticket runs do not fight over the same listeners. The selected ports are written to .runtime/dev-ports.env. The backend and client process IDs are written to .runtime/backend.pid and .runtime/client.pid, and logs go to .runtime/backend.log and .runtime/client.log.
Stop it with:
Restart it with:
Those commands are not decoration. They are the runtime bookkeeping. If you start the backend with a raw uvicorn command or the client with a raw npm run dev, dev-down cannot reliably clean up what you started. The repo rule is simple: local app lifecycle goes through dev-up, dev-down, and dev-restart.
Step 3: use the product the way it is built¶
This project is not a single-browser demo. It is a multi-device board game platform. A shared TABLE screen shows public state. Phones join as PLAYER controllers. A REFEREE can hold full-state authority. OBSERVER and REVIEW views have their own access rules. The server sends role-filtered views so private player information does not leak to the table.
So the first useful smoke test is not "click around one desktop tab." Open the table surface on a TV or second display, join from a phone on the same LAN, and play a short round. That is where the real product problems show up: a control is too low on a phone viewport, a table label is unreadable from across the room, a private detail is visible in the wrong role, or a join flow makes sense on desktop but not in a hand.
This step is also why the loop stays grounded. The ticket should come from the running product, not from a vague desire to improve the code.
Step 4: write one ticket, not a wish¶
When you notice the problem, do not ask a chat agent to "fix the lobby" or "make the UI better." Ask it to draft a ticket file under tickets/backlog/.
A useful ticket says:
- what behavior should change
- what files or surfaces it is allowed to touch
- what it depends on
- what counts as done
- which commands verify it
That shape is enforced by the authoring contracts under workflow/docs/authoring/. The important fields are visible in every backlog ticket: YAML frontmatter, ## Depends on, ## Touches, scope, acceptance criteria, and verification commands. For bug tickets, the rules are stricter: the ticket needs a concrete repro path and the eventual run needs evidence that the bug was exercised against the real app.
A ticket-shaped request is specific:
On a phone PLAYER surface, keep the room join code visible above the settings section at the default portrait viewport. Touch only the shell join screen and its layout-fit coverage. Verify with the multi-device fit spec.
A wish is not specific:
The orchestrator can build from the first one. It can only guess at the second.
Step 5: hand the ticket to the orchestrator¶
After the ticket exists, run it with the workflow dispatcher:
For a ticket file named tickets/backlog/join-code-above-settings.md, the stem is join-code-above-settings:
That command is not the same wrapper that starts the app. workflow/scripts/dev.sh owns the ticket lifecycle. Its ticket verb resolves the ticket, runs launch preflight, and hands the work to the orchestrator as a single-ticket run. The worker runs in a disposable ticket clone, implements the ticket, writes the required sibling run record, and sends the result through review and ship stages. If the gates pass, the orchestrator handles the merge lifecycle. If they do not, the run record and state files explain what happened.
You can inspect the queue and recent state through the same workflow dispatcher:
The useful artifact after a run is not just code. It is the pair: the ticket that said what was supposed to happen and the .run.md file that records what actually happened.
Two wrappers, two jobs¶
The repo has two wrappers because it has two different kinds of work.
| File | Job | Examples |
|---|---|---|
scripts/dev.sh |
Project development: run, test, lint, build docs, build the blog. | setup, dev-up, dev-down, dev-restart, test, lint, docs-build-strict, blog-build |
workflow/scripts/dev.sh |
Orchestrator lifecycle: move tickets and phases through the workflow. | ticket, phase, review, ship, status, backlog-list, history |
Use the project wrapper when you are working on the app. Use the workflow wrapper when you are moving work through the ticket system. The split is practical: project commands need the app's Python, Node, docs, and lint environment; workflow commands also maintain run state, ticket state, logs, launch attempts, and merge bookkeeping.
This is also the naming problem Tempo is meant to solve long term. Tempo is the in-progress Go port of the orchestrator. Its CLI already has orchestration-shaped commands such as tempo run <ticket-stem>, tempo group-run <name>, and tempo launch-preflight, and the port keeps generic lint gates and authoring machinery under tempo/. But in this repo today, real ticket execution still goes through bash workflow/scripts/dev.sh ticket <ticket-stem>. The honest statement is: Tempo is the productized direction; the workflow dispatcher is the current operational path.
What this unlocks¶
The first pass through the loop gives you three useful capabilities.
First, you can reproduce the product locally from one checkout. That matters because multi-device bugs are usually product bugs before they are code bugs.
Second, you can turn an observation into a schedulable contract. That matters because the AI worker does not need a mood or a preference; it needs a bounded ticket with a verification command.
Third, you can read the result after the fact. The run record preserves dead ends, judgment calls, and verification results, so the reason for a change is not trapped in a chat transcript.
Those are the same capabilities Tempo is trying to package for other repositories: setup discipline, ticket-shaped work, lifecycle state, review gates, and durable records. Running this sequence once is the small version of that future.
The honest edge¶
The sequence above is the clean path, not a promise that every ticket merges on the first try.
Review gates fail. A ticket can miss a dependency, trip a layout rule, break a lint, or hit a test failure. That is not the orchestrator being broken; that is the governance doing work. The point of the system is not that the AI always gets it right. The point is that failures are caught in a place where they leave evidence.
The ticket can also be wrong. If you describe a phone layout problem as a WebSocket problem, the worker may return a clean implementation of the wrong idea. The pipeline can check that code matches the ticket. It cannot prove the ticket matched the product. That is why the loop starts and ends with playing the real app.
So the first runnable line is not just setup. It is the whole first pass:
Once you have done that once, the workflow stops being a concept. It becomes a habit: run the app, observe one concrete problem, write the smallest honest ticket, let the orchestrator build it, and judge the result in the product again.
Or: subscribe for the rest of the mindset track.