---
title: 'Scaled Frameworks'
source: 'https://academia.sh/en/courses/process-and-team/scaled-frameworks'
course: 'Process, Team and Delivery'
language: en
updated: '2026-08-23T07:01:07+00:00'
license: 'CC BY-SA 4.0'
---

# Scaled Frameworks

Counting the price of multi-team coordination: the same twenty-four people and twenty-four modules are split across one to twelve teams, and coordination rounds per dependent work item, the number of shared channels, the coordination layer's delay, and the cost of a joint planning round are all measured.

Every measurement in the previous lesson was made on a single team: one backlog, one WIP limit, four
people. Once that assumption is removed, a new cost item is born. When a work item touches modules
owned by more than one team, a **coordination round** has to pass between the teams; that round is
neither work nor queue wait, it adds to flow time without bringing any item closer to done.

Scaled frameworks are an attempt to bring order to that round: a **coordination layer**, a regular
**joint planning round**, and a list that makes cross-team dependencies visible in advance. This
lesson takes up frameworks not by name but by these three elements, and it measures a single question
— how much the coordination cost grows as the number of teams rises. The through-line is the regional
library network, and it is fiction; the network's twenty-four modules and twenty-four-person
development roster are held fixed, and only the team boundaries change.

## The Same Work, Split Differently

The block below is a **model**, not a measurement.

**PM19 — twenty-four modules and twenty-four people are fixed; `E` teams only change the
boundaries.** Modules are split into contiguous blocks, and each team holds `24 / E` people. For the
comparison to mean anything, the roster does not grow: what is measured is not workforce, it is the
boundary itself.

**PM20 — forty work items are derived from a generator; the generator is self-written and the seed is
visible.** Every item touches one to four modules; every touch after the first carries a
**dependency type** — shared data, shared endpoint, shared infrastructure, or sequential delivery.

**PM21 — if an item spreads across `t` teams, it needs `t - 1` coordination rounds,** and every round
costs the two teams 2 person-days. A standing channel is also opened between every pair of teams the
item touches.

**PM22 — the coordination layer processes at most six rounds in one period**; an item waiting its turn
is delayed. The work itself, independent of coordination, takes eight periods; if coordination backs
up, the period count stretches.

**PM23 — at the joint planning round, every team presents and everyone listens.** Its cost grows with
the product of the period count, the people count, and the team count.

**PM24 — in-team coordination grows with the number of person pairs**: in an arrangement of `E` teams,
the total number of internal pairs is `E · s(s-1)/2`, where `s = 24 / E`. This is the only item that
**shrinks** as the number of teams grows.

