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.
- healops-0.1.0/LICENSE +21 -0
- healops-0.1.0/MANIFEST.in +4 -0
- healops-0.1.0/PKG-INFO +195 -0
- healops-0.1.0/README.md +164 -0
- healops-0.1.0/docs/adapter-guide.md +117 -0
- healops-0.1.0/docs/configuration.md +130 -0
- healops-0.1.0/docs/safety-and-limits.md +57 -0
- healops-0.1.0/examples/live_email_check.py +45 -0
- healops-0.1.0/examples/live_ollama_check.py +125 -0
- healops-0.1.0/examples/quickstart.py +28 -0
- healops-0.1.0/examples/selfhealing.example.yaml +131 -0
- healops-0.1.0/pyproject.toml +51 -0
- healops-0.1.0/setup.cfg +4 -0
- healops-0.1.0/src/healops/__init__.py +7 -0
- healops-0.1.0/src/healops/__main__.py +3 -0
- healops-0.1.0/src/healops/agents/__init__.py +0 -0
- healops-0.1.0/src/healops/agents/base.py +115 -0
- healops-0.1.0/src/healops/agents/factory.py +85 -0
- healops-0.1.0/src/healops/agents/fallback.py +57 -0
- healops-0.1.0/src/healops/agents/llm.py +268 -0
- healops-0.1.0/src/healops/agents/mock.py +153 -0
- healops-0.1.0/src/healops/agents/ratelimit.py +67 -0
- healops-0.1.0/src/healops/audit.py +165 -0
- healops-0.1.0/src/healops/capabilities.py +78 -0
- healops-0.1.0/src/healops/cli.py +242 -0
- healops-0.1.0/src/healops/collector.py +77 -0
- healops-0.1.0/src/healops/config.py +264 -0
- healops-0.1.0/src/healops/detection/__init__.py +0 -0
- healops-0.1.0/src/healops/detection/base.py +47 -0
- healops-0.1.0/src/healops/detection/thresholds.py +196 -0
- healops-0.1.0/src/healops/events.py +63 -0
- healops-0.1.0/src/healops/exceptions.py +82 -0
- healops-0.1.0/src/healops/facade.py +258 -0
- healops-0.1.0/src/healops/guard.py +121 -0
- healops-0.1.0/src/healops/healing/__init__.py +0 -0
- healops-0.1.0/src/healops/healing/approval.py +183 -0
- healops-0.1.0/src/healops/healing/history.py +82 -0
- healops-0.1.0/src/healops/healing/notify.py +123 -0
- healops-0.1.0/src/healops/healing/orchestrator.py +581 -0
- healops-0.1.0/src/healops/healing/policy.py +135 -0
- healops-0.1.0/src/healops/healing/smtp.py +172 -0
- healops-0.1.0/src/healops/interfaces.py +66 -0
- healops-0.1.0/src/healops/lifecycle.py +45 -0
- healops-0.1.0/src/healops/llm/__init__.py +17 -0
- healops-0.1.0/src/healops/llm/base.py +12 -0
- healops-0.1.0/src/healops/llm/http.py +60 -0
- healops-0.1.0/src/healops/llm/providers.py +231 -0
- healops-0.1.0/src/healops/loader.py +49 -0
- healops-0.1.0/src/healops/loop.py +464 -0
- healops-0.1.0/src/healops/outcomes.py +79 -0
- healops-0.1.0/src/healops/py.typed +0 -0
- healops-0.1.0/src/healops/records.py +67 -0
- healops-0.1.0/src/healops/redaction.py +72 -0
- healops-0.1.0/src/healops/registry.py +61 -0
- healops-0.1.0/src/healops/serialization.py +68 -0
- healops-0.1.0/src/healops/store.py +518 -0
- healops-0.1.0/src/healops/testing/__init__.py +15 -0
- healops-0.1.0/src/healops/testing/harness.py +119 -0
- healops-0.1.0/src/healops/testing/toy_app.py +97 -0
- healops-0.1.0/src/healops/types.py +71 -0
- healops-0.1.0/src/healops/verification/__init__.py +0 -0
- healops-0.1.0/src/healops/verification/base.py +38 -0
- healops-0.1.0/src/healops/verification/postconditions.py +163 -0
- healops-0.1.0/src/healops.egg-info/PKG-INFO +195 -0
- healops-0.1.0/src/healops.egg-info/SOURCES.txt +113 -0
- healops-0.1.0/src/healops.egg-info/dependency_links.txt +1 -0
- healops-0.1.0/src/healops.egg-info/entry_points.txt +2 -0
- healops-0.1.0/src/healops.egg-info/requires.txt +8 -0
- healops-0.1.0/src/healops.egg-info/top_level.txt +1 -0
- healops-0.1.0/tests/test_agent_base.py +171 -0
- healops-0.1.0/tests/test_approval.py +283 -0
- healops-0.1.0/tests/test_audit.py +203 -0
- healops-0.1.0/tests/test_capabilities.py +104 -0
- healops-0.1.0/tests/test_cli.py +296 -0
- healops-0.1.0/tests/test_collector.py +133 -0
- healops-0.1.0/tests/test_config.py +104 -0
- healops-0.1.0/tests/test_degradation.py +150 -0
- healops-0.1.0/tests/test_detection_base.py +77 -0
- healops-0.1.0/tests/test_docs.py +165 -0
- healops-0.1.0/tests/test_email.py +398 -0
- healops-0.1.0/tests/test_events.py +85 -0
- healops-0.1.0/tests/test_execution.py +282 -0
- healops-0.1.0/tests/test_facade.py +181 -0
- healops-0.1.0/tests/test_fallback_agent.py +201 -0
- healops-0.1.0/tests/test_guard.py +282 -0
- healops-0.1.0/tests/test_history.py +48 -0
- healops-0.1.0/tests/test_housekeeping.py +205 -0
- healops-0.1.0/tests/test_interfaces.py +90 -0
- healops-0.1.0/tests/test_lifecycle.py +95 -0
- healops-0.1.0/tests/test_llm_agent.py +355 -0
- healops-0.1.0/tests/test_llm_config.py +231 -0
- healops-0.1.0/tests/test_llm_providers.py +338 -0
- healops-0.1.0/tests/test_loader.py +91 -0
- healops-0.1.0/tests/test_loop.py +350 -0
- healops-0.1.0/tests/test_mock_agent.py +163 -0
- healops-0.1.0/tests/test_notify.py +130 -0
- healops-0.1.0/tests/test_orchestrator.py +283 -0
- healops-0.1.0/tests/test_orchestrator_audit.py +330 -0
- healops-0.1.0/tests/test_outcomes.py +111 -0
- healops-0.1.0/tests/test_persistence.py +358 -0
- healops-0.1.0/tests/test_policy.py +173 -0
- healops-0.1.0/tests/test_policy_limits.py +159 -0
- healops-0.1.0/tests/test_ratelimit.py +217 -0
- healops-0.1.0/tests/test_records.py +84 -0
- healops-0.1.0/tests/test_redaction.py +146 -0
- healops-0.1.0/tests/test_registry.py +87 -0
- healops-0.1.0/tests/test_scenarios.py +217 -0
- healops-0.1.0/tests/test_serialization.py +173 -0
- healops-0.1.0/tests/test_smoke.py +6 -0
- healops-0.1.0/tests/test_store.py +320 -0
- healops-0.1.0/tests/test_thresholds.py +179 -0
- healops-0.1.0/tests/test_toy_app.py +142 -0
- healops-0.1.0/tests/test_types.py +45 -0
- healops-0.1.0/tests/test_verification_flow.py +299 -0
- 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.
|
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).
|
healops-0.1.0/README.md
ADDED
|
@@ -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. |
|