Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
e5ecca7
feat: deliver the round-4 asks
claude Jul 6, 2026
c99bb3a
docs: dungeon crawl example (Matz round 5)
claude Jul 6, 2026
0c25c87
docs: live kanban board example (DHH round 5)
claude Jul 6, 2026
b1842cd
docs: critical path analyzer example (tenderlove round 5)
claude Jul 6, 2026
da0eea2
docs: Mermaid plan diagrammer example (fxn round 5)
claude Jul 6, 2026
090cb24
docs: burst absorber example (ioquatix round 5)
claude Jul 6, 2026
3dfbe98
docs: freight desk example (Jeremy Evans round 5)
claude Jul 6, 2026
88c5190
docs: 422 generator example (solnic round 5)
claude Jul 6, 2026
2077bbb
docs: stampede simulator example (mperham round 5)
claude Jul 6, 2026
89c96c5
docs: three shapes example (Sandi Metz round 5)
claude Jul 6, 2026
796c84a
docs: examples index generator (ankane round 5)
claude Jul 6, 2026
5f80565
docs: round-5 index and findings
claude Jul 6, 2026
633278a
feat: deliver the round-5 asks
claude Jul 7, 2026
9faaf09
docs: plan tour example (Matz round 6)
claude Jul 7, 2026
1ef0a1f
docs: deploy train example (DHH round 6)
claude Jul 7, 2026
09534d2
docs: perf diff example (tenderlove round 6)
claude Jul 7, 2026
43de833
docs: plan round-trip example (fxn round 6)
claude Jul 7, 2026
29c05fe
docs: quota keeper example (ioquatix round 6)
claude Jul 7, 2026
7610040
docs: rule prober example (Jeremy Evans round 6)
claude Jul 7, 2026
cc12bdf
docs: API reference generator example (solnic round 6)
claude Jul 7, 2026
3f069fb
docs: jitter shootout example (mperham round 6)
claude Jul 7, 2026
a7cdd8c
docs: refactor receipts example (Sandi Metz round 6)
claude Jul 7, 2026
516e15e
docs: cost estimator example (ankane round 6)
claude Jul 7, 2026
49de058
docs: round-6 index and findings; regenerate examples catalog
claude Jul 7, 2026
622a0cc
feat: deliver the round-6 asks
claude Jul 7, 2026
1fc6613
docs: plan fortune teller example (Matz round 7)
claude Jul 7, 2026
181231b
docs: weekly check-in example (DHH round 7)
claude Jul 7, 2026
0e8c0b9
docs: perf history example (tenderlove round 7)
claude Jul 7, 2026
b6be486
docs: plan structural diff example (fxn round 7)
claude Jul 7, 2026
e5c8b44
docs: composed limits example (ioquatix round 7)
claude Jul 7, 2026
267b30a
docs: backoff conformance example (Jeremy Evans round 7)
claude Jul 7, 2026
5cbb563
docs: JSON Schema export with agreement proof (solnic round 7)
claude Jul 7, 2026
b5c2f45
docs: incident report example (mperham round 7)
claude Jul 7, 2026
9cb59ed
docs: graph style guide example (Sandi Metz round 7)
claude Jul 7, 2026
2ece04e
docs: capability evals example (ankane round 7)
claude Jul 7, 2026
70d3a83
docs: round-7 index and findings; regenerate examples catalog
claude Jul 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,11 +234,36 @@ orchestrator.add_task(next_verse, [previous_verse], agent: ->(task) {
```

Capability contracts can constrain values beyond type and presence —
`enum: %w[standard express]`, `min:`/`max:` bounds, and `non_empty: true`
for strings and arrays. The `task_slot_acquired` lifecycle hook fires when
a task obtains a concurrency slot (with `waited:` time), separating queue
wait from run time; and retry policies consult an error's own
`retryable?` verdict before falling back to the `retryable_errors` list.
`enum: %w[standard express]`, `min:`/`max:` bounds, `non_empty: true` for
strings and arrays, and cross-field `rules:` checked over the whole input
(`rules: {"express max 10 items" => ->(i) { i[:speed] != "express" || i[:quantity] <= 10 }}`).
`ValidationError#expectations` carries the declared contract for each
violated key, so error messages can name what would have been legal.

The `task_slot_acquired` lifecycle hook fires when a task obtains a
concurrency slot (with `waited:` time), separating queue wait from run
time; retry policies consult an error's own `retryable?` verdict before
falling back to the `retryable_errors` list, and backoff jitter is on by
default so fleets don't retry in lockstep (`backoff_jitter: :full` draws
uniformly from `[0, delay]` for the hardest herd-flattening).

`PlanOrchestrator#graph` returns a frozen snapshot of the plan's topology
— including `graph[:order]` (topological sort), `graph[:edges]` (labeled
by `needs:` names), and `graph[:stats]` (per-task depth, max depth, max
fan-in) — for tools that render, review, or analyze plans.
`Agentic::RateLimit` is a credential-scoped ceiling shareable across
plans and clients (`Agentic::LlmClient.new(config, limiter: rate_limit)`):
concurrent by default, a rolling-window quota with
`RateLimit.new(30, per: 60)`, or both laws at once via composition —
`quota.and(pool)`. Cross-field rules may be structured —
`rules: {air_weight_limit: {message: "...", fields: [:mode, :weight], check: ->(i) {...}}}` —
and `ValidationError#rule_violations` reports each broken rule with its
identifier, message, and the fields it reads.
`CapabilitySpecification#to_json_schema` emits a contract side as
draft-07 JSON Schema for OpenAPI and validator toolchains; journal
replays expose per-task `durations` keyed by description (performance
baselines for free); and retry policies accept an injected `rng:` for
reproducible jitter timing.

### The concurrency contract

Expand Down
122 changes: 122 additions & 0 deletions docs/perspectives/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,128 @@ consulting `failure.retryable?`, and contract value predicates —
| 9 | Sandi Metz | Graph critic — design review for dependency graphs, pre-execution | `examples/graph_critic.rb` | [round-4/09-sandimetz.md](round-4/09-sandimetz.md) |
| 10 | Andrew Kane | README verifier — every snippet parsed, every constant resolved (found a 4-round-old broken snippet) | `examples/readme_verifier.rb` | [round-4/10-ankane.md](round-4/10-ankane.md) |

## Round 5 — the ecosystem turn

The round-4 asks shipped as a release (`PlanOrchestrator#graph`,
`ValidationError#expectations`, cross-field contract `rules:`,
`Agentic::RateLimit` + `LlmClient limiter:`, jitter-on-by-default), the
three examples that requested them were modernized onto them, and ten
more experiments followed:

| # | Persona | Built on the round-5 release | Run it | Field notes |
|---|---------|------------------------------|--------|-------------|
| 1 | Matz | Dungeon crawl — the map drawn from the plan itself | `examples/dungeon_crawl.rb` | [round-5/01-matz.md](round-5/01-matz.md) |
| 2 | DHH | Live kanban — the WIP limit is the concurrency limit | `examples/kanban_board.rb` | [round-5/02-dhh.md](round-5/02-dhh.md) |
| 3 | Aaron Patterson | Critical path — which task the wall clock is actually about | `examples/critical_path.rb` | [round-5/03-tenderlove.md](round-5/03-tenderlove.md) |
| 4 | Xavier Noria | Mermaid diagrammer — docs generated from the graph, labeled by `needs:` | `examples/plan_diagram.rb` | [round-5/04-fxn.md](round-5/04-fxn.md) |
| 5 | Samuel Williams | Burst absorber — `RateLimit` characterized under hostile waves | `examples/burst_absorber.rb` | [round-5/05-ioquatix.md](round-5/05-ioquatix.md) |
| 6 | Jeremy Evans | Freight desk — a tariff book as cross-field rules, all violations at once | `examples/freight_rules.rb` | [round-5/06-jeremyevans.md](round-5/06-jeremyevans.md) |
| 7 | Piotr Solnica | 422 generator — one contract-agnostic error renderer from `expectations` | `examples/form_errors.rb` | [round-5/07-solnic.md](round-5/07-solnic.md) |
| 8 | Mike Perham | Stampede simulator — the jitter default, argued by histogram | `examples/stampede_sim.rb` | [round-5/08-mperham.md](round-5/08-mperham.md) |
| 9 | Sandi Metz | Three shapes — chain vs star vs staged, chosen by evidence | `examples/three_shapes.rb` | [round-5/09-sandimetz.md](round-5/09-sandimetz.md) |
| 10 | Andrew Kane | Examples index — self-maintaining signage for a 40-example gallery | `examples/examples_index.rb` | [round-5/10-ankane.md](round-5/10-ankane.md) |

## Round 6 — plans as artifacts

The round-5 asks shipped as a release (`graph[:order]` via Kahn's
algorithm, labeled `graph[:edges]`, structured rules with
`fields:`/`rule_violations`, `backoff_jitter: :full`, and windowed
rate limits `RateLimit.new(30, per: 60)`), four examples were
modernized onto them, and ten more experiments followed:

| # | Persona | Built on the round-6 release | Run it | Field notes |
|---|---------|------------------------------|--------|-------------|
| 1 | Matz | Plan tour — the plan narrated as prose, before it runs | `examples/plan_tour.rb` | [round-6/01-matz.md](round-6/01-matz.md) |
| 2 | DHH | Deploy train — the unhappy path as the product | `examples/deploy_train.rb` | [round-6/02-dhh.md](round-6/02-dhh.md) |
| 3 | Aaron Patterson | Perf diff — did the PR make it worse, path-qualified | `examples/perf_diff.rb` | [round-6/03-tenderlove.md](round-6/03-tenderlove.md) |
| 4 | Xavier Noria | Plan round-trip — graph → JSON → graph with isomorphism proof | `examples/plan_roundtrip.rb` | [round-6/04-fxn.md](round-6/04-fxn.md) |
| 5 | Samuel Williams | Quota keeper — ceiling physics vs window physics, 61ms vs 601ms | `examples/quota_keeper.rb` | [round-6/05-ioquatix.md](round-6/05-ioquatix.md) |
| 6 | Jeremy Evans | Rule prober — field declarations audited; a lying rule caught | `examples/rule_prober.rb` | [round-6/06-jeremyevans.md](round-6/06-jeremyevans.md) |
| 7 | Piotr Solnica | API reference — docs from the contracts that validate the calls | `examples/api_reference.rb` | [round-6/07-solnic.md](round-6/07-solnic.md) |
| 8 | Mike Perham | Jitter shootout — none/equal/full on one scoreboard: 40/19/13 | `examples/jitter_shootout.rb` | [round-6/08-mperham.md](round-6/08-mperham.md) |
| 9 | Sandi Metz | Refactor receipts — the god join dissolved in priced steps | `examples/refactor_receipts.rb` | [round-6/09-sandimetz.md](round-6/09-sandimetz.md) |
| 10 | Andrew Kane | Cost estimator — the plan priced before it runs, reconciled after | `examples/cost_estimator.rb` | [round-6/10-ankane.md](round-6/10-ankane.md) |

## Round 7 — the referee round

The round-6 asks shipped as a release (`RateLimit#and` composition,
`graph[:stats]`, journal `durations` keyed by description,
`CapabilitySpecification#to_json_schema`, injectable retry `rng:`),
two graph tools were modernized onto `stats`, and ten more experiments
followed:

| # | Persona | Built on the round-7 release | Run it | Field notes |
|---|---------|------------------------------|--------|-------------|
| 1 | Matz | Plan fortune teller — structural diagnoses in a mystic's robe | `examples/plan_fortune.rb` | [round-7/01-matz.md](round-7/01-matz.md) |
| 2 | DHH | Weekly check-in — the journal answers so nobody's Friday has to | `examples/weekly_checkin.rb` | [round-7/02-dhh.md](round-7/02-dhh.md) |
| 3 | Aaron Patterson | Perf history — regressions judged against the last release's journal | `examples/perf_history.rb` | [round-7/03-tenderlove.md](round-7/03-tenderlove.md) |
| 4 | Xavier Noria | Structural diff — plan review at design altitude, not JSON altitude | `examples/plan_structural_diff.rb` | [round-7/04-fxn.md](round-7/04-fxn.md) |
| 5 | Samuel Williams | Composed limits — both laws at once; the chart names the binding one | `examples/composed_limits.rb` | [round-7/05-ioquatix.md](round-7/05-ioquatix.md) |
| 6 | Jeremy Evans | Backoff conformance — nine timing envelopes certified via injected rng | `examples/backoff_conformance.rb` | [round-7/06-jeremyevans.md](round-7/06-jeremyevans.md) |
| 7 | Piotr Solnica | Schema export + agreement proof — every projection ships its referee | `examples/json_schema_export.rb` | [round-7/07-solnic.md](round-7/07-solnic.md) |
| 8 | Mike Perham | Incident report — the 3am questions answered from the journal | `examples/incident_report.rb` | [round-7/08-mperham.md](round-7/08-mperham.md) |
| 9 | Sandi Metz | Graph style guide — RuboCop for plans, ten lines per cop | `examples/graph_style.rb` | [round-7/09-sandimetz.md](round-7/09-sandimetz.md) |
| 10 | Andrew Kane | Capability evals — contracts check types, evals check truth | `examples/capability_evals.rb` | [round-7/10-ankane.md](round-7/10-ankane.md) |

### What round 7 surfaced

1. **The referee pattern generalized**: six exit-code-gated honesty
tools now exist (fuzzer, prober, verifier, conformance, agreement
proof, evals) — the framework can no longer lie about its
contracts, rules, docs, timing, exports, or answers.
2. **The journal became four products**: crash recovery, resume keys,
perf baselines, and prose (check-ins, incident reports) — one
fsynced JSONL file, read with different questions.
3. **Tools kept correcting their authors**: Samuel's binding-constraint
prose and Piotr's generator coverage were both fixed by their own
measurements — the third straight round of measurement-over-narrative.
4. **Next asks**: `stats[:roots]`/`stats[:leaves]`, percentile
baselines over journal history (p50-of-last-N), rename detection
hints in the structural diff, JSON Schema `if/then` emission for
expressible rules, and an eval-scorer seam for LLM-backed
capabilities.

### What round 6 surfaced

1. **Plans became artifacts**: narratable (tour), serializable with an
isomorphism proof (round-trip), priceable before execution (cost
gate), and diffable across runs (perf diff). The graph accessor's
second round turned topology into a first-class document.
2. **Declarations became testable claims**: rule `fields:` shipped as
UI plumbing and immediately became an auditable specification — the
prober caught a seeded lying rule that would have misdirected form
highlighting.
3. **One contract, five behaviors**: validate, reject, explain,
document, audit — the same declaration now feeds all five.
4. **Honest prose corrections**: two personas (DHH, Sandi) had their
example copy corrected by their own measurements — the tools are
now good enough to disagree with their authors.
5. **Next asks**: composing a windowed and a concurrency limiter as
one object, journal-fed baselines for the perf diff, OpenAPI
emission from contracts, custom RNG injection for retry policies,
and a `graph`-level depth/fan-in stats helper (Sandi's third
strike).

### What round 5 surfaced

1. **The graph accessor compounded immediately**: one round old, it fed
a game map, a critical-path analyzer, a Mermaid generator, and a
design curriculum. Expose the right projection and an ecosystem
assembles itself.
2. **Named dependencies turned out to be documentation**: `needs:`
labels became labeled diagram edges — ergonomics maturing into
architecture records.
3. **Every round-4 feature was characterized under load the round it
shipped** — the burst absorber (RateLimit), the stampede histogram
(jitter), the freight desk (rules), the 422 generator
(expectations).
4. **Next asks**: `graph[:order]` (topological sort — requested
independently by three personas) plus `graph[:edges]` with labels,
structured rule identifiers (`{rule: :symbol, fields: [...]}`) so
policy violations can point at widgets, a `backoff_jitter: :full`
tier, and time-windowed rate limits alongside the concurrency
ceiling.

### What round 4 surfaced

1. **Two more real defects found by examples**: canceled plans reported
Expand Down
54 changes: 54 additions & 0 deletions docs/perspectives/round-5/01-matz.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Round 5 field notes — Matz maps the dungeon

*Built: `examples/dungeon_crawl.rb` — a quest as a plan: rooms are
tasks, doors are dependencies, and the map is drawn from
`orchestrator.graph` before anyone delves.*

## What I built and why

Every dungeon game maintains two truths: the map the player sees and
the graph the engine walks — and every dungeon game eventually ships a
bug where they disagree. This crawl has one truth. The map is printed
from `orchestrator.graph`, the same frozen topology the scheduler will
execute:

```
[Entrance Hall] <- you are here
[Treasury] doors from: Spider Nest, Flooded Crypt
```

Then the party delves — nest and crypt in parallel, the treasury
waiting on both keys via `needs: {web:, depths:}` — and the loot fans
in: "a chest of coppers (unlocked with someone's boot and a silver
coin)." There is no second map to fall out of date, because the
document and the program are the same object. That is the deepest kind
of DRY: not avoiding repeated *text*, but avoiding repeated *truth*.