```js
// scale.mjs — the same workload split across different team counts (model)
import { writeFileSync } from "node:fs";

const MODULE = 24, PEOPLE = 24;    // module and people counts are fixed; only team boundaries change
const ROUND_COST = 2;              // cost of one coordination round to two teams (person-days)
const INTERNAL_COST = 0.2;         // in-team coordination cost per person pair, per period (person-days)
const PLAN_COST = 0.1;             // joint planning round, per person and per team (person-days)
const BASE_PERIOD = 8;             // the work itself, independent of coordination, takes this many periods
const PERIOD_ROUNDS = 6;           // rounds the coordination layer can process in one period

// PM20: the generator is self-written, the seed is visible; the same item set is used at every team count.
let seed = 20251103;
const rand = () => (seed = (seed * 1103515245 + 12345) % 2147483648) / 2147483648;
const TYPE = ["shared data", "shared endpoint", "shared infrastructure", "sequential delivery"];
const ITEM = [...Array(40)].map((no) => {
  const count = 1 + Math.floor(rand() * 4);
  const modules = [], types = [];
  while (modules.length < count) {
    const m = Math.floor(rand() * MODULE);
    if (!modules.includes(m)) { modules.push(m); if (modules.length > 1) types.push(TYPE[Math.floor(rand() * 4)]); }
  }
  return { no, modules, types };
});

// PM19: E teams split the modules into contiguous blocks; each team has PEOPLE / E people.
const teamOf = (E) => (m) => Math.min(E - 1, Math.floor((m * E) / MODULE));

function run(E, skip = null) {
  const team = teamOf(E);
  let round = 0, dependent = 0, spread = 0;
  const pairs = new Set();
  const coordQueue = [];                              // rounds entering the coordination layer, item order
  for (const k of ITEM) {
    const touches = k.modules.filter((_, i) => i === 0 || k.types[i - 1] !== skip);   // the skipped type is dropped
    const t = [...new Set(touches.map(team))];
    if (t.length <= 1) continue;
    dependent += 1; spread += t.length; round += t.length - 1;
    for (let i = 0; i < t.length; i++)
      for (let j = i + 1; j < t.length; j++) pairs.add(`${Math.min(t[i], t[j])}-${Math.max(t[i], t[j])}`);
    coordQueue.push({ no: k.no, round: t.length - 1 });
  }
  // PM22: the coordination layer processes PERIOD_ROUNDS rounds per period; an item queued behind others is delayed.
  let backlog = 0, delayTotal = 0;
  for (const d of coordQueue) { backlog += d.round; delayTotal += Math.ceil(backlog / PERIOD_ROUNDS); }
  const periods = Math.max(BASE_PERIOD, Math.ceil(round / PERIOD_ROUNDS));
  const peoplePerTeam = PEOPLE / E;
  return { E, peoplePerTeam, dependent, round, pairs: pairs.size, periods,
    delay: coordQueue.length ? delayTotal / coordQueue.length : 0,
    spread: dependent ? spread / dependent : 1,
    externalCost: round * ROUND_COST,
    planCost: +(periods * PEOPLE * E * PLAN_COST).toFixed(1),   // every team presents, everyone listens
    internalCost: +(E * ((peoplePerTeam * (peoplePerTeam - 1)) / 2) * INTERNAL_COST * periods).toFixed(1) };
}

const TEAMS = [1, 2, 3, 4, 6, 8, 12];
const SCAN = TEAMS.map((E) => {
  const r = run(E);
  return { ...r, total: +(r.externalCost + r.planCost + r.internalCost).toFixed(1) };
});
writeFileSync("scale.json", JSON.stringify({ SCAN, TYPE,
  skipScan: TYPE.map((t) => ({ type: t, row: TEAMS.map((E) => run(E, t).round) })) }));

console.log(`modules: ${MODULE}, people: ${PEOPLE}, work items: ${ITEM.length}, ` +
  `dependency links: ${ITEM.reduce((a, k) => a + k.types.length, 0)} (seed ${20251103})`);
console.log(`type distribution: ${TYPE.map((t) =>
  `${t} ${ITEM.reduce((a, k) => a + k.types.filter((x) => x === t).length, 0)}`).join(", ")}`);

const four = SCAN[3];
console.log(`\nfour teams, detailed: ${four.peoplePerTeam} people per team, ${MODULE / 4} modules per team`);
console.log(`  ${four.dependent}/${ITEM.length} items spread across more than one team, ` +
  `spreading items touch an average of ${four.spread.toFixed(2)} teams`);
console.log(`  coordination rounds ${four.round}, team pairs needing a channel ${four.pairs}/${(4 * 3) / 2}, ` +
  `${four.periods} periods`);
console.log(`  cost: external coordination ${four.externalCost}, joint planning ${four.planCost}, ` +
  `in-team ${four.internalCost} person-days`);
```

```
modules: 24, people: 24, work items: 40, dependency links: 69 (seed 20251103)
type distribution: shared data 18, shared endpoint 13, shared infrastructure 21, sequential delivery 17

four teams, detailed: 6 people per team, 6 modules per team
  31/40 items spread across more than one team, spreading items touch an average of 2.61 teams
  coordination rounds 50, team pairs needing a channel 6/6, 9 periods
  cost: external coordination 100, joint planning 86.4, in-team 108 person-days
```

In the four-team arrangement, **thirty-one of the forty** items spread across more than one team, and
a spreading item touches an average of 2.61 teams. **All six** of the six possible team pairs end up
linked because of at least one item: none of the four teams is isolated from the others.

## Scanning the Team Count

