Skip to main content
A product team has more ideas than weeks. Requests arrive as a sentence each, some worth a cycle and some not, and the ones that get built tend to grow while they are being built until the time is gone and the thing is half done. What the team wants is to build the right few things inside a fixed time, with the scope hammered to fit the time rather than the time stretched to fit the scope, and to see where each build stands. Basecamp’s Shape Up is one answer, and this example takes its shape: a request is shaped into a pitch with an appetite, a betting table decides what gets built, a small team builds inside the appetite with variable scope, progress is a hill from figuring out to executing, and a cool-down follows. The method is Basecamp’s, and the book is free to read; this file is one cycle of it, offline. Obversa makes each part a workflow and the cycle a graph of them. The pitch is the brief. The appetite is the build stage’s time limit. The betting table is three seats from different model families, each view checked by a typed Jev assessment, and the bet is the person’s. Scope is written down as it is cut. The record is the hill: every stage says which side it was on. One graph runs the workflows in order and keeps one record, and the same shape scales from one cycle to a whole organisation.

Run it

Set the project up as Installation describes. Copy the file with briefs/, requests/, tools/, policy.json, bets.json and checks.json beside it, sign in to Claude Code and Codex and install OpenCode, and run it from that directory. Without SHAPE_UP_JEV=1, the check on each seat’s view replays the assessment recorded in checks.json; with it, and a TypeSafe endpoint and key in the environment, the check asks Jev:
Terminal
The output below is the proof’s offline run, with scripted seats standing in for the models, so the words are the script’s and the shape is the run’s. The final summary:
Output, from the offline proof
Two requests, two pitches, one bet, one build. Shaping went round once, because the first pitch for the CSV export had no no-gos and the reviewer said so. At the table all three seats bet on the export and their checks were clear; on the dark theme two seats passed, one said it needed research and its check was under the policy’s floor, so the pitch went to research before the person saw it. The person bet on the export and passed on the theme, with the reason on the record. The build went round once, because the first change added an export by email that the pitch had listed under no-gos; the second stayed inside, the scope file named two cuts, and the check passed. The cool-down wrote what shipped, what was cut and what goes back to the pile.
Transcript, from the offline proof

The file

A request is a sentence with an appetite in its front matter. The appetite is the request’s, fixed before shaping, and the pitch copies it:
requests/r-1.md
Shaping is a writer and a reviewer over the requests. The writer turns each into a pitch with five parts; the reviewer, from another family, checks the parts and the appetite and the writer goes again:
examples/use-cases/product/shape-up-cycle.ts (excerpt)
The betting table is a graph of its own. For each pitch, three seats write a view, a typed check reads each view, a function reads the checks, and an uncertain table sends the pitch to research before the person’s bet:
examples/use-cases/product/shape-up-cycle.ts (excerpt)
Each check is one Jev call with a choice question, bet, pass or needs-research, whose answer carries a confidence. The routing is the file’s: a stance of needs-research, or a confidence under the policy’s floor, sends the pitch to research. Jev decides nothing on its own, the floor is the author’s number, and a confident answer is not assumed correct; the person sees every view with its check and any research and makes the bet. optional: true on the bet means a pass on one pitch doesn’t stop the cycle. The build is a workflow per bet: the pitch is the brief, the appetite is the stage’s time limit, retry is the rounds, a scope stage writes down what was cut, and a check fails a change that names a no-go:
examples/use-cases/product/shape-up-cycle.ts (excerpt)
The cycle is a dag() whose nodes are the workflows. That is the composition the runtime supports: workflow() returns a job, and a graph node runs a job, so a graph of workflows needs nothing more. A build runs only for a pitch the person bet on:
examples/use-cases/product/shape-up-cycle.ts (excerpt)
briefs/shaping.md
briefs/build.md
examples/use-cases/product/shape-up-cycle.ts

The team’s shape

The hill

Shape Up draws progress as a hill: uphill is figuring out the approach, downhill is executing it. Here the hill is read from the record. Every stage’s description begins with the side it is on, and the host collects the dag:node events as they finish:
examples/use-cases/product/shape-up-cycle.ts (excerpt)
The summary above lists the result, twenty-two stages in the order they finished: shaping and the whole table uphill, the build crossing from uphill to downhill, scope, check and cool-down downhill. A pitch the person passed on has its research and its bet on the hill and nothing after them, which is what the hill should show for it.

What the run did

The proof runs the file against three scripted seats and the recorded checks, and checks what the page describes: two shaping rounds, three views and three checks per pitch, the theme researched and the export not, the person’s two decisions with the reason for the pass, one build of two rounds with the no-go gone from the change and the scope written, the passed pitch never built, the cool-down with its three headings, and a hill with both sides. Obversa recorded the cycle as one event log, with each workflow’s stages under it, so the record reads as the cycle: what was shaped, who said what at the table and what the check made of it, what the person bet, what was built and cut, and how the cycle closed. Nothing here says how long software takes. The appetite in the requests is the example’s policy and the build stage’s limit, and the numbers in the recorded checks are a fixture. In a live run, checks.json gives way to Jev, the seats to signed-in Claude Code, Codex and OpenCode, and bets.json to a person answering the question through a page.

Next steps