---
title: 'Continuous Learning Discipline'
source: 'https://academia.sh/en/courses/architect-role/continuous-learning-discipline'
course: "The Architect's Role"
language: en
updated: '2026-08-23T07:01:02+00:00'
license: 'CC BY-SA 4.0'
---

# Continuous Learning Discipline

Measuring knowledge freshness as a budget allocation: tying a decision's basis to its validity period, counting the rate of decisions turning out wrong when resting on a stale basis over a 72-month run, and showing the same annual 72 hours gives a rate of 0.3958, 0.3715, and 0.2222 when distributed equally, by staleness, and stalest-first.

The previous lesson built a candidate table and produced a winner. Every cell of that table was
true on the day it was measured: the candidate's age, breaking-release frequency, adopter count,
documented-function ratio. The decision was made, the table stays put. The numbers the table
rests on do not.

This holds for every decision this course has measured. A balancing decision's weights, a
simplification's accidental-complexity share, a cost of reversal — all of them come from a
measurement, and every measurement belongs to a date. This last lesson turns that into a model:
a decision's **basis** has a validity period, the probability of being wrong rises for a
decision resting on an expired basis, and the hours set aside for learning lower that
probability. Learning is not a virtue, it is a **budget allocation**; and the allocation itself
makes more difference than the size of the budget.

## The Basis's Validity Period

In the model, every decision rests on three **bases**. A basis is a piece of knowledge, and it
ages according to its category.

**AP16: a basis's validity period depends on its category.** A number that comes from a
measurement stays valid for six months, a tool's maturity and ecosystem profile for eighteen
months, a structural principle for a hundred and twenty months. Refresh costs diverge too:
rerunning a measurement takes one hour, re-deriving a tool's profile takes four hours, reviewing
a structural principle takes twelve hours.

**AP17: the probability of being wrong sits at the floor while a basis is fresh, and rises as
its age exceeds the validity period**; once age reaches three times the lifespan, the
probability reaches one. The floor is 0.05, meaning even a fresh basis can be wrong. A decision
is correct only when all three of its bases turn out correct.

```js
// learning/model.mjs — process model of knowledge freshness. There is no real organization,
// tool, or person. Time is not measured; months, decisions, stale bases, and wrong decisions are
// counted. Randomness comes from a generator written in-house, and the seed is visible.

// AP16: a basis's validity period depends on its category (months).
export const CATEGORY = {
  measurement: { count: 30, lifespan: 6, refreshHours: 1 },
  tool: { count: 20, lifespan: 18, refreshHours: 4 },
  structure: { count: 10, lifespan: 120, refreshHours: 12 },
};

export const MONTHS = 72, DECISIONS_PER_MONTH = 4, BASES_PER_DECISION = 3, FLOOR = 0.05;

export function generator(seed) {
  let t = seed;
  return () => ((t = (t * 1103515245 + 12345) % 2147483648) / 2147483648);
}

export function bases(random) {
  const d = [];
  for (const [cat, k] of Object.entries(CATEGORY))
    for (let i = 0; i < k.count; i++) d.push({ category: cat, age: random() * k.lifespan });
  return d;
}

// AP17: the probability of being wrong is FLOOR when a basis is fresh and rises as age exceeds
// the validity period; it reaches 1 once age reaches three times the lifespan.
export const errorProbability = (d) => {
  const lifespan = CATEGORY[d.category].lifespan;
  return FLOOR + (1 - FLOOR) * Math.min(1, Math.max(0, (d.age - lifespan) / (2 * lifespan)));
};

// Distribution strategies: split the monthly budget across categories. Shares sum to 1.
export const DISTRIBUTION = {
  "equal": () => ({ measurement: 1 / 3, tool: 1 / 3, structure: 1 / 3 }),
  "by staleness": () => {
    const h = Object.fromEntries(Object.entries(CATEGORY).map(([a, k]) => [a, k.count / k.lifespan]));
    const t = Object.values(h).reduce((x, y) => x + y, 0);
    return Object.fromEntries(Object.entries(h).map(([a, v]) => [a, v / t]));
  },
  "measurement only": () => ({ measurement: 1, tool: 0, structure: 0 }),
  "structure only": () => ({ measurement: 0, tool: 0, structure: 1 }),
};

// Strategy with no category share: spends starting from the stalest basis.
const stalestFirst = (d, budget) => {
  let remaining = budget;
  for (const x of [...d].sort((a, b) =>
      b.age / CATEGORY[b.category].lifespan - a.age / CATEGORY[a.category].lifespan)) {
    const m = CATEGORY[x.category].refreshHours;
    if (m > remaining) continue;
    remaining -= m; x.age = 0;
  }
  return remaining;
};

// Strategy that spends by category share: within each category, the oldest basis is refreshed first.
const byShare = (d, budget, share, pool) => {
  for (const cat of Object.keys(CATEGORY)) {
    pool[cat] = (pool[cat] ?? 0) + budget * share[cat];
    const m = CATEGORY[cat].refreshHours;
    const sorted = d.filter((x) => x.category === cat).sort((a, b) => b.age - a.age);
    for (const x of sorted) {
      if (m > pool[cat]) break;
      pool[cat] -= m; x.age = 0;
    }
  }
};

export function run({ strategy, budget, seed = 20260801 }) {
  const random = generator(seed);
  const d = bases(random);
  const pool = {};
  let decisions = 0, wrong = 0, staleBasis = 0, usedBasis = 0;
  for (let month = 0; month < MONTHS; month++) {
    for (const x of d) x.age += 1;
    if (budget > 0 && strategy === "stalest first") stalestFirst(d, budget);
    else if (budget > 0) byShare(d, budget, DISTRIBUTION[strategy](), pool);
    for (let i = 0; i < DECISIONS_PER_MONTH; i++) {
      let correct = 1;
      for (let j = 0; j < BASES_PER_DECISION; j++) {
        const x = d[Math.floor(random() * d.length)];
        usedBasis += 1;
        if (x.age > CATEGORY[x.category].lifespan) staleBasis += 1;
        correct *= 1 - errorProbability(x);
      }
      decisions += 1;
      if (random() > correct) wrong += 1;
    }
  }
  return { decisions, wrong, rate: wrong / decisions, staleRate: staleBasis / usedBasis,
    annualHours: budget * 12 };
}
```

