sanook-cli 0.4.0 → 0.5.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.
Files changed (235) hide show
  1. package/.env.example +19 -0
  2. package/CHANGELOG.md +144 -0
  3. package/README.md +153 -20
  4. package/README.th.md +136 -0
  5. package/dist/agentContext.js +4 -0
  6. package/dist/approval.js +6 -0
  7. package/dist/bin.js +394 -51
  8. package/dist/brain.js +92 -59
  9. package/dist/brand.js +47 -0
  10. package/dist/checkpoint.js +37 -0
  11. package/dist/commands.js +86 -6
  12. package/dist/compaction.js +76 -5
  13. package/dist/config.js +100 -12
  14. package/dist/cost.js +60 -3
  15. package/dist/doctor.js +92 -0
  16. package/dist/gateway/auth.js +2 -2
  17. package/dist/gateway/ledger.js +2 -2
  18. package/dist/gateway/scheduler.js +1 -0
  19. package/dist/gateway/serve.js +6 -4
  20. package/dist/gateway/server.js +10 -2
  21. package/dist/git.js +11 -2
  22. package/dist/hooks.js +43 -17
  23. package/dist/knowledge.js +48 -49
  24. package/dist/loop.js +182 -66
  25. package/dist/lsp/client.js +173 -0
  26. package/dist/lsp/framing.js +56 -0
  27. package/dist/lsp/index.js +138 -0
  28. package/dist/lsp/servers.js +82 -0
  29. package/dist/mcp-server.js +244 -0
  30. package/dist/mcp.js +184 -29
  31. package/dist/memory-store.js +559 -0
  32. package/dist/memory.js +143 -29
  33. package/dist/orchestrate.js +150 -0
  34. package/dist/providers/codex.js +2 -2
  35. package/dist/providers/keys.js +3 -2
  36. package/dist/providers/registry.js +133 -1
  37. package/dist/repomap.js +93 -0
  38. package/dist/search/chunk.js +158 -0
  39. package/dist/search/embed-store.js +187 -0
  40. package/dist/search/engine.js +203 -0
  41. package/dist/search/fuse.js +35 -0
  42. package/dist/search/index-core.js +187 -0
  43. package/dist/search/indexer.js +241 -0
  44. package/dist/search/store.js +77 -0
  45. package/dist/session.js +42 -8
  46. package/dist/skill-install.js +10 -10
  47. package/dist/skills.js +12 -9
  48. package/dist/summarize.js +31 -0
  49. package/dist/tools/bash.js +21 -2
  50. package/dist/tools/diagnostics.js +41 -0
  51. package/dist/tools/edit.js +29 -7
  52. package/dist/tools/index.js +8 -1
  53. package/dist/tools/list.js +7 -2
  54. package/dist/tools/permission.js +90 -9
  55. package/dist/tools/read.js +23 -4
  56. package/dist/tools/remember.js +1 -1
  57. package/dist/tools/sandbox.js +61 -0
  58. package/dist/tools/search.js +105 -4
  59. package/dist/tools/task.js +195 -29
  60. package/dist/tools/timeout.js +35 -0
  61. package/dist/tools/util.js +10 -0
  62. package/dist/tools/write.js +6 -4
  63. package/dist/trust.js +89 -0
  64. package/dist/ui/app.js +218 -27
  65. package/dist/ui/banner.js +4 -9
  66. package/dist/ui/history.js +30 -0
  67. package/dist/ui/mentions.js +44 -0
  68. package/dist/ui/setup.js +6 -5
  69. package/dist/ui/useEditor.js +83 -0
  70. package/dist/update.js +114 -0
  71. package/dist/worktree.js +173 -0
  72. package/package.json +11 -5
  73. package/scripts/postinstall.mjs +33 -0
  74. package/second-brain/.agents/_Index.md +30 -0
  75. package/second-brain/.agents/skills/_Index.md +30 -0
  76. package/second-brain/.agents/workflows/_Index.md +30 -0
  77. package/second-brain/AGENTS.md +4 -4
  78. package/second-brain/Acceptance/_Index.md +30 -0
  79. package/second-brain/Acceptance/golden-case-template.md +39 -0
  80. package/second-brain/Areas/_Index.md +30 -0
  81. package/second-brain/Bugs/System-OS/_Index.md +30 -0
  82. package/second-brain/Bugs/_Index.md +30 -0
  83. package/second-brain/CLAUDE.md +4 -1
  84. package/second-brain/Checklists/_Index.md +30 -0
  85. package/second-brain/Checklists/preflight-postflight-template.md +29 -0
  86. package/second-brain/Distillations/_Index.md +30 -0
  87. package/second-brain/Entities/_Index.md +30 -0
  88. package/second-brain/Entities/entity-template.md +33 -0
  89. package/second-brain/Evals/_Index.md +30 -0
  90. package/second-brain/Evals/correction-pairs.md +24 -0
  91. package/second-brain/Evals/failure-taxonomy.md +24 -0
  92. package/second-brain/Evals/golden-set.md +25 -0
  93. package/second-brain/Evals/quality-ledger.md +23 -0
  94. package/second-brain/Evals/self-eval-rubric.md +23 -0
  95. package/second-brain/GEMINI.md +4 -4
  96. package/second-brain/Goals/_Index.md +30 -0
  97. package/second-brain/Handoffs/_Index.md +30 -0
  98. package/second-brain/Home.md +7 -0
  99. package/second-brain/Intake/Raw Sources/_Index.md +30 -0
  100. package/second-brain/Intake/_Index.md +30 -0
  101. package/second-brain/Intake/_Quarantine/_Index.md +30 -0
  102. package/second-brain/Learning/_Index.md +30 -0
  103. package/second-brain/Playbooks/_Index.md +30 -0
  104. package/second-brain/Playbooks/playbook-template.md +23 -0
  105. package/second-brain/Projects/_Index.md +30 -0
  106. package/second-brain/Prompts/_Index.md +30 -0
  107. package/second-brain/README.md +2 -1
  108. package/second-brain/Research/_Index.md +30 -0
  109. package/second-brain/Retrospectives/_Index.md +30 -0
  110. package/second-brain/Reviews/_Index.md +30 -0
  111. package/second-brain/Runbooks/_Index.md +30 -0
  112. package/second-brain/Runbooks/eval-loop.md +24 -0
  113. package/second-brain/Sessions/_Index.md +30 -0
  114. package/second-brain/Shared/AI-Context-Index.md +20 -0
  115. package/second-brain/Shared/AI-Threads/_Index.md +30 -0
  116. package/second-brain/Shared/Archive/_Index.md +30 -0
  117. package/second-brain/Shared/Assets/_Index.md +30 -0
  118. package/second-brain/Shared/Context-Packs/_Index.md +30 -0
  119. package/second-brain/Shared/Context7-Docs/_Index.md +30 -0
  120. package/second-brain/Shared/Coordination/NOW.md +28 -0
  121. package/second-brain/Shared/Coordination/_Index.md +30 -0
  122. package/second-brain/Shared/Coordination/agent-registry.md +24 -0
  123. package/second-brain/Shared/Coordination/task-board/_Index.md +30 -0
  124. package/second-brain/Shared/Coordination/task-board/task-template.md +43 -0
  125. package/second-brain/Shared/Coordination/task-board.md +32 -0
  126. package/second-brain/Shared/Core-Facts/_Index.md +30 -0
  127. package/second-brain/Shared/Decision-Memory/_Index.md +30 -0
  128. package/second-brain/Shared/Glossary/_Index.md +30 -0
  129. package/second-brain/Shared/Memory-Inbox/_Index.md +30 -0
  130. package/second-brain/Shared/Operating-State/_Index.md +30 -0
  131. package/second-brain/Shared/Prompting/_Index.md +30 -0
  132. package/second-brain/Shared/Provenance/_Index.md +30 -0
  133. package/second-brain/Shared/Rules/_Index.md +30 -0
  134. package/second-brain/Shared/Rules/contextual-note-rule.md +30 -0
  135. package/second-brain/Shared/Rules/frontmatter-standard.md +10 -0
  136. package/second-brain/Shared/Rules/memory-write-protocol.md +28 -0
  137. package/second-brain/Shared/Rules/procedural-runbook-header.md +40 -0
  138. package/second-brain/Shared/Rules/review-and-staleness-policy.md +22 -0
  139. package/second-brain/Shared/Rules/rules-formatting.md +34 -0
  140. package/second-brain/Shared/Scripts/_Index.md +30 -0
  141. package/second-brain/Shared/Scripts-Archive/_Index.md +30 -0
  142. package/second-brain/Shared/Tech-Standards/_Index.md +30 -0
  143. package/second-brain/Shared/Tech-Standards/verification-standard.md +40 -0
  144. package/second-brain/Shared/User-Memory/_Index.md +30 -0
  145. package/second-brain/Shared/User-Persona/_Index.md +30 -0
  146. package/second-brain/Shared/User-Persona/owner-profile.md +25 -0
  147. package/second-brain/Shared/Working-Memory/_Index.md +30 -0
  148. package/second-brain/Shared/_Index.md +30 -0
  149. package/second-brain/Shared/mcp-servers/_Index.md +30 -0
  150. package/second-brain/Skills/_Index.md +30 -0
  151. package/second-brain/Templates/_Index.md +30 -0
  152. package/second-brain/Templates/bug.md +2 -0
  153. package/second-brain/Templates/handoff.md +2 -0
  154. package/second-brain/Templates/session.md +2 -0
  155. package/second-brain/Tools/_Index.md +30 -0
  156. package/second-brain/Traces/_Index.md +30 -0
  157. package/second-brain/Vault Structure Map.md +33 -1
  158. package/second-brain/copilot/_Index.md +30 -0
  159. package/skills/audit-license-compliance/SKILL.md +117 -0
  160. package/skills/author-codemod/SKILL.md +110 -0
  161. package/skills/build-audit-logging/SKILL.md +112 -0
  162. package/skills/build-cdc-streaming-pipeline/SKILL.md +123 -0
  163. package/skills/build-cli-tool/SKILL.md +108 -0
  164. package/skills/build-data-table/SKILL.md +141 -0
  165. package/skills/build-native-mobile-ui/SKILL.md +154 -0
  166. package/skills/build-offline-first-sync/SKILL.md +118 -0
  167. package/skills/build-realtime-channel/SKILL.md +122 -0
  168. package/skills/build-vector-search/SKILL.md +131 -0
  169. package/skills/compose-local-dev-stack/SKILL.md +149 -0
  170. package/skills/configure-bundler-build/SKILL.md +166 -0
  171. package/skills/configure-dns-tls/SKILL.md +142 -0
  172. package/skills/configure-reverse-proxy-lb/SKILL.md +129 -0
  173. package/skills/configure-security-headers-csp/SKILL.md +122 -0
  174. package/skills/contract-testing/SKILL.md +140 -0
  175. package/skills/datetime-timezone-correctness/SKILL.md +125 -0
  176. package/skills/debug-ci-pipeline-failure/SKILL.md +134 -0
  177. package/skills/debug-flaky-tests/SKILL.md +128 -0
  178. package/skills/defend-llm-prompt-injection/SKILL.md +110 -0
  179. package/skills/deliver-webhooks/SKILL.md +116 -0
  180. package/skills/design-api-pagination/SKILL.md +144 -0
  181. package/skills/design-authorization-model/SKILL.md +119 -0
  182. package/skills/design-backup-dr-recovery/SKILL.md +113 -0
  183. package/skills/design-event-sourcing-cqrs/SKILL.md +143 -0
  184. package/skills/design-multi-tenancy/SKILL.md +100 -0
  185. package/skills/design-protobuf-grpc-service/SKILL.md +146 -0
  186. package/skills/design-relational-schema/SKILL.md +129 -0
  187. package/skills/design-search-index-infra/SKILL.md +151 -0
  188. package/skills/design-state-machine/SKILL.md +108 -0
  189. package/skills/design-token-system/SKILL.md +109 -0
  190. package/skills/distributed-locks-leases/SKILL.md +120 -0
  191. package/skills/encrypt-sensitive-data/SKILL.md +148 -0
  192. package/skills/feature-flags-rollout/SKILL.md +130 -0
  193. package/skills/file-upload-object-storage/SKILL.md +107 -0
  194. package/skills/fuzz-dynamic-security-test/SKILL.md +111 -0
  195. package/skills/harden-llm-app-reliability/SKILL.md +126 -0
  196. package/skills/i18n-localization-setup/SKILL.md +113 -0
  197. package/skills/idempotency-keys/SKILL.md +107 -0
  198. package/skills/implement-push-notifications/SKILL.md +142 -0
  199. package/skills/ingest-webhook-secure/SKILL.md +120 -0
  200. package/skills/integrate-oauth-oidc/SKILL.md +126 -0
  201. package/skills/load-stress-test/SKILL.md +129 -0
  202. package/skills/map-privacy-data-gdpr/SKILL.md +146 -0
  203. package/skills/model-nosql-data/SKILL.md +118 -0
  204. package/skills/money-decimal-arithmetic/SKILL.md +123 -0
  205. package/skills/monitor-ml-drift/SKILL.md +109 -0
  206. package/skills/numeric-precision-units/SKILL.md +144 -0
  207. package/skills/optimize-llm-cost-latency/SKILL.md +103 -0
  208. package/skills/optimize-react-rerenders/SKILL.md +124 -0
  209. package/skills/orchestrate-agent-workflow/SKILL.md +100 -0
  210. package/skills/payments-billing-integration/SKILL.md +114 -0
  211. package/skills/pin-toolchain-versions/SKILL.md +116 -0
  212. package/skills/plan-strangler-migration/SKILL.md +95 -0
  213. package/skills/property-based-testing/SKILL.md +108 -0
  214. package/skills/publish-package-registry/SKILL.md +130 -0
  215. package/skills/recover-git-state/SKILL.md +119 -0
  216. package/skills/remediate-web-vulnerabilities/SKILL.md +125 -0
  217. package/skills/resilience-timeouts-retries/SKILL.md +104 -0
  218. package/skills/resolve-merge-rebase-conflict/SKILL.md +97 -0
  219. package/skills/rewrite-git-history/SKILL.md +109 -0
  220. package/skills/scaffold-cross-platform-app/SKILL.md +137 -0
  221. package/skills/schema-evolution-compatibility/SKILL.md +121 -0
  222. package/skills/send-transactional-email/SKILL.md +126 -0
  223. package/skills/serve-deploy-ml-model/SKILL.md +107 -0
  224. package/skills/setup-cdn-edge-waf/SKILL.md +107 -0
  225. package/skills/setup-devcontainer-env/SKILL.md +131 -0
  226. package/skills/setup-lint-format-precommit/SKILL.md +140 -0
  227. package/skills/setup-monorepo-tooling/SKILL.md +125 -0
  228. package/skills/ship-mobile-app-store-release/SKILL.md +137 -0
  229. package/skills/structured-output-llm/SKILL.md +86 -0
  230. package/skills/supply-chain-sbom-provenance/SKILL.md +120 -0
  231. package/skills/test-data-factories/SKILL.md +158 -0
  232. package/skills/threat-model-stride/SKILL.md +123 -0
  233. package/skills/train-evaluate-ml-model/SKILL.md +109 -0
  234. package/skills/unicode-text-correctness/SKILL.md +109 -0
  235. package/skills/visual-regression-testing/SKILL.md +120 -0
