margin 0.9.32__tar.gz → 0.11.0__tar.gz

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.

Potentially problematic release.


This version of margin might be problematic. Click here for more details.

Files changed (119) hide show
  1. {margin-0.9.32/margin.egg-info → margin-0.11.0}/PKG-INFO +67 -24
  2. {margin-0.9.32 → margin-0.11.0}/README.md +64 -23
  3. margin-0.11.0/adapters/__init__.py +0 -0
  4. margin-0.11.0/adapters/agent/__init__.py +34 -0
  5. margin-0.11.0/adapters/agent/flow.py +72 -0
  6. margin-0.11.0/adapters/agent/metrics.py +78 -0
  7. margin-0.11.0/adapters/agent/wrap.py +114 -0
  8. margin-0.11.0/adapters/dataframe/__init__.py +7 -0
  9. margin-0.11.0/adapters/dataframe/pandas_hook.py +99 -0
  10. margin-0.11.0/adapters/dataframe/quality.py +110 -0
  11. margin-0.11.0/adapters/fastapi/__init__.py +6 -0
  12. margin-0.11.0/adapters/fastapi/endpoints.py +99 -0
  13. margin-0.11.0/adapters/fastapi/middleware.py +116 -0
  14. margin-0.11.0/adapters/mcp/__init__.py +50 -0
  15. margin-0.11.0/adapters/mcp/__main__.py +5 -0
  16. margin-0.11.0/adapters/mcp/server.py +177 -0
  17. margin-0.11.0/adapters/mcp/state.py +96 -0
  18. margin-0.11.0/adapters/numpy/__init__.py +9 -0
  19. margin-0.11.0/adapters/numpy/array_health.py +220 -0
  20. margin-0.11.0/adapters/pytest/__init__.py +7 -0
  21. margin-0.11.0/adapters/pytest/plugin.py +255 -0
  22. margin-0.11.0/adapters/pytest/suite.py +104 -0
  23. {margin-0.9.32 → margin-0.11.0}/margin/__init__.py +44 -21
  24. {margin-0.9.32 → margin-0.11.0}/margin/__main__.py +19 -0
  25. {margin-0.9.32 → margin-0.11.0}/margin/causal.py +13 -0
  26. {margin-0.9.32 → margin-0.11.0}/margin/composite.py +4 -0
  27. {margin-0.9.32 → margin-0.11.0}/margin/diff.py +54 -16
  28. {margin-0.9.32 → margin-0.11.0}/margin/loop.py +44 -0
  29. {margin-0.9.32 → margin-0.11.0}/margin/provenance.py +0 -3
  30. {margin-0.9.32 → margin-0.11.0}/margin/uncertain.py +0 -6
  31. {margin-0.9.32 → margin-0.11.0/margin.egg-info}/PKG-INFO +67 -24
  32. {margin-0.9.32 → margin-0.11.0}/margin.egg-info/SOURCES.txt +22 -5
  33. margin-0.11.0/margin.egg-info/entry_points.txt +2 -0
  34. margin-0.11.0/margin.egg-info/requires.txt +3 -0
  35. margin-0.11.0/margin.egg-info/top_level.txt +2 -0
  36. {margin-0.9.32 → margin-0.11.0}/pyproject.toml +9 -2
  37. margin-0.9.32/margin.egg-info/top_level.txt +0 -1
  38. margin-0.9.32/tests/test_healthcare.py +0 -210
  39. margin-0.9.32/tests/test_neuro.py +0 -159
  40. margin-0.9.32/tests/test_printer3d.py +0 -151
  41. margin-0.9.32/tests/test_ros2.py +0 -190
  42. margin-0.9.32/tests/test_transformer.py +0 -301
  43. {margin-0.9.32 → margin-0.11.0}/LICENSE +0 -0
  44. {margin-0.9.32 → margin-0.11.0}/NOTICE +0 -0
  45. {margin-0.9.32 → margin-0.11.0}/margin/algebra.py +0 -0
  46. {margin-0.9.32 → margin-0.11.0}/margin/anomaly.py +0 -0
  47. {margin-0.9.32 → margin-0.11.0}/margin/bridge.py +0 -0
  48. {margin-0.9.32 → margin-0.11.0}/margin/calibrate.py +0 -0
  49. {margin-0.9.32 → margin-0.11.0}/margin/confidence.py +0 -0
  50. {margin-0.9.32 → margin-0.11.0}/margin/config.py +0 -0
  51. {margin-0.9.32 → margin-0.11.0}/margin/contract.py +0 -0
  52. {margin-0.9.32 → margin-0.11.0}/margin/controller.py +0 -0
  53. {margin-0.9.32 → margin-0.11.0}/margin/correlate.py +0 -0
  54. {margin-0.9.32 → margin-0.11.0}/margin/drift.py +0 -0
  55. {margin-0.9.32 → margin-0.11.0}/margin/events.py +0 -0
  56. {margin-0.9.32 → margin-0.11.0}/margin/fingerprint.py +0 -0
  57. {margin-0.9.32 → margin-0.11.0}/margin/flow.py +0 -0
  58. {margin-0.9.32 → margin-0.11.0}/margin/forecast.py +0 -0
  59. {margin-0.9.32 → margin-0.11.0}/margin/health.py +0 -0
  60. {margin-0.9.32 → margin-0.11.0}/margin/intent.py +0 -0
  61. {margin-0.9.32 → margin-0.11.0}/margin/issue.py +0 -0
  62. {margin-0.9.32 → margin-0.11.0}/margin/ledger.py +0 -0
  63. {margin-0.9.32 → margin-0.11.0}/margin/observation.py +0 -0
  64. {margin-0.9.32 → margin-0.11.0}/margin/persist.py +0 -0
  65. {margin-0.9.32 → margin-0.11.0}/margin/policy/__init__.py +0 -0
  66. {margin-0.9.32 → margin-0.11.0}/margin/policy/compose.py +0 -0
  67. {margin-0.9.32 → margin-0.11.0}/margin/policy/core.py +0 -0
  68. {margin-0.9.32 → margin-0.11.0}/margin/policy/temporal.py +0 -0
  69. {margin-0.9.32 → margin-0.11.0}/margin/policy/trace.py +0 -0
  70. {margin-0.9.32 → margin-0.11.0}/margin/policy/tuning.py +0 -0
  71. {margin-0.9.32 → margin-0.11.0}/margin/policy/validate.py +0 -0
  72. {margin-0.9.32 → margin-0.11.0}/margin/predicates.py +0 -0
  73. {margin-0.9.32 → margin-0.11.0}/margin/streaming.py +0 -0
  74. {margin-0.9.32 → margin-0.11.0}/margin/transitions.py +0 -0
  75. {margin-0.9.32 → margin-0.11.0}/margin/validity.py +0 -0
  76. {margin-0.9.32 → margin-0.11.0}/margin.egg-info/dependency_links.txt +0 -0
  77. {margin-0.9.32 → margin-0.11.0}/setup.cfg +0 -0
  78. {margin-0.9.32 → margin-0.11.0}/tests/test_absence.py +0 -0
  79. {margin-0.9.32 → margin-0.11.0}/tests/test_algebra.py +0 -0
  80. {margin-0.9.32 → margin-0.11.0}/tests/test_anomaly.py +0 -0
  81. {margin-0.9.32 → margin-0.11.0}/tests/test_bridge.py +0 -0
  82. {margin-0.9.32 → margin-0.11.0}/tests/test_calibrate.py +0 -0
  83. {margin-0.9.32 → margin-0.11.0}/tests/test_causal.py +0 -0
  84. {margin-0.9.32 → margin-0.11.0}/tests/test_composite.py +0 -0
  85. {margin-0.9.32 → margin-0.11.0}/tests/test_confidence.py +0 -0
  86. {margin-0.9.32 → margin-0.11.0}/tests/test_config.py +0 -0
  87. {margin-0.9.32 → margin-0.11.0}/tests/test_contract.py +0 -0
  88. {margin-0.9.32 → margin-0.11.0}/tests/test_controller.py +0 -0
  89. {margin-0.9.32 → margin-0.11.0}/tests/test_correlate.py +0 -0
  90. {margin-0.9.32 → margin-0.11.0}/tests/test_diff.py +0 -0
  91. {margin-0.9.32 → margin-0.11.0}/tests/test_drift.py +0 -0
  92. {margin-0.9.32 → margin-0.11.0}/tests/test_events.py +0 -0
  93. {margin-0.9.32 → margin-0.11.0}/tests/test_fingerprint_ext.py +0 -0
  94. {margin-0.9.32 → margin-0.11.0}/tests/test_float_tolerance.py +0 -0
  95. {margin-0.9.32 → margin-0.11.0}/tests/test_flow.py +0 -0
  96. {margin-0.9.32 → margin-0.11.0}/tests/test_forecast.py +0 -0
  97. {margin-0.9.32 → margin-0.11.0}/tests/test_health.py +0 -0
  98. {margin-0.9.32 → margin-0.11.0}/tests/test_intent.py +0 -0
  99. {margin-0.9.32 → margin-0.11.0}/tests/test_issue.py +0 -0
  100. {margin-0.9.32 → margin-0.11.0}/tests/test_ledger.py +0 -0
  101. {margin-0.9.32 → margin-0.11.0}/tests/test_loop.py +0 -0
  102. {margin-0.9.32 → margin-0.11.0}/tests/test_observation.py +0 -0
  103. {margin-0.9.32 → margin-0.11.0}/tests/test_persist.py +0 -0
  104. {margin-0.9.32 → margin-0.11.0}/tests/test_pipeline_features.py +0 -0
  105. {margin-0.9.32 → margin-0.11.0}/tests/test_policy.py +0 -0
  106. {margin-0.9.32 → margin-0.11.0}/tests/test_policy_compose.py +0 -0
  107. {margin-0.9.32 → margin-0.11.0}/tests/test_policy_temporal.py +0 -0
  108. {margin-0.9.32 → margin-0.11.0}/tests/test_policy_trace.py +0 -0
  109. {margin-0.9.32 → margin-0.11.0}/tests/test_policy_tuning.py +0 -0
  110. {margin-0.9.32 → margin-0.11.0}/tests/test_policy_validate.py +0 -0
  111. {margin-0.9.32 → margin-0.11.0}/tests/test_predicates.py +0 -0
  112. {margin-0.9.32 → margin-0.11.0}/tests/test_provenance.py +0 -0
  113. {margin-0.9.32 → margin-0.11.0}/tests/test_reverse_bridge.py +0 -0
  114. {margin-0.9.32 → margin-0.11.0}/tests/test_staleness.py +0 -0
  115. {margin-0.9.32 → margin-0.11.0}/tests/test_streaming.py +0 -0
  116. {margin-0.9.32 → margin-0.11.0}/tests/test_transitions.py +0 -0
  117. {margin-0.9.32 → margin-0.11.0}/tests/test_uncertain.py +0 -0
  118. {margin-0.9.32 → margin-0.11.0}/tests/test_validity.py +0 -0
  119. {margin-0.9.32 → margin-0.11.0}/tests/test_windowing.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: margin
