skill-systems-thinking 0.1.0 → 0.1.1
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.
- package/CHANGELOG.md +37 -25
- package/LICENSE +21 -21
- package/README.md +482 -396
- package/SKILL.md +125 -125
- package/assets/runtime-prompt.txt +21 -21
- package/evals/checks.json +88 -88
- package/init.ps1 +68 -0
- package/package.json +11 -4
- package/references/bounded-control.md +82 -82
- package/references/closed-loop-workflow.md +89 -89
- package/references/discrete-systems.md +79 -79
- package/references/disturbance.md +98 -98
- package/references/modeling.md +87 -87
- package/references/multivariable.md +112 -112
- package/references/original-text.md +58 -58
- package/references/stability.md +97 -97
- package/references/state-and-control.md +84 -84
- package/templates/change-proposal.md +51 -51
- package/templates/debugging-checklist.md +44 -44
|
@@ -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.
|
package/references/modeling.md
CHANGED
|
@@ -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.
|