@@ -0,0 +1,144 @@
1
+ ---
2
+ name: numeric-precision-units
3
+ description: Prevents numeric-precision and units defects by enforcing epsilon/ULP/relative float comparison, Kahan/Welford stable accumulation, NaN/Inf and div-by-zero guards, checked/saturating integer arithmetic, lossless int64/decimal transport across JSON/JS/DB boundaries, and explicit unit-typed conversions with consistent rounding.
4
+ when_to_use: Code does scientific/statistical math, accumulates many floats, compares floats with ==, converts units (metric/imperial, time, data sizes, angles), or moves large integers/decimals across JSON/JS/DB/language boundaries; or bugs involve flaky float equality, NaN/Inf, silent overflow, or lost int64→double precision. Distinct from money-decimal-arithmetic (monetary rounding/allocation correctness) and validate-data-quality (schema/null/range checks).
5
+ ---
6
+
7
+ ## When to Use
8
+
9
+ Reach for this skill when the defect is about **the number itself** — its representation, precision, or unit — not its monetary rounding, schema, or business validity:
10
+
11
+ - "These two floats are equal but `a == b` returns false" / flaky test on a computed total
12
+ - "Summing a million values gives a different answer depending on order"
13
+ - "Mean/variance is wrong / NaN on large or near-equal data"
14
+ - "My int64 ID comes back rounded after a round-trip through JSON/JS"
15
+ - "A big integer turned into `1.0000000000000002e18` in the browser / spreadsheet"
16
+ - "Counter wrapped to a negative number" / `i32` overflow / cast truncated a value
17
+ - "We mixed meters and feet / ms and seconds / radians and degrees / KB(1000) and KiB(1024)"
18
+ - Division by a value that can be zero; `0.1 + 0.2 != 0.3`; signed-zero or `-0.0` surprises
19
+
20
+ NOT this skill:
21
+ - Monetary correctness — cents/`Decimal`, banker's rounding, splitting a charge so it sums exactly, FX → **money-decimal-arithmetic** (this skill keeps money *out* of binary float and intact across boundaries; that one does the rounding/allocation math)
22
+ - Duration/clock precision, monotonic vs wall-clock, leap seconds, DST math → **datetime-timezone-correctness** (this skill stores time as an integer; that one interprets it)
23
+ - Checking a field is present / in range / right type at ingest → **validate-data-quality**
24
+ - Configuring the compiler/linter to forbid implicit numeric coercion (tsconfig, mypy, clippy) → **type-safety-strict**
25
+ - Choosing `NUMERIC` vs `BIGINT` column types for a *schema migration* → **db-migration-safety**
26
+ - Declaring the wire shape of a number in an API (string vs int64 in the contract) → **rest-graphql-contract**
27
+ - Writing the test harness/property-test scaffolding itself → **write-tests**
28
+ - Making a `SUM` aggregate query fast → **optimize-sql-query**
29
+
30
+ ## Steps
31
+
32
+ 1. **Decide the representation first — float is a default, not a law.**
33
+
34
+ | Domain | Use | Never |
35
+ |---|---|---|
36
+ | IDs, counts, timestamps (ns/ms) | integer (`int64`) | `double` (loses precision > 2^53) |
37
+ | Physical/scientific measurement | `float64` (`double`) | `float32` unless memory-bound and tolerance allows |
38
+ | Exact fractions / ratios | rational type or scaled integer | float |
39
+ | Probabilities, weights, signals | `float64` | `float32` |
40
+ | Money / currency | → defer to **money-decimal-arithmetic** | binary `float`/`double` |
41
+
42
+ Rule: if two values must compare *exactly equal*, they must not be binary floats.
43
+
44
+ 2. **Never `==` floats. Pick the tolerance by scale.** Absolute epsilon fails for large magnitudes; relative fails near zero. Use a combined check:
45
+
46
+ ```python
47
+ def close(a, b, rel=1e-9, abs_tol=1e-12):
48
+ if a == b: # exact / both inf same sign
49
+ return True
50
+ if math.isnan(a) or math.isnan(b):
51
+ return False # NaN is never close to anything
52
+ return abs(a - b) <= max(rel * max(abs(a), abs(b)), abs_tol)
53
+ ```
54
+ - Library defaults: Python `math.isclose(a, b, rel_tol=1e-9)`, NumPy `np.isclose`/`allclose`, Rust `approx::relative_eq!`, JS — write the above (no stdlib equivalent).
55
+ - **Near zero, relative tolerance collapses** — that's why `abs_tol` exists; set it to the smallest difference you consider zero in your domain.
56
+ - ULP comparison (`a` and `b` within N representable steps) only for low-level kernels where you control rounding mode — overkill for app code; use relative+abs.
57
+
58
+ 3. **Accumulate stably — order and algorithm change the answer.** Naive left-to-right `sum` accumulates rounding error ∝ n·ε and suffers **catastrophic cancellation** when subtracting near-equal large numbers.
59
+ - Summation: use **pairwise** (NumPy's `np.sum` already does this) or **Kahan/Neumaier compensated** summation for long running totals:
60
+ ```python
61
+ def kahan_sum(xs):
62
+ s = 0.0; c = 0.0 # c = running compensation
63
+ for x in xs:
64
+ y = x - c
65
+ t = s + y
66
+ c = (t - s) - y
67
+ s = t
68
+ return s
69
+ ```
70
+ - Mean/variance: **never** `sum(x²)/n - mean²` (cancellation → negative variance / NaN). Use **Welford's online** algorithm:
71
+ ```python
72
+ n = 0; mean = 0.0; M2 = 0.0
73
+ for x in data:
74
+ n += 1
75
+ d = x - mean; mean += d / n
76
+ M2 += d * (x - mean)
77
+ var = M2 / n # population; M2/(n-1) for sample
78
+ ```
79
+ - Sort ascending by magnitude before summing wildly different scales if you can't use compensated summation.
80
+
81
+ 4. **Guard special values at the source, not three layers downstream.**
82
+ - Before any `/`: reject or branch on a zero/near-zero divisor (`if abs(d) < abs_tol: raise/return sentinel`). Float `x/0.0` yields `±inf`/`nan` *silently*; integer `/0` traps/UB.
83
+ - Treat `NaN`/`Inf` as poison: one `NaN` propagates through every subsequent op and **every comparison with it is false** (including `nan == nan`). Validate inputs with `math.isfinite(x)` at boundaries; assert finiteness on outputs.
84
+ - `-0.0 == 0.0` is true but `1/-0.0 == -inf` while `1/0.0 == +inf`; normalize with `x + 0.0` or `x == 0.0 ? 0.0 : x` when sign of zero leaks into results.
85
+ - `log`/`sqrt`/`acos` domain: clamp inputs (`acos(min(1, max(-1, x)))`) — rounding can push a value to `1.0000000002` and yield `NaN`.
86
+
87
+ 5. **Integer arithmetic: assume it overflows, prove it doesn't.** Fixed-width ints wrap (C/Go/Rust release/Java) or trap (Rust debug) or silently promote (Python/JS bignum-ish). Choose explicit semantics:
88
+
89
+ | Need | Rust | Go/C | Java | Generic |
90
+ |---|---|---|---|---|
91
+ | Detect overflow | `checked_add` → `Option` | compare after op / `bits.Add64` | `Math.addExact` (throws) | check before/after |
92
+ | Clamp at bound | `saturating_add` | manual `min`/`max` | manual | manual |
93
+ | Intentional wrap | `wrapping_add` | default `+` | default `+` | mask |
94
+ - **Narrowing casts lose data silently**: `(int32)bigLong`, `i64 as i32`, `Number → Int32`. Range-check before narrowing; never cast a length/ID down.
95
+ - Multiplication overflows long before addition — `a*b` for two `int32` near 2^16 already wraps; widen to `int64` *before* multiplying.
96
+ - `INT_MIN / -1` and `-INT_MIN` overflow; `abs(INT_MIN)` is still negative.
97
+
98
+ 6. **Cross-boundary precision: the 2^53 trap is the #1 silent corruption.** JSON has one number type; JS `Number` is `float64` with exact integers only up to `2^53-1` (9007199254740991). Any `int64` above that **loses its low bits** the instant a JS/JSON parser touches it — no error.
99
+ - **Send large ints and exact decimals as JSON strings.** Contract: `{"id": "9223372036854775807", "ratio": "12345.6789"}`. Parse to `BigInt`/`int64`/`Decimal` explicitly on each side.
100
+ - Guard in JS: `Number.isSafeInteger(x)` before trusting any integer; use `BigInt` + a string-aware parser (`json-bigint`) when you can't change the wire format.
101
+ - DB: `DOUBLE`/`FLOAT` columns are binary — IDs and exact decimals go in `NUMERIC(p,s)`/`DECIMAL`/`BIGINT`. Read them through a driver path that returns `Decimal`/string, not float (many drivers default to float for `NUMERIC` — configure it off).
102
+ - Language interop (protobuf/Thrift/FFI): `int64`→language `long`/`BigInt`, never `double`; protobuf JSON mapping *already* encodes `int64` as string — keep it.
103
+
104
+ 7. **Units: make the unit part of the type or the name — no bare numbers cross a function boundary.**
105
+ - Suffix every quantity: `timeout_ms`, `dist_m`, `angle_rad`, `size_bytes`, `temp_c`. A parameter named `timeout` is a bug waiting to happen.
106
+ - Convert through one explicit factor table; round **once, at the conversion**, to the target's precision — don't let conversions compound. Conventions that bite:
107
+
108
+ | Domain | Trap | Rule |
109
+ |---|---|---|
110
+ | Data size | KB=1000 vs KiB=1024 | use IEC (`KiB/MiB`) for binary; label which |
111
+ | Angle | trig functions take **radians** | convert `deg * π/180` at the edge |
112
+ | Temperature | C/F is **affine** (offset), not a ratio | `F = C*9/5 + 32`, never `*9/5` alone |
113
+ | Time | ms vs s vs ns | store epoch as int ns/ms; never float seconds |
114
+ | Imperial | 1 mi = 1609.344 m (exact) | keep full-precision factors, round at end |
115
+ - Dimensional consistency: only add/subtract same-unit quantities; multiply/divide changes the dimension (m/s · s = m). If a units library exists (`pint`, `uom`, `js-quantities`), use it; otherwise centralize factors in one module and unit-test round-trips.
116
+
117
+ ## Common Errors
118
+
119
+ - **`if x == 0.1 + 0.2`** — false; `0.1+0.2 == 0.30000000000000004`. Use a tolerance compare (step 2).
120
+ - **`abs(a-b) < 1e-9` as a universal epsilon** — passes for tiny numbers, fails for `1e12`. Scale tolerance relatively (step 2).
121
+ - **`sum(sq)/n - mean**2` for variance** — catastrophic cancellation gives negative/NaN variance. Use Welford (step 3).
122
+ - **`json.parse` of `{"id": 9007199254740993}` in JS** — silently becomes `...992`. Send IDs as strings; `Number.isSafeInteger` guards.
123
+ - **Storing an int64 ID in `FLOAT`/`double`** — loses the low bits above 2^53 on round-trip. Use `BIGINT`/`NUMERIC` and an integer/string driver path (step 6).
124
+ - **`(int) (a * b)` with two int32s** — overflows before the cast even runs. Widen to int64 before multiplying (step 5).
125
+ - **`while (n != target)` on a float loop counter** — may never hit `target` exactly; loop forever. Iterate with an integer index, compute the float.
126
+ - **`nan == nan` to detect NaN** — always false. Use `isnan`/`isFinite`; sort/min/max with NaN present is also undefined.
127
+ - **`Math.sqrt(neg)` / `acos(1.0000001)`** — returns `NaN` from rounding overshoot. Clamp domain before the call (step 4).
128
+ - **Passing `deg` to `Math.sin`** — silently wrong, no error. Sin takes radians; convert at the boundary.
129
+ - **`°C → °F` as a pure scale (`*9/5`)** — drops the `+32` offset; temperature conversions are affine, not linear.
130
+ - **Mixing 1000- and 1024-based sizes** — "5 GB" disk vs "5 GiB" RAM differ by ~7%. Label and use IEC binary units.
131
+ - **Casting a `length`/`count`/`id` to a narrower int** — truncates above the bound with no error. Range-check or keep it wide.
132
+
133
+ ## Verify
134
+
135
+ 1. **Float equality:** every float comparison in the diff uses a tolerance helper (or compares a quantity that is provably integer/decimal). `grep -nE '==|!=' ` over float paths returns no bare float `==`.
136
+ 2. **Accumulation:** sum a 1e6-element array forwards vs reversed vs Kahan — Kahan matches a higher-precision (`Decimal`/`float128`) reference within tolerance; naive may not. Variance of near-equal large values is ≥ 0 and finite (Welford), not NaN.
137
+ 3. **Special values:** feed `0`, `-0.0`, `NaN`, `+Inf`, `-Inf`, and a near-zero divisor through each public function — none crash silently; divide-by-zero is rejected or returns a documented sentinel; outputs pass an `isfinite` assertion.
138
+ 4. **Integer bounds:** test at `MAX`, `MAX-1`, `MIN`, `0`, `MIN/-1` for every fixed-width arithmetic op — overflow is detected/saturated/intentionally-wrapped per the chosen semantics, never an undocumented wrap. Narrowing casts reject or clamp out-of-range input.
139
+ 5. **Boundary round-trip:** serialize the value `9223372036854775807` (`int64` max) and a `12345.6789` decimal to JSON, parse on the other side (especially JS) → byte-identical value restored. `Number.isSafeInteger` is checked on any JS integer path.
140
+ 6. **DB round-trip:** write `NUMERIC(38,9)` max-precision values and an int64-max ID, read back → equal as `Decimal`/integer/string (not float-coerced). No ID or exact-decimal column is `FLOAT`/`DOUBLE`.
141
+ 7. **Units:** round-trip every conversion (`m→ft→m`, `C→F→C`, `KiB→bytes→KiB`) returns the original within rounding tolerance; trig inputs are radians; affine conversions keep their offset; mismatched-unit add/subtract is impossible (typed) or covered by a failing-on-mix test.
142
+ 8. **Property tests:** generators include extremes (`±MAX`, `±0.0`, `NaN`, `Inf`, subnormals, near-epsilon pairs, overflow boundaries) — not just typical mid-range values.
143
+
144
+ Done = no bare float `==`, no ID/exact-decimal in binary float, every cross-boundary int64/decimal survives a JSON/JS/DB round-trip bit-for-bit, all fixed-width integer ops have defined overflow behavior, divide-by-zero and NaN/Inf are guarded at the boundary, and every unit-bearing quantity is named/typed with its unit and round-trips through conversion within tolerance.
@@ -0,0 +1,103 @@
1
+ ---
2
+ name: optimize-llm-cost-latency
3
+ description: Cuts LLM token cost and tail latency via context trimming, provider prompt caching on stable prefixes, model tiering/routing, semantic answer caching, batch APIs, and streaming, proving a measured before/after on cost-per-request and p50/p95 at equal output quality.
4
+ when_to_use: An LLM feature is too slow or too expensive, or usage is scaling and the bill/latency matters. Distinct from prompt-engineering (output quality), rag-pipeline (retrieval quality), and harden-llm-app-reliability (timeouts/retries/fallback).
5
+ ---
6
+
7
+ ## When to Use
8
+
9
+ Reach for this skill when the problem is **spend or speed**, not what the model says:
10
+
11
+ - "Our LLM bill is $X/month and climbing — cut it"
12
+ - "This call takes 8s; make it feel fast"
13
+ - "Token usage per request is huge — trim it"
14
+ - "We send the same 6k-token system prompt on every call"
15
+ - "We re-answer near-identical questions all day"
16
+ - "We have a nightly batch of 50k classifications — too slow/expensive"
17
+
18
+ NOT this skill:
19
+ - Making the *output* better/more correct/structured → prompt-engineering
20
+ - Improving *what gets retrieved* into context (chunking/embeddings/reranking) → rag-pipeline
21
+ - Timeouts, retries, fallback models, circuit breakers when a call fails → harden-llm-app-reliability
22
+ - Blocking malicious instructions in inputs/tools → defend-llm-prompt-injection
23
+ - Generic HTTP/data response caching unrelated to LLM token economics → caching-strategy
24
+ - Cutting compute/storage/egress spend on the infra bill → cloud-cost-optimize
25
+
26
+ ## Steps
27
+
28
+ 1. **Measure first — no optimization without a baseline.** Log per request: `input_tokens`, `output_tokens`, model id, end-to-end latency, and time-to-first-token (TTFT). Get exact counts from the provider's usage object or a count-tokens endpoint — never estimate from `len(text)/4` for billing decisions. Compute `$/req` from current per-token prices and aggregate **p50/p95** (means hide the tail that users actually feel). You cannot claim a win without these numbers before and after.
29
+
30
+ ```python
31
+ # cost per request from the provider usage object (Anthropic-style)
32
+ u = resp.usage # input_tokens / output_tokens / cache_creation_input_tokens / cache_read_input_tokens
33
+ cost = (u.input_tokens*IN + u.output_tokens*OUT
34
+ + u.cache_creation_input_tokens*CACHE_WRITE # ~1.25x input
35
+ + u.cache_read_input_tokens*CACHE_READ) / 1e6 # ~0.1x input
36
+ ```
37
+
38
+ 2. **Apply levers in ROI order — cheapest, highest-impact first.** Do not jump to a fancy router before you've capped output and cached the prefix.
39
+
40
+ | Lever | Typical win | Effort | Use when |
41
+ |---|---|---|---|
42
+ | Cap `max_tokens` + stop sequences | 10–40% output cost | trivial | output runs longer than needed |
43
+ | Context diet (trim history, drop dead few-shot) | 20–60% input cost | low | long/growing prompts |
44
+ | **Prompt caching** (cache stable prefix) | **up to 90% on the cached portion**, lower TTFT | low | long fixed system prompt / RAG docs reused across calls |
45
+ | Streaming | TTFT 5–10x better (perceived) | low | user-facing chat/UI |
46
+ | Model tiering/routing | 50–95% on easy traffic | medium | mixed easy/hard requests |
47
+ | Semantic cache | ~100% on a cache hit | medium | repeated/near-duplicate queries |
48
+ | Batch API | ~50% on $ | medium | offline, non-urgent jobs |
49
+
50
+ 3. **Context diet — shrink input before you optimize how you send it.** Every input token is paid on every call. (a) **Cap history**: keep the last N turns; once over a token budget, **summarize** older turns into a compact running summary instead of carrying raw transcript. (b) **Cut dead few-shot examples**: drop each example and re-run the eval — keep only those that move accuracy. Most prompts carry 3–4 examples that earn nothing. (c) Strip boilerplate, redundant instructions, and verbose tool schemas. Re-measure tokens after each cut.
51
+
52
+ 4. **Prompt caching — usually the single biggest win.** Put the **stable, large** content first (system prompt, tool definitions, RAG documents, long instructions) and the **variable** part (the user's actual turn) last, then mark the boundary with the provider's cache control so the prefix is reused across requests. Order matters: the cache keys on an exact prefix match, so one moving token near the top busts the whole cache.
53
+
54
+ ```python
55
+ # Anthropic: cache_control breakpoint on the stable prefix; variable user msg stays uncached
56
+ system=[{"type":"text","text":BIG_STABLE_PROMPT,"cache_control":{"type":"ephemeral"}}]
57
+ # OpenAI: automatic prefix caching — just keep the prefix byte-identical and put it first
58
+ ```
59
+ Cache reads are ~10% of input price; writes ~25% more than input — so caching pays off once a prefix is reused even a handful of times within its TTL (~5 min ephemeral). Verify hits via `cache_read_input_tokens > 0`.
60
+
61
+ 5. **Model tiering + routing — send easy work to the cheap model.** Default the bulk of traffic to a small/fast model; escalate only when needed. Pick a **deterministic router** over an LLM-classifier router when you can (no extra call, no extra latency): route on cheap signals — input length, required output schema, presence of code/math, retrieval confidence, or an explicit difficulty flag. Use a tiny classifier model only when rules can't separate easy from hard. Always escalate on low-confidence or a validation failure rather than returning a bad cheap answer.
62
+
63
+ ```python
64
+ def route(req):
65
+ if req.tokens < 800 and not req.needs_reasoning: return SMALL # ~1/10 the cost
66
+ if req.needs_long_reasoning or req.high_stakes: return LARGE
67
+ return MID
68
+ # escalate: if small-model output fails schema/confidence check -> retry once on LARGE
69
+ ```
70
+
71
+ 6. **Semantic cache for repeated/near-duplicate queries.** Embed the normalized query, look it up in a vector store; on cosine similarity ≥ ~0.95 return the cached answer (0 LLM tokens). Set a conservative threshold (too low → wrong cached answers), scope the key by anything that changes the answer (user/tenant/locale/tool-version), and TTL it so stale facts expire. Bypass the cache for personalized or time-sensitive responses. This is *exact/near-exact answer reuse*, layered on top of prompt caching (which reuses the prefix, not the answer).
72
+
73
+ 7. **Batch the offline work.** Anything not blocking a user — nightly classification, backfills, evals, bulk summarization — goes through the provider **Batch API** (Anthropic Message Batches / OpenAI Batch) for ~50% off, accepting up to ~24h turnaround. Never batch interactive requests.
74
+
75
+ 8. **Stream to cut *perceived* latency.** For any user-facing surface, stream tokens (`stream=True` / SSE) so first text shows in a few hundred ms instead of after the full generation. This doesn't reduce cost or total wall-clock, but it collapses TTFT — often the only latency users perceive. Combine with caching: a cached prefix also lowers real TTFT.
76
+
77
+ 9. **Re-measure and prove equal quality.** Re-run the same metrics (step 1) after changes, and run your eval set to confirm output quality didn't regress (see Verify). A cost win that drops accuracy is not a win — report cost/latency **and** the quality delta together.
78
+
79
+ ## Common Errors
80
+
81
+ - **Optimizing without a baseline.** "Feels faster/cheaper" is not a number. Capture p50/p95 and $/req before touching anything, or you can't prove (or trust) the result.
82
+ - **Estimating tokens with `len/4`.** Fine for a rough guess, wrong for billing and for `max_tokens` budgeting. Use the provider usage object / count-tokens endpoint.
83
+ - **Cache-busting the prefix.** Putting a timestamp, request id, randomized example order, or the user message *before* the stable block invalidates the cache every call. Stable content first, byte-identical; variable content last.
84
+ - **Caching the wrong span.** Marking a tiny or rarely-reused prefix as cached pays the ~25% write premium for no reads. Cache large prefixes reused within the TTL; confirm with `cache_read_input_tokens`.
85
+ - **Semantic-cache threshold too loose.** A 0.85 similarity match returns a confidently wrong answer for a different question. Start ≥0.95, log near-miss hits, and never cache personalized/time-sensitive answers.
86
+ - **Unscoped cache key.** Caching by query text alone leaks one tenant/user/locale's answer to another. Include every dimension that changes the correct answer in the key.
87
+ - **Router that always escalates.** A misconfigured or overcautious router sends everything to the big model — you pay more *and* added a routing hop. Verify the cheap-model hit rate; if <50% on easy traffic, the rules are wrong.
88
+ - **Unbounded `max_tokens`.** Leaving it at the model max lets a runaway generation bill thousands of output tokens. Set it to the real ceiling and add stop sequences.
89
+ - **Batching interactive traffic.** Batch APIs trade latency for price; a user waiting on a 24h-window response is a broken product. Batch only offline work.
90
+ - **Streaming to a non-interactive consumer.** Streaming into code that just `.join()`s the whole thing adds complexity with zero benefit — and can hide errors that arrive mid-stream. Stream only where a human sees partial output.
91
+ - **Trimming context until quality silently drops.** Aggressive history truncation or cutting a load-bearing few-shot example tanks accuracy. Gate every cut behind the eval (step 9).
92
+
93
+ ## Verify
94
+
95
+ 1. **Baseline captured:** before/after table exists with `input_tokens`, `output_tokens`, `$/req`, p50, p95, and TTFT — real measured numbers, not estimates.
96
+ 2. **Cost dropped:** post-change `$/req` is measurably lower (state the %), computed from the provider usage object at current prices.
97
+ 3. **Latency dropped:** p95 (and TTFT for user-facing paths) improved; report the actual ms, not "feels faster."
98
+ 4. **Cache is hot:** `cache_read_input_tokens > 0` on repeat requests, and the measured hit rate is reported. A prompt cache that never reads is dead config.
99
+ 5. **Router behaves:** the cheap model handles the majority of easy traffic (hit-rate stated), and escalation fires on low-confidence/validation-fail (one logged example each).
100
+ 6. **Semantic cache is safe:** a deliberately *different* query below threshold does **not** return a cached answer; key scoping (tenant/locale) prevents cross-leak.
101
+ 7. **Quality held:** the eval set scores within tolerance of baseline (state the delta) — the cost/latency win did not regress accuracy. If it did, the change is rejected.
102
+
103
+ Done = a before/after table shows lower $/req and lower p50/p95 (with cache hit rate and cheap-model share stated), and the eval confirms output quality is unchanged at the new, cheaper, faster configuration.
@@ -0,0 +1,124 @@
1
+ ---
2
+ name: optimize-react-rerenders
3
+ description: Eliminates wasted React re-renders by measuring first then fixing — profile with the React DevTools Profiler (flamegraph + "why did this render") and why-did-you-render to find the actual offenders, then apply the right fix: stable references (hoist constants, useCallback/useMemo only where a referentially-equal prop/dep actually matters), correct list keys (stable id, never index), React.memo with a custom comparator on genuinely-hot leaf components, context splitting + selector subscriptions (useSyncExternalStore / Zustand / use-context-selector) to stop whole-tree re-renders, derive-don't-store to kill redundant state, and list virtualization (TanStack Virtual) for long lists — while knowing when NOT to memo (cheap renders, unstable deps, and the React 19 Compiler which auto-memoizes and makes most manual memo dead weight).
4
+ when_to_use: A React app re-renders too much — typing lags, a list re-renders every row on one change, the Profiler shows components rendering with unchanged props, or you're sprinkling useMemo/useCallback/React.memo and want to know what actually helps. Distinct from optimize-core-web-vitals (load/paint metrics — LCP/INP/CLS and asset/JS strategy, not render-count) and manage-client-server-state (data-fetching/caching architecture with TanStack Query; this skill fixes the render churn that fetched data triggers).
5
+ ---
6
+
7
+ ## When to Use
8
+
9
+ Reach for this skill when the problem is **too many React renders / render churn**, not load time, not data fetching:
10
+
11
+ - "Typing in this input lags / the whole form re-renders on every keystroke"
12
+ - "Changing one row re-renders the entire list of 500 items"
13
+ - "The Profiler shows components re-rendering even though their props didn't change"
14
+ - "I added `useMemo`/`useCallback`/`React.memo` everywhere and it's not faster (or slower)"
15
+ - "A context update re-renders half the tree"
16
+ - "Should I memoize this?" / "Is this `useMemo` worth it?" / "We're on React 19 — do I still need this?"
17
+ - "Long list scrolls slowly / mounts thousands of DOM nodes"
18
+
19
+ NOT this skill:
20
+ - Slow initial load, late paint, layout shift, or a red Lighthouse score (LCP/INP/CLS, image/font/JS-bundle strategy) → optimize-core-web-vitals (it owns paint/load metrics; this skill owns render *count*. Note: cutting re-renders during interaction also improves INP, but go there for the metric-driven workflow)
21
+ - Data fetching, cache invalidation, optimistic updates, SSR hydration, or picking a store (TanStack Query / Zustand / Redux) → manage-client-server-state (it architects *where state lives and how it's fetched*; this skill stops the renders that state changes cause)
22
+ - Building a new component's structure/props/a11y from scratch → build-react-component
23
+ - A big interactive grid feature (sorting/filtering/column resize/selection) as a unit → build-data-table (it builds the table; this skill makes its rows stop re-rendering)
24
+ - Backend/server query latency or a CPU profile of non-React code → performance-profiling
25
+ - Using the browser/Chrome DevTools to debug a runtime bug (state, network, errors) generally → debug-frontend-browser (this skill is the render-perf specialization of profiling)
26
+
27
+ ## Steps
28
+
29
+ 1. **Measure before you touch anything — never memoize on a hunch.** Manual memoization is a tradeoff (extra comparisons + cache memory); applied blindly it often makes things *slower* and always makes them harder to read. Get evidence first:
30
+
31
+ | Tool | What it tells you | How |
32
+ |---|---|---|
33
+ | **React DevTools Profiler** | which components rendered, how long, **why** | record an interaction → flamegraph + ranked chart |
34
+ | **"Highlight updates when components render"** (DevTools → ⚙ → Components) | visual flash on every render — instant "this re-renders on every keystroke" signal | toggle on, interact |
35
+ | **`why-did-you-render`** | logs the *exact prop/state/hook that changed* (and whether it was a deep-equal-but-referentially-different value) | dev-only, see step 2 |
36
+ | **React 19 `<Profiler onRender>`** / `performance.measure` | programmatic render timings in tests/CI | wrap a subtree |
37
+
38
+ In the Profiler, enable **"Record why each component rendered"** (⚙ → Profiler). Re-renders show a reason: *props changed*, *hooks changed*, *parent rendered*, *context changed*. That reason picks the fix below — don't guess.
39
+
40
+ 2. **Wire up `why-did-you-render` to catch referential-equality bugs (dev only).** It surfaces the classic "props are deep-equal but a new object/array/function identity every render" case that React.memo can't catch.
41
+ ```js
42
+ // wdyr.js — import FIRST, before React renders anything
43
+ import React from 'react';
44
+ if (process.env.NODE_ENV === 'development') {
45
+ const wdyr = require('@welldone-software/why-did-you-render');
46
+ wdyr(React, { trackAllPureComponents: true, collapseGroups: true });
47
+ }
48
+ ```
49
+ A log like *"props.style changed: ({}) → ({}) (different objects that are equal by value)"* means: hoist the literal or memoize the reference — not wrap the child in `memo`.
50
+
51
+ 3. **If you're on React 19, turn on the React Compiler FIRST — it auto-memoizes and makes most manual memo dead weight.** The compiler (`babel-plugin-react-compiler`, also in the Next.js / Vite plugin) memoizes components and hook values automatically at build time, so `useMemo`/`useCallback`/`React.memo` become largely redundant. Before hand-tuning:
52
+ - Install and enable it; run `npx react-compiler-healthcheck` to see how many components are compatible (it skips components that break the Rules of React — those are your real bugs to fix).
53
+ - **Don't mix strategies blindly:** keep code Rules-of-React-clean (no mutation of props/state, no conditional hooks) so the compiler can optimize. New manual `useMemo`/`useCallback` you add on top is usually noise. Lean on `<StrictMode>` to surface impurity.
54
+ - The compiler does **not** fix algorithmic problems (huge lists, O(n²) renders) — you still need virtualization (step 9) and correct keys (step 7). Treat the rest of these steps as either pre-19 work or the things the compiler can't do.
55
+
56
+ 4. **Kill unstable references at the source — this is the #1 cause of "memo doesn't work".** `React.memo` and dep arrays compare by reference (`Object.is`). A new `{}`, `[]`, or arrow function created in render is a new identity every time, so it busts every downstream memo and effect. Fix the *source*, don't paper over it:
57
+
58
+ | Anti-pattern (new identity each render) | Fix |
59
+ |---|---|
60
+ | `<Child style={{ margin: 8 }} />` | hoist the object to a module-level `const` (it never changes) |
61
+ | `<Child onClick={() => doX(id)} />` passed to a memoized child | `useCallback(() => doX(id), [id])` |
62
+ | `const opts = { a, b }` then used in a dep array | `useMemo(() => ({ a, b }), [a, b])` |
63
+ | `data.filter(...)` computed inline each render into a memoized child | `useMemo(() => data.filter(...), [data])` |
64
+ | default prop `items = []` (new array each call) | hoist `const EMPTY = []` and default to it |
65
+
66
+ Static values (handlers with no closure deps, constant config) belong **outside the component** entirely — zero runtime cost.
67
+
68
+ 5. **Use `useCallback`/`useMemo` only where a referentially-equal value actually changes behavior — otherwise skip it.** They are not free: each stores a closure + dep array and runs an equality check every render. They earn their keep in exactly these cases — and nowhere else:
69
+ - The memoized value/callback is **a dependency of another hook** (`useEffect`, `useMemo`) where a changing identity would refire the effect.
70
+ - It's **passed as a prop to a `React.memo`'d child** (otherwise the child's memo is pointless).
71
+ - `useMemo` wraps a **genuinely expensive computation** (sort/filter of thousands, parse, heavy derive) — measure; "expensive" is rarely a `.map` over 20 items.
72
+
73
+ **Skip them when** the consumer isn't memoized, the deps change every render anyway (then the cache never hits — pure overhead), or the body is cheap. A `useCallback` whose result flows only into a non-memoized DOM `<button onClick>` does nothing useful.
74
+
75
+ 6. **`React.memo` only the genuinely-hot leaf, and give it the right comparator.** `memo` skips a re-render when props are shallow-equal. Apply it to a component that (a) renders often due to *parent* re-renders, (b) is expensive or numerous (list rows), and (c) gets **stable props** (you did step 4). Without stable props it's worse than nothing.
76
+ - Default shallow compare is correct most of the time. Provide a custom `areEqual(prev, next)` only for a known-shape prop where shallow misses (e.g. compare `prev.item.id === next.item.id && prev.selected === next.selected`) — and keep it cheaper than the render it saves.
77
+ - `memo` compares **props only** — it does **not** stop re-renders from internal `useState`/`useContext` changes. If the offender is context (step 8) or state, `memo` won't help.
78
+ ```jsx
79
+ const Row = memo(function Row({ item, onSelect }) { /* ... */ },
80
+ (a, b) => a.item.id === b.item.id && a.item.label === b.item.label && a.onSelect === b.onSelect);
81
+ ```
82
+
83
+ 7. **Fix list keys — wrong keys force remounts and break memoized rows.** Use a **stable, unique id** from the data (`item.id`), never the array index for any list that can reorder, insert, or delete. Index keys make React reuse the wrong DOM/state on reorder (lost input focus, wrong row highlighted) and defeat per-row `memo`. Don't use `Math.random()`/`uuid()` in render either — a new key every render remounts the row each time. Stable identity → React diffs in place → memoized rows actually skip.
84
+
85
+ 8. **Stop context from re-rendering the whole subtree — split it or use a selector.** Every consumer of a context re-renders whenever **any** field of the context value changes, even fields it doesn't read. Three escalating fixes:
86
+
87
+ | Technique | When | How |
88
+ |---|---|---|
89
+ | **Memoize the provider value** | value is `{}`/`[]` literal inline | `value={useMemo(() => ({ user, setUser }), [user])}` — without this *every* consumer re-renders every parent render |
90
+ | **Split contexts by change frequency** | one context mixes hot + cold data (e.g. `theme` + live `cursorPosition`) | separate providers; a component subscribes only to what it uses → high-frequency updates don't touch theme consumers |
91
+ | **Selector subscription** | consumers read different slices of a big store | `useSyncExternalStore` with a selector, `use-context-selector`, or a store lib (**Zustand**: `useStore(s => s.field)`, **Redux**: `useSelector`) — re-render only when the *selected* slice changes |
92
+
93
+ Splitting state-vs-dispatch contexts is a cheap classic win: a component that only dispatches never re-renders on state changes.
94
+
95
+ 9. **Virtualize long lists instead of memoizing 10,000 rows.** No amount of `memo` saves you from mounting thousands of DOM nodes. Render only the visible window (+overscan) with **TanStack Virtual** (`@tanstack/react-virtual`, headless, framework-agnostic) or `react-virtuoso`. It computes which rows intersect the viewport and absolutely-positions them; DOM stays ~constant regardless of dataset size. Combine with stable keys (step 7) and a memoized row. This is the fix when the *count* is the problem, not per-row work.
96
+
97
+ 10. **Derive, don't store — redundant state is redundant renders.** Anything computable from props/existing state during render should be **computed in render** (optionally `useMemo`'d), not held in its own `useState` synced via `useEffect`. The `useState`+`useEffect`-to-sync pattern adds an extra render every change and drifts out of sync. Likewise: lift state **down** (push it into the smallest component that needs it so updates don't re-render siblings), and split a god-component so a chatty piece of state isn't wired into a large subtree. Fewer state cells in fewer places = fewer renders.
98
+
99
+ 11. **Don't over-memoize — premature memoization has a real cost.** Each `memo`/`useMemo`/`useCallback` adds an equality check + retained closure + cognitive load, and a wrong dep array becomes a stale-closure bug. Rules of thumb: leave cheap components un-memoized; never wrap a component whose props are unstable (fix the props instead); never memoize purely to "be safe." On React 19, prefer the compiler over manual memo. Memoize when the Profiler shows a *measured* hot path — not before. Remove memos that the Profiler shows aren't being hit.
100
+
101
+ ## Common Errors
102
+
103
+ - **`React.memo` with unstable props.** Memo'd child still re-renders because a parent passes a fresh `{}`/`() => {}` each render. Fix: stabilize the prop at the source (step 4) — `memo` without stable props is pure overhead.
104
+ - **`useCallback`/`useMemo` feeding a non-memoized consumer.** A stable callback handed to a plain `<button>` or non-`memo` child changes nothing but adds overhead. Fix: only memoize values that cross into a `memo`'d child or another hook's deps.
105
+ - **Dep array that changes every render.** The memo never caches (cache miss every time) — all cost, no benefit. Fix: stabilize the deps too, or drop the memo.
106
+ - **Index as list key.** Reorders/inserts reuse the wrong DOM/state and break per-row memo; lost focus, wrong highlight. Fix: stable `item.id` key.
107
+ - **`Math.random()`/`uuid()` key in render.** Remounts every row every render — the opposite of memoization. Fix: derive a stable id once.
108
+ - **Inline object/array provider value.** `<Ctx.Provider value={{a,b}}>` re-renders every consumer on every parent render. Fix: `useMemo` the value, or split contexts.
109
+ - **One fat context for hot + cold data.** A 60fps field re-renders theme/auth consumers. Fix: split by update frequency; use a selector subscription.
110
+ - **`useState` mirrored from props via `useEffect`.** Extra render + drift. Fix: derive in render (`useMemo` if pricey).
111
+ - **Memoizing everything by default.** Slower and unreadable; stale-closure bugs from wrong deps. Fix: memoize measured hot paths only; on React 19 let the compiler do it.
112
+ - **Expecting `memo` to stop context/state re-renders.** `memo` compares props only. Fix: address the actual reason the Profiler reports (context → split/selector; state → derive/lift).
113
+
114
+ ## Verify
115
+
116
+ 1. **Profiler shows the offender gone:** record the same interaction before/after — the component that flashed/rendered "props changed (but equal)" or "parent rendered" no longer appears in the commit (or its render time drops). Keep the before flamegraph as proof.
117
+ 2. **`why-did-you-render` is silent on the fixed path:** no "different objects that are equal by value" logs for the props you stabilized.
118
+ 3. **Typing/interaction is smooth:** the input/list that lagged updates per keystroke without re-rendering unrelated siblings; "Highlight updates" flashes only the changed node.
119
+ 4. **One-row change → one row renders:** mutating a single list item re-renders that row only, not the whole list (visible in the Profiler ranked chart).
120
+ 5. **Context change is scoped:** updating a hot context field re-renders only its real consumers; theme/auth consumers stay dark.
121
+ 6. **Long list DOM is bounded:** the virtualized list mounts ~viewport+overscan rows, and node count stays roughly constant as the dataset grows from 100 → 10,000.
122
+ 7. **No memo is dead weight:** every remaining `memo`/`useMemo`/`useCallback` corresponds to a Profiler-confirmed hot path or a real dep/`memo`-prop boundary; the rest were removed. On React 19, `react-compiler-healthcheck` passes and manual memo is minimal.
123
+
124
+ Done = the measured re-render(s) the Profiler flagged are eliminated by the matching fix (stable refs, correct keys, context split/selector, derive-don't-store, or virtualization), every remaining manual memo is justified by evidence (or replaced by the React 19 compiler), and the before/after Profiler traces prove the churn is gone — not added overhead.
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: orchestrate-agent-workflow
3
+ description: Designs reliable multi-step LLM agent loops — tool-call orchestration, state/memory between steps, explicit stop conditions, per-step verification, retries/replanning, subagent decomposition, and budget/approval gates — so an agent finishes long tasks without drifting or looping forever.
4
+ when_to_use: Building an agent that plans and calls tools across multiple steps, not a single prompt-and-response. Distinct from agent-tool-mcp-builder (designing the individual tools/MCP server), prompt-engineering (single-prompt/structured-output design), and harden-llm-app-reliability (transport-level retry/timeout of one model call).
5
+ ---
6
+
7
+ ## When to Use
8
+
9
+ Reach for this skill when the work spans **multiple model→tool→observe turns** under one goal:
10
+
11
+ - "Build an agent that researches, then drafts, then files a PR / ticket"
12
+ - "My agent loops forever / repeats the same tool call / never decides it's done"
13
+ - "It drifts off-task halfway through and starts solving a different problem"
14
+ - "Split this big task across subagents and stitch the results back together"
15
+ - "Add a cost/step cap and a human-approval gate before it deletes / deploys / pays"
16
+
17
+ NOT this skill:
18
+ - Designing the tool schemas / error shapes / MCP server the agent calls → agent-tool-mcp-builder
19
+ - Writing or hardening a single prompt or its JSON/function-call contract → prompt-engineering
20
+ - Making *one* model call survive timeouts/429s/5xx (retry, backoff, circuit breaker) → harden-llm-app-reliability
21
+ - Cutting token cost / latency of the model calls themselves (caching, model routing) → optimize-llm-cost-latency
22
+ - Treating tool output / web content as untrusted input → defend-llm-prompt-injection
23
+
24
+ ## Steps
25
+
26
+ 1. **Pick the simplest topology that works — escalate only when forced.** Default down this ladder, not up.
27
+
28
+ | Pattern | Use when | Control flow | Failure mode to fear |
29
+ |---|---|---|---|
30
+ | **Single-agent tool loop** | One goal, <~15 steps, work fits one context | Model decides each next tool | Infinite loop / drift |
31
+ | **Code-orchestrated workflow (DAG)** | Steps + order are known *ahead of time* | Your code sequences; model fills each node | Rigid; can't adapt mid-run |
32
+ | **Multi-agent (planner + subagents)** | One context literally can't hold the work, or independent parallel branches | Orchestrator spawns sub-loops, merges summaries | Coordination cost, lost context at handoff |
33
+
34
+ Start with the loop. Move to a coded DAG the moment the step graph is fixed (cheaper, deterministic, testable). Reach for multi-agent **only** when a single context window can't hold the task — never for "it feels big."
35
+
36
+ 2. **Make the loop terminate — two independent kills.** Every loop needs BOTH a semantic stop and a hard cap. One alone is insufficient: the model's "I'm done" can be wrong, and a raw cap truncates good work.
37
+
38
+ ```python
39
+ MAX_STEPS, MAX_USD, t0 = 12, 0.50, time.time()
40
+ for step in range(MAX_STEPS): # hard cap (anti-infinite-loop)
41
+ msg = model(messages, tools)
42
+ if msg.stop_reason == "end_turn": # semantic stop: model emits final, no tool call
43
+ return msg.text
44
+ result = run_tool(msg.tool_call) # exactly one tool per turn keeps it auditable
45
+ messages += [msg, tool_result(result)]
46
+ if spent_usd() > MAX_USD or time.time()-t0 > 120: # budget / wall-clock guard
47
+ return escalate("budget exceeded", messages)
48
+ return escalate("max steps reached", messages) # NEVER silently return partial as success
49
+ ```
50
+ Also detect a **no-progress loop**: hash `(tool_name, args)`; if the same call repeats ≥2× with no state change, break and replan — don't wait for MAX_STEPS.
51
+
52
+ 3. **Carry state in an explicit scratchpad — not the raw message list.** The growing transcript is not memory; it's noise that costs tokens and dilutes the goal. Keep a small structured state object the loop reads/writes every turn:
53
+ ```json
54
+ {"goal": "<one immutable sentence>", "facts": [...], "open_questions": [...],
55
+ "done": ["fetched X", "parsed Y"], "next": "draft summary", "artifacts": {"pr_url": null}}
56
+ ```
57
+ Re-inject `goal` + `next` into the prompt **every step** (anti-drift re-grounding). When the transcript grows large, summarize old turns into `facts`/`done` and drop the raw tool dumps — keep state, discard chatter.
58
+
59
+ 4. **Verify each step's output before continuing — gate, don't trust.** After every tool result, check it actually advanced the goal *before* the model plans the next move. Cheapest sufficient check wins:
60
+ - Schema/shape check on tool output (parses? non-empty? expected fields?) — pure code, no model.
61
+ - Goal-relevance check: "does this result move us toward `goal`, or sideways?" If sideways → discard and re-ground, don't append it as progress.
62
+ - For generated artifacts (code, SQL, configs): run the real check (compile/lint/`pytest`/dry-run), not a model's self-assessment. A failing check feeds back into the loop as a tool result.
63
+
64
+ 5. **Retry then replan on tool failure — distinguish transient from logical.** Tool error ≠ retry-forever.
65
+ - **Transient** (timeout, 429, 5xx): bounded retry with backoff — but that's transport reliability; delegate it to harden-llm-app-reliability, don't re-implement here.
66
+ - **Logical** (bad args, "not found", validation reject): do **not** retry the identical call. Feed the error text back as an observation and let the model *replan* (fix args, pick another tool, or revise the plan). Cap replans (≤2) per subgoal; exceeding it → escalate, don't thrash.
67
+
68
+ 6. **Decompose to subagents only when one context can't hold the work — and return summaries, not dumps.** A subagent gets a *narrow* objective, its own fresh context and tool subset, and returns a **compact result** (the answer + key facts + artifact refs), never its raw transcript. The orchestrator merges summaries into parent state. This is the whole point: parallel/large work happens in child contexts so the parent stays lean. If you find yourself piping a subagent's full message history back up, you've defeated it.
69
+
70
+ 7. **Gate irreversible actions behind explicit approval; meter spend.** Classify each tool: read-only / reversible-write / **irreversible** (delete, deploy, send money, email customers, `DROP`). Irreversible tools require a human-approval checkpoint (or a strict policy allowlist) *before* execution — the loop pauses and surfaces the proposed action + args. Track cumulative tokens/$ per run (step 2's `MAX_USD`); a runaway agent is a billing incident. Emit a structured per-step trace (step #, tool, args, result-status, cost) so a stuck run is debuggable after the fact → build-audit-logging.
71
+
72
+ 8. **Prove it on a multi-step harness with machine-checkable success criteria — built before you ship.** A loop that works on one happy-path demo proves nothing. Write the harness first: define each scenario's *success oracle* in code (artifact exists / `pytest` green / expected end-state reached / final answer matches a regex), not a model's "looks good." Cover ≥3 scenarios — one happy path, one designed-to-fail (asserts escalation, never an infinite loop), one with a distractor in tool output (asserts the goal held). Run it in CI on every change to the loop, prompt, or tool set; a passing harness is the only evidence the orchestration is reliable. Spec the checks in the Verify section below.
73
+
74
+ ## Common Errors
75
+
76
+ - **Stop condition = only "model says done".** The model declares victory early or never. Always pair the semantic stop with a hard `MAX_STEPS` cap.
77
+ - **No max-step / max-cost cap.** One bad plan → infinite tool calls → runaway bill. Both caps are mandatory, and hitting them must escalate, not silently return a partial answer as if it succeeded.
78
+ - **Treating the message list as memory.** Relying on the growing transcript means the goal drowns in tool noise and context blows up. Keep an explicit scratchpad; re-inject the goal every step.
79
+ - **Never re-grounding → drift.** Without re-stating `goal` each turn, a long run quietly migrates to a neighboring task. Inject goal + next-action every step and discard off-goal results.
80
+ - **Retrying a logical error unchanged.** Re-sending the same bad args on a validation failure just burns steps. Transient → backoff retry; logical → replan with the error fed back as an observation.
81
+ - **Subagents returning raw transcripts.** Dumping a child's full history into the parent defeats decomposition and re-bloats the context you spun it off to avoid. Children return summaries + artifact refs only.
82
+ - **Multi-agent when a loop would do.** Coordination overhead, lost context at handoffs, and harder debugging — for work one context could have held. Escalate topology only when forced (step 1).
83
+ - **More than one tool call per turn without isolation.** Parallel tool calls in one turn make the trace ambiguous and ordering bugs invisible. Keep one tool per turn unless the calls are provably independent.
84
+ - **No verification between steps.** Appending an empty/erroring/irrelevant tool result as "progress" compounds garbage across the run. Gate every result before it enters state.
85
+ - **Irreversible action with no approval gate.** The agent deletes/deploys/pays on a hallucinated plan. Classify tools; pause for human approval on irreversible ones.
86
+ - **No per-step trace.** When a run gets stuck you have nothing to debug. Emit structured step records (tool, args, status, cost) from day one.
87
+
88
+ ## Verify
89
+
90
+ 1. **Terminates on success:** a happy-path multi-step task reaches the semantic stop and returns the artifact *before* `MAX_STEPS` — caps are a safety net, not the normal exit.
91
+ 2. **Terminates on failure:** force an unsolvable goal → the run hits `MAX_STEPS`/`MAX_USD` and **escalates** (returns a clear "could not complete" + trace), never an infinite loop and never a partial dressed up as success.
92
+ 3. **No-progress break:** inject a tool that returns the same value forever → the loop detects the repeated `(tool,args)` and breaks/replans within ≤2 repeats, not at MAX_STEPS.
93
+ 4. **Anti-drift:** run a long task with a distractor in tool output → final answer still serves the original `goal`; the off-goal result was discarded, not built upon.
94
+ 5. **Replan on logical error:** make a tool reject the first args → the agent fixes args / switches tool and proceeds, and does **not** re-send the identical failing call.
95
+ 6. **Budget guard:** set `MAX_USD` low → the run halts and escalates when spend exceeds it; cumulative cost in the trace matches actual usage.
96
+ 7. **Approval gate:** an irreversible tool is never executed without the approval checkpoint firing first (assert it pauses and surfaces args).
97
+ 8. **Subagent isolation:** parent context size after a subagent task stays bounded — the child returned a summary, not its transcript (check token count, not just correctness).
98
+ 9. **Harness:** ≥3 multi-step scenarios with machine-checkable success criteria (artifact exists / check passes / expected state reached) run green in CI, including at least one designed-to-fail case from check 2.
99
+
100
+ Done = on the multi-step harness the agent finishes happy-path tasks via the semantic stop, every failure/runaway path escalates within the step+budget caps (no infinite loops, no partial-as-success), each step is verified and re-grounded on the goal, irreversible actions are gated, and subagents return summaries — all evidenced by the per-step trace.