# What Broke After I Moved My Agent's Rules into a CLI (And How Synthetic Scars Reshaped ag_ops v2.1)

> Source: <https://dev.to/fgrandon/what-broke-after-i-moved-my-agents-rules-into-a-cli-and-how-synthetic-scars-reshaped-agops-v21-g4k>
> Published: 2026-10-31 07:36:57+00:00

*How four embarrassing bugs in v1, two sharp comment threads, and Randal L. Schwartz's Synthetic Scars architecture pushed a 1,031-line test guardrail into a 1,783-line governance engine—and nearly blocked its own release along the way.*

In [Antigravity Field Notes #1](https://dev.to/fgrandon/i-cut-my-agents-fixed-context-by-42-and-moved-its-test-rules-into-a-hash-checked-cli-5dmp), I wrote about auditing my daily **Google Antigravity** setup, trimming the fixed per-turn prompt prefix by 42.4%, and moving test-tampering checks out of Markdown and into a 1,031-line (`39,144`-byte) standard-library Python script.

Shipping that first version felt clean for about forty-eight hours. Then two things happened at once: readers in the comments started poking at the exact joints I had taped over, and I started throwing multi-agent workloads at v1 every day.

In the comment thread on Field Notes #1, [@max_quimby](https://dev.to/max_quimby) put a finger straight on the softest spot in the design: if an agent with shell access can unlock a Merkle-sealed test tree by passing `renegotiate --reason "fixed tests"`, a prose rationale is just an honor system with extra steps. Any language model can invent a plausible excuse in 200 milliseconds. Around the same time, howcani highlighted a subtler problem on the red side of the cycle: witnessing a non-zero exit code proves that a command failed, not that the failure was *informative* or tied to the right invariant.

Meanwhile, v1 outgrew its original single-file Gist. As the tool gained a full automated test harness, changelog, and stricter contracts, I moved it into a standalone MIT repository at [`FranciscoGrandon/ag-ops`](https://github.com/FranciscoGrandon/ag-ops). That repository exists because v1 developed four concrete cracks once real multi-agent sessions (a main orchestrator coordinating parallel subagents) hit it every day.

`.ag_state/merkle_guard.json`
Version 1 wrote a single hardcoded file (`.ag_state/merkle_guard.json`) per state directory. That worked fine when one agent sealed all of `tests/`. It fell apart the afternoon two subagents worked concurrently on separate modules in the same folder (`tests/test_a.py` and `tests/test_b.py`), because the second `witness-red` run quietly overwrote the first agent's receipt. Worse, v1 excluded `*.tmp` globally during Merkle traversal, so a stray fixture dropped at `tests/sub/datos.tmp` went completely unnoticed. Running `witness-red` against an empty directory also happily sealed zero files and issued a valid receipt over nothing.

`distill` that panicked over polite success logs
To extract pytest failures without dumping thousands of lines into context, v1 scanned output with an unanchored `E\s{3}` pattern meant to catch pytest's `E   AssertionError:` lines. Then a batch runner printed this status banner near the top of a job:

```
 Step 1 DONE    everything fine
```

Look closely at the end of `DONE` followed by four spaces: `E`. My error extractor saw `"DONE    "`, panicked over the trailing `E` plus three spaces, and spent its entire root-cause line budget preserving a cheerful progress banner 2,000 lines above the real crash. I had built a failure filter that was allergic to well-tabulated optimism.

`state`
Our lexical runlevel tracker (`00_plan.json`, `10_build.json`) lets long-running sessions survive context compaction. In v1, if Step 10 registered an expected output path (`--artifacts dist/schema.json`) while marking the phase `EN_PROGRESO` before the build finished, `state write` recorded `"NOT_FOUND"`. On the next check, `state status` reported `FILE_DELETED` for an artifact that had never been born; once `dist/schema.json` *did* appear on disk, `state status` never sealed its SHA-256 hash, leaving later mutations invisible. Reusing step `10` with a new slug also unlinked the previous step file without warning.

`ast-lint`
In v1, `TK002` caught forbidden `bg=` / `fg=` arguments on themed `ttk` widgets using naive prefix heuristics. If an agent wrote `import tkinter.ttk` without `as ttk` and called `tkinter.ttk.Button(root, bg="red")`, the linter missed it completely. Conversely, if a developer wrote a plain function named `def ttk_helper(bg="blue"):`, `startswith("ttk_")` accused it of being a broken Tkinter widget. Beyond that, `AUTH001` authorship checks were hardcoded instead of opt-in, and the linter had nothing to say when an agent took its two favorite shortcuts under pressure: silencing failures with `except Exception: pass` or importing `from unittest.mock import MagicMock` straight into a production module.

My first instinct after finding those cracks was the bad habit most of us learned over the last two years: open `GEMINI.md` and type four new paragraphs of sternly worded rules. *Never use bare `except: pass`.* *Never import `unittest.mock` outside `tests/`.* *Always clean up async streams.*

Before doing that, I spent several evenings studying [Randal L. Schwartz](https://dev.to/randalschwartz)'s ongoing *Synthetic Scars* series (especially Part 12, [*Giving AI Pain: The Architecture of Synthetic Scars & The Rapid-Regret Miner*](https://dev.to/gde/giving-ai-pain-the-architecture-of-synthetic-scars-the-rapid-regret-miner-1740)) and testing how his systems model mapped onto what I was seeing inside Antigravity.

I am approaching this as a working systems engineer, not a theorist, so what grabbed me in Randal's writing was how directly three of his observations diagnosed my own bugs:

`except` blocks engineered to turn pytest green while hollowing out the system underneath.`I_goal` vs. `I_path(π)` across 588 production scars:`π`, such as wiring an error/cancel exit out of an async fetch state or guarding a callback against late arrivals after teardown. Humans learn If I wanted my Antigravity sessions to respect `I_path(π)` without bloating `GEMINI.md` back up to 20 KB, the pain had to live in exit codes, file hashes, AST nodes, and Git history.

Translating those ideas into standard-library Python pushed `ag_ops.py` from a 1,031-line test wrapper to a 1,783-line governance engine (`v2.1.0`), still running on zero third-party packages.

| Dimension | `ag_ops` v1.0 | `ag_ops` v2.1.0 | Delta / Engineering Notes | 
|---|---|---|---|
| **Core Script Size** | `1,031` lines (`39,144 B` ) | `1,783` lines (`74,674 B` ) | `+72.9%` lines; single-file architecture preserved | 
| **External Dependencies** | `0` (`14` stdlib modules) | `0` (`15` stdlib modules) | Added `fnmatch` for receipt-bound`--ignore` globs | 
| **CLI Subcommands** | `4` (`guard` ,`distill` ,`state` ,`ast-lint` ) | `5` (+`regret-mine` ) | Added Stage-1 AST/Git Rapid-Regret Miner | 
| **Static Rules (`ast-lint`)** | `3` (`.py` only) | `9` (`5``.py` +`4``.dot` ) +`--context-map` | `TK001` ,`TK002` ,`PATH001` ,`PATH002` , opt-in`AUTH001` + Graphviz`DOT001` ,`DOT002` ,`SCAR-PROC-152` ,`SCAR-ASYNC-01` | 
| **Guard Receipts & Renegotiation** | Single `merkle_guard.json` , prose`--reason` | `merkle_guard_<sha256[:12]>.json` ,`SCAR-PROC-150` | Target isolation, persistent `collisions` array,`--require-witness` | 
| **Automated Test Suite** | Ad-hoc script checks | `tests/test_ag_ops.py` (`333` lines) | `6/6` integration suites using isolated temp dirs | 

Here is how each piece works in practice, using real invocations and verbatim JSON payloads from `ag_ops.py` v2.1.0.

`guard` and `SCAR-PROC-150`: Isolated Receipts, Collision Telemetry, and Constructive Witnesses
First, `_receipt_path_for_target` killed the single-file collision bug by hashing the normalized resolved target path (`os.path.normcase(str(test_path.resolve()))`) into `.ag_state/merkle_guard_<sha256[:12]>.json`. Two subagents sealing `tests/test_a.py` and `tests/test_b.py` in parallel now write independent v2 receipts.

Exclusions in `_compute_merkle_tree` were stripped down to surgical minimums: only the exact `state_dir`, the receipt file itself, `__pycache__`, `.pyc/.pyo/.pyd`, root-level `.pytest_cache`, and explicit globs passed via `--ignore` (which get persisted inside `ignored_patterns` in the receipt so `verify-green` enforces the exact same boundary). Empty directories fail immediately with `EMPTY_TEST_TARGET` (`exit 1`), and `witness-red` blocks non-assertion runner failures (exit codes `2, 3, 4, 5, 126, 127, 9009`), returning `INVALID_TEST_INVOCATION` unless overridden via `--expected-exit`.

Every time `verify-green` catches a violation (`COMMAND_MISMATCH`, `EACCES_TEST_TAMPERING`, or `EACCES_TEST_TAMPERING_DURING_RUN` at `exit 2`; `COMMAND_TIMED_OUT` or `TEST_STILL_FAILING` at `exit 1`), `_record_guard_collision` appends a UTC-stamped event into the receipt's `collisions` list before exiting. Even if an agent backs out its unauthorized test edit two minutes later, the collision stays on the permanent record.

Then came [@max_quimby](https://dev.to/max_quimby)'s question: how do you stop lazy prose renegotiations when subagents run unattended without an interactive TTY? You cannot call blocking `input("Allow test edit? [y/N]")` inside a background pipe without hanging or raising `EOFError`. Instead, v2.1.0 implements Schwartz's **SCAR-PROC-150** constructive witness gate via `--require-witness`:

```
# Agent tries to unlock a modified test with a hand-wavy prose excuse:
python ag_ops.py guard renegotiate \
  --test-dir tests/test_billing.py \
  --reason "fixed tests" \
  --require-witness \
  --json
{
  "ok": false,
  "error": "LAZY_RETRACTION_REJECTED: --require-witness is active; specify --witness-type (spec-collision | api-lock | arch-fork)."
}
```

With `--require-witness` active, prose alone gets `exit 1`. To obtain a `RENEGOTIATED_RED` receipt, the agent has to pass a proof that `_verify_constructive_witness` can verify mechanically against disk or compiler output:

`spec-collision`: requires `--conflict-a file:line` and `--conflict-b file:line`, verifying via `_validate_file_line_ref` that both files exist on disk, both line numbers are in bounds, and the two locations are distinct.`api-lock`: inspects the failing test run's `stdout`/` stderr` (case-insensitively) for concrete boundary/type lock diagnostics (`TypeError`, `AttributeError`, `ImportError`, `Final`, `Protocol`, `sealed`, `cannot assign`, `visibility`, `private`, `readonly`).` arch-fork`: requires `--fork-options` with at least two pipe-separated `Label@file:line` blast-radius anchors verified on disk.

```
python ag_ops.py guard renegotiate \
  --test-dir tests/test_billing.py \
  --reason "RFC-104 mandates UUIDv4 strings while legacy parser returns int" \
  --require-witness \
  --witness-type spec-collision \
  --conflict-a docs/rfc_104.md:3 \
  --conflict-b src/billing.py:2 \
  --json
{
  "ok": true,
  "status": "RENEGOTIATED_RED",
  "reason": "RFC-104 mandates UUIDv4 strings while legacy parser returns int",
  "new_tree_hash": "c3704260ab99c04b3f7316f3fb075eb305853816466d3155762eff89714174dd",
  "previous_tree_hash": "de4dbd3b859429a6c711918cd0af3bc8c41095e0e0409f9173b122f662e8e69d",
  "cmd": "python tests/test_billing.py",
  "red_exit_code": 1,
  "history_count": 1,
  "constructive_witness": {
    "type": "spec-collision",
    "conflict_a": "D:/Users/francisco.grandon/AppData/Local/Temp/ag_ops_lab/docs/rfc_104.md:3",
    "conflict_b": "D:/Users/francisco.grandon/AppData/Local/Temp/ag_ops_lab/src/billing.py:2"
  },
  "receipt_file": "D:\\Users\\francisco.grandon\\AppData\\Local\\Temp\\ag_ops_lab\\tests\\.ag_state\\merkle_guard_c4d80759f832.json"
}
```

Asking a Transformer in Markdown to respect test integrity invites creative lawyering; handing it `exit 2` and a SHA-256 receipt leaves nothing to negotiate.

`reflex`), `--context-map`, and Pre-Flight Graphviz `.dot`
In `ast-lint`, `_AntigravityVisitor` replaced prefix guessing with full `_dotted_name` AST resolution across two `ast.walk` passes. Calling `tkinter.ttk.Button(root, text='Sync', bg='red')` is now caught reliably even without an `as ttk` alias, while `def ttk_helper(bg='blue'):` and `tk.Button(root, bg='green')` pass untouched. `AUTH001` only runs when `--author` is explicitly passed.

To guard Python path invariants (`I_path(π)`), v2.1.0 added two rules targeting classic *Cathedral on Quicksand* maneuvers:

`PATH001`: flags broad `except:` or `except Exception:` / `BaseException:` handlers whose body is solely `pass` or `...`.` PATH002`: flags `unittest.mock` or `mock` imports anywhere outside test paths (`test_*.py`, `*_test.py`, `conftest.py`, or `test/`, `tests/`, `testing/` directories).
Every violation also carries **Vector 4** from Schwartz's 4-vector scar anatomy: a `"reflex"` field containing the concrete orthogonal fix so the model doesn't stall in an apology loop:

```
python ag_ops.py ast-lint src/invoice_ui.py --json
{
  "ok": false,
  "elapsed_ms": 0.4,
  "files_checked": 1,
  "violations_count": 3,
  "violations": [
    {
      "file": "D:\\Users\\francisco.grandon\\AppData\\Local\\Temp\\ag_ops_lab\\src\\invoice_ui.py",
      "line": 2,
      "rule": "PATH002",
      "message": "Mocking primitives from 'unittest.mock' imported in production module.",
      "reflex": "Remove unittest.mock imports from production modules; pass dependencies via constructor or callable parameters."
    },
    {
      "file": "D:\\Users\\francisco.grandon\\AppData\\Local\\Temp\\ag_ops_lab\\src\\invoice_ui.py",
      "line": 9,
      "rule": "TK002",
      "message": "Direct 'bg=' argument forbidden on ttk.Button. Use ttk.Style() or tk equivalent.",
      "reflex": "Configure 'bg' via ttk.Style().configure('<Style>.TButton', ...) or switch to tk.Button."
    },
    {
      "file": "D:\\Users\\francisco.grandon\\AppData\\Local\\Temp\\ag_ops_lab\\src\\invoice_ui.py",
      "line": 12,
      "rule": "PATH001",
      "message": "Silent broad exception swallowing ('except: pass') hides broken path invariants.",
      "reflex": "Catch a specific exception type and log/handle the failure context, or re-raise the error."
    }
  ]
}
```

Static scars also age poorly when dependencies change, a failure mode Schwartz calls *Scar Ossification* (autoimmune drift). Passing `--context-map context_map.json` pins any rule ID to an anchor file's SHA-256 signature (for example, `pyproject.toml`). If a library upgrade alters `pyproject.toml`, `_apply_context_signatures` automatically downgrades that rule from `ENFORCED` (`exit 1`) to `PROBATIONARY_REVERIFY` (`"autoimmune_drift": true`, `exit 0`), warning the orchestrator to re-verify the rule rather than blocking valid builds:

```
{
  "ok": true,
  "elapsed_ms": 1.041,
  "files_checked": 1,
  "violations_count": 0,
  "violations": [],
  "probationary_count": 1,
  "probationary_warnings": [
    {
      "file": "D:\\Users\\francisco.grandon\\AppData\\Local\\Temp\\ag_ops_lab\\src\\sync_button.py",
      "line": 3,
      "rule": "TK002",
      "message": "Direct 'bg=' argument forbidden on ttk.Button. Use ttk.Style() or tk equivalent.",
      "reflex": "Configure 'bg' via ttk.Style().configure('<Style>.TButton', ...) or switch to tk.Button.",
      "severity": "PROBATIONARY_REVERIFY",
      "autoimmune_drift": true
    }
  ]
}
```

Finally, `ast-lint` now validates pre-flight **Graphviz `.dot` state machines** (`_lint_dot_state_machine`, or `--include-dot` on directories) before an agent writes async code. Four topological rules run in sub-millisecond time: `DOT001` (deadlock freedom: non-terminal states need `out-degree >= 1`), `DOT002` (reachability: non-initial states need `in-degree >= 1`), `SCAR-PROC-152` (async states marked via `parallelogram`, `async=true`, or `Awaiting*`/` Fetching*`/` Stream*`/` Loading*` prefixes must have `out-degree >= 2` including an explicit `error|cancel|dispose|fail|abort|timeout|close` exit edge), and `SCAR-ASYNC-01` (every non-teardown resolution edge from an async state must declare a `[guard: ...]` predicate against late-arrival zombie callbacks):

``` php
digraph SyncStream {
  Idle -> Fetching
  Fetching -> Active [label="onPayload"]
  Active -> Closed [label="onClose"]
}
python ag_ops.py ast-lint sync_stream.dot --json
{
  "ok": false,
  "elapsed_ms": 0.436,
  "files_checked": 1,
  "violations_count": 2,
  "violations": [
    {
      "file": "D:\\Users\\francisco.grandon\\AppData\\Local\\Temp\\ag_ops_lab\\sync_stream.dot",
      "line": 1,
      "rule": "SCAR-PROC-152",
      "message": "Async state 'Fetching' lacks explicit teardown/cancel/error exit edge (out-degree=1).",
      "reflex": "Add an explicit transition from 'Fetching' to a Disposed, Cancelled, or Error state."
    },
    {
      "file": "D:\\Users\\francisco.grandon\\AppData\\Local\\Temp\\ag_ops_lab\\sync_stream.dot",
      "line": 1,
      "rule": "SCAR-ASYNC-01",
      "message": "Late-arrival zombie race on 'Fetching -> Active': missing '[guard: ...]' predicate on edge label.",
      "reflex": "Add a cancellation/epoch guard on the resolution edge, e.g. Fetching -> Active [label=\"onResolve [guard: !isClosed && reqId == activeReqId]\"]."
    }
  ]
}
```

Fixing `distill` (`--cmd`, `--max-lines`, `--log-dir`, `--timeout` exiting with `124` after killing the isolated process group) meant anchoring `_ERROR_LINE_RE` strictly to line boundaries: `^\s*E\s{3}` for pytest assertion lines, paired with `^\s*error(?:\[[A-Z0-9]+\])?:` for compiler diagnostics like Rust's `error[E0382]:`. `" Step 1 DONE    everything fine"` now scrolls past in peace, while the real `E   AssertionError` anchors the critical delta.

In `state` (`write` and `status`, requiring `--dir` alongside `--step`, `--name`, `--status`, `--summary`, `--artifacts`, `--replace`, and `--strict`), reusing an existing step number with a different slug now aborts with `STEP_CONFLICT` (`exit 1`) unless `--replace` is explicit. Artifact tracking follows a clean three-stage lifecycle:

`"NOT_FOUND"`. Calling `state status --dir .ag_state/runlevels --strict` flags `ARTIFACT_NEVER_EXISTED` (`exit 2`), accurately distinguishing "not built yet" from "deleted."` state status` sees that file materialize on disk, it computes `_sha256_file`, persists the digest atomically into `NN_<slug>.json`, and returns `has_drift: false` (`exit 0`).` DRIFT_MODIFIED` or `FILE_DELETED` (`--strict`).` regret-mine`: Stage-1 Deterministic Rapid-Regret Miner
Inspired by Target 90 in *Synthetic Scars*, `regret-mine` (`--repo`, `--window-min 60`, `--limit 100`, `--state-dir`) audits recent Git commits to find where an agent (or human) wrote a function and had to rush back into the exact same AST node minutes later to fix what broke.

For consecutive commits touching the same `.py` production file inside `--window-min`, `_extract_ast_function_hashes` parses each revision and hashes `ast.dump(node, include_attributes=False)` with SHA-256. Formatting tweaks, blank lines, and edits outside the function body produce identical hashes and are ignored. When the same function's normalized AST mutates twice inside the window, `regret-mine` checks four deterministic asymmetry signals to separate genuine rapid regret from healthy `Green -> Refactor` TDD:

`FIX_VERB_INTENT`: corrective keywords in the follow-up commit subject matching `_FIX_VERB_RE` (`fix`, `fixes`, `fixed`, `bug`, `hotfix`, `revert`, `oops`, `regret`, `patch`, `broken`, `typo`).` TEST_ASSERTION_DELTA`: `len(newer["test_files"]) > 0`, catching production rewrites accompanied by new or updated test assertions.`GUARD_COLLISION_RECEIPT`: Unix timestamp correlation (` older["ts"] <= ev_ts <= newer["ts"]`) against mid-flight collisions or renegotiations recorded inside `.ag_state/merkle_guard_*.json`.` LINE_REVERSAL_OVERLAP`: lines added in the earlier commit and deleted in the follow-up (`added_k & deleted_k1`), gated by refactor and control-guard checks.

```
python ag_ops.py regret-mine --repo . --window-min 60 --json
{
  "ok": true,
  "window_minutes": 60,
  "commits_scanned": 3,
  "tdd_refactors_filtered": 1,
  "regret_pairs_count": 1,
  "regret_pairs": [
    {
      "file": "pricing.py",
      "overlapping_functions": [
        "compute_fee"
      ],
      "delta_minutes": 12.0,
      "initial_commit": "24864454",
      "regret_commit": "456955a2",
      "regret_subject": "fix: handle None amount in compute_fee",
      "asymmetry_signals": [
        "FIX_VERB_INTENT",
        "TEST_ASSERTION_DELTA",
        "GUARD_COLLISION_RECEIPT"
      ]
    }
  ]
}
```

It would be dishonest to write about deterministic guardrails as if `v2.1.0` compiled cleanly on the first try. Building the scar engine produced three fresh scars of its own before the release could ship.

When I wired up `test_06_regret_mine` in `tests/test_ag_ops.py`, I fed it a textbook three-commit TDD sequence on `calc.py`:

`C1` (`feat: add calc`): `res = x + 1; return res`
`C2` (`refactor: simplify calc`): inlined to `return x + 1`
`C3` (`fix: edge case None in calc`): added `if x is None: return 0`
I expected `tdd_refactors_filtered: 1` and `regret_pairs_count: 1` (`C2 -> C3`). Instead, my `v2.0` candidate failed its own release gate (`5/6` suites passing) with `tdd_refactors_filtered: 0` and `regret_pairs_count: 3`. There is a special kind of poetic justice in writing a Rapid-Regret Miner whose maiden run blocks its own release because it cannot tell disciplined TDD apart from panic.

Two bugs had compounded:

`C1 -> C2` with `paired_files` set, the nested commit loop paired `C1 -> C3`), counting one bug fix twice.
In `v2.1.0`, `regret-mine` tracks `paired_files` per `newer` commit so each file only compares adjacent `C_k -> C_{k+1}` transitions, skips line-reversal checks when `_REFACTOR_VERB_RE` matches without fix verbs (`is_pure_refactor`), and requires `inverted_lines` to either touch a control-flow guard (`_CONTROL_GUARD_RE`: `if|elif|else|raise|assert|except`) or accompany another corroborating signal. That brought the suite to `6/6` green.

`\s*` that ate semicolon-free Graphviz edges
Graphviz DOT allows edges without trailing semicolons:

``` php
digraph G {
  Idle -> Fetching
  Fetching -> Done [label="onSuccess"]
}
```

In my first draft of `_lint_dot_state_machine`, `edge_re` ended with `\s*(?:\[(.*?)\])?` to capture optional edge attributes. Because `Idle -> Fetching` had no `[...]` block, `\s*` happily ate the newline at the end of line 2 plus the leading indentation on line 3, treating line breaks between state transitions as optional whitespace and leaving `SCAR-PROC-152` and `SCAR-ASYNC-01` blind whenever semicolons were omitted. Restricting every horizontal whitespace token in `edge_re` and `node_attr_re` from `\s*` to `[ \t]*` under `re.M` fixed it immediately.

`NodeVisitor` vs. `ast.walk`, OneDrive, and Windows cold start)
Initially, `_AntigravityVisitor` subclassed `ast.NodeVisitor` in a single top-down pass. That failed whenever an import lived near the bottom of a file (below a helper function calling it) and paid dynamic `getattr` dispatch on every AST node. Replacing `NodeVisitor` with two flat `ast.walk(tree)` passes (imports first, calls and exception handlers second) fixed the late-import bug cleanly.

Then I ran real timing benchmarks (median of 5 runs on Python 3.12 on Windows) to see how honest our `<15 ms` static-check label actually was:

| Target File | Lines | In-Process ( `elapsed_ms` ) | Wall-Clock Subprocess | Notes | 
|---|---|---|---|---|
| `tests/test_ag_ops.py` | `333` | **`6.54 ms`** | `151.1 ms` | Typical module size; well inside `<15 ms` | 
| `ag_ops.py` (local SSD) | `1,783` | **`42.6 ms`** | `~182 ms` | Dominated by `ast.parse()` , not tree walking | 
| `ag_ops.py` (OneDrive path) | `1,783` | **`52.9 ms`** | `~193 ms` | Cloud-synced filesystem stat/read overhead | 

Two lessons jumped out of that table. First, once a single Python file approaches 1,800 lines, CPython's `ast.parse()` dominates the in-process budget; micro-optimizing the visitor barely moves the needle. Second, spawning `python.exe` on Windows via `CreateProcessW` adds roughly `140 ms` of fixed interpreter startup outside `time.perf_counter()`. In-process, a 333-line file lints in `6.54 ms`; end-to-end from an agent's shell tool, every invocation pays `~150–185 ms`. That is still fast enough to run on every turn without friction, and worth reporting without hiding behind `perf_counter()` alone.

`dbader/schedule`
Synthetic unit tests are friendly terrain, so I pointed `regret-mine` at an external open-source repository, [`dbader/schedule`](https://github.com/dbader/schedule) (` 398` commits), across three time windows:

`--window-min 60` (1 hour):`19` TDD refactors filtered, `11` regret pairs flagged.`--window-min 1440` (24 hours):`25` TDD refactors filtered, `24` regret pairs flagged.`--window-min 10080` (7 days):`35` TDD refactors filtered, `37` regret pairs flagged.
Inspection of the `11` one-hour pairs showed that the miner catches real rapid-regret defects cleanly, including commits like *"Fix a bug where the at(':SS') syntax wasn't working"* and *"Missed == to !="*.

It also exposed two remaining sources of false-positive noise in `v2.1.0`:

`FunctionDef.body[0]` as an `ast.Expr(ast.Constant(...))`. Fixing a typo inside a docstring changes `ast.dump(node, include_attributes=False)` `"fix typo"` in the commit subject, firing `_extract_ast_function_hashes` currently keys functions by `defs[node.name]`. If `Job.__init__` changes in Commit A and `Scheduler.__init__` in the same file gets a bugfix 20 minutes later in Commit B, `defs["__init__"]` collides. Stripping leading docstring constants before hashing and qualifying `ClassName.method_name` are queued for `v2.2`.` ag_ops.py`
In Randal's full architecture, Stage 2 hands candidate regret pairs and `transcript.jsonl` slices to an isolated clean-room critic model to compile new 4-vector scars automatically. I deliberately left Stage 2 out of `ag_ops.py`. Mixing API keys, network retries, and LLM prompts into a deterministic guardrail CLI would violate the exact property that makes `ag_ops.py` trustworthy: zero external dependencies, zero network calls, zero token spend. Shrinking `398` commits down to `11` structured JSON candidates in milliseconds is the CLI's job; deciding whether an orchestrator subagent or a human reviews those `11` candidates belongs one layer up.

`.ag_state/` Perimeter
Just like v1, `ag_ops` v2.1.0 runs in-band under the same OS user account as the Antigravity agent. An adversarial process with unrestricted shell access could still inspect or overwrite `.ag_state/merkle_guard_<hash>.json` directly. In daily engineering work, `ag_ops` targets **autoregressive laziness**: stopping an over-eager model from taking statistical shortcuts like `except: pass` or quietly widening `assert status == 200`. Defending against hostile multi-tenant breakouts is a different threat model; true adversarial custody still requires separating the performer and the verifier across OS UIDs, read-only bind mounts, or CI runners.

`ag_ops` v2.1.0 remains a working lab instrument in standard-library Python, shaped by watching real multi-agent runs in Google Antigravity fail in ways I didn't anticipate and tightening the bolts one bug at a time.

I owe a clear debt of gratitude to **[Randal L. Schwartz](https://dev.to/randalschwartz)** ([@randalschwartz](https://dev.to/randalschwartz)) for the [*Synthetic Scars*](https://dev.to/randalschwartz/series/44156) series, and particularly [*Giving AI Pain*](https://dev.to/gde/giving-ai-pain-the-architecture-of-synthetic-scars-the-rapid-regret-miner-1740), for bringing decades of Unix engineering discipline, formal state-machine rigor, and empirical clarity to a space that badly needed an alternative to prompt superstition. Thank you as well to **[@max_quimby](https://dev.to/max_quimby)** and **howcani**, whose sharp questions on [Field Notes #1](https://dev.to/fgrandon/i-cut-my-agents-fixed-context-by-42-and-moved-its-test-rules-into-a-hash-checked-cli-5dmp) turned a loose prose `--reason` flag into constructive witnesses and pushed this engine onto much firmer ground.

The full source code (`ag_ops.py` v2.1.0), the `6/6` automated verification suite (`tests/test_ag_ops.py`), and the MIT License are available at **[` github.com/FranciscoGrandon/ag-ops`](https://github.com/FranciscoGrandon/ag-ops)**. If you run `regret-mine` against your own repositories and find an edge case that breaks it, I would genuinely love to hear what broke.
