healops 0.1.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.
Files changed (115) hide show
  1. healops-0.1.0/LICENSE +21 -0
  2. healops-0.1.0/MANIFEST.in +4 -0
  3. healops-0.1.0/PKG-INFO +195 -0
  4. healops-0.1.0/README.md +164 -0
  5. healops-0.1.0/docs/adapter-guide.md +117 -0
  6. healops-0.1.0/docs/configuration.md +130 -0
  7. healops-0.1.0/docs/safety-and-limits.md +57 -0
  8. healops-0.1.0/examples/live_email_check.py +45 -0
  9. healops-0.1.0/examples/live_ollama_check.py +125 -0
  10. healops-0.1.0/examples/quickstart.py +28 -0
  11. healops-0.1.0/examples/selfhealing.example.yaml +131 -0
  12. healops-0.1.0/pyproject.toml +51 -0
  13. healops-0.1.0/setup.cfg +4 -0
  14. healops-0.1.0/src/healops/__init__.py +7 -0
  15. healops-0.1.0/src/healops/__main__.py +3 -0
  16. healops-0.1.0/src/healops/agents/__init__.py +0 -0
  17. healops-0.1.0/src/healops/agents/base.py +115 -0
  18. healops-0.1.0/src/healops/agents/factory.py +85 -0
  19. healops-0.1.0/src/healops/agents/fallback.py +57 -0
  20. healops-0.1.0/src/healops/agents/llm.py +268 -0
  21. healops-0.1.0/src/healops/agents/mock.py +153 -0
  22. healops-0.1.0/src/healops/agents/ratelimit.py +67 -0
  23. healops-0.1.0/src/healops/audit.py +165 -0
  24. healops-0.1.0/src/healops/capabilities.py +78 -0
  25. healops-0.1.0/src/healops/cli.py +242 -0
  26. healops-0.1.0/src/healops/collector.py +77 -0
  27. healops-0.1.0/src/healops/config.py +264 -0
  28. healops-0.1.0/src/healops/detection/__init__.py +0 -0
  29. healops-0.1.0/src/healops/detection/base.py +47 -0
  30. healops-0.1.0/src/healops/detection/thresholds.py +196 -0
  31. healops-0.1.0/src/healops/events.py +63 -0
  32. healops-0.1.0/src/healops/exceptions.py +82 -0
  33. healops-0.1.0/src/healops/facade.py +258 -0
  34. healops-0.1.0/src/healops/guard.py +121 -0
  35. healops-0.1.0/src/healops/healing/__init__.py +0 -0
  36. healops-0.1.0/src/healops/healing/approval.py +183 -0
  37. healops-0.1.0/src/healops/healing/history.py +82 -0
  38. healops-0.1.0/src/healops/healing/notify.py +123 -0
  39. healops-0.1.0/src/healops/healing/orchestrator.py +581 -0
  40. healops-0.1.0/src/healops/healing/policy.py +135 -0
  41. healops-0.1.0/src/healops/healing/smtp.py +172 -0
  42. healops-0.1.0/src/healops/interfaces.py +66 -0
  43. healops-0.1.0/src/healops/lifecycle.py +45 -0
  44. healops-0.1.0/src/healops/llm/__init__.py +17 -0
  45. healops-0.1.0/src/healops/llm/base.py +12 -0
  46. healops-0.1.0/src/healops/llm/http.py +60 -0
  47. healops-0.1.0/src/healops/llm/providers.py +231 -0
  48. healops-0.1.0/src/healops/loader.py +49 -0
  49. healops-0.1.0/src/healops/loop.py +464 -0
  50. healops-0.1.0/src/healops/outcomes.py +79 -0
  51. healops-0.1.0/src/healops/py.typed +0 -0
  52. healops-0.1.0/src/healops/records.py +67 -0
  53. healops-0.1.0/src/healops/redaction.py +72 -0
  54. healops-0.1.0/src/healops/registry.py +61 -0
  55. healops-0.1.0/src/healops/serialization.py +68 -0
  56. healops-0.1.0/src/healops/store.py +518 -0
  57. healops-0.1.0/src/healops/testing/__init__.py +15 -0
  58. healops-0.1.0/src/healops/testing/harness.py +119 -0
  59. healops-0.1.0/src/healops/testing/toy_app.py +97 -0
  60. healops-0.1.0/src/healops/types.py +71 -0
  61. healops-0.1.0/src/healops/verification/__init__.py +0 -0
  62. healops-0.1.0/src/healops/verification/base.py +38 -0
  63. healops-0.1.0/src/healops/verification/postconditions.py +163 -0
  64. healops-0.1.0/src/healops.egg-info/PKG-INFO +195 -0
  65. healops-0.1.0/src/healops.egg-info/SOURCES.txt +113 -0
  66. healops-0.1.0/src/healops.egg-info/dependency_links.txt +1 -0
  67. healops-0.1.0/src/healops.egg-info/entry_points.txt +2 -0
  68. healops-0.1.0/src/healops.egg-info/requires.txt +8 -0
  69. healops-0.1.0/src/healops.egg-info/top_level.txt +1 -0
  70. healops-0.1.0/tests/test_agent_base.py +171 -0
  71. healops-0.1.0/tests/test_approval.py +283 -0
  72. healops-0.1.0/tests/test_audit.py +203 -0
  73. healops-0.1.0/tests/test_capabilities.py +104 -0
  74. healops-0.1.0/tests/test_cli.py +296 -0
  75. healops-0.1.0/tests/test_collector.py +133 -0
  76. healops-0.1.0/tests/test_config.py +104 -0
  77. healops-0.1.0/tests/test_degradation.py +150 -0
  78. healops-0.1.0/tests/test_detection_base.py +77 -0
  79. healops-0.1.0/tests/test_docs.py +165 -0
  80. healops-0.1.0/tests/test_email.py +398 -0
  81. healops-0.1.0/tests/test_events.py +85 -0
  82. healops-0.1.0/tests/test_execution.py +282 -0
  83. healops-0.1.0/tests/test_facade.py +181 -0
  84. healops-0.1.0/tests/test_fallback_agent.py +201 -0
  85. healops-0.1.0/tests/test_guard.py +282 -0
  86. healops-0.1.0/tests/test_history.py +48 -0
  87. healops-0.1.0/tests/test_housekeeping.py +205 -0
  88. healops-0.1.0/tests/test_interfaces.py +90 -0
  89. healops-0.1.0/tests/test_lifecycle.py +95 -0
  90. healops-0.1.0/tests/test_llm_agent.py +355 -0
  91. healops-0.1.0/tests/test_llm_config.py +231 -0
  92. healops-0.1.0/tests/test_llm_providers.py +338 -0
  93. healops-0.1.0/tests/test_loader.py +91 -0
  94. healops-0.1.0/tests/test_loop.py +350 -0
  95. healops-0.1.0/tests/test_mock_agent.py +163 -0
  96. healops-0.1.0/tests/test_notify.py +130 -0
  97. healops-0.1.0/tests/test_orchestrator.py +283 -0
  98. healops-0.1.0/tests/test_orchestrator_audit.py +330 -0
  99. healops-0.1.0/tests/test_outcomes.py +111 -0
  100. healops-0.1.0/tests/test_persistence.py +358 -0
  101. healops-0.1.0/tests/test_policy.py +173 -0
  102. healops-0.1.0/tests/test_policy_limits.py +159 -0
  103. healops-0.1.0/tests/test_ratelimit.py +217 -0
  104. healops-0.1.0/tests/test_records.py +84 -0
  105. healops-0.1.0/tests/test_redaction.py +146 -0
  106. healops-0.1.0/tests/test_registry.py +87 -0
  107. healops-0.1.0/tests/test_scenarios.py +217 -0
  108. healops-0.1.0/tests/test_serialization.py +173 -0
  109. healops-0.1.0/tests/test_smoke.py +6 -0
  110. healops-0.1.0/tests/test_store.py +320 -0
  111. healops-0.1.0/tests/test_thresholds.py +179 -0
  112. healops-0.1.0/tests/test_toy_app.py +142 -0
  113. healops-0.1.0/tests/test_types.py +45 -0
  114. healops-0.1.0/tests/test_verification_flow.py +299 -0
  115. healops-0.1.0/tests/test_verifier.py +324 -0
