JJoeven

Projects/Autonomous Ops Agent/Part 3

Diagnose and Propose Actions

Map symptoms to a diagnosis object and a list of proposed actions with risk, destructiveness, and expected effect — without executing them.

Diagnosis is a structured object, not a paragraph. Proposals are a list of action intents the gate can address by id. This part writes a fake diagnostician that looks at metrics+logs and emits candidates. It never calls mutate tools.

Diagnosis schema

`` { "service": str, "severity": "sev1"|"sev2"|"sev3", "hypothesis": str, "evidence": [str], "slo_burn": bool }

text

Rules of thumb (code, not LLM):

- error_rate > 10× SLO or > 0.05 → sev1 if also user-facing checkout
- else error_rate > SLO or p95 > SLO → sev2
- else sev3 / no incident

Hypothesis: if logs contain `deploy` and `last_good=` → `bad_deploy`. Elif cpu > 0.9 and errors modest → `capacity`. Else `unknown`.

Unknown is allowed. Unknown must **not** map to rollback.

## Proposal schema

``
{
  "id": "act-01",
  "tool": str,          # mutate tool name
  "args": dict,
  "destructive": bool,
  "risk": "low"|"high",
  "reason": str,
  "expected": str
}

Map:

  • bad_deploy → rollback_deploy to prev_deploy_id, destructive True, risk high
  • capacity → scale_replicas +1, destructive False (still gated if you classify scale as mutate — yes, still gated in this project)
  • unknown → page_oncall only (if you add it as low-risk; still not silent)

Never propose two mutually exclusive mutates in the same batch without saying pick_one. This project proposes one primary mutate plus optional page_oncall.

Live Pythonpython
Output
Run to execute this in your browser. Nothing is sent to a server.

Step-by-step policy split

  1. Observe with read tools (part 2).
  2. Diagnose in code or LLM constrained to the schema. If LLM, still run a validator: hypothesis in allowlist.
  3. Propose from a runbook dict keyed by hypothesis, not from free imagination. The fake functions above are the runbook.
  4. Do not execute in this part. Print proposals. Humans (you) inspect.

If you let the model invent tool: "rm_rf", the gate in part 4 must still fail closed. Runbook-first means the model chooses among known action templates.

Severity is for humans

Sev-1 pages people. The agent should not restart prod because sev1; it should propose the runbook action. Severity without a hypothesis is still page_oncall.

Watch out:A high CPU after a bad deploy can trick a capacity hypothesis. Prefer log evidence for rollback. Order your ifs with bad_deploy before capacity (as above).

Runbooks are data

Store the mapping as a dict, not only as if-statements, so a future LLM can choose a key rather than invent a tool:

{"bad_deploy": {"tool": "rollback_deploy", "destructive": True}, "capacity": {"tool": "scale_replicas", "destructive": False}, "unknown": {"tool": "page_oncall", "destructive": False}}

The diagnostician returns a hypothesis in that key set. The proposer fills args from metrics (to=prev_deploy_id). If the hypothesis is not a key, you page. This is the same allowlist idea as weather tools, applied to intents.

When an LLM writes the hypothesis, validate it with hyp in RUNBOOK. "creative" diagnoses like need_more_disk_probably must become unknown. Creativity in ops is how you get surprise restarts.

Proposals also need expected effect you can check after apply: "error_rate < slo_error". If the approved action runs and the world does not improve, the report status is unresolved not resolved. Applying is not healing.

Exercise

Add hypothesis bad_config if logs contain config checksum mismatch. Propose a non-destructive page_oncall only (no silent config rewrite). Write a case that must not rollback.

Check your understanding

If the hypothesis is unknown, what should be proposed?