## Same Budget, Different Distribution

Six hours a month is seventy-two hours a year. The run below distributes those hours five
different ways and counts the decisions that turn out wrong across sixty bases and 288
decisions.

```js
// learning/budget.mjs — the same hourly budget distributed differently changes the rate of wrong decisions
import { CATEGORY, MONTHS, DECISIONS_PER_MONTH, FLOOR, BASES_PER_DECISION, DISTRIBUTION, run } from "./model.mjs";

const BUDGET = 6;
console.log(`months ${MONTHS}, decisions/month ${DECISIONS_PER_MONTH}, total decisions ${MONTHS * DECISIONS_PER_MONTH}, seed 20260801`);
console.log(`bases: ${Object.entries(CATEGORY).map(([a, k]) => `${a} ${k.count} (lifespan ${k.lifespan} mo, refresh ${k.refreshHours} hr)`).join("; ")}\n`);

const noBudget = run({ strategy: "equal", budget: 0 });
console.log(`budget ${BUDGET} hours/month = ${BUDGET * 12} hours/year`);
console.log("strategy           measurement  tool  structure  stale basis  wrong decisions  rate    decrease");
console.log("----------------- ------------ ----- ---------- ------------ ---------------- ------ ----------");
console.log(`${"no budget".padEnd(17)} ${"-".padStart(12)} ${"-".padStart(5)} ${"-".padStart(10)} ` +
  `${noBudget.staleRate.toFixed(4).padStart(12)} ${String(noBudget.wrong).padStart(16)} ` +
  `${noBudget.rate.toFixed(4).padStart(6)} ${"-".padStart(10)}`);
for (const name of [...Object.keys(DISTRIBUTION), "stalest first"]) {
  const r = run({ strategy: name, budget: BUDGET });
  const p = DISTRIBUTION[name] ? DISTRIBUTION[name]() : null;
  const s = (k, w) => (p ? p[k].toFixed(2) : "-").padStart(w);
  console.log(`${name.padEnd(17)} ${s("measurement", 12)} ${s("tool", 5)} ${s("structure", 10)} ` +
    `${r.staleRate.toFixed(4).padStart(12)} ${String(r.wrong).padStart(16)} ` +
    `${r.rate.toFixed(4).padStart(6)} ${`${((1 - r.rate / noBudget.rate) * 100).toFixed(1)}%`.padStart(10)}`);
}

console.log(`\nas the budget grows (strategy: by staleness):`);
console.log("hr/month  hr/year  stale basis  wrong decisions  rate    error per hour");
console.log("-------- -------- ------------ ---------------- ------ ----------------");
let previousRate = noBudget.rate, previousHours = 0;
for (const b of [0, 2, 4, 6, 12, 24, 48]) {
  const r = run({ strategy: "by staleness", budget: b });
  const margin = b === 0 ? null : (previousRate - r.rate) / ((b - previousHours) * 12);
  console.log(`${String(b).padStart(8)} ${String(b * 12).padStart(8)} ${r.staleRate.toFixed(4).padStart(12)} ` +
    `${String(r.wrong).padStart(16)} ${r.rate.toFixed(4).padStart(6)} ` +
    `${(margin === null ? "-" : margin.toExponential(2)).padStart(16)}`);
  previousRate = r.rate; previousHours = b;
}

console.log(`\nseed sensitivity (budget ${BUDGET}, strategy: by staleness):`);
console.log("seed        rate");
for (const t of [20260801, 991, 4242]) {
  const r = run({ strategy: "by staleness", budget: BUDGET, seed: t });
  console.log(`${String(t).padStart(9)} ${r.rate.toFixed(4).padStart(9)}`);
}

console.log(`\nfloor: even if every basis is fresh, error per decision = ${(1 - (1 - FLOOR) ** BASES_PER_DECISION).toFixed(4)}`);
```