healops-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ali Huzaifa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ include LICENSE README.md
2
+ recursive-include docs *.md
3
+ recursive-include examples *.py *.yaml
4
+ recursive-include tests *.py
healops-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,195 @@
1
+ Metadata-Version: 2.4
2
+ Name: healops
3
+ Version: 0.1.0
4
+ Summary: Safe, model-agnostic self-healing for machine-learning applications: detect, diagnose, check against policy, act, verify.
5
+ Author: Ali Huzaifa, Muhammad Rafeh, Sufyan Khan, Maaz Tariq Soomro
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/AliHuzaifa-cs/HealOps
8
+ Project-URL: Issues, https://github.com/AliHuzaifa-cs/HealOps/issues
9
+ Keywords: mlops,self-healing,monitoring,llm,agents,machine-learning,reliability
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Classifier: Topic :: System :: Monitoring
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.11
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: pydantic>=2.7
24
+ Requires-Dist: pyyaml>=6
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=8; extra == "dev"
27
+ Requires-Dist: ruff; extra == "dev"
28
+ Requires-Dist: build; extra == "dev"
29
+ Requires-Dist: twine; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ # HealOps
33
+
34
+ **Safe, model-agnostic self-healing for machine-learning applications.**
35
+
36
+ HealOps watches an ML application, detects when it breaks (a crash, a slow service, a
37
+ bad model, drifting data), asks a diagnosis agent what to do, checks the proposal against
38
+ strict policies, carries it out only if allowed, and verifies that the application really
39
+ recovered. People are asked whenever the policy says so.
40
+
41
+ > **Status: alpha (0.1.0).** The core loop is built and tested, but it has been exercised
42
+ > on a toy application, not yet on production systems. Read
43
+ > [docs/safety-and-limits.md](docs/safety-and-limits.md) before trusting it with anything
44
+ > that matters.
45
+
46
+ ## The one rule
47
+
48
+ ```
49
+ Monitor -> Detect -> Diagnose -> Plan -> Validate -> Act -> Verify -> Record
50
+ ```
51
+
52
+ | Who | May do | May not do |
53
+ | --- | --- | --- |
54
+ | The **LLM** (or the rule-based agent) | propose a diagnosis and one action | change anything |
55
+ | The **policy engine** | allow, deny, or require a person's approval | invent actions |
56
+ | Your **adapter** | execute an allowed action | decide what to run |
57
+ | The **verifier** | confirm recovery from observed evidence | take the agent's word for it |
58
+
59
+ The language model is never trusted: its output is validated, it can only choose from
60
+ actions your application really supports, its confidence is capped, and if it is down or
61
+ over budget a deterministic rule-based agent takes over. Everything is written to an
62
+ append-only audit log.
63
+
64
+ ## Install
65
+
66
+ ```
67
+ pip install healops
68
+ ```
69
+
70
+ From a source checkout: `pip install -e .` (add `".[dev]"` for the tests and `ruff`).
71
+ Python 3.11 or newer.
72
+
73
+ ## Five-minute quickstart
74
+
75
+ HealOps ships a tiny fake model service so you can see the whole loop without any setup.
76
+
77
+ ```python
78
+ from healops import HealOps
79
+ from healops.testing.toy_app import COMPONENT, Fault, ToyApp
80
+
81
+ config = {
82
+ "dry_run": False, # let HealOps really act (the default is a safe dry-run)
83
+ "audit_log_path": "quickstart_audit.jsonl",
84
+ "state_db_path": "quickstart_state.db",
85
+ "check_interval_seconds": 1,
86
+ "min_confidence_for_auto": 0.3,
87
+ "thresholds": {"latency_ms": 500, "error_rate": 0.05, "consecutive_breaches": 2},
88
+ "verification": {"settle_seconds": 0, "checks": 2, "interval_seconds": 0},
89
+ "policies": {"restart_component": {"verdict": "allow", "cooldown_seconds": 0}},
90
+ }
91
+
92
+ app = ToyApp()
93
+ healops = HealOps(config)
94
+ healops.register("toy-app", app, component=COMPONENT)
95
+
96
+ app.inject(Fault.CRASH)
97
+ print("healthy before:", app.get_health())
98
+ healops.run(max_ticks=6)
99
+ print("healthy after: ", app.get_health())
100
+ print("actions taken: ", app.actions)
101
+ ```
102
+
103
+ Expected output:
104
+
105
+ ```
106
+ healthy before: False
107
+ healthy after: True
108
+ actions taken: ['restart_component:model-server']
109
+ ```
110
+
111
+ The same file is [examples/quickstart.py](examples/quickstart.py). The run leaves
112
+ `quickstart_state.db` and `quickstart_audit.jsonl` in the current folder (delete them any time).
113
+
114
+ ## Connect your own application
115
+
116
+ You write a small **adapter**: a class with only the methods your application really has
117
+ (`get_health`, `get_metrics`, `restart_component`, ...). HealOps discovers what it can do and
118
+ never calls anything else. See [docs/adapter-guide.md](docs/adapter-guide.md).
119
+
120
+ ```python
121
+ healops = HealOps.from_config("selfhealing.yaml")
122
+ healops.register("my-model-api", MyAdapter(), component="model-server")
123
+ healops.run() # until healops.stop() is called
124
+ ```
125
+
126
+ All settings live in one YAML file, with safe defaults:
127
+ [docs/configuration.md](docs/configuration.md) and
128
+ [examples/selfhealing.example.yaml](examples/selfhealing.example.yaml).
129
+
130
+ ## Which language model?
131
+
132
+ None is required: without an `llm:` section a rule-based agent is used. To use a model, add
133
+ a few lines of configuration; no code changes, and any provider that speaks the OpenAI
134
+ chat format works (OpenAI, Ollama, Gemini's compatibility endpoint, ...) plus Anthropic.
135
+
136
+ ```yaml
137
+ llm:
138
+ provider: ollama # ollama | openai | anthropic | openai_compatible
139
+ model: llama3.2:3b
140
+ # api_key_env: OPENAI_API_KEY # the NAME of an environment variable, never the key
141
+ ```
142
+
143
+ API keys are read from environment variables only. A per-hour call budget
144
+ (`max_calls_per_hour`, default 20) bounds cost, and when the model fails or the budget is
145
+ used up, the rule-based agent answers with its confidence capped, so a person decides.
146
+
147
+ ## Approving from another terminal
148
+
149
+ When the policy needs a person, HealOps parks the incident and alerts you (console, the
150
+ state store and, if configured, email). Answer from any terminal:
151
+
152
+ ```
153
+ healops list
154
+ healops approve 3f9a1c2e --by ali
155
+ healops reject 3f9a1c2e --by ali --reason "not now"
156
+ ```
157
+
158
+ The command only records your answer in the state file; the running HealOps carries it out
159
+ through the same policy, audit and verification path. An approval that expired is never
160
+ applied. (`python -m healops ...` does the same.)
161
+
162
+ ## What is in the box
163
+
164
+ | Area | What it does |
165
+ | --- | --- |
166
+ | Detection | thresholds that must hold for several checks in a row; model-quality degradation against a baseline |
167
+ | Diagnosis | rule-based agent; LLM agent with strict JSON validation; automatic fallback; call budget |
168
+ | Policy | allow / needs approval / deny per action, retry limits, cooldowns, minimum confidence |
169
+ | Approval | human-in-the-loop queue that survives restarts; silence is never approval |
170
+ | Safety | dry-run by default; time limits on every adapter call; write-ahead audit before real actions |
171
+ | Verification | re-checks health and the incident's own metric after the action |
172
+ | State | SQLite file with observations, incidents, approvals and alerts (for dashboards) |
173
+ | Alerts | console, state store, email with rate limits |
174
+
175
+ ## Documentation
176
+
177
+ - [Adapter guide](docs/adapter-guide.md): connect your application
178
+ - [Configuration reference](docs/configuration.md): every option and its default
179
+ - [Safety and known limits](docs/safety-and-limits.md): what is guaranteed and what is not
180
+
181
+ ## Authors and acknowledgements
182
+
183
+ HealOps is the final-year project "Agentic Self-Healing MLOps".
184
+
185
+ - Ali Huzaifa (maintainer)
186
+ - Muhammad Rafeh
187
+ - Sufyan Khan
188
+ - Maaz Tariq Soomro
189
+ - Supervisor: Sir Abdullah Bin Tahir
190
+
191
+ Thanks to everyone whose feedback shaped the design.
192
+
193
+ ## License
194
+
195
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,164 @@
1
+ # HealOps
2
+
3
+ **Safe, model-agnostic self-healing for machine-learning applications.**
4
+
5
+ HealOps watches an ML application, detects when it breaks (a crash, a slow service, a
6
+ bad model, drifting data), asks a diagnosis agent what to do, checks the proposal against
7
+ strict policies, carries it out only if allowed, and verifies that the application really
8
+ recovered. People are asked whenever the policy says so.
9
+
10
+ > **Status: alpha (0.1.0).** The core loop is built and tested, but it has been exercised
11
+ > on a toy application, not yet on production systems. Read
12
+ > [docs/safety-and-limits.md](docs/safety-and-limits.md) before trusting it with anything
13
+ > that matters.
14
+
15
+ ## The one rule
16
+
17
+ ```
18
+ Monitor -> Detect -> Diagnose -> Plan -> Validate -> Act -> Verify -> Record
19
+ ```
20
+
21
+ | Who | May do | May not do |
22
+ | --- | --- | --- |
23
+ | The **LLM** (or the rule-based agent) | propose a diagnosis and one action | change anything |
24
+ | The **policy engine** | allow, deny, or require a person's approval | invent actions |
25
+ | Your **adapter** | execute an allowed action | decide what to run |
26
+ | The **verifier** | confirm recovery from observed evidence | take the agent's word for it |
27
+
28
+ The language model is never trusted: its output is validated, it can only choose from
29
+ actions your application really supports, its confidence is capped, and if it is down or
30
+ over budget a deterministic rule-based agent takes over. Everything is written to an
31
+ append-only audit log.
32
+
33
+ ## Install
34
+
35
+ ```
36
+ pip install healops
37
+ ```
38
+
39
+ From a source checkout: `pip install -e .` (add `".[dev]"` for the tests and `ruff`).
40
+ Python 3.11 or newer.
41
+
42
+ ## Five-minute quickstart
43
+
44
+ HealOps ships a tiny fake model service so you can see the whole loop without any setup.
45
+
46
+ ```python
47
+ from healops import HealOps
48
+ from healops.testing.toy_app import COMPONENT, Fault, ToyApp
49
+
50
+ config = {
51
+ "dry_run": False, # let HealOps really act (the default is a safe dry-run)
52
+ "audit_log_path": "quickstart_audit.jsonl",
53
+ "state_db_path": "quickstart_state.db",
54
+ "check_interval_seconds": 1,
55
+ "min_confidence_for_auto": 0.3,
56
+ "thresholds": {"latency_ms": 500, "error_rate": 0.05, "consecutive_breaches": 2},
57
+ "verification": {"settle_seconds": 0, "checks": 2, "interval_seconds": 0},
58
+ "policies": {"restart_component": {"verdict": "allow", "cooldown_seconds": 0}},
59
+ }
60
+
61
+ app = ToyApp()
62
+ healops = HealOps(config)
63
+ healops.register("toy-app", app, component=COMPONENT)
64
+
65
+ app.inject(Fault.CRASH)
66
+ print("healthy before:", app.get_health())
67
+ healops.run(max_ticks=6)
68
+ print("healthy after: ", app.get_health())
69
+ print("actions taken: ", app.actions)
70
+ ```
71
+
72
+ Expected output:
73
+
74
+ ```
75
+ healthy before: False
76
+ healthy after: True
77
+ actions taken: ['restart_component:model-server']
78
+ ```
79
+
80
+ The same file is [examples/quickstart.py](examples/quickstart.py). The run leaves
81
+ `quickstart_state.db` and `quickstart_audit.jsonl` in the current folder (delete them any time).
82
+
83
+ ## Connect your own application
84
+
85
+ You write a small **adapter**: a class with only the methods your application really has
86
+ (`get_health`, `get_metrics`, `restart_component`, ...). HealOps discovers what it can do and
87
+ never calls anything else. See [docs/adapter-guide.md](docs/adapter-guide.md).
88
+
89
+ ```python
90
+ healops = HealOps.from_config("selfhealing.yaml")
91
+ healops.register("my-model-api", MyAdapter(), component="model-server")
92
+ healops.run() # until healops.stop() is called
93
+ ```
94
+
95
+ All settings live in one YAML file, with safe defaults:
96
+ [docs/configuration.md](docs/configuration.md) and
97
+ [examples/selfhealing.example.yaml](examples/selfhealing.example.yaml).
98
+
99
+ ## Which language model?
100
+
101
+ None is required: without an `llm:` section a rule-based agent is used. To use a model, add
102
+ a few lines of configuration; no code changes, and any provider that speaks the OpenAI
103
+ chat format works (OpenAI, Ollama, Gemini's compatibility endpoint, ...) plus Anthropic.
104
+
105
+ ```yaml
106
+ llm:
107
+ provider: ollama # ollama | openai | anthropic | openai_compatible
108
+ model: llama3.2:3b
109
+ # api_key_env: OPENAI_API_KEY # the NAME of an environment variable, never the key
110
+ ```
111
+
112
+ API keys are read from environment variables only. A per-hour call budget
113
+ (`max_calls_per_hour`, default 20) bounds cost, and when the model fails or the budget is
114
+ used up, the rule-based agent answers with its confidence capped, so a person decides.
115
+
116
+ ## Approving from another terminal
117
+
118
+ When the policy needs a person, HealOps parks the incident and alerts you (console, the
119
+ state store and, if configured, email). Answer from any terminal:
120
+
121
+ ```
122
+ healops list
123
+ healops approve 3f9a1c2e --by ali
124
+ healops reject 3f9a1c2e --by ali --reason "not now"
125
+ ```
126
+
127
+ The command only records your answer in the state file; the running HealOps carries it out
128
+ through the same policy, audit and verification path. An approval that expired is never
129
+ applied. (`python -m healops ...` does the same.)
130
+
131
+ ## What is in the box
132
+
133
+ | Area | What it does |
134
+ | --- | --- |
135
+ | Detection | thresholds that must hold for several checks in a row; model-quality degradation against a baseline |
136
+ | Diagnosis | rule-based agent; LLM agent with strict JSON validation; automatic fallback; call budget |
137
+ | Policy | allow / needs approval / deny per action, retry limits, cooldowns, minimum confidence |
138
+ | Approval | human-in-the-loop queue that survives restarts; silence is never approval |
139
+ | Safety | dry-run by default; time limits on every adapter call; write-ahead audit before real actions |
140
+ | Verification | re-checks health and the incident's own metric after the action |
141
+ | State | SQLite file with observations, incidents, approvals and alerts (for dashboards) |
142
+ | Alerts | console, state store, email with rate limits |
143
+
144
+ ## Documentation
145
+
146
+ - [Adapter guide](docs/adapter-guide.md): connect your application
147
+ - [Configuration reference](docs/configuration.md): every option and its default
148
+ - [Safety and known limits](docs/safety-and-limits.md): what is guaranteed and what is not
149
+
150
+ ## Authors and acknowledgements
151
+
152
+ HealOps is the final-year project "Agentic Self-Healing MLOps".
153
+
154
+ - Ali Huzaifa (maintainer)
155
+ - Muhammad Rafeh
156
+ - Sufyan Khan
157
+ - Maaz Tariq Soomro
158
+ - Supervisor: Sir Abdullah Bin Tahir
159
+
160
+ Thanks to everyone whose feedback shaped the design.
161
+
162
+ ## License
163
+
164
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,117 @@
1
+ # Connect your application: the adapter guide
2
+
3
+ HealOps never touches your application directly. You give it an **adapter**: a plain Python
4
+ object whose methods HealOps may call. Implement only what your application really has;
5
+ HealOps checks which methods exist and offers the agent only the actions that are possible.
6
+
7
+ ## The methods
8
+
9
+ | Method | Returns | What HealOps does with it | Needed for |
10
+ | --- | --- | --- | --- |
11
+ | `get_health()` | `bool` | `False` (or an error or a timeout) means unhealthy | detecting crashes |
12
+ | `get_metrics()` | `dict[str, float]` | compares named numbers with your thresholds and baselines | slow service, errors, model quality |
13
+ | `get_drift_scores()` | `dict[str, float]` | compares drift scores with `thresholds.drift_score` | data drift |
14
+ | `restart_component(component)` | `str` | restarts one named component | action `restart_component` |
15
+ | `reload_model()` | `str` | reloads the current model artifact | action `reload_model` |
16
+ | `rollback_model()` | `str` | returns to a known-good model version | action `rollback_model` |
17
+ | `request_retraining()` | `str` | starts a retraining run (does **not** deploy the result) | action `request_retraining` |
18
+ | `request_redeployment()` | `str` | redeploys (decides what is live) | action `request_redeployment` |
19
+
20
+ Action methods return a short message describing what happened (it goes in the audit log).
21
+ If something fails, **raise an exception**: HealOps treats it as a failed action, does not
22
+ retry it by itself and asks a person.
23
+
24
+ ## Rules your adapter must follow
25
+
26
+ 1. **Return real numbers.** Metrics and drift scores must be finite numbers (`bool`, `NaN`
27
+ and infinity are ignored). Never invent a value when you cannot read one: raise instead.
28
+ 2. **Use the metric names the config watches.** `latency_ms` and `error_rate` are matched by
29
+ the `thresholds` section; model-quality metrics are matched by the names you list under
30
+ `metric_degradation` (for example `f1_score`). If a watched name is never reported,
31
+ HealOps warns once after `warn_unreported_after_checks` checks.
32
+ 3. **Be safe to call from another thread.** Every call runs in a helper thread with a time
33
+ limit (`adapter_timeouts`). Do not share objects that must stay on one thread (for
34
+ example a SQLite connection created in the main thread).
35
+ 4. **Return quickly or raise.** A read that takes longer than `read_seconds` counts as a
36
+ failed reading; an action that takes longer than `action_seconds` is escalated to a
37
+ person and **not** retried, because HealOps cannot know whether it finished.
38
+ 5. **Make actions safe to repeat.** A restart that happens twice should do no harm.
39
+ 6. **Keep secrets out of messages.** What you return and raise is written to logs (HealOps
40
+ hides well-known secret shapes, but that is a safety net, not a guarantee).
41
+
42
+ ## Example
43
+
44
+ A complete, runnable adapter for an in-memory service (swap the bodies for calls to your
45
+ real service):
46
+
47
+ ```python
48
+ # run-in-tests
49
+ from healops import HealOps
50
+
51
+
52
+ class MyServiceAdapter:
53
+ """Pretends to be a model API. Replace the bodies with real calls."""
54
+
55
+ def __init__(self):
56
+ self.up = False
57
+ self.restarts = 0
58
+
59
+ def get_health(self) -> bool:
60
+ return self.up
61
+
62
+ def get_metrics(self) -> dict[str, float]:
63
+ return {"latency_ms": 120.0, "error_rate": 0.0}
64
+
65
+ def restart_component(self, component: str) -> str:
66
+ self.restarts += 1
67
+ self.up = True
68
+ return f"restarted {component}"
69
+
70
+
71
+ config = {
72
+ "dry_run": False,
73
+ "audit_log_path": None, # keep the audit log in memory for this example
74
+ "state_db_path": None, # and the state too
75
+ "min_confidence_for_auto": 0.3,
76
+ "thresholds": {"latency_ms": 500, "consecutive_breaches": 2},
77
+ "verification": {"settle_seconds": 0, "checks": 2, "interval_seconds": 0},
78
+ "policies": {"restart_component": {"verdict": "allow", "cooldown_seconds": 0}},
79
+ }
80
+
81
+ adapter = MyServiceAdapter()
82
+ healops = HealOps(config)
83
+ healops.register("my-service", adapter, component="api-server")
84
+ for _ in range(5):
85
+ healops.tick()
86
+ assert adapter.up and adapter.restarts == 1
87
+ print("recovered after", adapter.restarts, "restart")
88
+ ```
89
+
90
+ `register(name, adapter, component=...)` needs `component` whenever the adapter can restart
91
+ components: it is the name passed to `restart_component`. Several applications can be
92
+ registered under different names; each keeps its own component.
93
+
94
+ ## Running it
95
+
96
+ ```python
97
+ healops.run() # monitor until healops.stop() is called (for example from a signal handler)
98
+ healops.run(max_ticks=10) # or a fixed number of checks (handy in tests)
99
+ healops.tick() # a single check
100
+ ```
101
+
102
+ Useful calls while it runs: `healops.pending_approvals()`, `healops.incidents()`,
103
+ `healops.approve(...)`, `healops.reject(...)`, `healops.acknowledge(incident_id)` and
104
+ `healops.stuck_calls()`. People can also answer from another terminal with the `healops`
105
+ command (see the README).
106
+
107
+ ## Start in dry-run
108
+
109
+ `dry_run` is **on by default**: proposed actions are logged and alerted, never executed.
110
+ Run like that first, read the audit log (`healops_audit.jsonl`), and only then set
111
+ `dry_run: false` and give the actions you trust the verdict `allow`.
112
+
113
+ ## Turning off the time limits
114
+
115
+ `healops.register(name, adapter, component=..., guard=False)` calls the adapter directly in
116
+ the monitoring thread. Only do this if the adapter cannot be called from another thread, and
117
+ accept that a hanging call then hangs the loop.
@@ -0,0 +1,130 @@
1
+ # Configuration reference
2
+
3
+ HealOps reads one YAML file (`HealOps.from_config("selfhealing.yaml")`) or a Python
4
+ dictionary (`HealOps({...})`). Unknown keys are **rejected**, so a typo cannot silently
5
+ switch a check off. Every option has a safe default. A commented, ready-to-edit file is
6
+ [examples/selfhealing.example.yaml](../examples/selfhealing.example.yaml).
7
+
8
+ Secrets never go in this file: where a key or password is needed you write the **name of an
9
+ environment variable** (`api_key_env`, `password_env`) and set the variable in your shell.
10
+
11
+ ## Top level
12
+
13
+ | Option | Default | Meaning |
14
+ | --- | --- | --- |
15
+ | `dry_run` | `true` | Log and alert proposed actions but never execute them. Turn off only after reading the audit log. |
16
+ | `check_interval_seconds` | `30` | Seconds between checks. |
17
+ | `min_confidence_for_auto` | `0.6` | An action the policy would allow automatically is downgraded to "needs approval" if the diagnosis confidence is below this (0 to 1). The gate only ever makes things stricter. |
18
+ | `approval_timeout_seconds` | `3600` | How long a person has to answer. After that the incident is escalated; silence is never approval. |
19
+ | `audit_log_path` | `healops_audit.jsonl` | Append-only record of every step. `null` keeps it in memory. |
20
+ | `state_db_path` | `healops_state.db` | SQLite file with observations, incidents, approvals and alerts. `null` keeps it in memory (nothing survives a restart). |
21
+ | `state_retention_days` | `30` | Observations older than this are deleted. Incidents are kept. |
22
+ | `max_recovery_attempts` | `3` | Recovery actions tried for one incident (1 to 10) before a person takes over. |
23
+ | `warn_unreported_after_checks` | `5` | Warn once about anything the config watches that the application never reported. `0` turns the warning off. |
24
+
25
+ ## `thresholds`
26
+
27
+ A threshold left out is simply not monitored. A breach must repeat for
28
+ `consecutive_breaches` checks in a row before an incident is raised, so one odd reading does
29
+ not trigger anything.
30
+
31
+ | Option | Default | Meaning |
32
+ | --- | --- | --- |
33
+ | `latency_ms` | none | Incident `high_latency` when the metric `latency_ms` is above this. |
34
+ | `error_rate` | none | Incident `high_error_rate` when the metric `error_rate` is above this (0 to 1). |
35
+ | `drift_score` | none | Incident `data_drift` when any drift score is above this. |
36
+ | `consecutive_breaches` | `3` | Breaches in a row needed (1 to 20). |
37
+
38
+ A failing health check always raises a `health_failure` incident.
39
+
40
+ ## `metric_degradation`
41
+
42
+ Model-quality metrics your application reports through `get_metrics()`, for example:
43
+
44
+ ```yaml
45
+ metric_degradation:
46
+ f1_score:
47
+ baseline: 0.90 # the normal level
48
+ max_change: 0.10 # how far it may move in the bad direction
49
+ higher_is_better: true
50
+ ```
51
+
52
+ | Option | Default | Meaning |
53
+ | --- | --- | --- |
54
+ | `baseline` | required | Normal value of the metric. |
55
+ | `max_change` | required | Allowed movement in the bad direction (greater than 0). |
56
+ | `higher_is_better` | `true` | `false` for metrics where lower is better (such as a loss). |
57
+
58
+ ## `verification`
59
+
60
+ After an action HealOps watches the application before calling it recovered.
61
+
62
+ | Option | Default | Meaning |
63
+ | --- | --- | --- |
64
+ | `settle_seconds` | `10` | Wait before the first check. |
65
+ | `checks` | `3` | Number of checks (1 to 20). All must pass for "verified". |
66
+ | `interval_seconds` | `10` | Pause between checks. |
67
+ | `max_window_seconds` | `300` | The whole wait must fit inside this. |
68
+
69
+ ## `default_policy` and `policies`
70
+
71
+ `policies` maps an action name to its rules; `default_policy` applies to every action not
72
+ listed. Actions: `restart_component`, `reload_model`, `rollback_model`, `request_retraining`,
73
+ `request_redeployment` (`run_diagnostic`, `escalate` and `no_action` are handled by HealOps).
74
+
75
+ | Option | Default | Meaning |
76
+ | --- | --- | --- |
77
+ | `verdict` | `needs_approval` | `allow` (run without asking), `needs_approval` (a person decides) or `deny` (never). |
78
+ | `max_retries` | `1` | Retries after the first run, per incident. |
79
+ | `cooldown_seconds` | `300` | Minimum time between runs of the same action on the same application. |
80
+
81
+ ## `adapter_timeouts`
82
+
83
+ Every call into your adapter has a time limit. See the
84
+ [adapter guide](adapter-guide.md) for what happens on a timeout.
85
+
86
+ | Option | Default | Meaning |
87
+ | --- | --- | --- |
88
+ | `read_seconds` | `10` | Limit for `get_health`, `get_metrics`, `get_drift_scores`. |
89
+ | `action_seconds` | `120` | Limit for restart, reload, rollback and retraining calls. |
90
+ | `max_stuck_calls` | `3` | After this many calls to one application are stuck, no new call is made until one finishes. |
91
+
92
+ ## `llm` (optional)
93
+
94
+ Leave the section out to use the rule-based agent only.
95
+
96
+ | Option | Default | Meaning |
97
+ | --- | --- | --- |
98
+ | `provider` | required | `ollama`, `openai`, `anthropic` or `openai_compatible`. |
99
+ | `model` | required | Model name as the provider knows it. |
100
+ | `base_url` | provider default | Required for `openai_compatible`. Ollama defaults to `http://localhost:11434`. |
101
+ | `api_key_env` | none | **Name** of the environment variable holding the key. Not needed for local Ollama. |
102
+ | `chat_path` | provider default | Path of the chat endpoint, for services with a different one (must start with `/`). |
103
+ | `json_mode` | `true` | Ask for a JSON reply; set `false` for services that reject it. |
104
+ | `timeout_seconds` | provider default | Per-call time limit. |
105
+ | `max_tokens` | `800` | Reply size limit. |
106
+ | `max_confidence` | `0.9` | Cap on the confidence the model may claim. |
107
+ | `max_calls_per_hour` | `20` | Budget for model calls. `null` removes the limit. |
108
+ | `use_fallback` | `true` | When the model fails or the budget is used up, the rule-based agent answers instead. |
109
+ | `fallback_confidence_cap` | `0.2` | Confidence cap for those fallback answers, so a person decides. |
110
+
111
+ ## `email` (optional)
112
+
113
+ Alerts by SMTP when an action needs approval or an incident needs a person. The email only
114
+ informs; it contains the command to run, never a link. Held-back alerts are summarised in the
115
+ next email and always kept in the state store.
116
+
117
+ | Option | Default | Meaning |
118
+ | --- | --- | --- |
119
+ | `host` | required | SMTP server. |
120
+ | `port` | `587` | SMTP port. |
121
+ | `security` | `starttls` | `starttls`, `ssl` or `none` (`none` only for localhost). |
122
+ | `username` | none | Login name; needs `password_env`. |
123
+ | `password_env` | none | **Name** of the environment variable holding the password. |
124
+ | `sender` | required | From address. |
125
+ | `recipients` | required | One to twenty addresses. |
126
+ | `timeout_seconds` | `15` | SMTP connection limit. |
127
+ | `max_emails_per_hour` | `6` | Hourly limit. |
128
+ | `cooldown_minutes` | `15` | One email per application and incident type in this time. |
129
+ | `email_in_dry_run` | `false` | Email alerts that come from dry-run mode too. |
130
+ | `subject_prefix` | `[HealOps]` | Start of every subject line. |