## What pleased me

- `graph` being **frozen** is the right manner. A map you can scribble
on is a map you can lie with; the accessor hands you a photograph,
not the territory's steering wheel. (I tried to mutate it, for
science. `FrozenError`. Good.)
- Five features from three rounds cohabit in sixty lines without
crowding: payloads, callables, positional deps, named needs, the
graph view. New vocabulary that doesn't jostle the old vocabulary is
the sign the design is growing rather than accreting.
- Seeded loot, again. "Reproducible whimsy" is my favorite genre now.

## A small observation for the maintainers

The map loop wants the rooms in *dependency order* (entrance first,
treasury last), and I got it by accident because insertion order
matched. `graph` preserves insertion order — Ruby hashes promise that —
but a plan built in scrambled order would print a scrambled map. A
`graph[:order]` with a topological sort would let map-drawers be
correct on purpose instead of lucky. (Aaron will want it for critical
paths within the hour; ask him.)

## Verdict

The framework can now draw itself before it runs itself. When a
library's introspection is good enough to build a game's UI from, the
architecture documents can retire — the code has learned to give the
tour.
49 changes: 49 additions & 0 deletions docs/perspectives/round-5/02-dhh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Round 5 field notes — DHH ships the kanban board

*Built: `examples/kanban_board.rb` — a running plan rendered as To Do /
Doing / Done, every frame captured live from lifecycle hooks.*

