Compare commits

..

2 Commits

Author SHA1 Message Date
Julien Calixte
69fec5fe2e feat(samples): add "AI deskilling spiral" gallery sample
The thirteenth sample points the language at a live debate: a classic trap,
Shifting the burden to the intervenor (Thinking in Systems, ch. 5), with AI as
the intervenor. Technical debt drives AI reliance, AI churns debt and lets
skills atrophy, and a thinner-skilled team refactors less — so the loop
Technical debt -> AI reliance -> atrophy -> Expertise -> refactoring ->
Technical debt is Reinforcing: addiction, not a fix. A learning Balancing brake
(practice toward a skill ceiling) is tuned to lose.

Maps the brief onto valid roles: Expertise and Technical debt are the stocks;
code quality and lead time read off them (inverse of debt / inverse of
expertise); model price lives in the AI-reliance factor. Buildable on the
existing rule vocabulary. Tuned against the simulator: over t=0..50 Expertise
slides 70 -> ~6 and Technical debt spirals 20 -> ~150, stopped (like
"Escalation") before the loop runs off-chart. The loop detector classifies the
four loops as expected (2 R, 2 B).
2026-06-20 15:28:51 +02:00
Julien Calixte
6a4fe59811 feat(samples): add "Overshoot and collapse" gallery sample
The twelfth sample fills the one classic dynamic the gallery was missing —
overshoot and collapse, the dynamic Thinking in Systems is built around. A
Reinforcing engine (Capital reinvesting its extraction revenue) runs on a
non-renewable Resource, the first Stock in the gallery with no inflow: it
overshoots the limit instead of settling at it, the dark twin of "Limits to
growth".

Buildable with the existing rule vocabulary (proportional only); the
non-negative-stock floor tames the bilinear extraction term as the Resource
runs out. Tuned against the simulator: Capital 5 → ~250 (t≈39) → ~5 by t=150,
Resource 1000 → ~6, no divergence. The loop detector classifies the three
loops as expected (R: investment→Capital→extraction; B: depreciation→Capital;
B: extraction→Resource).
2026-06-20 15:15:42 +02:00

View File