```
months 72, decisions/month 4, total decisions 288, seed 20260801
bases: measurement 30 (lifespan 6 mo, refresh 1 hr); tool 20 (lifespan 18 mo, refresh 4 hr); structure 10 (lifespan 120 mo, refresh 12 hr)

budget 6 hours/month = 72 hours/year
strategy           measurement  tool  structure  stale basis  wrong decisions  rate    decrease
----------------- ------------ ----- ---------- ------------ ---------------- ------ ----------
no budget                    -     -          -       0.8461              261 0.9063          -
equal                     0.33  0.33       0.33       0.4039              114 0.3958      56.3%
by staleness              0.81  0.18       0.01       0.2662              107 0.3715      59.0%
measurement only          1.00  0.00       0.00       0.3403              150 0.5208      42.5%
structure only            0.00  0.00       1.00       0.7778              260 0.9028       0.4%
stalest first                -     -          -       0.2940               64 0.2222      75.5%

as the budget grows (strategy: by staleness):
hr/month  hr/year  stale basis  wrong decisions  rate    error per hour
-------- -------- ------------ ---------------- ------ ----------------
       0        0       0.8461              261 0.9063                -
       2       24       0.6227              206 0.7153          7.96e-3
       4       48       0.4144              138 0.4792          9.84e-3
       6       72       0.2662              107 0.3715          4.48e-3
      12      144       0.2130               67 0.2326          1.93e-3
      24      288       0.0648               49 0.1701          4.34e-4
      48      576       0.0475               47 0.1632          2.41e-5

seed sensitivity (budget 6, strategy: by staleness):
seed        rate
 20260801    0.3715
      991    0.3854
     4242    0.3715

floor: even if every basis is fresh, error per decision = 0.1426
```

All these numbers belong to this run; the generator is written in-house and the seed is visible.
The seed-sensitivity rows show the rate moving between 0.3715 and 0.3854, meaning the
comparisons do not depend on the seed.

## The Result of the Distribution

The first table shows that where the same seventy-two hours gets put can place the
wrong-decision rate anywhere between 0.9028 and 0.2222. The gap is more than fourfold, and the
budget never changed.

The sharpest row is `structure only`. When the entire budget is spent on structural principles,
the rate is 0.9028 — only 0.4 percent better than 0.9063 with no budget at all. Because
structural principles stay valid for a hundred and twenty months, they never go stale within a
seventy-two-month run in the first place; refreshing them does not save a single decision.
Learning's most respectable-looking form changes nothing measurable in this model.

The opposite extreme is not right either, on its own. `measurement only` puts the entire budget
on the fastest-aging category and lowers the rate to 0.5208 — worse than equal distribution's
0.3958. Completely neglecting one category lets every basis in it go stale.

The `by staleness` distribution derives its shares from each category's aging speed:
measurement 0.81, tool 0.18, structure 0.01. The result, 0.3715, beats equal distribution but
is not the best. **`stalest first`** does not compute a share at all; every month it spends its
budget starting from the basis that has most exceeded its validity period, and lowers the rate
to **0.2222** — 64 wrong decisions instead of 107.

A fixed share has to spend its hour in a category even once no stale basis is left to spend it
on there; `stalest first` keeps every basis in a single queue. There is a side measure too: the
stale-basis rate is 0.2662 with `by staleness` and 0.2940 with `stalest first` — **more** stale
bases remain, yet **fewer** wrong decisions result. Counting stale bases is not enough; how stale
they are has to be counted.

## The Budget's Limit

The second table grows the budget. Going from 24 to 48 annual hours, the error gain per hour
rises from 7.96e-3 to 9.84e-3; after that it keeps falling, dropping to 2.41e-5 on the move from
288 to 576 hours. In that last step, 288 more hours a year get spent and the rate falls by only
0.0069.

The reason is written in the last row: the floor is 0.1426. Even if every basis is fresh, that is
the probability of a decision resting on three bases turning out wrong, because even a fresh
measurement can be mistaken. A learning budget cannot lower this floor; it can only cut the
excess that comes from staleness.

The learning decision thus comes down to two numbers: at what point the budget approaches the
floor, and by what rule the available budget is distributed. The second makes more difference —
at seventy-two hours, changing the distribution rule lowers the rate from 0.3958 to 0.2222,
while growing the budget from seventy-two to a hundred and forty-four hours only takes it from
0.3715 to 0.2326.

