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.
@@ -1,82 +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
-
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
+
@@ -1,89 +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
-
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
+
@@ -1,79 +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
-
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
+