```js
// coordination.mjs — reads the scan results scale.mjs wrote; team count is scanned end to end
import { readFileSync } from "node:fs";

const { SCAN, TYPE, skipScan } = JSON.parse(readFileSync("scale.json", "utf8"));
const table = (header, row, caption) => {
  const write = (h) => console.log(h.map((c, i) => String(c).padStart(header[i][1])).join(""));
  if (caption) { console.log(); console.log(caption); }
  write(header.map((b) => b[0])); row.forEach(write);
};

table([["team", 6], ["people/team", 13], ["dependent items", 18], ["spread", 9], ["coord. rounds", 15],
       ["team pairs", 12], ["delay/item", 22]],
  SCAN.map((r) => [r.E, r.peoplePerTeam, `${r.dependent}/40`, r.spread.toFixed(2), r.round, r.pairs,
                     r.delay.toFixed(1)]));

// PM24: the growth pattern is read through two ratios; whichever one stays flat tells the order of growth.
table([["team", 6], ["coord. rounds", 15], ["rounds / team", 14], ["team pairs", 12],
       ["pairs / (E(E-1)/2)", 20]],
  SCAN.filter((r) => r.E > 1).map((r) => [r.E, r.round, (r.round / r.E).toFixed(1), r.pairs,
    (r.pairs / ((r.E * (r.E - 1)) / 2)).toFixed(2)]),
  "growth pattern:");

table([["team", 6], ["periods", 9], ["ext. coord.", 13], ["joint planning", 16], ["in-team", 10],
       ["total", 9]],
  SCAN.map((r) => [r.E, r.periods, r.externalCost, r.planCost, r.internalCost, r.total]),
  "coordination cost (person-days):");

const least = SCAN.reduce((a, b) => (b.total < a.total ? b : a));
console.log(`\nleast coordination cost at ${least.E} teams (${least.total} person-days); ` +
  `at one team ${SCAN[0].total}, at twelve teams ${SCAN[SCAN.length - 1].total} person-days`);

table([["dependency type", 24], ...SCAN.map((r) => [`${r.E} teams`, 10])],
  skipScan.map((a) => [a.type, ...a.row.map((t, i) => t - SCAN[i].round)]),
  "change in coordination rounds if a dependency type is removed:");
const four = skipScan.map((a) => ({ type: a.type, gain: SCAN[3].round - a.row[3] }))
  .sort((a, b) => b.gain - a.gain);
console.log(`\nmost rounds gained at four teams: ${four.map((d) => `${d.type} (${d.gain})`).join(", ")}`);
```

```
  team  people/team   dependent items   spread  coord. rounds  team pairs            delay/item
     1           24              0/40     1.00              0           0                   0.0
     2           12             27/40     2.00             27           1                   2.8
     3            8             30/40     2.37             41           3                   3.8
     4            6             31/40     2.61             50           6                   4.6
     6            4             32/40     2.69             54          15                   4.8
     8            3             33/40     2.76             58          27                   5.2
    12            2             34/40     2.94             66          55                   5.9

growth pattern:
  team  coord. rounds rounds / team  team pairs  pairs / (E(E-1)/2)
     2             27          13.5           1                1.00
     3             41          13.7           3                1.00
     4             50          12.5           6                1.00
     6             54           9.0          15                1.00
     8             58           7.3          27                0.96
    12             66           5.5          55                0.83

coordination cost (person-days):
  team  periods  ext. coord.  joint planning   in-team    total
     1        8            0            19.2     441.6    460.8
     2        8           54            38.4     211.2    303.6
     3        8           82            57.6     134.4      274
     4        9          100            86.4       108    294.4
     6        9          108           129.6      64.8    302.4
     8       10          116             192        48      356
    12       11          132           316.8      26.4    475.2

least coordination cost at 3 teams (274 person-days); at one team 460.8, at twelve teams 475.2 person-days

change in coordination rounds if a dependency type is removed:
         dependency type   1 teams   2 teams   3 teams   4 teams   6 teams   8 teams  12 teams
             shared data         0        -6       -10       -12       -15       -14       -18
         shared endpoint         0        -4        -5        -8        -8       -11       -13
   shared infrastructure         0        -4        -9       -11       -11       -17       -18
     sequential delivery         0        -7       -11       -14       -15       -14       -17

most rounds gained at four teams: sequential delivery (14), shared data (12), shared infrastructure (11), shared endpoint (8)
```