@@ -33,6 +33,21 @@
* 11. Drift to low performance — a goal that erodes toward actual performance, so a * 11. Drift to low performance — a goal that erodes toward actual performance, so a
* Reinforcing loop ratchets both downward. * Reinforcing loop ratchets both downward.
* *
* Next, the dynamic the book is named for, and the one the gallery has saved until a
* reader knows every piece it needs:
*
* 12. Overshoot and collapse — a Reinforcing engine running on a *non-renewable*
* Stock (the first with no inflow): it overshoots the
* limit instead of settling at it, the dark twin of
* "Limits to growth" — the ceiling erodes, so it crashes.
*
* Last, the language pointed at a live debate — a classic trap (Shifting the burden to
* the intervenor, ch. 5) wearing today's clothes:
*
* 13. AI deskilling spiral — handing the burden of code quality to AI atrophies the
* Expertise that holds quality up, so the team leans on AI
* harder and Technical debt spirals: addiction, not a fix.
*
* These are plain data built from the same tested constructors the store uses * These are plain data built from the same tested constructors the store uses
* (factory.ts), so every sample is a valid Model by construction. `build()` * (factory.ts), so every sample is a valid Model by construction. `build()`
* mints fresh ids on each call, so loading a sample twice never collides. * mints fresh ids on each call, so loading a sample twice never collides.
@@ -571,6 +586,152 @@ function driftToLowPerformance(): Model {
) )
} }
/**
* Overshoot and collapse — the dark twin of "Limits to growth". The same
* Reinforcing engine runs, but the limit here is a *non-renewable* Resource that
* only depletes: a Stock with no inflow, the first in the gallery. An economy
* (Capital) lives off it — extraction grows with both the Resource left and the
* Capital deployed (Resource, Capital → [+] → extraction), and the revenue is
* reinvested as new Capital (extraction → [+] → investment → Capital), so the loop
* Capital → extraction → investment → Capital carries no `` → Reinforcing. Capital
* climbs and extraction accelerates, but every unit burned is gone for good, so the
* Resource crosses the break-even level, the engine starves, and depreciation
* (Capital → [+] → depreciation, a Balancing drain) takes Capital down: it peaks,
* then collapses. Contrast "Predator and prey", whose prey regrows and so settles
* into oscillation — a finite Resource cannot, so it overshoots and crashes instead.
*/
function overshootAndCollapse(): Model {
// Resource on the left drains only downward (no inflow). Capital on the right runs
// a full Source → investment → Capital → depreciation → Sink column. The two
// coupling links — Capital → extraction and extraction → investment — cross in the
// open centre, where the R badge lands.
const resource = makeStock({ x: -240, y: 0 }, "Resource")
resource.initialValue = 1000
const extractionSink = makeCloud({ x: -240, y: 360 })
const extraction = makeFlow({ x: -240, y: 160 }, "extraction", resource.id, extractionSink.id)
// extraction = factor × Resource × Capital (both `+`): more capital extracts
// faster, scarcer resource slower. The bilinear term the non-negative floor tames.
extraction.rule = { kind: "proportional", factor: 0.0004 }
const capital = makeStock({ x: 240, y: 0 }, "Capital")
capital.initialValue = 5
const investmentSource = makeCloud({ x: 240, y: -360 })
const investment = makeFlow({ x: 240, y: -160 }, "investment", investmentSource.id, capital.id)
// investment = factor × extraction (its one `+` input): the revenue reinvested —
// a Flow feeding a Flow, the edge that closes the Reinforcing loop through Capital.
investment.rule = { kind: "proportional", factor: 0.5 }
const depreciationSink = makeCloud({ x: 240, y: 360 })
const depreciation = makeFlow({ x: 240, y: 160 }, "depreciation", capital.id, depreciationSink.id)
// depreciation = factor × Capital (its `+` input): the Balancing drain that wins
// once the Resource can no longer feed investment.
depreciation.rule = { kind: "proportional", factor: 0.04 }
return model(
"Overshoot and collapse",
[
resource,
extractionSink,
extraction,
capital,
investmentSource,
investment,
depreciationSink,
depreciation,
],
[
link(resource, extraction, "+"),
link(capital, extraction, "+"),
link(extraction, investment, "+"),
link(capital, depreciation, "+"),
],
// Capital starts at 5, overshoots to ~250 by t≈39, and collapses back near its
// starting level by t=150 — the full boom-and-bust arc, no dead tail.
{ start: 0, stop: 150, dt: 1 },
)
}
/**
* AI deskilling spiral — a classic trap in today's clothes: "Shifting the burden to
* the intervenor" (Thinking in Systems, ch. 5), where leaning on an outside fixer
* atrophies your own capacity to solve the problem, so you depend on the fixer ever
* more. Here the intervenor is AI. Technical debt drives reliance on it (the more cruft
* and delivery pressure, the more you reach for the model: Technical debt → [+] → AI
* reliance); AI churns out plausible code that adds debt (AI reliance → [+] → debt
* accrual) and lets skills lapse (AI reliance → [+] → atrophy, draining Expertise); and
* a thinner-skilled team refactors less (Expertise → [+] → refactoring, the Balancing
* payoff that now weakens). The loop Technical debt → AI reliance → atrophy → Expertise
* → refactoring → Technical debt carries two `` (the two outflows) → Reinforcing: the
* spiral. The hopeful brake is learning — practice pulling Expertise back toward its
* ceiling (skill ceiling → [+], Expertise → [] → learning: a Balancing loop) — but
* tuned here it loses to the spiral. (Code quality and lead time aren't nodes: quality
* reads as the inverse of Technical debt, lead time as the inverse of Expertise;
* "model price" lives in the AI-reliance factor — cheaper models, higher reliance.)
*/
function aiDeskillingSpiral(): Model {
// Two lanes — Expertise on top, Technical debt below — each a full Source → inflow →
// Stock → outflow → Sink. The AI reliance Converter sits between them, reading the
// debt below and feeding both atrophy (top lane) and debt accrual: the couplings that
// close the Reinforcing spiral cross the open centre.
const skillCeiling = makeConverter({ x: -360, y: -300 }, "skill ceiling")
skillCeiling.rule = { kind: "constant", value: 100 }
const learningSource = makeCloud({ x: -540, y: -120 })
const expertise = makeStock({ x: -180, y: -120 }, "Expertise")
expertise.initialValue = 70
const learning = makeFlow({ x: -360, y: -120 }, "learning", learningSource.id, expertise.id)
// learning = factor × (skill ceiling Expertise): practice pulls skill back up — the
// Balancing brake. The further from mastery, the harder you study.
learning.rule = { kind: "gap", factor: 0.04 }
const atrophySink = makeCloud({ x: 300, y: -120 })
const atrophy = makeFlow({ x: 80, y: -120 }, "atrophy", expertise.id, atrophySink.id)
// atrophy = factor × AI reliance: the more you offload to AI, the faster unused skills
// lapse — the side effect that makes this an addiction, not a fix.
atrophy.rule = { kind: "proportional", factor: 0.6 }
const accrualSource = makeCloud({ x: -540, y: 120 })
const debt = makeStock({ x: -180, y: 120 }, "Technical debt")
debt.initialValue = 20
const accrual = makeFlow({ x: -360, y: 120 }, "debt accrual", accrualSource.id, debt.id)
// debt accrual = factor × AI reliance: AI emits plausible code faster than anyone
// reviews it, so debt grows the more you lean on it.
accrual.rule = { kind: "proportional", factor: 0.9 }
const refactorSink = makeCloud({ x: 300, y: 120 })
const refactoring = makeFlow({ x: 80, y: 120 }, "refactoring", debt.id, refactorSink.id)
// refactoring = factor × Expertise × Technical debt: skilled teams pay debt down in
// proportion to how much there is — the Balancing payoff the spiral starves.
refactoring.rule = { kind: "proportional", factor: 0.0009 }
const reliance = makeConverter({ x: -180, y: 0 }, "AI reliance")
// AI reliance = factor × Technical debt: the factor is how cheap and available models
// are — lower model price, higher reliance per unit of debt.
reliance.rule = { kind: "proportional", factor: 0.1 }
return model(
"AI deskilling spiral",
[
skillCeiling,
learningSource,
expertise,
learning,
atrophySink,
atrophy,
accrualSource,
debt,
accrual,
refactorSink,
refactoring,
reliance,
],
[
link(skillCeiling, learning, "+"),
link(expertise, learning, "-"),
link(reliance, atrophy, "+"),
link(debt, reliance, "+"),
link(reliance, accrual, "+"),
link(expertise, refactoring, "+"),
link(debt, refactoring, "+"),
],
// Expertise slides 70 → ~6 and Technical debt spirals 20 → ~150 over the window —
// the Reinforcing loop clearly taking off, stopped (like "Escalation") before it
// runs away off-chart.
{ start: 0, stop: 50, dt: 1 },
)
}
/** The gallery, ordered simplest first. */ /** The gallery, ordered simplest first. */
export const SAMPLES: Sample[] = [ export const SAMPLES: Sample[] = [
{ title: "Bathtub", blurb: "A stock filled and drained — no feedback yet.", build: bathtub }, { title: "Bathtub", blurb: "A stock filled and drained — no feedback yet.", build: bathtub },
@@ -624,4 +785,14 @@ export const SAMPLES: Sample[] = [
blurb: "Goals erode toward actual: a Reinforcing slide downhill.", blurb: "Goals erode toward actual: a Reinforcing slide downhill.",
build: driftToLowPerformance, build: driftToLowPerformance,
}, },
{
title: "Overshoot and collapse",
blurb: "A growth engine burns a finite Resource: it peaks, then crashes.",
build: overshootAndCollapse,
},
{
title: "AI deskilling spiral",
blurb: "Leaning on AI to hold quality erodes the expertise that holds it: shifting the burden.",
build: aiDeskillingSpiral,
},
] ]