skill-systems-thinking 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,98 +1,98 @@
1
- # Reference: Disturbance Compensation & Time-Delay
2
-
3
- Book anchors: §6.7 (third method: disturbance compensation / invariance), §11 (systems with
4
- time lag / delay).
5
-
6
- ## Two ways to reject a disturbance
7
-
8
- Outside forces push your system off-target: flaky networks, load spikes, other teams'
9
- services, GC pauses, clock drift.
10
-
11
- 1. **Feedback (reactive).** Observe the deviation `ε = x* − x` and correct. Robust, but *lags*
12
- — by the time you measure and act, the disturbance has already moved you.
13
- 2. **Feedforward / disturbance compensation (proactive, §6.7).** If the disturbance is
14
- *measurable*, compensate for it directly so the **relationship** stays invariant. Combine
15
- both → *composite control*: accuracy from feedforward, stability from feedback.
16
-
17
- ## LLM behavior pattern: Feedforward when measurable, never crank gain on delay
18
-
19
- > "For measurable disturbances, compensate proactively. For delayed loops, add damping, not gain. Never crank gain on a delayed loop — it drives the system unstable."
20
-
21
- The LLM must explicitly evaluate whether disturbances are measurable and whether the feedback
22
- loop involves delay. If it proposes increasing retries/logic frequency without addressing the
23
- delay, it is an unstable control action.
24
-
25
- - Apply invariance to the **coordination relation**, not just one variable (§6.7): compensate
26
- so the *relation* holds under the measurable kick.
27
- - For *unmeasurable* disturbances, you can only fall back to feedback — so maximize
28
- observability of likely kicks.
29
-
30
- ## Time-delay systems (§11)
31
-
32
- Many software loops have intrinsic delay: network round-trips, async pipelines, batch jobs,
33
- human-in-the-loop. Delay is the enemy of stability.
34
-
35
- - Expect **reduced stability margin** and **oscillation** when loop delay is large relative to
36
- the dynamics (§11: delay systems need special stability criteria).
37
- - **Do not crank up gain** to compensate for a slow/delayed loop — that pushes it unstable
38
- (cf. §4/§5 phase margin). Instead:
39
- - reduce effective loop gain or add damping;
40
- - shorten the loop (tighter sampling, §10);
41
- - predict/delay-compensate if the delay is known and measurable.
42
- - Watch for limit cycles that appear *only* because of the delay (e.g. a retry that fires
43
- exactly when the late response arrives).
44
-
45
- ## Worked example: flaky dependency → feedforward + feedback
46
-
47
- **Before (feedback only):**
48
- ```python
49
- def call_external():
50
- for attempt in range(5):
51
- try:
52
- return requests.get(EXTERNAL_URL, timeout=2)
53
- except Exception:
54
- time.sleep(1) # fixed backoff — no knowledge of cause
55
- ```
56
- If the dependency is down for 30 seconds, every call burns 5×2 seconds and still fails.
57
- The disturbance (dependency outage) is *measurable* — we have a health endpoint — but we
58
- do not use it. Pure feedback on a delayed, unmeasured disturbance: unstable under load.
59
-
60
- **After (composite control):**
61
- ```python
62
- _healthy = None
63
-
64
- def call_external():
65
- global _healthy
66
- if _healthy is False:
67
- raise CircuitBreakerOpen() # feedforward: fail fast on known outage
68
- try:
69
- resp = requests.get(EXTERNAL_URL, timeout=2)
70
- _healthy = True
71
- return resp
72
- except Exception:
73
- _healthy = False
74
- time.sleep(2 ** attempt + random.uniform(0, 0.1))
75
- raise
76
- ```
77
-
78
- - Feedforward: health check measured at startup and on every failure; known outage → open
79
- loop immediately (invariance from the disturbance).
80
- - Feedback: backoff on transient errors still corrects the trajectory.
81
- - Composite control: accuracy from invariant relation, stability from feedback.
82
-
83
- ---
84
-
85
- ## Retry storms are unstable delay loops
86
-
87
- A retry-without-backoff is a high-gain, zero-damping controller on a delayed plant →
88
- oscillation that grows. Fix: bounded retries + backoff (damping) + jitter (break sync) +
89
- circuit breaker (open the loop before it diverges).
90
-
91
- ## LLM anti-pattern detection
92
-
93
- When a system has a delay, the LLM must not propose:
94
- - More retries (increasing gain)
95
- - Shorter timeouts without backoff (reducing delay margin)
96
- - Any change that amplifies the loop before reducing the delay or adding damping
97
-
98
- These are textbook unstable trajectories. The skill must prevent them.
1
+ # Reference: Disturbance Compensation & Time-Delay
2
+
3
+ Book anchors: §6.7 (third method: disturbance compensation / invariance), §11 (systems with
4
+ time lag / delay).
5
+
6
+ ## Two ways to reject a disturbance
7
+
8
+ Outside forces push your system off-target: flaky networks, load spikes, other teams'
9
+ services, GC pauses, clock drift.
10
+
11
+ 1. **Feedback (reactive).** Observe the deviation `ε = x* − x` and correct. Robust, but *lags*
12
+ — by the time you measure and act, the disturbance has already moved you.
13
+ 2. **Feedforward / disturbance compensation (proactive, §6.7).** If the disturbance is
14
+ *measurable*, compensate for it directly so the **relationship** stays invariant. Combine
15
+ both → *composite control*: accuracy from feedforward, stability from feedback.
16
+
17
+ ## LLM behavior pattern: Feedforward when measurable, never crank gain on delay
18
+
19
+ > "For measurable disturbances, compensate proactively. For delayed loops, add damping, not gain. Never crank gain on a delayed loop — it drives the system unstable."
20
+
21
+ The LLM must explicitly evaluate whether disturbances are measurable and whether the feedback
22
+ loop involves delay. If it proposes increasing retries/logic frequency without addressing the
23
+ delay, it is an unstable control action.
24
+
25
+ - Apply invariance to the **coordination relation**, not just one variable (§6.7): compensate
26
+ so the *relation* holds under the measurable kick.
27
+ - For *unmeasurable* disturbances, you can only fall back to feedback — so maximize
28
+ observability of likely kicks.
29
+
30
+ ## Time-delay systems (§11)
31
+
32
+ Many software loops have intrinsic delay: network round-trips, async pipelines, batch jobs,
33
+ human-in-the-loop. Delay is the enemy of stability.
34
+
35
+ - Expect **reduced stability margin** and **oscillation** when loop delay is large relative to
36
+ the dynamics (§11: delay systems need special stability criteria).
37
+ - **Do not crank up gain** to compensate for a slow/delayed loop — that pushes it unstable
38
+ (cf. §4/§5 phase margin). Instead:
39
+ - reduce effective loop gain or add damping;
40
+ - shorten the loop (tighter sampling, §10);
41
+ - predict/delay-compensate if the delay is known and measurable.
42
+ - Watch for limit cycles that appear *only* because of the delay (e.g. a retry that fires
43
+ exactly when the late response arrives).
44
+
45
+ ## Worked example: flaky dependency → feedforward + feedback
46
+
47
+ **Before (feedback only):**
48
+ ```python
49
+ def call_external():
50
+ for attempt in range(5):
51
+ try:
52
+ return requests.get(EXTERNAL_URL, timeout=2)
53
+ except Exception:
54
+ time.sleep(1) # fixed backoff — no knowledge of cause
55
+ ```
56
+ If the dependency is down for 30 seconds, every call burns 5×2 seconds and still fails.
57
+ The disturbance (dependency outage) is *measurable* — we have a health endpoint — but we
58
+ do not use it. Pure feedback on a delayed, unmeasured disturbance: unstable under load.
59
+
60
+ **After (composite control):**
61
+ ```python
62
+ _healthy = None
63
+
64
+ def call_external():
65
+ global _healthy
66
+ if _healthy is False:
67
+ raise CircuitBreakerOpen() # feedforward: fail fast on known outage
68
+ try:
69
+ resp = requests.get(EXTERNAL_URL, timeout=2)
70
+ _healthy = True
71
+ return resp
72
+ except Exception:
73
+ _healthy = False
74
+ time.sleep(2 ** attempt + random.uniform(0, 0.1))
75
+ raise
76
+ ```
77
+
78
+ - Feedforward: health check measured at startup and on every failure; known outage → open
79
+ loop immediately (invariance from the disturbance).
80
+ - Feedback: backoff on transient errors still corrects the trajectory.
81
+ - Composite control: accuracy from invariant relation, stability from feedback.
82
+
83
+ ---
84
+
85
+ ## Retry storms are unstable delay loops
86
+
87
+ A retry-without-backoff is a high-gain, zero-damping controller on a delayed plant →
88
+ oscillation that grows. Fix: bounded retries + backoff (damping) + jitter (break sync) +
89
+ circuit breaker (open the loop before it diverges).
90
+
91
+ ## LLM anti-pattern detection
92
+
93
+ When a system has a delay, the LLM must not propose:
94
+ - More retries (increasing gain)
95
+ - Shorter timeouts without backoff (reducing delay margin)
96
+ - Any change that amplifies the loop before reducing the delay or adding damping
97
+
98
+ These are textbook unstable trajectories. The skill must prevent them.
@@ -1,87 +1,87 @@
1
- # Reference: Modeling & Engineering Approximation
2
-
3
- Book anchors: §1.4 (almost every real system is nonlinear; linearize deliberately), §1.6
4
- (model identification: analytic, experimental, statistical).
5
-
6
- ## Deliberate simplification (§1.4)
7
-
8
- > Almost any physical system is nonlinear if analyzed precisely enough. We call a system
9
- > "linear" only when, *for the question being asked*, a linear system represents it precisely
10
- > enough that the difference is irrelevant.
11
-
12
- - Don't reach for the full-complexity model first. Build the **simplest model that answers the
13
- current question**, and **state its validity range**.
14
- - **Pick the model to the question:**
15
- - Near a stable equilibrium → linearize, ignore nonlinearity (§1.4, Lyapunov first
16
- approximation).
17
- - Studying oscillation / self-excited vibration / limit cycles → the nonlinearity *is* the
18
- cause; keep it. Linearizing would delete the very phenomenon you're investigating.
19
- - This is the control-theoretic twin of the simplicity rule: simplify, but **know where
20
- the simplification breaks** — and say so.
21
-
22
- ## LLM behavior pattern: State the model and its limits
23
-
24
- > "I model the plant as X. This model holds under conditions Y. It breaks when Z."
25
-
26
- The LLM must explicitly state its mental model of the system and where it breaks, before
27
- synthesizing a fix. Implicit models lead to implicit breakage.
28
-
29
- - If a bug contradicts your model, **the model was wrong, not the bug**. Update the model.
30
- - Distinguish the *model you use to decide* from the *plant that actually runs*. The gap
31
- between them is where instability hides.
32
-
33
- ## Model identification: you don't know the plant exactly (§1.6)
34
-
35
- You rarely have a perfect model of the system you're editing. Three ways to get one:
36
-
37
- 1. **Analytic** — decompose into units, model each, compose. Works *only* when decomposition
38
- is faithful. Beware emergent behavior at the seams: a component's model in isolation can
39
- differ qualitatively from its behavior inside the system.
40
- 2. **Experimental (preferred for code)** — inject a known input `u`, record the output `x`,
41
- infer the model. *This is exactly running the code and observing.* Let behavior, not
42
- assumption, teach you the plant.
43
- 3. **Statistical** — when the plant is too complex for clean analytic or single-shot
44
- experimental identification, use frequency/statistical probing (§1.6: sine sweep →
45
- amplitude/phase → transfer function).
46
-
47
- ## Treat the model as approximate and updatable
48
-
49
- - After a surprise during debugging, **update your mental model** of the plant. A bug that
50
- contradicts your model means the model was wrong, not the bug.
51
- - Distinguish the *model you use to decide* from the *plant that actually runs*. The gap
52
- between them is where instability hides.
53
-
54
- ## Worked example: linear model breaks at scale
55
-
56
- **Scenario:** a list-merging function works for n=10 but segfaults at n=10^7.
57
-
58
- ```python
59
- def merge(a, b):
60
- return a + b # model: O(n) concatenation, works for small n
61
- ```
62
-
63
- **Analysis:**
64
- - Mental model: "concatenation is cheap; I can use it anywhere"
65
- - This model breaks at n=10^7 because `a + b` allocates a new list of size `len(a)+len(b)`.
66
- - Memory spikes → OOM → crash. The model assumed linear cost; reality is linear *coefficient*
67
- with a fixed allocation overhead that dominates at scale.
68
-
69
- **Correct model (stated validity range):**
70
- ```
71
- x = memory usage; model = O(n) allocation per merge; valid for n << cache size.
72
- Breaks when n approaches memory limit.
73
- ```
74
-
75
- **Fix informed by correct model:**
76
- ```python
77
- def merge(a, b, out=None):
78
- if out is None:
79
- out = []
80
- out.extend(a)
81
- out.extend(b) # in-place; model remains valid at scale
82
- return out
83
- ```
84
-
85
- - Model validity range is now explicit.
86
- - Bug informed the model update; model informed the fix.
87
-
1
+ # Reference: Modeling & Engineering Approximation
2
+
3
+ Book anchors: §1.4 (almost every real system is nonlinear; linearize deliberately), §1.6
4
+ (model identification: analytic, experimental, statistical).
5
+
6
+ ## Deliberate simplification (§1.4)
7
+
8
+ > Almost any physical system is nonlinear if analyzed precisely enough. We call a system
9
+ > "linear" only when, *for the question being asked*, a linear system represents it precisely
10
+ > enough that the difference is irrelevant.
11
+
12
+ - Don't reach for the full-complexity model first. Build the **simplest model that answers the
13
+ current question**, and **state its validity range**.
14
+ - **Pick the model to the question:**
15
+ - Near a stable equilibrium → linearize, ignore nonlinearity (§1.4, Lyapunov first
16
+ approximation).
17
+ - Studying oscillation / self-excited vibration / limit cycles → the nonlinearity *is* the
18
+ cause; keep it. Linearizing would delete the very phenomenon you're investigating.
19
+ - This is the control-theoretic twin of the simplicity rule: simplify, but **know where
20
+ the simplification breaks** — and say so.
21
+
22
+ ## LLM behavior pattern: State the model and its limits
23
+
24
+ > "I model the plant as X. This model holds under conditions Y. It breaks when Z."
25
+
26
+ The LLM must explicitly state its mental model of the system and where it breaks, before
27
+ synthesizing a fix. Implicit models lead to implicit breakage.
28
+
29
+ - If a bug contradicts your model, **the model was wrong, not the bug**. Update the model.
30
+ - Distinguish the *model you use to decide* from the *plant that actually runs*. The gap
31
+ between them is where instability hides.
32
+
33
+ ## Model identification: you don't know the plant exactly (§1.6)
34
+
35
+ You rarely have a perfect model of the system you're editing. Three ways to get one:
36
+
37
+ 1. **Analytic** — decompose into units, model each, compose. Works *only* when decomposition
38
+ is faithful. Beware emergent behavior at the seams: a component's model in isolation can
39
+ differ qualitatively from its behavior inside the system.
40
+ 2. **Experimental (preferred for code)** — inject a known input `u`, record the output `x`,
41
+ infer the model. *This is exactly running the code and observing.* Let behavior, not
42
+ assumption, teach you the plant.
43
+ 3. **Statistical** — when the plant is too complex for clean analytic or single-shot
44
+ experimental identification, use frequency/statistical probing (§1.6: sine sweep →
45
+ amplitude/phase → transfer function).
46
+
47
+ ## Treat the model as approximate and updatable
48
+
49
+ - After a surprise during debugging, **update your mental model** of the plant. A bug that
50
+ contradicts your model means the model was wrong, not the bug.
51
+ - Distinguish the *model you use to decide* from the *plant that actually runs*. The gap
52
+ between them is where instability hides.
53
+
54
+ ## Worked example: linear model breaks at scale
55
+
56
+ **Scenario:** a list-merging function works for n=10 but segfaults at n=10^7.
57
+
58
+ ```python
59
+ def merge(a, b):
60
+ return a + b # model: O(n) concatenation, works for small n
61
+ ```
62
+
63
+ **Analysis:**
64
+ - Mental model: "concatenation is cheap; I can use it anywhere"
65
+ - This model breaks at n=10^7 because `a + b` allocates a new list of size `len(a)+len(b)`.
66
+ - Memory spikes → OOM → crash. The model assumed linear cost; reality is linear *coefficient*
67
+ with a fixed allocation overhead that dominates at scale.
68
+
69
+ **Correct model (stated validity range):**
70
+ ```
71
+ x = memory usage; model = O(n) allocation per merge; valid for n << cache size.
72
+ Breaks when n approaches memory limit.
73
+ ```
74
+
75
+ **Fix informed by correct model:**
76
+ ```python
77
+ def merge(a, b, out=None):
78
+ if out is None:
79
+ out = []
80
+ out.extend(a)
81
+ out.extend(b) # in-place; model remains valid at scale
82
+ return out
83
+ ```
84
+
85
+ - Model validity range is now explicit.
86
+ - Bug informed the model update; model informed the fix.
87
+
@@ -1,112 +1,112 @@
1
- # Reference: Multivariable — Decouple What Hurts, Coordinate What Helps
2
-
3
- Book anchors: §6.2–§6.3 (multi-variable control, non-interaction / diagonalization),
4
- §6.7 (coordination control: coordination error, internal setpoint, disturbance compensation).
5
-
6
- ## Codebases are multivariable
7
-
8
- Real systems have many modules and concerns coupled through shared state, APIs, and data.
9
- The control question (§6.3) is: *which cross-terms must be zero, and which may stay?*
10
-
11
- ### Decoupling (§6.3, "non-interacting control")
12
-
13
- - When two concerns must not interfere, drive the cross-terms to zero: isolate, single
14
- responsibility, pure functions, no shared mutable state, explicit interfaces.
15
- - Once diagonalized, each subsystem can be designed as its own single-variable system —
16
- dramatically simpler (§6.3). This is the control-theory justification for modular design.
17
-
18
- ## LLM behavior pattern: Evaluate coupling before changing structure
19
-
20
- > "Decouple harmful coupling; keep beneficial coupling. Do not default to 'isolate everything'."
21
-
22
- When proposing a refactor or architecture change, the LLM **must** distinguish:
23
- - **Harmful coupling**: cross-terms that cause instability, race conditions, or incorrect results
24
- under perturbation. These must be driven to zero.
25
- - **Beneficial coupling**: synchronization, shared invariants, backpressure that keeps the
26
- system coherent. Removing these creates residual coupling elsewhere.
27
-
28
- Blindly removing all coupling: complicates the system (more sensors/actuators to fake the
29
- lost natural coupling); leaves residual coupling that *worsens* quality because the real plant
30
- is uncertain.
31
-
32
- ## Coordination (§6.7) — do NOT over-decouple
33
-
34
- > Non-interacting control is not the only design principle. Often subsystems must *cooperate*
35
- > and stay in a coordination relation, not be independent.
36
-
37
- - Some coupling is **beneficial**: forced synchronization, shared invariants, mechanical
38
- coupling that keeps things in lockstep.
39
- - Keep helpful coupling; cancel only the **harmful** cross-talk (§6.7: use controller coupling
40
- to offset harmful plant coupling, preserve beneficial plant coupling).
41
-
42
- ## Coordination error (协调偏差) — control the relation, not just absolutes
43
-
44
- - For coupled subsystems, regulate the **relation** between variables (e.g. "these two must
45
- stay consistent / proportional / in sync"), not only each variable's absolute value.
46
- - Use an **internal setpoint**: when one subsystem moves, re-derive the others' targets from
47
- the *actual* state, so the whole returns to coordination. External fixed setpoints alone
48
- cannot hold a coordination relation under disturbance.
49
- - Example mappings: distributed cache + DB (consistency relation); producer/consumer queues
50
- (backpressure relation); replicated services (quorum/sync relation).
51
-
52
- ## Worked example: shared mutable state → harmful coupling / beneficial coupling
53
-
54
- **Harmful coupling (before):**
55
- ```python
56
- # module_a.py
57
- _counter = 0
58
-
59
- def increment():
60
- global _counter
61
- _counter += 1
62
-
63
- # module_b.py
64
- from module_a import _counter
65
-
66
- def report():
67
- return _counter # reads mutable state module_a owns
68
- ```
69
- `module_b.report()` correctness depends on `module_a` execution order. Under concurrency
70
- this is a race condition — the cross-term is nonzero and causes instability. Decouple with
71
- explicit interface:
72
-
73
- **After (decoupled):**
74
- ```python
75
- # module_a.py
76
- _state = {"count": 0}
77
-
78
- def increment(state):
79
- state["count"] += 1
80
- return state
81
-
82
- # module_b.py — no knowledge of module_a internals
83
- def report(state):
84
- return state["count"]
85
- ```
86
-
87
- **Beneficial coupling (preserved):**
88
- ```python
89
- # Producer/consumer with backpressure (keep this coupling)
90
- queue = asyncio.Queue(maxsize=100)
91
-
92
- async def producer():
93
- for item in source():
94
- await queue.put(item) # natural back pressure
95
-
96
- async def consumer():
97
- while True:
98
- item = await queue.get() # consumption rate regulates production rate
99
- ```
100
- Removing the queue and calling producer/consumer independently destroys the natural
101
- coordination. Regulate the *relation* (producer rate ≤ consumer rate), not just each
102
- absolute.
103
-
104
- ---
105
-
106
- ## Decision rule
107
-
108
- - Will interference cause incorrectness or instability? → **decouple** (diagonalize).
109
- - Must subsystems move together or hold a ratio/invariant? → **coordinate** (internal
110
- setpoint + relation feedback), keep beneficial coupling.
111
- - Uncertain which? Prefer the simpler structure, then *test the coupling* under disturbance
112
- before adding decoupling machinery.
1
+ # Reference: Multivariable — Decouple What Hurts, Coordinate What Helps
2
+
3
+ Book anchors: §6.2–§6.3 (multi-variable control, non-interaction / diagonalization),
4
+ §6.7 (coordination control: coordination error, internal setpoint, disturbance compensation).
5
+
6
+ ## Codebases are multivariable
7
+
8
+ Real systems have many modules and concerns coupled through shared state, APIs, and data.
9
+ The control question (§6.3) is: *which cross-terms must be zero, and which may stay?*
10
+
11
+ ### Decoupling (§6.3, "non-interacting control")
12
+
13
+ - When two concerns must not interfere, drive the cross-terms to zero: isolate, single
14
+ responsibility, pure functions, no shared mutable state, explicit interfaces.
15
+ - Once diagonalized, each subsystem can be designed as its own single-variable system —
16
+ dramatically simpler (§6.3). This is the control-theory justification for modular design.
17
+
18
+ ## LLM behavior pattern: Evaluate coupling before changing structure
19
+
20
+ > "Decouple harmful coupling; keep beneficial coupling. Do not default to 'isolate everything'."
21
+
22
+ When proposing a refactor or architecture change, the LLM **must** distinguish:
23
+ - **Harmful coupling**: cross-terms that cause instability, race conditions, or incorrect results
24
+ under perturbation. These must be driven to zero.
25
+ - **Beneficial coupling**: synchronization, shared invariants, backpressure that keeps the
26
+ system coherent. Removing these creates residual coupling elsewhere.
27
+
28
+ Blindly removing all coupling: complicates the system (more sensors/actuators to fake the
29
+ lost natural coupling); leaves residual coupling that *worsens* quality because the real plant
30
+ is uncertain.
31
+
32
+ ## Coordination (§6.7) — do NOT over-decouple
33
+
34
+ > Non-interacting control is not the only design principle. Often subsystems must *cooperate*
35
+ > and stay in a coordination relation, not be independent.
36
+
37
+ - Some coupling is **beneficial**: forced synchronization, shared invariants, mechanical
38
+ coupling that keeps things in lockstep.
39
+ - Keep helpful coupling; cancel only the **harmful** cross-talk (§6.7: use controller coupling
40
+ to offset harmful plant coupling, preserve beneficial plant coupling).
41
+
42
+ ## Coordination error (协调偏差) — control the relation, not just absolutes
43
+
44
+ - For coupled subsystems, regulate the **relation** between variables (e.g. "these two must
45
+ stay consistent / proportional / in sync"), not only each variable's absolute value.
46
+ - Use an **internal setpoint**: when one subsystem moves, re-derive the others' targets from
47
+ the *actual* state, so the whole returns to coordination. External fixed setpoints alone
48
+ cannot hold a coordination relation under disturbance.
49
+ - Example mappings: distributed cache + DB (consistency relation); producer/consumer queues
50
+ (backpressure relation); replicated services (quorum/sync relation).
51
+
52
+ ## Worked example: shared mutable state → harmful coupling / beneficial coupling
53
+
54
+ **Harmful coupling (before):**
55
+ ```python
56
+ # module_a.py
57
+ _counter = 0
58
+
59
+ def increment():
60
+ global _counter
61
+ _counter += 1
62
+
63
+ # module_b.py
64
+ from module_a import _counter
65
+
66
+ def report():
67
+ return _counter # reads mutable state module_a owns
68
+ ```
69
+ `module_b.report()` correctness depends on `module_a` execution order. Under concurrency
70
+ this is a race condition — the cross-term is nonzero and causes instability. Decouple with
71
+ explicit interface:
72
+
73
+ **After (decoupled):**
74
+ ```python
75
+ # module_a.py
76
+ _state = {"count": 0}
77
+
78
+ def increment(state):
79
+ state["count"] += 1
80
+ return state
81
+
82
+ # module_b.py — no knowledge of module_a internals
83
+ def report(state):
84
+ return state["count"]
85
+ ```
86
+
87
+ **Beneficial coupling (preserved):**
88
+ ```python
89
+ # Producer/consumer with backpressure (keep this coupling)
90
+ queue = asyncio.Queue(maxsize=100)
91
+
92
+ async def producer():
93
+ for item in source():
94
+ await queue.put(item) # natural back pressure
95
+
96
+ async def consumer():
97
+ while True:
98
+ item = await queue.get() # consumption rate regulates production rate
99
+ ```
100
+ Removing the queue and calling producer/consumer independently destroys the natural
101
+ coordination. Regulate the *relation* (producer rate ≤ consumer rate), not just each
102
+ absolute.
103
+
104
+ ---
105
+
106
+ ## Decision rule
107
+
108
+ - Will interference cause incorrectness or instability? → **decouple** (diagonalize).
109
+ - Must subsystems move together or hold a ratio/invariant? → **coordinate** (internal
110
+ setpoint + relation feedback), keep beneficial coupling.
111
+ - Uncertain which? Prefer the simpler structure, then *test the coupling* under disturbance
112
+ before adding decoupling machinery.