3
- Version: 0.9.32
3
+ Version: 0.11.0
4
4
  Summary: Typed health classification, uncertainty algebra, and correction auditing for any system that measures things and needs to explain what happened.
5
5
  Author: Cope Labs LLC
6
6
  License: MIT
@@ -25,6 +25,8 @@ Requires-Python: >=3.10
25
25
  Description-Content-Type: text/markdown
26
26
  License-File: LICENSE
27
27
  License-File: NOTICE
28
+ Provides-Extra: mcp
29
+ Requires-Dist: mcp>=1.0; extra == "mcp"
28
30
  Dynamic: license-file
29
31
 
30
32
  # margin
@@ -34,9 +36,57 @@ Dynamic: license-file
34
36
  [![Downloads/month](https://static.pepy.tech/badge/margin/month)](https://pepy.tech/project/margin)
35
37
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
36
38
 
37
- **Typed health classification for systems that measure things.**
39
+ **Typed primitives for system self-awareness.**
38
40
 
39
- Every system with health bars, thresholds, alerts, or status dashboards solves the same problem: take a number, decide if it's healthy, correct it if it isn't, explain what happened. Margin is that pattern, typed once, with the polarity bug fixed.
41
+ When a system has to answer *"how am I doing, and what should I do about it?"* — an LLM agent between steps, an ML pipeline mid-inference, a data job deciding whether to ship — margin gives it the vocabulary. Typed `Health`, `Confidence`, `Absence`, `Issue`, `Flow`, `Intent`, `Contract` — composable, serializable, zero-dependency Python.
42
+
43
+ ```python
44
+ from adapters.agent import agent_parser, AgentStep, agent_flow
45
+
46
+ parser = agent_parser()
47
+ flow = agent_flow() # plan → act → reflect
48
+
49
+ # Record what you already measure after one LLM + tool step:
50
+ step = AgentStep(latency_ms=2400, tokens_used=1800,
51
+ tool_success_rate=0.9, retry_count=1, confidence=0.72)
52
+
53
+ expr = parser.parse(step.to_values())
54
+ guidance = flow.observe(step.to_values())
55
+
56
+ print(expr.to_string())
57
+ # [latency_ms:INTACT(-0.60σ)] [tokens_used:INTACT(-1.25σ)]
58
+ # [tool_success_rate:INTACT(-0.05σ)] [retry_count:INTACT(+0.00σ)]
59
+ # [confidence:INTACT(-0.10σ)]
60
+
61
+ print(guidance.to_atom())
62
+ # flow:agent:plan[1/3] → ADVANCE
63
+ ```
64
+
65
+ Every metric is polarity-aware (more tokens = worse, higher confidence = better), sigma-normalised so you can compare them, and emits typed diagnostics when something goes wrong. Nothing stringly-typed; your decision loop reads typed enums.
66
+
67
+ ### Works with any MCP-capable client
68
+
69
+ Install the MCP extra and you get an stdio server any model's client (Claude Desktop, Claude Code, Cursor, Cline, Windsurf, …) can spawn:
70
+
71
+ ```bash
72
+ pip install margin[mcp]
73
+ ```
74
+
75
+ ```json
76
+ {
77
+ "mcpServers": {
78
+ "margin": {
79
+ "command": "margin-mcp"
80
+ }
81
+ }
82
+ }
83
+ ```
84
+
85
+ The model now has a `margin_observe(values)` tool and can read `margin://state` / `margin://issues` between turns to reason about its own typed self-state. Full details: [adapters/mcp/README.md](adapters/mcp/README.md).
86
+
87
+ ---
88
+
89
+ Not building an agent? Margin works on any scalar with a threshold:
40
90
 
41
91
  ```python
42
92
  from margin import Parser, Thresholds
@@ -48,13 +98,12 @@ parser = Parser(
48
98
  "error_rate": Thresholds(intact=0.005, ablated=0.05, higher_is_better=False),
49
99
  },
50
100
  )
51
-
52
101
  expr = parser.parse({"throughput": 480.0, "error_rate": 0.03})
53
102
  print(expr.to_string())
54
103
  # [throughput:INTACT(-0.04σ)] [error_rate:DEGRADED(-14.00σ)]
55
104
  ```
56
105
 
57
- Throughput and error rate on the same scale. One is higher-is-better, the other is lower-is-better. Both classified correctly. Sigma-normalised so you can compare them.
106
+ Throughput and error rate on the same scale. One higher-is-better, the other lower-is-better. Both classified correctly. Sigma-normalised so you can compare them.
58
107
 
59
108
  ## Install
60
109
 
@@ -203,30 +252,24 @@ parser = parser_from_calibration(
203
252
 
204
253
  `full_step()` orchestrates the first seven in one call. `Flow` composes them into a sequence. `Issue` is ambient — every layer emits diagnostics into the same buffer.
205
254
 
206
- ## Domain adapters
255
+ ## Adapters
207
256
 
208
- Ready-to-use threshold profiles for specific domains:
257
+ Core adapters ship in the package. Each is a Parser factory + optional Flow / Intent / Monitor helpers with domain-appropriate thresholds:
209
258
 
210
- | Adapter | What it monitors | Polarity |
211
- |---|---|---|
212
- | [**healthcare**](https://github.com/Cope-Labs/margin/tree/main/adapters/healthcare/) | Vital signs (HR, BP, SpO2, temp, glucose) — WHO/AHA ranges, sepsis screening | bands |
213
- | [**godot**](https://github.com/sethc5/margin-godot) | Game systems (food, morale, stress, stamina) — native GDScript | mixed |
214
- | [**homeassistant**](https://github.com/Cope-Labs/margin/tree/main/adapters/homeassistant/) | Smart home sensors (temp, humidity, battery, solar, power) | mixed |
215
- | [**evcharging**](https://github.com/Cope-Labs/margin/tree/main/adapters/evcharging/) | EV charging (SoC, grid draw, solar surplus, efficiency) | mixed |
216
- | [**infrastructure**](https://github.com/Cope-Labs/margin/tree/main/adapters/infrastructure/) | Server monitoring (CPU, memory, disk, latency, error rate) | mixed |
217
- | [**aquarium**](https://github.com/Cope-Labs/margin/tree/main/adapters/aquarium/) | Water chemistry (pH, ammonia, nitrite, temp, hardness) | bands |
218
- | [**greenhouse**](https://github.com/Cope-Labs/margin/tree/main/adapters/greenhouse/) | Growing environment (soil moisture, CO₂, light, VPD) | bands |
219
- | [**fitness**](https://github.com/Cope-Labs/margin/tree/main/adapters/fitness/) | Wearables (resting HR, HRV, sleep, steps, stress) | mixed |
220
- | [**transformer**](https://github.com/Cope-Labs/margin/tree/main/adapters/transformer/) | ML circuit interpretability (pythia-6.9b) | higher |
221
- | [**fastapi**](https://github.com/Cope-Labs/margin/tree/main/adapters/fastapi/) | Endpoint health (latency, error rate, throughput, queue depth) | mixed |
222
- | [**database**](https://github.com/Cope-Labs/margin/tree/main/adapters/database/) | DB health (pool usage, query latency, replication lag, deadlocks) | mixed |
223
- | [**celery**](https://github.com/Cope-Labs/margin/tree/main/adapters/celery/) | Task queue (queue depth, failure rate, worker utilization, retries) | mixed |
224
- | [**dataframe**](https://github.com/Cope-Labs/margin/tree/main/adapters/dataframe/) | Data quality (completeness, null rate, drift, freshness, schema) | mixed |
225
- | [**pytest**](https://github.com/Cope-Labs/margin/tree/main/adapters/pytest/) | Test suite health (pass rate, flake rate, coverage, duration) | mixed |
259
+ | Adapter | What it monitors |
260
+ |---|---|
261
+ | [**agent**](https://github.com/Cope-Labs/margin/tree/main/adapters/agent/) | LLM / agent step health — latency, tokens, tool success rate, retries, confidence. Includes a three-stage `plan → act → reflect` Flow and a default feasibility Intent. |
262
+ | [**mcp**](https://github.com/Cope-Labs/margin/tree/main/adapters/mcp/) | stdio Model Context Protocol server. Exposes `margin_observe` / `margin_reset` tools and `margin://state` / `margin://issues` resources to any MCP client. `pip install margin[mcp]`. |
263
+ | [**dataframe**](https://github.com/Cope-Labs/margin/tree/main/adapters/dataframe/) | Data quality (completeness, null rate, drift, freshness, schema) |
264
+ | [**numpy**](https://github.com/Cope-Labs/margin/tree/main/adapters/numpy/) | Array health (NaN rate, drift, distribution shape) |
265
+ | [**fastapi**](https://github.com/Cope-Labs/margin/tree/main/adapters/fastapi/) | Endpoint health (latency, error rate, throughput, queue depth) — ASGI middleware |
266
+ | [**pytest**](https://github.com/Cope-Labs/margin/tree/main/adapters/pytest/) | Test suite health (pass rate, flake rate, coverage, duration) — conftest plugin |
267
+
268
+ Additional domain profiles (healthcare, ros2, transformer circuit interpretability, homeassistant, aquarium, greenhouse, evcharging, fitness, infrastructure, weather, printer3d, neuro, godot, celery, database) live in [`contrib/`](https://github.com/Cope-Labs/margin/tree/main/contrib/) — reference material, not shipped in the pip package.
226
269
 
227
270
  ## Docs
228
271
 
229
- Full specification: [margin-language.md](https://github.com/Cope-Labs/margin/blob/main/margin/margin-language.md)
272
+ Full specification: [margin-language.md](https://github.com/Cope-Labs/margin/blob/main/margin/margin-language.md) — a typed grammar for system self-awareness.
230
273
 
231
274
  ## License
232
275
 
@@ -5,9 +5,57 @@
5
5
  [![Downloads/month](https://static.pepy.tech/badge/margin/month)](https://pepy.tech/project/margin)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
- **Typed health classification for systems that measure things.**
8
+ **Typed primitives for system self-awareness.**
9
9
 
10
- Every system with health bars, thresholds, alerts, or status dashboards solves the same problem: take a number, decide if it's healthy, correct it if it isn't, explain what happened. Margin is that pattern, typed once, with the polarity bug fixed.
10
+ When a system has to answer *"how am I doing, and what should I do about it?"* — an LLM agent between steps, an ML pipeline mid-inference, a data job deciding whether to ship — margin gives it the vocabulary. Typed `Health`, `Confidence`, `Absence`, `Issue`, `Flow`, `Intent`, `Contract` — composable, serializable, zero-dependency Python.
11
+
12
+ ```python
13
+ from adapters.agent import agent_parser, AgentStep, agent_flow
14
+
15
+ parser = agent_parser()
16
+ flow = agent_flow() # plan → act → reflect
17
+
18
+ # Record what you already measure after one LLM + tool step:
19
+ step = AgentStep(latency_ms=2400, tokens_used=1800,
20
+ tool_success_rate=0.9, retry_count=1, confidence=0.72)
21
+
22
+ expr = parser.parse(step.to_values())
23
+ guidance = flow.observe(step.to_values())
24
+
25
+ print(expr.to_string())
26
+ # [latency_ms:INTACT(-0.60σ)] [tokens_used:INTACT(-1.25σ)]
27
+ # [tool_success_rate:INTACT(-0.05σ)] [retry_count:INTACT(+0.00σ)]
28
+ # [confidence:INTACT(-0.10σ)]
29
+
30
+ print(guidance.to_atom())
31
+ # flow:agent:plan[1/3] → ADVANCE
32
+ ```
33
+
34
+ Every metric is polarity-aware (more tokens = worse, higher confidence = better), sigma-normalised so you can compare them, and emits typed diagnostics when something goes wrong. Nothing stringly-typed; your decision loop reads typed enums.
35
+
36
+ ### Works with any MCP-capable client
37
+
38
+ Install the MCP extra and you get an stdio server any model's client (Claude Desktop, Claude Code, Cursor, Cline, Windsurf, …) can spawn:
39
+
40
+ ```bash
41
+ pip install margin[mcp]
42
+ ```
43
+
44
+ ```json
45
+ {
46
+ "mcpServers": {
47
+ "margin": {
48
+ "command": "margin-mcp"
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ The model now has a `margin_observe(values)` tool and can read `margin://state` / `margin://issues` between turns to reason about its own typed self-state. Full details: [adapters/mcp/README.md](adapters/mcp/README.md).
55
+
56
+ ---
57
+
58
+ Not building an agent? Margin works on any scalar with a threshold:
11
59
 
12
60
  ```python
13
61
  from margin import Parser, Thresholds
@@ -19,13 +67,12 @@ parser = Parser(
19
67
  "error_rate": Thresholds(intact=0.005, ablated=0.05, higher_is_better=False),
20
68
  },
21
69
  )
22
-
23
70
  expr = parser.parse({"throughput": 480.0, "error_rate": 0.03})
24
71
  print(expr.to_string())
25
72
  # [throughput:INTACT(-0.04σ)] [error_rate:DEGRADED(-14.00σ)]
26
73
  ```
27
74
 
28
- Throughput and error rate on the same scale. One is higher-is-better, the other is lower-is-better. Both classified correctly. Sigma-normalised so you can compare them.
75
+ Throughput and error rate on the same scale. One higher-is-better, the other lower-is-better. Both classified correctly. Sigma-normalised so you can compare them.
29
76
 
30
77
  ## Install
31
78
 
@@ -174,30 +221,24 @@ parser = parser_from_calibration(
174
221
 
175
222
  `full_step()` orchestrates the first seven in one call. `Flow` composes them into a sequence. `Issue` is ambient — every layer emits diagnostics into the same buffer.
176
223
 
177
- ## Domain adapters
224
+ ## Adapters
178
225
 
179
- Ready-to-use threshold profiles for specific domains:
226
+ Core adapters ship in the package. Each is a Parser factory + optional Flow / Intent / Monitor helpers with domain-appropriate thresholds:
180
227
 
181
- | Adapter | What it monitors | Polarity |
182
- |---|---|---|
183
- | [**healthcare**](https://github.com/Cope-Labs/margin/tree/main/adapters/healthcare/) | Vital signs (HR, BP, SpO2, temp, glucose) — WHO/AHA ranges, sepsis screening | bands |
184
- | [**godot**](https://github.com/sethc5/margin-godot) | Game systems (food, morale, stress, stamina) — native GDScript | mixed |
185
- | [**homeassistant**](https://github.com/Cope-Labs/margin/tree/main/adapters/homeassistant/) | Smart home sensors (temp, humidity, battery, solar, power) | mixed |
186
- | [**evcharging**](https://github.com/Cope-Labs/margin/tree/main/adapters/evcharging/) | EV charging (SoC, grid draw, solar surplus, efficiency) | mixed |
187
- | [**infrastructure**](https://github.com/Cope-Labs/margin/tree/main/adapters/infrastructure/) | Server monitoring (CPU, memory, disk, latency, error rate) | mixed |
188
- | [**aquarium**](https://github.com/Cope-Labs/margin/tree/main/adapters/aquarium/) | Water chemistry (pH, ammonia, nitrite, temp, hardness) | bands |
189
- | [**greenhouse**](https://github.com/Cope-Labs/margin/tree/main/adapters/greenhouse/) | Growing environment (soil moisture, CO₂, light, VPD) | bands |
190
- | [**fitness**](https://github.com/Cope-Labs/margin/tree/main/adapters/fitness/) | Wearables (resting HR, HRV, sleep, steps, stress) | mixed |
191
- | [**transformer**](https://github.com/Cope-Labs/margin/tree/main/adapters/transformer/) | ML circuit interpretability (pythia-6.9b) | higher |
192
- | [**fastapi**](https://github.com/Cope-Labs/margin/tree/main/adapters/fastapi/) | Endpoint health (latency, error rate, throughput, queue depth) | mixed |
193
- | [**database**](https://github.com/Cope-Labs/margin/tree/main/adapters/database/) | DB health (pool usage, query latency, replication lag, deadlocks) | mixed |
194
- | [**celery**](https://github.com/Cope-Labs/margin/tree/main/adapters/celery/) | Task queue (queue depth, failure rate, worker utilization, retries) | mixed |
195
- | [**dataframe**](https://github.com/Cope-Labs/margin/tree/main/adapters/dataframe/) | Data quality (completeness, null rate, drift, freshness, schema) | mixed |
196
- | [**pytest**](https://github.com/Cope-Labs/margin/tree/main/adapters/pytest/) | Test suite health (pass rate, flake rate, coverage, duration) | mixed |
228
+ | Adapter | What it monitors |
229
+ |---|---|
230
+ | [**agent**](https://github.com/Cope-Labs/margin/tree/main/adapters/agent/) | LLM / agent step health — latency, tokens, tool success rate, retries, confidence. Includes a three-stage `plan → act → reflect` Flow and a default feasibility Intent. |
231
+ | [**mcp**](https://github.com/Cope-Labs/margin/tree/main/adapters/mcp/) | stdio Model Context Protocol server. Exposes `margin_observe` / `margin_reset` tools and `margin://state` / `margin://issues` resources to any MCP client. `pip install margin[mcp]`. |
232
+ | [**dataframe**](https://github.com/Cope-Labs/margin/tree/main/adapters/dataframe/) | Data quality (completeness, null rate, drift, freshness, schema) |
233
+ | [**numpy**](https://github.com/Cope-Labs/margin/tree/main/adapters/numpy/) | Array health (NaN rate, drift, distribution shape) |
234
+ | [**fastapi**](https://github.com/Cope-Labs/margin/tree/main/adapters/fastapi/) | Endpoint health (latency, error rate, throughput, queue depth) — ASGI middleware |
235
+ | [**pytest**](https://github.com/Cope-Labs/margin/tree/main/adapters/pytest/) | Test suite health (pass rate, flake rate, coverage, duration) — conftest plugin |
236
+
237
+ Additional domain profiles (healthcare, ros2, transformer circuit interpretability, homeassistant, aquarium, greenhouse, evcharging, fitness, infrastructure, weather, printer3d, neuro, godot, celery, database) live in [`contrib/`](https://github.com/Cope-Labs/margin/tree/main/contrib/) — reference material, not shipped in the pip package.
197
238
 
198
239
  ## Docs
199
240
 
200
- Full specification: [margin-language.md](https://github.com/Cope-Labs/margin/blob/main/margin/margin-language.md)
241
+ Full specification: [margin-language.md](https://github.com/Cope-Labs/margin/blob/main/margin/margin-language.md) — a typed grammar for system self-awareness.
201
242
 
202
243
  ## License
203
244
 
File without changes
@@ -0,0 +1,34 @@
1
+ """
2
+ margin adapter for agent / LLM-pipeline self-awareness.
3
+
4
+ The primitives (Confidence, Absence, Issue, Flow, Intent, Contract) that margin
5
+ exposes are *exactly* the vocabulary an LLM agent needs when it has to answer
6
+ "how am I doing, and should I continue?" at each step of its run.
7
+
8
+ This adapter packages sensible defaults so you can go from zero to a typed
9
+ health read-out of an agent step in three lines:
10
+
11
+ from adapters.agent import agent_parser, AgentStep
12
+
13
+ parser = agent_parser()
14
+ step = AgentStep(latency_ms=2400, tokens_used=1800,
15
+ tool_success_rate=0.9, retry_count=1, confidence=0.72)
16
+ print(parser.parse(step.to_values()).to_string())
17
+ # [latency_ms:INTACT(+0.40\u03c3)] [tokens_used:INTACT(+0.75\u03c3)]
18
+ # [tool_success_rate:INTACT(-0.05\u03c3)] [retry_count:DEGRADED(...)]
19
+ # [confidence:INTACT(-0.10\u03c3)]
20
+
21
+ Use :func:`agent_flow` when your agent runs in stages (plan \u2192 act \u2192 reflect) and
22
+ you want typed live guidance (ADVANCE/HOLD/RETRY/ROLLBACK) between them.
23
+
24
+ Zero SDK dependencies. See module docstrings for wiring to Anthropic / OpenAI.
25
+ """
26
+ from .metrics import AGENT_BASELINES, AGENT_THRESHOLDS, agent_parser
27
+ from .flow import AGENT_STAGES, agent_flow
28
+ from .wrap import AgentStep, record_step, agent_intent
29
+
30
+ __all__ = [
31
+ "AGENT_BASELINES", "AGENT_THRESHOLDS", "agent_parser",
32
+ "AGENT_STAGES", "agent_flow",
33
+ "AgentStep", "record_step", "agent_intent",
34
+ ]
@@ -0,0 +1,72 @@
1
+ """
2
+ A Flow template for a typical three-stage agent loop:
3
+
4
+ PLAN \u2192 ACT \u2192 REFLECT \u2192 (COMPLETE)
5
+
6
+ Each stage uses the same agent_parser, but advances on different predicates:
7
+
8
+ PLAN \u2192 a concrete plan is produced (confidence above threshold)
9
+ ACT \u2192 tool calls are succeeding (tool_success_rate holds)
10
+ REFLECT \u2192 the run is converging (retries haven't exploded)
11
+
12
+ Flow.observe() returns typed live guidance (ADVANCE / HOLD / RETRY / ROLLBACK /
13
+ ESCALATE) at every step, and emits typed Issues for predicate failures and
14
+ degraded stage entries. This means your agent gets a typed answer to
15
+ "continue, retry, or escalate?" after every observation \u2014 not a string you have
16
+ to parse.
17
+ """
18
+ from __future__ import annotations
19
+
20
+ from margin import Flow, Stage, Expression
21
+
22
+ from .metrics import agent_parser
23
+
24
+
25
+ def _has_plan(expr: Expression) -> bool:
26
+ """PLAN advances once the model's stated confidence crosses intact."""
27
+ conf = next((o for o in expr.observations if o.name == "confidence"), None)
28
+ return conf is not None and conf.health.value == "INTACT"
29
+
30
+
31
+ def _tools_holding(expr: Expression) -> bool:
32
+ """ACT advances once tool success rate is intact and retries aren't ablated."""
33
+ success = next((o for o in expr.observations if o.name == "tool_success_rate"), None)
34
+ retries = next((o for o in expr.observations if o.name == "retry_count"), None)
35
+ if success is None or success.health.value != "INTACT":
36
+ return False
37
+ if retries is not None and retries.health.value == "ABLATED":
38
+ return False
39
+ return True
40
+
41
+
42
+ def _converging(expr: Expression) -> bool:
43
+ """REFLECT advances once retry_count is intact and confidence is intact."""
44
+ retries = next((o for o in expr.observations if o.name == "retry_count"), None)
45
+ conf = next((o for o in expr.observations if o.name == "confidence"), None)
46
+ return (
47
+ retries is not None and retries.health.value == "INTACT"
48
+ and conf is not None and conf.health.value == "INTACT"
49
+ )
50
+
51
+
52
+ AGENT_STAGES = ("plan", "act", "reflect")
53
+
54
+
55
+ def agent_flow(label: str = "agent") -> Flow:
56
+ """
57
+ Build a typical three-stage agent Flow with shared thresholds.
58
+
59
+ Each Flow.observe() call returns a FlowResult whose ``guidance`` is one of
60
+ ADVANCE / HOLD / RETRY / ROLLBACK / ESCALATE / COMPLETE. Use
61
+ ``result.to_atom()`` to get a single-line summary suitable for logging,
62
+ e.g. ``flow:agent:act[2/3] \u2192 ADVANCE``.
63
+ """
64
+ parser = agent_parser()
65
+ return Flow(
66
+ stages=[
67
+ Stage(name="plan", parser=parser, advance_when=_has_plan),
68
+ Stage(name="act", parser=parser, advance_when=_tools_holding),
69
+ Stage(name="reflect", parser=parser, advance_when=_converging),
70
+ ],
71
+ label=label,
72
+ )
@@ -0,0 +1,78 @@
1
+ """
2
+ Default baselines and thresholds for common agent / LLM-pipeline metrics.
3
+
4
+ Polarity is the critical part: for cost-like metrics (latency, tokens, retries)
5
+ more is worse, so ``higher_is_better=False``. For quality metrics (confidence,
6
+ tool success rate) more is better. Margin's sigma normalisation threads that
7
+ polarity correctly through every comparison downstream.
8
+
9
+ The baselines below are deliberately conservative defaults for a mid-size
10
+ Claude / GPT call with occasional tool use. Override per-component for your
11
+ specific agent.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from typing import Optional
16
+
17
+ from margin import Parser, Thresholds
18
+
19
+
20
+ AGENT_BASELINES: dict[str, float] = {
21
+ "latency_ms": 1500.0, # typical end-to-end call
22
+ "tokens_used": 800.0, # input + output
23
+ "tool_success_rate": 0.95, # fraction of tool calls that succeeded
24
+ "retry_count": 0.0, # zero retries is the happy path
25
+ "confidence": 0.80, # model-stated or rubric-derived
26
+ }
27
+
28
+
29
+ AGENT_THRESHOLDS: dict[str, Thresholds] = {
30
+ "latency_ms": Thresholds(
31
+ intact=3000.0, ablated=10000.0, higher_is_better=False,
32
+ ),
33
+ "tokens_used": Thresholds(
34
+ intact=3000.0, ablated=10000.0, higher_is_better=False,
35
+ ),
36
+ "tool_success_rate": Thresholds(
37
+ intact=0.80, ablated=0.50, higher_is_better=True,
38
+ ),
39
+ "retry_count": Thresholds(
40
+ intact=1.0, ablated=3.0, higher_is_better=False,
41
+ ),
42
+ "confidence": Thresholds(
43
+ intact=0.60, ablated=0.30, higher_is_better=True,
44
+ ),
45
+ }
46
+
47
+
48
+ def agent_parser(
49
+ baselines: Optional[dict[str, float]] = None,
50
+ thresholds: Optional[dict[str, Thresholds]] = None,
51
+ ) -> Parser:
52
+ """
53
+ Build a Parser with sensible agent-metric defaults.
54
+
55
+ ``baselines`` and ``thresholds`` merge over the defaults, so you can pass
56
+ just the overrides you care about::
57
+
58
+ parser = agent_parser(
59
+ baselines={"latency_ms": 5000.0}, # slower model
60
+ thresholds={"confidence": Thresholds(
61
+ intact=0.75, ablated=0.50, higher_is_better=True,
62
+ )},
63
+ )
64
+ """
65
+ merged_baselines = dict(AGENT_BASELINES)
66
+ if baselines:
67
+ merged_baselines.update(baselines)
68
+ merged_thresholds = dict(AGENT_THRESHOLDS)
69
+ if thresholds:
70
+ merged_thresholds.update(thresholds)
71
+ # Default per-component thresholds cover every baseline, so the Parser-level
72
+ # fallback is only hit if the caller adds an unrelated component.
73
+ fallback = Thresholds(intact=0.0, ablated=0.0)
74
+ return Parser(
75
+ baselines=merged_baselines,
76
+ thresholds=fallback,
77
+ component_thresholds=merged_thresholds,
78
+ )
@@ -0,0 +1,114 @@
1
+ """
2
+ Lightweight helpers for recording an agent step and deriving a feasibility
3
+ Intent from it.
4
+
5
+ The ``AgentStep`` dataclass carries the five canonical agent metrics so you can
6
+ collect them once at the call site and feed them to margin in typed form.
7
+ There is no hard dependency on any LLM SDK \u2014 wire your own adapter in a couple
8
+ of lines, e.g. for Anthropic::
9
+
10
+ import time, anthropic
11
+ from adapters.agent import AgentStep, agent_parser
12
+
13
+ client = anthropic.Anthropic()
14
+ parser = agent_parser()
15
+
16
+ def call(messages, tools=None):
17
+ t0 = time.perf_counter()
18
+ resp = client.messages.create(
19
+ model="claude-opus-4-7",
20
+ max_tokens=1024,
21
+ messages=messages,
22
+ tools=tools or [],
23
+ )
24
+ step = AgentStep(
25
+ latency_ms=(time.perf_counter() - t0) * 1000,
26
+ tokens_used=resp.usage.input_tokens + resp.usage.output_tokens,
27
+ tool_success_rate=1.0, # fill in from your tool-runner's accounting
28
+ retry_count=0,
29
+ confidence=0.8, # rubric-derived or model-stated
30
+ )
31
+ expr = parser.parse(step.to_values())
32
+ return resp, expr
33
+ """
34
+ from __future__ import annotations
35
+
36
+ from dataclasses import dataclass
37
+ from typing import Optional
38
+
39
+ from margin import (
40
+ Expression, Intent, Requirement, Health, Ledger, Record, Op,
41
+ )
42
+
43
+
44
+ @dataclass
45
+ class AgentStep:
46
+ """
47
+ One agent step's observable metrics.
48
+
49
+ latency_ms: end-to-end wall time for the call in milliseconds
50
+ tokens_used: input + output tokens
51
+ tool_success_rate: fraction of tool calls that succeeded (0.0\u20131.0)
52
+ retry_count: how many retries this step consumed
53
+ confidence: model-stated or rubric-derived confidence (0.0\u20131.0)
54
+ """
55
+ latency_ms: float
56
+ tokens_used: float
57
+ tool_success_rate: float
58
+ retry_count: float
59
+ confidence: float
60
+
61
+ def to_values(self) -> dict[str, float]:
62
+ """Dict shape expected by ``agent_parser().parse(...)``."""
63
+ return {
64
+ "latency_ms": self.latency_ms,
65
+ "tokens_used": self.tokens_used,
66
+ "tool_success_rate": self.tool_success_rate,
67
+ "retry_count": self.retry_count,
68
+ "confidence": self.confidence,
69
+ }
70
+
71
+
72
+ def record_step(
73
+ ledger: Ledger,
74
+ step_index: int,
75
+ expression: Expression,
76
+ tag: str = "",
77
+ ) -> Record:
78
+ """
79
+ Append one agent step's first observation to the ledger so margin's
80
+ drift/forecast/contract layers can reason about trajectory across steps.
81
+ """
82
+ obs = expression.observations[0] if expression.observations else None
83
+ if obs is None:
84
+ return None
85
+ record = Record(
86
+ step=step_index,
87
+ tag=tag or f"step-{step_index}",
88
+ before=obs,
89
+ fired=False,
90
+ op=Op.NOOP,
91
+ alpha=0.0,
92
+ magnitude=0.0,
93
+ )
94
+ ledger.append(record)
95
+ return record
96
+
97
+
98
+ def agent_intent(deadline_seconds: Optional[float] = None) -> Intent:
99
+ """
100
+ Build a default Intent covering 'the agent run is still feasible' \u2014 a
101
+ compact goal-feasibility check you can drop into ``full_step(... intent=...)``.
102
+
103
+ Requirements say latency must stay at DEGRADED-or-better and confidence must
104
+ stay at DEGRADED-or-better. ``deadline_seconds`` sets an ETA-to-violation
105
+ window from the moment ``Intent.evaluate()`` is called.
106
+ """
107
+ return Intent(
108
+ goal="agent run is still feasible",
109
+ requirements=[
110
+ Requirement(component="latency_ms", min_health=Health.DEGRADED),
111
+ Requirement(component="confidence", min_health=Health.DEGRADED),
112
+ ],
113
+ deadline_seconds=deadline_seconds,
114
+ )
@@ -0,0 +1,7 @@
1
+ """
2
+ DataFrame / data pipeline adapter for margin.
3
+
4
+ Data quality metrics: completeness, null rate, drift, freshness, row count.
5
+ Works with any tabular data — no pandas dependency required.
6
+ """
7
+ from .quality import DQ_METRICS, parse_quality, pipeline_expression