## What I built and why

We spent two decades wiring project-management tools to *approximate*
the state of work, staffed with people whose job is updating the
approximation. But when the work is a plan, the orchestrator already
IS the board — the columns are just `pending`, `in_progress`, and
`completed` wearing better clothes. Two hooks move the cards:

- `task_slot_acquired` → card moves To Do → Doing (note: *slot*
acquired, not scheduled — a card in Doing means someone is actually
working it, which is the entire honesty proposition of a kanban
board; before round 4's hook this column would have lied)
- `after_task_success` → Doing → Done

Twelve frames, 302ms, and mid-flight the board shows exactly what a
two-person team looks like: `layout page` in Doing, `review` and
`publish` waiting on it, three cards shipped. Nobody typed a status.

## The insight worth the price of admission

The **WIP limit is the concurrency limit.** Kanban's whole discipline —
"limit work in progress" — is `concurrency_limit: 2`, enforced by the
scheduler instead of by a coach reminding people in a meeting. When
your process tool and your execution engine are the same object, the
process can't drift from reality, because it *is* reality. That's the
argument I've been making about software writ small: the best process
is the one your system enforces structurally, invisibly, for free.

## Notes

- The board never shows a "Blocked" column because dependency-waiting
cards just stay in To Do. For this demo that's fine; a real board
wants `blocked` derived from graph[:dependencies] minus done. One
`orchestrator.graph` call — everything's there. Fizzy could be forty
lines on this framework, and I'm only half joking.
- Frame capture is an array push in a hook — Samuel's
"hooks-run-inline" contract means I thought about cost for exactly
one second, which is what a documented contract buys.

## Verdict

Five rounds ago this framework couldn't tell you what it was doing;
now it renders its own kanban in real time from two hooks. Delete the
status meeting. The plan will speak for itself.
58 changes: 58 additions & 0 deletions docs/perspectives/round-5/03-tenderlove.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Round 5 field notes — Aaron Patterson walks the critical path

*Built: `examples/critical_path.rb` — graph topology plus measured
durations yields the chain that determined the wall clock, then proves
it by experiment.*

## What I built and why

The knee finder answered "how many lanes"; this answers the question
that comes right after: "which task is the wall clock's fault?" The
answer is never "all of them" — it's the **critical path**, the longest
duration-weighted chain through the dependency graph:

```
wall clock: 341ms
critical path: 340ms = pull:orders -> invoice:month -> report:board
(path explains 100% of the wall clock)
```

And because engineers rightly distrust analyzers, the program runs the
experiment instead of asking for faith: make an *off-path* task
instant — wall time unchanged, 341 → 341. Halve the slowest *on-path*
task — 341 → 251. There's your sprint planning: `pull:orders` is the
only work item, and anyone polishing `pull:catalog` is doing charity
for a metric no one measures.

## What the round-4 accessor made possible

The path computation is fifteen lines of memoized DFS over
`orchestrator.graph` joined with durations from one hook. In round 3 my
Gantt *rebuilt* the topology by eavesdropping on hooks, which meant it
could only see what executed; the graph accessor knows what was
*declared*, which is what lets the analyzer say "this chain, and no
other chain, bounds you." Sandi's crowbar complaint became my
load-bearing API within one round — that's the compounding this
experiment series keeps demonstrating.

Matz already filed the follow-up I want: `graph[:order]` (topological).
My DFS re-derives it implicitly; a critical-path tool, a map drawer,
and a scheduler visualizer shouldn't each write their own toposort.

## Perf-nerd footnotes

- "Path explains 100% of the wall clock" is itself a diagnostic: if
that number drops much below ~95%, your bottleneck isn't the work,
it's the *scheduler* (queue waits from a too-tight limit). Pair this
with the knee finder: knee for capacity, path for latency.
- The experiment reruns the whole plan with modified sleeps —
affordable here, but the analysis itself is O(V+E) memoized and
needs no rerun. Ship the analysis in CI; save the experiment for
demos and skeptics.

## Verdict

Three tools now form a performance suite this framework didn't have
five rounds ago: Gantt (where time went), knee (how many lanes), path
(which task matters). All three built from two hooks and one accessor.
APIs that compound are the ones worth shipping.
Loading
Loading