## Not Linear — Two Separate Patterns

The answer is not a single growth pattern, it is two separate patterns, and they read in opposite
directions.

**The coordination round count stays below linear.** As team count rises sixfold, the round count
rises from 27 to 66; the `rounds / team` ratio **drops** from 13.5 to 5.5. The reason is a ceiling:
because an item touches at most four modules, it can spread across at most four teams, and its round
count cannot exceed three. As the team count grows, no new dependent items are found — the existing
ones just spread a little further, and spread rises from 2.00 to 2.94 and stops there.

**The number of shared channels, by contrast, grows exactly quadratically.** The `pairs / (E(E-1)/2)`
ratio is exactly 1.00 at two, three, four, and six teams: **every possible team pair** ends up linked
because of at least one item. Saturation only begins at eight and twelve teams (0.96 and 0.83), and
even there the pair count is 27 and 55. The coordination round count is bounded, but the **channel
count** those rounds spread across is not.

The cost table shows the sum of these two patterns. With one team, external coordination is zero, but
internal coordination among the twenty-four people costs 441.6 person-days. With twelve teams,
internal coordination falls to 26.4 person-days, but the joint planning round rises to 316.8
person-days — because every team presents and everyone listens, this item grows directly with team
count. The total **is lowest at three teams (274 person-days)** and nearly equal at both ends: 460.8
versus 475.2. The same work, the same twenty-four people, costs twice as much by two different routes.

The coordination layer's own delay is a separate column: it rises from 2.8 periods to 5.9 periods per
dependent item. For that entire stretch, no one is working on the item.

## What Removing Which Dependency Gains

The last table breaks down where the rounds come from. In the four-team arrangement, removing the
**sequential delivery** dependency erases fourteen of the fifty rounds, **shared data** twelve,
**shared infrastructure** eleven, **shared endpoint** eight. The order shifts with team count: at
twelve teams, shared data and shared infrastructure move to the front (eighteen rounds each), and
sequential delivery drops to seventeen.

What matters here is the measure, not the ranking. At four teams, eliminating a single dependency type
erases **between 16% and 28%** of the rounds; it is not necessary to remove all four, but none of them
solves the problem alone either. The cheapest improvement available without changing the team boundary
is removing the dependency type that produces the most rounds from the architecture.

## Summary

- The same 24 modules and 24 people were split across one to twelve teams; because the roster was
  held fixed, the only thing measured is the boundary itself.
- The coordination round count grows below linear (27 to 66, `rounds / team` drops from 13.5 to 5.5),
  because an item touches at most four modules and its spread stops at 2.94.
- Shared-channel count grows quadratically: between two and six teams every possible team pair ends
  up linked (`pairs / (E(E-1)/2)` exactly 1.00), and at twelve teams the pair count is 55.
- Total coordination cost is lowest at three teams (274 person-days) and nearly equal at both ends —
  441.6 person-days of internal coordination at one team, 316.8 person-days of joint planning at
  twelve teams.
- At four teams, removing a single dependency type erases 8 to 14 of the fifty rounds; sequential
  delivery gains the most, shared endpoint the least, and the order shifts with team count.

## Next Step

Every number in this lesson stands on one assumption: when a defect is found, there is a return trip.
How many rounds that return trip costs was counted, but **when the defect is found** was always
assumed to sit in the same place — at the end of the work. The next lesson removes that assumption and
closes the topic: the distance between the step a defect is born in and the step it is found in, that
is, **feedback delay**, is measured directly. Integration frequency is scanned from once a day to once
a week, pairing ratio is scanned from zero to one, and at every setting, how many steps and how many
hours this delay spans, how much rework changes, and what the total work effort comes to are all
counted. Pair programming's two-person cost is factored in — having two people do one piece of work,
even if it cheapens the defect found, doubles the work effort.
