assignment 2 · EPD planning cadence
act before the block
The assignment
How does an EPD org plan for 3 months? How do we commit? What checks and balances keep us on track? How do we flag delays proactively? How do we measure progress? Assume Slack + Linear.
What I shipped: A working offline prototype: a five-tab workspace, a synthetic quarter, and a six-step scenario replay.
I built a small workspace that lets me step through a quarter: a healthy plan slips, raises a warning, records an explicit decision, recovers, delivers, and then gets checked against the outcome it was meant to move.
I kept coming back to five questions: how do I plan a quarter, commit to it, keep the plan honest, surface delays early, and measure progress? A document can answer each one. I wanted a founder to watch the loop run, though, so I built the loop instead of asking them to trust a description.
Nothing here is live, and I’d rather say exactly what stands in for what than let a demo imply access I don’t have:
Structurally it’s one route with five tabs , overview, plan, risks, learning, evidence , one frozen data set, and six fixed scenario steps. The step controls sit above every tab, so advancing the quarter never makes you leave the view you’re looking at. There is no network call anywhere in it, and a test scans the source and fails the build if one sneaks in. It’s a working explanation of how the operating model behaves, not a production planning tool.
“signal → context → action → verification”
I didn’t pick one methodology and apply it. I took the parts that still work at thirty people and left the rest, because most of what makes planning heavy at a big company is exactly what a small org can’t afford.
OKRs, for the outcomes
SAFe, three parts of it
Shape Up, for scope
Kanban and Monte Carlo, for dates
What the four have in common is the thing I actually wanted: a small set of numbers, computed the same way every week, that say something is wrong while it’s still cheap to fix.
I set the quarter from 5 October to 25 December: twelve execution weeks, with decision windows at weeks 1–4, 5–8 and 9–12. I treat those windows as moments to make a call, not as forced project durations.
I put twelve bets across four pods and split them into three tiers. Four are Committed, four Target, four Stretch. The distinction I wanted to make impossible to miss is that tier is a promise, not a probability. Committed says “this is what we told people.” The forecast separately says “here is how likely it is.” If I collapse those into one number, planning turns into theatre.
In the tool, I render Committed rows, then the literal cut line, then Target and Stretch below it. Anything below the line isn’t forecast; it’s conditional work. I’d rather say that plainly than print a confidence number for something nobody promised.
Committed
Target
Stretch
I calculate capacity per pod, and only per pod. I deliberately leave out per-person throughput. The minute a planning surface can be read as an individual performance metric, people start managing the metric, and I didn’t want this tool to create that incentive.
planned = floor(gross capacity × availability factor) usable = max(0, planned − interrupt reserve) headroom = usable − committed demand util = committed demand / usable over = committed demand > usable
23 committed of 23 usable units
17 committed of 22 usable units
8 committed of 17 usable units
15 committed of 21 usable units
Pod A is exactly at 100% of usable capacity and has zero headroom. I wanted the tool to say that plainly without quietly demoting anything. Full is a planning fact, not an error to auto-correct; only a human decision moves committed work.
| Pod | Gross | Avail. | Reserve | WIP limit | P85 cycle |
|---|---|---|---|---|---|
| A · Core Product | 32 | 0.90 | 5 | 5 | 9 business days |
| B · Integrations | 30 | 0.90 | 5 | 4 | 8 business days |
| C · ML & Inference | 24 | 0.875 | 4 | 3 | 12 business days |
| D · Experience | 28 | 0.90 | 4 | 4 | 7 business days |
I kept thinking about the quiet failure mode: a cross-team dependency that everyone assumes is fine until it isn’t. I turned it into a contract with named fields, rather than leaving it as a line on a roadmap.
At the start of the quarter the provider expects to be ready on 12 October and Pod A’s latest safe start is 16 October, so I have four business days of slack. Nothing is wrong yet. Then the provider’s date moves to 19 October and slack becomes −1.
“Warnings before a downstream pod becomes blocked.”
Here’s the part I wanted a founder to see: Pod A is still notblocked. Nothing is on fire, and standup would still report green. But the arithmetic says a block is coming while there are still days to make a cheap decision.
Provider date slips
12 Oct → 19 Oct
arithmetic
Slack goes negative
+4 → −1 business days
trigger
Two warnings raised
dependency slack + forecast confidence
not yet an outage
Pod A still not blocked
this is the whole window of action
named cause + options
Explicit decision recorded
cut scope · approved, with a rationale
human approves
Decision applied
1 unit cut · latest start 16 → 20 Oct
recompute
Slack +1 · confidence 84%
critical warning resolves
Every warning carries the same six things: what caused it, in words; what it puts at risk; the records it was derived from; when it was first seen; the date by which someone has to decide; and the options available. Without a named cause and a deadline, a warning is just a colour.
| Rule | Fires when | Critical when |
|---|---|---|
| Forecast confidence | Probability in the 65–80% watch band, or drops ≥15 points | Below 65% |
| Dependency slack | Slack ≤ 2 business days | Slack is negative |
| Dependency cycle | Any cycle in the provider→consumer graph | Always |
| Latest safe start | Critical-path item unstarted at its latest safe start | Past the date |
| Blocked duration | Blocked ≥ 3 business days | ≥ 5 days |
| Item aging | Active longer than the pod's P85 cycle time | Beyond 1.5× P85 |
| Scope growth | Post-commit additions > 5% of baseline | ≥ 15% |
| Plan churn | Cumulative absolute revisions > 10% of baseline | ≥ 20% |
| WIP limit | Active items above the pod's WIP limit | Above 1.5× the limit |
| Throughput drop | Recent throughput < 75% of historical median | < 50% |
| Carry-over | An item carried over one cycle | Two or more cycles |
| Review latency | In review ≥ 3 business days | ≥ 5 days |
| ML gate | Gate incomplete within 3 business days of due | Overdue |
| Outcome target | A measured key result misses its target | Always warning |
I also gave warnings a lifecycle. When one recurs, it keeps its original identity and first-seen timestamp while last-seen updates, so lead time stays measurable. Once the underlying condition disappears, I mark it resolved instead of deleting it. I want the record to say “this fired for six days and then stopped,” not just show the current list.
Deterministic
Probabilistic
Never inferred
For a committed bet, I use a Monte Carlo simulation rather than a judgement call. I run ten thousand seeded simulations per bet so the number is reproducible. Each run walks the remaining weeks and samples one week of that pod’s historical throughput at a time.
I model the dependency inside the simulation rather than bolt it on afterwards. Units blocked by an unresolved dependency can’t be consumed until the week the dependency is expected ready; only independent work can absorb capacity before that. A run counts as a hit only when both the independent and blocked units reach zero.
80.8%
healthy baseline
61.8%
after the slip
84.3%
after the cut
100%
delivered
I keep the bands fixed: ≥80% healthy · 65–80% watch · below 65% at risk · zero missed
There are two cases where I refuse to print a number at all: research work, and a pod with fewer than six weeks of history to sample from. And two where the honest answer is zero rather than nothing , the target date has already passed with work still open, or the history says that pod has never shipped anything. Research is the case where most planning tools start pretending.
One of my four committed bets is intent-clustering feasibility. It’s genuinely unknowable: I can’t forecast a research result from throughput history. So the tool never produces a number for it. Instead, I give it a timeboxed learning gate with three checkable criteria and a due date.
| Criterion | Threshold | Actual at verification |
|---|---|---|
| Macro F1 | ≥ 0.78 | 0.81 ✓ |
| P95 inference latency | ≤ 800 ms | 740 ms ✓ |
| Labelled evaluation sample | ≥ 500 records | 620 ✓ |
Passing the gate still doesn’t produce a delivery probability for the research timebox. It gives me permission to shape a separate, bounded production bet, which can then be forecast because it has scope. The gate turns uncertainty into a decision on a date; it doesn’t hide uncertainty behind a number.
“Uncertain research · no delivery probability”
The scenario replay is where I demonstrate the model. I made six fixed steps; each one replays from the frozen baseline and applies its recorded changes in order, so someone can walk forward and back and get the same numbers every time.
S0
Healthy baseline
slack +4 · no warnings
S1
Dependency risk
slack −1 · 61.8% · not blocked
S2
Decision approved
recorded, not yet applied
S3
Forecast recovery
1 unit cut · slack +1 · 84.3%
S4
Delivery verified
evidence recorded · gate passed
S5
Outcome measured
delivered ≠ target met
Sequence of 6 pegged cards.
Two steps are easy to blur, so I kept them separate. S2 and S3 are separate on purpose.Approval and effect are different events. If I merge them, the plan can look healthier before anyone has actually applied the decision. In S2 the numbers stay exactly where they are: the decision is approved but pending. Only in S3, when the one-unit CSV-export slice is actually cut, do remaining units drop, the consumer’s latest safe start move from 16 to 20 October, slack turn positive, and the forecast recover.
I also made the scope cut deterministic rather than discretionary. Cuttable slices are ordered Stretch, then Target, then Committed; within a tier by descending priority, then a defined cut order, then id. The same pressure therefore proposes the same cut, and the argument is about whether to cut, not about what.
I kept the boundary between Slack and Linear deliberately simple. The division of labour matters more here than the integration detail.
Linear holds the execution truth
Slack is where the signal has to land
The dependency reminder is the one I would show first. It lands in the provider pod’s channel while the consumer is still unblocked, and carries the need-by date, the current forecast, the explicit not-blocked-yet status, and the decision options. That gives someone an action to take, rather than a status to interpret.
“Act before the block”
When I ask “how do we measure progress,” I hear three different questions. I kept them separate because shipping and succeeding aren’t the same thing.
Delivery clock
Outcome clock
Control-system clock
The third clock is the one I’d defend hardest. A warning system nobody can grade turns into noise inside a quarter. So I measure how much warning we actually got, in business days between a warning first appearing and the date it was warning about, and how long we took to answer it, in hours from that first sighting to an approved decision. For accuracy I use a Brier score , the standard way to score probability forecasts against what actually happened , and I leave it blank until there are at least five finished forecasts, rather than showing a number built on two.
At the end of the quarter I show all four committed bets delivered and a say/do ratio of 1. Three of the four key results still miss their targets: weekly enterprise search adoption reaches 48% against a 52% target, search-related negative feedback falls to 34 against a target of 28, and sync success reaches 99.1% against 99.5%.
“Shipping is not success; this bet is delivered but only partially validated.”
I leave one more uncomfortable number visible in that step. Original dependency reliability is zero: the provider delivered after the date it originally committed to, even though the mitigation protected the consumer and the commitment was met. Both facts are true, so the tool refuses to average them into a single green tile. The intervention worked; the original promise still slipped.
“I’m not adding ceremony. I’m making truth visible while there’s still time to act.”