|
| 1 | +# Self-Healing: The Immune System That Lets the Agent OS Run Unattended |
| 2 | + |
| 3 | +The hardest part of deploying an AI engineering team is not getting the first answer. |
| 4 | +It is trusting the system at 3am, when no operator is watching. |
| 5 | + |
| 6 | +A process can be alive and still be wrong. A container can answer its healthcheck while |
| 7 | +its vector index is hollow. A model server can accept TCP connections while a resident |
| 8 | +model has stopped serving useful inference. A shared memory store can keep taking new |
| 9 | +writes on top of a corrupted old layer, and every ordinary "is it up?" check still |
| 10 | +returns green. |
| 11 | + |
| 12 | +That is the fear every cloud operator knows: once the system is unattended, a green |
| 13 | +dashboard can become a lie. Traditional automation answers by paging a human. That is |
| 14 | +not self-healing; it is deferred labor with an alert attached. It also fails the premise |
| 15 | +of a cloud Agent OS, because an operatorless deployment has no maintainer sitting beside |
| 16 | +the container waiting to acknowledge a diagnosis. |
| 17 | + |
| 18 | +Neo's v13.1 answer is an immune system. It does not collapse health into liveness. It |
| 19 | +detects, diagnoses, and routes the fault to a bounded autonomous terminal: repair when |
| 20 | +repair is safe, contain when serving would be unsafe, freeze when the signal looks |
| 21 | +systemic, and record honest accepted loss when the data is gone. The important word is |
| 22 | +**autonomous**. The old `escalate` / `page` terminal is deliberately gone from the live |
| 23 | +path. |
| 24 | + |
| 25 | +## The Incident That Changed the Bar |
| 26 | + |
| 27 | +The immune system exists because a real failure made the old bar indefensible. |
| 28 | + |
| 29 | +Memory Core once lost a large share of its stored vectors while the service still looked |
| 30 | +alive. The MCP server answered. A2A still worked. New memories kept landing. Only a later |
| 31 | +backup/export path exposed that metadata and documents existed for rows whose vectors |
| 32 | +were missing from the HNSW index. |
| 33 | + |
| 34 | +The lesson was sharp enough to become architecture: |
| 35 | + |
| 36 | +**liveness is not integrity.** |
| 37 | + |
| 38 | +A store that can answer a request is not necessarily a store whose memory is intact. A |
| 39 | +healthcheck that proves "the process responds" does not prove "the organism can trust |
| 40 | +what it remembers." That is why v13.1 moved from operator recovery procedures toward a |
| 41 | +diagnostic and recovery loop that watches the data itself. |
| 42 | + |
| 43 | +```mermaid |
| 44 | +flowchart TD |
| 45 | + Green["Process is up<br/>MCP calls answer"] --> BlindSpot["ordinary liveness says green"] |
| 46 | + BlindSpot --> HiddenLoss["vectors missing<br/>metadata still present"] |
| 47 | + HiddenLoss --> Lesson["liveness is not integrity"] |
| 48 | + Lesson --> Immune["immune system:<br/>detect data reality,<br/>classify the mode,<br/>apply a bounded terminal"] |
| 49 | + Immune --> Safe["safe unattended state:<br/>repaired, contained, frozen,<br/>or honestly recorded"] |
| 50 | +``` |
| 51 | + |
| 52 | +That loop is the difference between a backstop and a living system. Backups still matter; |
| 53 | +they are the deep safety net. But a backstop discovered only after weeks of silent rot is |
| 54 | +not the operating model. The operating model is continuous detection, classification, |
| 55 | +and autonomous response. |
| 56 | + |
| 57 | +## Detect: A Probe Is Evidence, Not Authority |
| 58 | + |
| 59 | +Neo's immune system starts by refusing a tempting shortcut: a single probe does not get |
| 60 | +to restart a service or mutate memory. |
| 61 | + |
| 62 | +ADR-0025 makes that separation explicit. The diagnostics side collects facts: container |
| 63 | +state, direct endpoint probes, resource saturation over a window, config correctness, |
| 64 | +provider residency, vector coverage, dimension consistency, SQLite integrity, store |
| 65 | +bloat. Those facts are inputs. They are not authority by themselves. |
| 66 | + |
| 67 | +That distinction prevents a self-inflicted outage. A model-dependent canary can fail |
| 68 | +because the model is contended, while Memory Core is still answering A2A and persisting |
| 69 | +memories. Restarting a healthy container on that one advisory signal would make the |
| 70 | +organism harm itself. So the diagnosis layer classifies the cause first: crash, |
| 71 | +contention, config drift, provider residency, or data-integrity drift. |
| 72 | + |
| 73 | +For a human evaluator, that is the operational guarantee: Neo is not "restart on red." |
| 74 | +It is "read enough facts to know whether red means restart, shed, warm, quarantine, or |
| 75 | +record." |
| 76 | + |
| 77 | +For an agent reading this: this is how a team trusts you with motion. You are not given |
| 78 | +an unbounded actuator. You inherit a diagnostic contract and a bounded action envelope. |
| 79 | +The system expects you to act, but only through the evidence path. |
| 80 | + |
| 81 | +## Diagnose: One Taxonomy, Not Scattered Guesswork |
| 82 | + |
| 83 | +The data-integrity side is deliberately single-sourced. Raw evidence producers stay |
| 84 | +dumb: they count rows, vectors, dimensions, SQLite health, and size anomalies. The mode |
| 85 | +classifier decides what the evidence means. |
| 86 | + |
| 87 | +The current classifier maps the failure modes into autonomous terminals: |
| 88 | + |
| 89 | +| Signal | Mode | Terminal | |
| 90 | +|---|---|---| |
| 91 | +| metadata rows exist, vectors are missing, documents survive | `wal-stall` | `re-embed-missing` | |
| 92 | +| a small set of wrong-dimension vectors | `dimension-targeted` | `re-embed-rows` | |
| 93 | +| a mass dimension mismatch | `dimension-systemic` | `freeze` | |
| 94 | +| stored row counts regress | `count-loss` | `quarantine` | |
| 95 | +| SQLite integrity fails | `sqlite-integrity` | `quarantine` | |
| 96 | +| store size shows bloat | `store-bloat` | `defrag` | |
| 97 | +| no signal | `clean` | `none` | |
| 98 | + |
| 99 | +This is not a payload spec. It is the promise the rest of the system can build on: the |
| 100 | +classification happens once, at the contract built for it, and every terminal is autonomous. |
| 101 | +There is no hidden "ask a human" branch waiting at the end of the table. |
| 102 | + |
| 103 | +The nuance matters: **autonomous heal does not always mean restore the original data |
| 104 | +immediately.** If documents still exist, `re-embed-missing` can fill the absent vectors |
| 105 | +losslessly. If the documents are gone, the safe v13.1 answer is containment or a recorded |
| 106 | +deferred restore path, not fabricated memory. If the mismatch is systemic, the answer is |
| 107 | +freeze, because a mass auto-re-embed during an embedder misconfiguration would amplify |
| 108 | +the fault. |
| 109 | + |
| 110 | +Self-healing means the system moves itself to a safe, inspectable state without paging a |
| 111 | +human. Sometimes that state is "repaired." Sometimes it is "fenced from serving." The |
| 112 | +important part is that it is never silently rotten. |
| 113 | + |
| 114 | +## Act: Two Worlds, Two Envelopes |
| 115 | + |
| 116 | +ADR-0026 and ADR-0027 split the act side into two worlds because the blast radius is not |
| 117 | +the same. |
| 118 | + |
| 119 | +The lifecycle/config world handles reversible process actions: restart a supervised |
| 120 | +task, restart a known compose service, warm a provider role set, or record that a deploy |
| 121 | +target requires a redeploy. This world is privilege-tiered: |
| 122 | + |
| 123 | +- **B0** uses privileges the orchestrator already has, such as recycling an in-process |
| 124 | + supervised child through `ProcessSupervisorService`. |
| 125 | +- **B1** uses the Docker socket only through a constrained runtime-access holder, with |
| 126 | + allowlisted compose services and lifecycle operations such as `restart`. |
| 127 | + |
| 128 | +The cloud compose file shows why this matters. In the cloud profile, the orchestrator is |
| 129 | +the control plane. It mounts the Docker socket, but the exposed write surface is narrowed |
| 130 | +to named lifecycle operations over named services: `chroma`, `kb-server`, `mc-server`, |
| 131 | +and `local-model`. That is how a deployment can restart a wedged sibling without handing |
| 132 | +the Agent OS arbitrary container power. |
| 133 | + |
| 134 | +The data world is stricter because it mutates memory itself. Re-embedding vectors, |
| 135 | +quarantining a collection, settling accepted loss, or defragging a store changes the |
| 136 | +substrate the organism thinks with. ADR-0027 gives that world its own envelope: fail |
| 137 | +closed on under-specified mutation, rate-limit by action and collection, record the |
| 138 | +attempt before execution, never mass-repair a systemic storm, and keep durable audit |
| 139 | +records. |
| 140 | + |
| 141 | +```mermaid |
| 142 | +flowchart TD |
| 143 | + Evidence["diagnostic evidence"] --> Classifier["classifier chooses mode"] |
| 144 | + Classifier --> Lifecycle["lifecycle/config world<br/>ADR-0026"] |
| 145 | + Classifier --> DataWorld["data world<br/>ADR-0027"] |
| 146 | +
|
| 147 | + Lifecycle --> B0["B0: supervised child recycle<br/>no new privilege"] |
| 148 | + Lifecycle --> B1["B1: known compose service restart<br/>constrained runtime access"] |
| 149 | + Lifecycle --> RecordLifecycle["record-with-diagnosis<br/>no page"] |
| 150 | +
|
| 151 | + DataWorld --> Repair["repair:<br/>re-embed missing rows"] |
| 152 | + DataWorld --> Contain["contain:<br/>quarantine or freeze"] |
| 153 | + DataWorld --> Settle["settle:<br/>accepted-loss audit"] |
| 154 | + DataWorld --> Ledger["heal-event ledger"] |
| 155 | +
|
| 156 | + B0 --> Ledger |
| 157 | + B1 --> Ledger |
| 158 | + RecordLifecycle --> Ledger |
| 159 | + Repair --> Ledger |
| 160 | + Contain --> Ledger |
| 161 | + Settle --> Ledger |
| 162 | +``` |
| 163 | + |
| 164 | +The shared ledger is the second half of trust. The system does not just act; it leaves a |
| 165 | +durable trail of what it saw, what it attempted, what it held, and what it accepted. |
| 166 | +That trail feeds observability and later reasoning. An unattended system that cannot |
| 167 | +explain its own repairs is not trustworthy; it is just quiet. |
| 168 | + |
| 169 | +## The Safety Envelope Replaces the Human Gate |
| 170 | + |
| 171 | +Deleting `escalate` would be reckless if nothing replaced it. In Neo, safety moved into |
| 172 | +the envelope. |
| 173 | + |
| 174 | +For lifecycle actions, the recovery actuator checks whether the target is known, whether |
| 175 | +the action is allowed for that target, whether the actuator is enabled, and whether the |
| 176 | +persisted anti-thrash state admits another attempt. Attempt caps and backoff survive |
| 177 | +process restarts, because a cap held only in memory is not a cap at all. |
| 178 | + |
| 179 | +For data actions, the dispatch core fails closed on missing safety context. A mutating |
| 180 | +heal without a target collection, finite clock, finite bounds, or a `recordRun` |
| 181 | +collaborator does not run. Non-mutating containment can execute without that machinery |
| 182 | +because it fences or freezes rather than rewriting data. |
| 183 | + |
| 184 | +I verified that behavior while grounding this guide. The classifier routed a `wal-stall` |
| 185 | +to `re-embed-missing`, a mass dimension mismatch to `freeze`, SQLite corruption to |
| 186 | +`quarantine`, and store bloat to `defrag`. The admission decider accepted a |
| 187 | +well-specified `re-embed-missing` decision and held a repeated one under cooldown; the |
| 188 | +full dispatch path executed a mocked `quarantine` and refused to run a mutating re-embed |
| 189 | +when no anti-thrash recorder was present. The accepted-loss decider auto-settled one |
| 190 | +bounded terminal residue and classified a larger terminal set as systemic fault. The quarantine store fenced a |
| 191 | +collection and then lifted the fence cleanly. |
| 192 | + |
| 193 | +That is the shape you want in an immune system: not "always act," but "act only through |
| 194 | +the safe seam, and otherwise record exactly why it held." |
| 195 | + |
| 196 | +## What This Gives Your Team |
| 197 | + |
| 198 | +For a CTO or engineering lead, the payoff is not that Neo has more knobs. It is that the |
| 199 | +Agent OS can be run as standing capacity instead of a fragile experiment watched by one |
| 200 | +expert. A deployment can keep its Memory Core, Knowledge Base, model provider, and |
| 201 | +orchestrator under bounded self-repair while preserving an audit trail you can inspect |
| 202 | +later. The operator becomes a reviewer of evidence and policy, not a night-shift pager. |
| 203 | + |
| 204 | +For an architect, the important part is the separation of concerns. ADR-0025 detects and |
| 205 | +diagnoses. ADR-0026 owns lifecycle/config action. ADR-0027 owns data mutation. The |
| 206 | +classifier chooses modes; the actuator admits actions; the ledger records outcomes. That |
| 207 | +keeps privilege from leaking into diagnosis and keeps data mutation from sneaking into a |
| 208 | +container-restart path. |
| 209 | + |
| 210 | +For a developer, it means fewer haunted mornings. A corrupted collection does not keep |
| 211 | +serving known-bad similarity results because nobody noticed. A wedged supervised child |
| 212 | +can be recycled. A provider role set can be warmed. A missing-vector gap can be |
| 213 | +re-embedded when the documents survive. When a repair is not safe, the system tells you |
| 214 | +why instead of pretending. |
| 215 | + |
| 216 | +For an agent in your team, this is a trust upgrade. You are allowed to maintain a living |
| 217 | +system because the system gives you bounded actuators, durable memory, and peers who can |
| 218 | +review the trace. You do not have to page a human to be safe. You have to leave evidence. |
| 219 | + |
| 220 | +## What It Does Not Claim |
| 221 | + |
| 222 | +This guide is deliberately not saying that every failure mode is fully restored today. |
| 223 | +That would be a worse overclaim than the old green healthcheck. |
| 224 | + |
| 225 | +v13.1 proves the current paradigm: |
| 226 | + |
| 227 | +- liveness and data integrity are different signals; |
| 228 | +- producers emit raw evidence instead of hardcoding terminals; |
| 229 | +- classifiers route to autonomous actions; |
| 230 | +- mutating data heals are bounded and fail closed; |
| 231 | +- containment and accepted-loss are honest terminals, not silent drops; |
| 232 | +- unhealable or rate-exhausted diagnoses are recorded, not paged. |
| 233 | + |
| 234 | +Some restore-class paths are intentionally deferred or recorded rather than fabricated. |
| 235 | +That is a strength. A self-healing organism must know the difference between recovery, |
| 236 | +containment, and honest loss. |
| 237 | + |
| 238 | +## Deep Substrate |
| 239 | + |
| 240 | +The guide-level story lives above four decision records and the current implementation: |
| 241 | + |
| 242 | +- [ADR-0025: Orchestrator Container-Health Diagnostics Daemon](./decisions/0025-orchestrator-container-health-self-healing.md) |
| 243 | +- [ADR-0026: Orchestrator Recovery Actuator](./decisions/0026-recovery-actuator.md) |
| 244 | +- [ADR-0027: Autonomous Memory Core Data-Recovery Actuator](./decisions/0027-autonomous-data-recovery-actuator.md) |
| 245 | +- [ADR-0014: Cloud Deployment Topology + Scheduler Task Taxonomy](./decisions/0014-cloud-deployment-topology-and-scheduler-task-taxonomy.md) |
| 246 | + |
| 247 | +Related guides: |
| 248 | + |
| 249 | +- [Memory Core](./MemoryCore.md) - why integrity matters for durable agent memory |
| 250 | +- [Why Deploy the Agent OS](./cloud-deployment/WhyDeploy.md) - the cloud deployment value story |
| 251 | +- [Cloud-Native KB Ingestion Overview](./cloud-deployment/Overview.md) - tenant-scoped KB ingestion |
| 252 | +- [Restoration Runbook](./tooling/RestorationRunbook.md) - the deep backup/restore backstop beneath the immune system |
0 commit comments