## Summary

- A decision's basis is an object with a validity period; the period depends on the category
  (measurement 6, tool 18, structure 120 months), and as it is exceeded, the decision's
  probability of being wrong rises from the floor toward one (AP16, AP17).
- In the seventy-two-month run, the regime with no learning budget at all got 261 of 288
  decisions wrong (rate 0.9063); refreshing only structural principles with the same budget
  lowered the rate to 0.9028, a drop of only 0.4 percent.
- The same annual 72 hours gave 0.3958 (equal), 0.3715 (by staleness), and 0.2222 (stalest
  first), depending on distribution. Learning is a budget allocation, and the distribution rule
  makes more difference than the budget itself.
- The count of stale bases alone is misleading: the strategy that gave the best result left more
  stale bases (0.2940 against 0.2662) but lowered wrong decisions from 107 to 64.
- Growing the budget has diminishing returns and stops at a floor: 0.1426, the wrongness share
  remaining even when every basis is fresh. Past 288 annual hours, every additional hour spent
  returns 2.41e-5.

## Course Wrap-Up

| Lesson | Modeled object | Measured quantity | Comparison result |
|---|---|---|---|
| Software Architecture Definition | Decision-module mapping and dependency closure | Cost of reversal (files touched) | Body-size ranking and cost-of-reversal ranking inverted in 31 of 55 pairs |
| Architecture Levels | Decision scope and deployment-unit mapping | Units covered, uninformed units, repeated decisions | The local scheme produced 47 uninformed units and 31 conflicting implementations; the centralized scheme produced 0 conflicts but 99 consults |
| The Architect's Responsibilities | Responsibility matrix and machine-checkable constraint | Rate of decisions that lose their correspondence in code | Compliance was 25 percent under the unsupervised scheme, 88 percent under the supervised scheme |
| The Architect and Developer Relationship | The decision-maker's known import graph versus the real graph | Rate of stillborn decisions and knowledge gap (edges) | With the hands-off architect, 8 of 18 decisions were stillborn versus 0 of 18 for the hands-on architect; knowledge gap was 13.8 edges against 3.1 |
| Consulting and Coaching | Decision-transfer mode and the pattern/edge-case distinction | Correct-application rate and gain per hour of transfer | Across 168 cases, directive gave 68 percent, written rationale 85 percent, working together 87 percent; gain per hour was 3.71 against 0.55 |
| Striking Balance | Alternative-quality table and weight vector | Number of weights that could change the winner but had no written source | Across 1,771 vectors, winner regions covered 17.7 percent and 68.4 percent; weights with no written source dropped from 1 to 0 |
| Simplifying | Module graph versus requirement graph | Essential and accidental complexity share | Simplification lowered the total from 37 to 26, while touched modules rose from 13 to 16 |
| Decision Making | Decision set and dependency set | Cost of reversal and wait time | The `always-long` policy gave 58.70 person-days and 120 days; the `by-breakeven` policy gave 38.70 person-days and 55 days |
| Stakeholder Management | Stakeholder vocabulary, concern set, and statement sequence | Questions answered, approval rounds, skipped constraints | The technical narrative: 27 questions and 8 rounds; the outcome-focused narrative: 0 questions but 7 total rounds with 4 surprises; the constraint-carrying narrative: 3 rounds |
| Technology Evaluation | Candidate feature set and a twelve-module source tree | Exit cost (hours), winner by weight | Five weight sets gave three different winners; the wrapper cut the code share from 33.5 hours to 4.0 hours, the 24.0-hour data share never changed |
| Continuous Learning Discipline | Basis age and validity period | Rate of decisions turning out wrong | The same annual 72 hours gave 0.3958, 0.3715, and 0.2222 depending on distribution; the no-budget regime gave 0.9063 |

The shared shape of these eleven rows is the course's rule: **a practice's value can only be
defended by a measured difference.** In no lesson did the sentence "the architect must be
balanced," "the architect must keep the design simple," or "the architect must communicate" count as a
conclusion. Every time, the practice's product was turned into a data structure, two forms of
applying it were run on the same input, and the difference between them was written down as a
number. If the difference cannot be measured, there is no practice left to defend.

Decisions were made throughout this course: balances were struck, alternatives were eliminated,
thresholds were derived, constraints were carried to stakeholders, candidates were weighted, and
bases were refreshed. All of them share one flaw. All of them live **in the decision-maker's
memory**. The weight vector's source, why the eliminated alternative was eliminated, which
breaking point the threshold came from, what break-even probability the wrapper was written
against — none of this was ever written into code. Six months later, there is nothing to hand
someone who asks "why is this the way it is." The next course, **Architectural Decisions and
Documentation**, starts from here.
