Lesson 11 / 11
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.
Contents
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.
// 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.
// 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.
To keep your progress and take notes, Log in
My notes
Log in to take notes.