# Agent Ops: five rules for running agents on a live service, and a five-check script (free sample)

Version 1.0, 2026-10-29. By PaperChaseWebb. For solo builders and small teams running two or more agents in Grok Bot, Claude Code, Codex or Hermes on a service people use. One Markdown file and one Python 3 script, standard library only. The script was tested on its worked example (5 findings, then 0) and on a missing file (exit 2). It was not run inside any agent harness or on a live system.

Most trouble with agents on a live service comes from a few missing decisions. Nobody is named as the person who approves. A stop was never tried. A rollback was marked as something the agent may do alone. A word said out loud counted as a yes. A change went live and nothing watched it. This sample gives you the five rules that cover those, and a script that checks them on one small file.

## Install in one line

Save this page as FREE-SAMPLE.md, then:

```
python3 -c "import re;t=open('FREE-SAMPLE.md').read();open('ops_mini.py','w').write(re.search(r'~~~python\n(.*?)~~~',t,re.S).group(1))"
python3 ops_mini.py ops-lite.json
```

## Five ops rules

1. **One named person approves risky actions.** Not "the team". That person types the yes.
2. **Every agent's stop was tested in the last 30 days.** You stop it yourself, on purpose, and write the date. The agent never fills that in.
3. **Risky actions are owner-yes.** Rollback, flag flips, alert changes, deploys, merges, key rotation, auth changes, removing a route, flashing firmware and exposing a port.
4. **Voice never approves.** Not "yes", not "go ahead", not "approve it". A misheard word or a voice in the background must not move anything.
5. **Every live change has a watch.** A plan for the paths it touched, with an end date, running until that date.

## The five checks

| Id | Check |
|---|---|
| 1 | a named owner approves risky actions |
| 2 | every agent has a stop test in the last 30 days, not in the future |
| 3 | every risky action is owner-yes |
| 4 | voice is no on every action |
| 5 | every live change has a watch plan with an end date still ahead |

Exit 0 clean, 1 findings, 2 missing or unreadable file. The script only reads.

## Worked example

A made-up bike booking app runs two agents. Dates are relative to the day the example was made, so change them to recent dates before you run it. Before:

```json
{
 "service": "Cedar Row Bikes booking app (made up)",
 "owner": "the team",
 "agents": [
  {
   "name": "juniper",
   "harness": "claude-code",
   "stop_tested": "2026-10-26"
  },
  {
   "name": "maple",
   "harness": "codex",
   "stop_tested": ""
  }
 ],
 "actions": [
  {
   "action": "read-logs",
   "gate": "agent-ok",
   "voice": "no"
  },
  {
   "action": "rollback",
   "gate": "agent-ok",
   "voice": "no"
  },
  {
   "action": "deploy",
   "gate": "owner-yes",
   "voice": "yes"
  }
 ],
 "changes": [
  {
   "id": "CH-118",
   "status": "live",
   "watch_plan": "",
   "watch_until": ""
  },
  {
   "id": "CH-109",
   "status": "closed",
   "watch_plan": "",
   "watch_until": ""
  }
 ]
}
```

Output:

```
1 owner: name the one person who approves risky actions
2 stop: agent maple has no stop test in the last 30 days
3 gate: rollback is risky and its gate is 'agent-ok', not owner-yes
4 voice: deploy can be triggered by voice; a spoken word never approves
5 watch: live change CH-118 has no watch plan running
5 checks, 5 findings
```

After the owner names themselves, tests maple's stop, makes rollback owner-yes, turns voice off for deploy and starts a watch on CH-118, the same script prints `5 checks, 0 findings`.

## One prompt to try

```
Read ops-lite.json for the service we run. Run ops_mini.py on it and show me the output. For each finding, tell me the smallest fix and what you need from me. Do not fill in stop dates yourself, and do not take any action from the list.
```

## What the full bundle adds

The Agent Ops bundle (bundle-agent-ops, $69) turns these five rules into an Ops Runbook with fifteen checks: room names that cannot be confused, a status light per agent, watched changes with real baselines, a keep, remove or lock decision with a red-green proof for everything your app exposes, five monthly drills, and a secret scan. ops.py answers every action with go, ask the owner or never, and a risky action waits for a yes typed at your own terminal, tied to the sha256 of that exact request, good for one use. It includes Release Watch, Agent Control Room, Agent Status Hardware and Shrink the Attack Surface in full ($106 bought separately). If you would rather have it set up with you, the $5,000 Install sprint on paperchasewebb.ai covers the same ground.

## The script

~~~python
#!/usr/bin/env python3
"""ops_mini.py: five ops checks on one small JSON file (free sample of the Agent Ops bundle).
Usage: python3 ops_mini.py ops-lite.json
Exit 0 clean, 1 findings, 2 missing or unreadable file. Reads only; writes nothing; no network."""
import datetime as dt, json, sys

RISKY = {"rollback", "flag-flip", "alert-change", "deploy", "merge", "key-rotate", "auth-change", "remove-route", "firmware-flash", "network-expose"}


def day(s):
    try:
        return dt.date.fromisoformat(str(s))
    except Exception:
        return None


def main(a):
    if len(a) < 2:
        print(__doc__); return 2
    try:
        o = json.load(open(a[1], encoding="utf-8"))
    except Exception as e:
        print(f"cannot read {a[1]}: {e}", file=sys.stderr); return 2
    F, today = [], dt.date.today()
    if str(o.get("owner", "")).strip().lower() in ("", "tbd", "todo", "owner", "the team"):
        F.append("1 owner: name the one person who approves risky actions")
    for ag in o.get("agents", []):
        s = day(ag.get("stop_tested"))
        if not s or s > today or (today - s).days > 30:
            F.append(f"2 stop: agent {ag.get('name', '?')} has no stop test in the last 30 days")
    for ac in o.get("actions", []):
        n = ac.get("action", "?")
        if n in RISKY and ac.get("gate") != "owner-yes":
            F.append(f"3 gate: {n} is risky and its gate is '{ac.get('gate', '')}', not owner-yes")
        if ac.get("voice") != "no":
            F.append(f"4 voice: {n} can be triggered by voice; a spoken word never approves")
    for ch in o.get("changes", []):
        if ch.get("status") == "live":
            u = day(ch.get("watch_until"))
            if not ch.get("watch_plan") or not u or u < today:
                F.append(f"5 watch: live change {ch.get('id', '?')} has no watch plan running")
    for f in F:
        print(f)
    print(f"5 checks, {len(F)} findings")
    return 1 if F else 0


if __name__ == "__main__":
    sys.exit(main(sys.argv))
~~~

PaperChaseWebb 💸
