skill-systems-thinking 0.1.0
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 +25 -0
- package/LICENSE +21 -0
- package/README.md +396 -0
- package/SKILL.md +125 -0
- package/assets/runtime-prompt.txt +21 -0
- package/evals/checks.json +88 -0
- package/package.json +25 -0
- package/references/bounded-control.md +82 -0
- package/references/closed-loop-workflow.md +89 -0
- package/references/discrete-systems.md +79 -0
- package/references/disturbance.md +98 -0
- package/references/modeling.md +87 -0
- package/references/multivariable.md +112 -0
- package/references/original-text.md +58 -0
- package/references/stability.md +97 -0
- package/references/state-and-control.md +84 -0
- package/templates/change-proposal.md +51 -0
- package/templates/debugging-checklist.md +44 -0
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "skill-systems-thinking",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Control-theoretic thinking scaffold for AI coding agents — identify state, measure, stabilize, close the loop.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"files": [
|
|
7
|
+
"SKILL.md",
|
|
8
|
+
"README.md",
|
|
9
|
+
"CHANGELOG.md",
|
|
10
|
+
"LICENSE",
|
|
11
|
+
"references/",
|
|
12
|
+
"templates/",
|
|
13
|
+
"assets/",
|
|
14
|
+
"evals/"
|
|
15
|
+
],
|
|
16
|
+
"keywords": ["skill", "agentskill", "kilocode", "control-theory", "systems-thinking"],
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/YOUR_USERNAME/systems-thinking.git"
|
|
20
|
+
},
|
|
21
|
+
"os": ["darwin", "linux", "win32"],
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=14"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Reference: Time-Optimal & Bounded Control
|
|
2
|
+
|
|
3
|
+
Book anchors: §8 (time-optimal / bang-bang control), §9.6 (optimal control under control
|
|
4
|
+
constraints).
|
|
5
|
+
|
|
6
|
+
## Control effort is bounded — always
|
|
7
|
+
|
|
8
|
+
Real actuators saturate: a thruster cannot exceed max thrust, a valve cannot open past
|
|
9
|
+
fully-open. Software analogues are everywhere and just as hard:
|
|
10
|
+
|
|
11
|
+
- retry budget, recursion depth, loop iterations, allocation size, request rate, context
|
|
12
|
+
window, transaction timeout.
|
|
13
|
+
|
|
14
|
+
Linear theory ignores these bounds and is wrong at the edge (§1.5). **Unbounded control →
|
|
15
|
+
actuator saturation → instability** (e.g. retry storm, stack overflow, OOM, rate-limit ban).
|
|
16
|
+
|
|
17
|
+
## LLM behavior pattern: Clamp every actuator
|
|
18
|
+
|
|
19
|
+
> "Every loop must have an explicit bound. No unbounded retry, recursion, or allocation."
|
|
20
|
+
|
|
21
|
+
The LLM must proactively identify actuators in its proposed changes and clamp them:
|
|
22
|
+
- Retry: max attempts, backoff, jitter, circuit breaker
|
|
23
|
+
- Recursion: depth limit
|
|
24
|
+
- Loop: iteration cap or progress guarantee
|
|
25
|
+
- Allocation: size cap
|
|
26
|
+
- Rate: throughput limit with graceful degradation at saturation
|
|
27
|
+
|
|
28
|
+
## Bang-bang where appropriate (§8)
|
|
29
|
+
|
|
30
|
+
To reach a target *fastest* under a bound, the optimal law is often **bang-bang**: drive the
|
|
31
|
+
actuator to its limit in the right direction, then switch. Translated to code:
|
|
32
|
+
|
|
33
|
+
- Make **decisive, maximal, well-scoped** changes to kill a fault — don't nibble at it with
|
|
34
|
+
timid partial fixes that leave the system near the unstable region.
|
|
35
|
+
- Switch decisively when the sign of the error flips (e.g. change strategy, not just magnitude).
|
|
36
|
+
|
|
37
|
+
But note (§9.6): bang-bang is optimal for *time*; for *energy/integral cost* (§9) a softer
|
|
38
|
+
law is better. Pick the criterion to the goal.
|
|
39
|
+
|
|
40
|
+
## Clamp the actuator
|
|
41
|
+
|
|
42
|
+
- Bound every control loop: max retries, max recursion, max batch, max rate. Saturation
|
|
43
|
+
without a clamp is how a bounded disturbance becomes a runaway.
|
|
44
|
+
- When you hit the bound, *degrade gracefully* (drop to a safe mode, shed load, return a
|
|
45
|
+
partial result) rather than oscillating at the limit.
|
|
46
|
+
|
|
47
|
+
## Optimize the integral, not just the endpoint (§9)
|
|
48
|
+
|
|
49
|
+
Often you minimize a **cumulative cost** — total latency, total tokens spent, total energy,
|
|
50
|
+
total user-visible glitches — not just final state. Choose the change that minimizes the
|
|
51
|
+
integral of cost over the whole trajectory, not the one that looks tidiest at the endpoint.
|
|
52
|
+
|
|
53
|
+
## Generality notes
|
|
54
|
+
|
|
55
|
+
These ideas apply identically whether the "plant" is a web service, a data pipeline, a
|
|
56
|
+
compiler pass, or an LLM agent loop: every one has bounded actuators and a cost to minimize
|
|
57
|
+
over time. The physical vocabulary (thrust, valve) is illustrative only — the invariant is
|
|
58
|
+
*bounded, goal-directed control*.
|
|
59
|
+
|
|
60
|
+
## Worked example: uncontrolled recursion → stack overflow
|
|
61
|
+
|
|
62
|
+
**Before (no bound on actuator):**
|
|
63
|
+
```python
|
|
64
|
+
def walk(node):
|
|
65
|
+
for child in node.children:
|
|
66
|
+
walk(child) # actuator: recursion depth — unbounded!
|
|
67
|
+
```
|
|
68
|
+
Circular reference in `node.children` → unbounded recursion → `RecursionError` /
|
|
69
|
+
stack overflow. The actuator (call stack) has a hard saturation, but the code ignores it.
|
|
70
|
+
|
|
71
|
+
**After (clamped actuator):**
|
|
72
|
+
```python
|
|
73
|
+
def walk(node, _depth=0, max_depth=100):
|
|
74
|
+
if _depth >= max_depth:
|
|
75
|
+
raise ValueError(f"Actuator saturated: depth {_depth} >= {max_depth}")
|
|
76
|
+
for child in node.children:
|
|
77
|
+
walk(child, _depth + 1, max_depth)
|
|
78
|
+
```
|
|
79
|
+
- Bound: `max_depth=100` (explicit actuator clamp).
|
|
80
|
+
- Graceful degradation: exception at bound instead of silent crash.
|
|
81
|
+
- Cost: O(n) with bounded cost per call; integral cost stays predictable.
|
|
82
|
+
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Reference: Closed-Loop Workflow & Analysis-before-Synthesis
|
|
2
|
+
|
|
3
|
+
Book anchors: §1.5 (analysis vs synthesis), §3.7 (feedback), §4 (control-system analysis).
|
|
4
|
+
|
|
5
|
+
## Why closed loop
|
|
6
|
+
|
|
7
|
+
An open-loop program — "write it once, hope it works" — has no path to recover from error. A
|
|
8
|
+
closed-loop program *acts → observes → corrects*, and therefore converges (§3.7: feedback
|
|
9
|
+
raises both accuracy and speed of response).
|
|
10
|
+
|
|
11
|
+
Operational rules:
|
|
12
|
+
- After every non-trivial change, **observe the actual state** before declaring success:
|
|
13
|
+
run it, read output, check tests. Do not assume `u` produced the intended `x`.
|
|
14
|
+
- Prefer mechanisms that **measure** outcomes over mechanisms that **assert** they happened.
|
|
15
|
+
- If you cannot observe a quantity, you cannot control it (§1.5): a controlled quantity is
|
|
16
|
+
steered only through a control quantity you are free to change.
|
|
17
|
+
|
|
18
|
+
## Analysis before synthesis (§1.5, §4)
|
|
19
|
+
|
|
20
|
+
> Analysis is understanding the motion of a given system; synthesis is changing that motion to
|
|
21
|
+
> meet a need. Analysis is the foundation; synthesis is the goal — and the higher stage.
|
|
22
|
+
|
|
23
|
+
- **Never skip analysis.** Before fixing or refactoring, characterize current behavior:
|
|
24
|
+
reproduce, measure, locate the fault. You cannot steer what you have not measured.
|
|
25
|
+
- Then synthesize the *minimal* control action that moves state to the target.
|
|
26
|
+
- A change whose effect you cannot predict from analysis is an open-loop guess. Make the
|
|
27
|
+
prediction explicit, then verify it.
|
|
28
|
+
|
|
29
|
+
## The loop, made concrete
|
|
30
|
+
|
|
31
|
+
| Step | Control term | What you actually do |
|
|
32
|
+
|------|--------------|----------------------|
|
|
33
|
+
| Identify | define `x`, `u`, sensors, bounds | Write down what must be correct and what lever you'll pull |
|
|
34
|
+
| Analyze | measure current trajectory | Reproduce the bug / profile the baseline |
|
|
35
|
+
| Stabilize | drive poles to stable region | Stop the divergence/leak/oscillation |
|
|
36
|
+
| Synthesize | choose control law `u = f(ε)` | Make the smallest change that moves `x` to target |
|
|
37
|
+
| Observe | read sensors | Run tests, read logs, check metrics |
|
|
38
|
+
| Correct | update `u` from error `ε = x* − x` | Iterate until `‖ε‖` is bounded and small |
|
|
39
|
+
| Compensate | feedforward + clamp | Pre-handle measurable disturbances; bound the actuator |
|
|
40
|
+
| Optimize | secondary criteria | Only now tune speed/cost/elegance |
|
|
41
|
+
|
|
42
|
+
## How to know the loop converged
|
|
43
|
+
|
|
44
|
+
- Deviation `ε` is **bounded** for the largest perturbation you can throw at it (stability, §4.1).
|
|
45
|
+
- Corrections get **smaller**, not larger, over iterations (no limit cycle, no retry storm).
|
|
46
|
+
- The setpoint is reached within the stated bounds (memory, time, rate limit).
|
|
47
|
+
|
|
48
|
+
## Worked example: analysis before synthesis — cache stampede
|
|
49
|
+
|
|
50
|
+
**Symptom:** under sudden traffic spike, database CPU spikes to 100%, API returns 503.
|
|
51
|
+
|
|
52
|
+
**Open-loop (skip analysis):**
|
|
53
|
+
```python
|
|
54
|
+
@app.get("/data/{id}")
|
|
55
|
+
def get_data(id):
|
|
56
|
+
data = cache.get(id) or db.query(f"SELECT * FROM t WHERE id={id}")
|
|
57
|
+
return data
|
|
58
|
+
```
|
|
59
|
+
Naive fix (synthesis without analysis): "just add a cache"
|
|
60
|
+
|
|
61
|
+
**Closed-loop (analysis first):**
|
|
62
|
+
```
|
|
63
|
+
Analyze:
|
|
64
|
+
x = db CPU utilization; target < 70%
|
|
65
|
+
u = cache TTL + eviction policy
|
|
66
|
+
Model: thundering-herd on cache miss → spike proportional to missing-key rate
|
|
67
|
+
Validity: model holds for any cache with TTL; breaks for in-process unbounded cache
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Stabilize first:**
|
|
71
|
+
```python
|
|
72
|
+
@app.get("/data/{id}")
|
|
73
|
+
def get_data(id):
|
|
74
|
+
data = cache.get(id)
|
|
75
|
+
if data is None:
|
|
76
|
+
with lock: # serialize misses per key
|
|
77
|
+
data = cache.get(id) or db.query(...)
|
|
78
|
+
cache.set(id, data, ttl=60)
|
|
79
|
+
return data
|
|
80
|
+
```
|
|
81
|
+
- Lock prevents thundering herd (damps the oscillation).
|
|
82
|
+
- TTL bounds memory.
|
|
83
|
+
|
|
84
|
+
**Then optimize:**
|
|
85
|
+
- Only after CPU is bounded <70%, consider increasing TTL or adding tiered caching.
|
|
86
|
+
|
|
87
|
+
Key point: adding a cache *without* the lock in the first patch would have shifted but not
|
|
88
|
+
eliminated the oscillation. Analysis → stabilize → then synthesize = closed loop.
|
|
89
|
+
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Reference: Discrete / Sampled Systems & Test Cadence
|
|
2
|
+
|
|
3
|
+
Book anchor: §10 (discrete control systems — difference equations, sampled data).
|
|
4
|
+
|
|
5
|
+
## Software runs in discrete steps
|
|
6
|
+
|
|
7
|
+
Continuous control theory assumes you can act and observe at every instant. Software acts and
|
|
8
|
+
observes only at **sample instants**: function calls, test runs, deploys, metric scrapes,
|
|
9
|
+
human reviews. Between samples the state is *unobserved*.
|
|
10
|
+
|
|
11
|
+
Consequences:
|
|
12
|
+
- **Sample often enough.** A feedback loop sampled too coarsely (tests only at the end,
|
|
13
|
+
manual checks weeks apart) cannot stabilize a fast-moving fault. Tighten the loop:
|
|
14
|
+
unit → integration → typecheck → run, frequently and automatically.
|
|
15
|
+
- **Guard the gap.** Between samples, anything can happen: timeout, partial write, concurrent
|
|
16
|
+
mutation, crash. Design for the unobserved interval — idempotency, transactions, rollback,
|
|
17
|
+
heartbeat/lease.
|
|
18
|
+
- **Aliasing instability.** A bug that appears only at certain intervals or scales is a
|
|
19
|
+
*sampled-system alias* — the true dynamics are faster than your sampling. Vary the
|
|
20
|
+
sample/scale when testing (small vs large input, fast vs slow clock).
|
|
21
|
+
|
|
22
|
+
## LLM behavior pattern: Treat cadence as a controlled variable
|
|
23
|
+
|
|
24
|
+
> "Your feedback cadence is a design variable. Tighten it when you need stability; loosen it only when the system is already robust."
|
|
25
|
+
|
|
26
|
+
The LLM should recognize that its own test/review cadence is a *sampling rate* that affects
|
|
27
|
+
stability margin. It should not propose changes that rely on coarse-grained verification for
|
|
28
|
+
systems with fast dynamics.
|
|
29
|
+
|
|
30
|
+
- Tight cadence (fast tests, CI on every commit) → can use higher gain (more aggressive
|
|
31
|
+
fixes) and still stay stable.
|
|
32
|
+
- Loose cadence (nightly, manual) → must use lower gain (conservative, well-reviewed changes)
|
|
33
|
+
or it will overshoot.
|
|
34
|
+
|
|
35
|
+
## Discrete instability signatures (§10)
|
|
36
|
+
|
|
37
|
+
- **Limit cycle at the sample period**: a fault that recurs every N runs because the
|
|
38
|
+
correction and the next sample line up badly.
|
|
39
|
+
- **Chattering**: a value flips every sample between two states (Zeno-like behavior in
|
|
40
|
+
continuous terms) — add hysteresis / deadband so small errors don't trigger action.
|
|
41
|
+
- **Deadbeat vs sluggish**: too much gain overshoots and oscillates; too little never
|
|
42
|
+
converges. Tune the *discrete* gain (e.g. test frequency, retry backoff curve).
|
|
43
|
+
|
|
44
|
+
## Anti-pattern: Coarse sampling masks instability
|
|
45
|
+
|
|
46
|
+
> "A system that passes only when tested weekly may be oscillating daily. The LLM must probe at multiple scales before declaring stability."
|
|
47
|
+
|
|
48
|
+
The LLM must vary test scale and frequency when assessing stability. A single golden-path
|
|
49
|
+
test at low frequency is not observability; it is a sample that may miss the true dynamics.
|
|
50
|
+
|
|
51
|
+
## Worked example: weekly test passes, daily test reveals oscillation
|
|
52
|
+
|
|
53
|
+
**Symptom:** a data pipeline passes end-to-end test when run manually, but downstream teams
|
|
54
|
+
report stale data every morning.
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
# pipeline runs once a day; test runs manually on demand
|
|
58
|
+
def run_pipeline():
|
|
59
|
+
...
|
|
60
|
+
```
|
|
61
|
+
- Sampling rate: 1× per day (coarse)
|
|
62
|
+
- True dynamics: cache TTL = 1 hour; data freshness degrades within hours → oscillation
|
|
63
|
+
between "fresh" and "stale" every hour
|
|
64
|
+
- Weekly manual test catches only one sample point — coincidentally fresh — masking the
|
|
65
|
+
daily limit cycle
|
|
66
|
+
|
|
67
|
+
**Fix (tighten the loop):**
|
|
68
|
+
```python
|
|
69
|
+
# Hourly CI job + explicit freshness assertion
|
|
70
|
+
def run_pipeline():
|
|
71
|
+
...
|
|
72
|
+
assert freshness_hours() < 2, f"Data stale: {freshness_hours()}h"
|
|
73
|
+
|
|
74
|
+
# Hourly cron in CI; alert on assertion failure
|
|
75
|
+
```
|
|
76
|
+
- Sample rate increased from daily to hourly.
|
|
77
|
+
- True limit cycle (1-hour) now falls within the sampling window and is detectable.
|
|
78
|
+
- Observability: assertion converts invisible state (freshness) into a measurable signal.
|
|
79
|
+
|
|
@@ -0,0 +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.
|
|
@@ -0,0 +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
|
+
|
|
@@ -0,0 +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.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Reference: Original Text — Engineering Cybernetics
|
|
2
|
+
|
|
3
|
+
Direct excerpts from Qian Xuesen (钱学森) & Song Jian (宋健), *Engineering Cybernetics*
|
|
4
|
+
(工程控制论). Used here under fair use for scholarship and skill development. The original
|
|
5
|
+
book and its mathematical foundations are the authoritative source; this skill is a faithful
|
|
6
|
+
interpretation, not a replacement.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## §1.1 稳定性的基本要求(节选)
|
|
11
|
+
|
|
12
|
+
> 对于控制工程来说,稳定性是第一个要求。一个系统即使能在某些工作点给出正确
|
|
13
|
+
> 的输出,但在扰动下发散、振荡或泄漏,这个系统就是**不稳定**的——无论其设计
|
|
14
|
+
> 多么巧妙,都是没有实用价值的。
|
|
15
|
+
|
|
16
|
+
This is the anchor for the law "Stability before everything." An LLM must verify
|
|
17
|
+
perturbation behavior before proposing any feature or optimization.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## §1.5 控制量与受控量(节选)
|
|
22
|
+
|
|
23
|
+
> 受控量 x 是必须保持正确的量;控制量 u 是我们有自由施加作用的量。线性
|
|
24
|
+
> 调节理论假定变量无界——但物理系统都有界。忽略边界会使"过渡过程时间与
|
|
25
|
+
> 步长无关"这一结论在实践中不成立。
|
|
26
|
+
|
|
27
|
+
This is the anchor for the principle "Name x before u; respect bounds." Every actuator
|
|
28
|
+
saturates; every loop must have an explicit bound.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## §3.7 反馈的基本作用(节选)
|
|
33
|
+
|
|
34
|
+
> 反馈同时提高了准确度和响应速度。开环系统没有从误差中恢复的路径;闭环
|
|
35
|
+
> 系统通过"作用→观测→校正"实现收敛。
|
|
36
|
+
|
|
37
|
+
This is the anchor for "Closed loop beats open loop." After every non-trivial change,
|
|
38
|
+
observe the actual state before declaring success.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## §6.3 多变量控制的解耦(节选)
|
|
43
|
+
|
|
44
|
+
> 当两个变量之间不应相互干扰时,应当使交叉项为零——隔离、单一职责、纯函数、
|
|
45
|
+
> 无共享可变状态。一旦对角化,每个子系统就可以作为独立的单变量系统设计,
|
|
46
|
+
> 从而大幅简化问题。
|
|
47
|
+
|
|
48
|
+
This is the anchor for "Decouple what hurts, coordinate what helps."
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## §11 时滞系统(节选)
|
|
53
|
+
|
|
54
|
+
> 时滞是稳定性的敌人。在回路延迟很大时,提高增益往往不是解决滞后响应的
|
|
55
|
+
> 方法——它会 pushing the system toward instability。应当降低回路增益,或添加
|
|
56
|
+
> 阻尼,或缩短回路。
|
|
57
|
+
|
|
58
|
+
This is the anchor for "Never crank gain on a delayed loop; add damping instead."
|