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, 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 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. 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's ongoing Synthetic Scars series (especially Part 12, Giving AI Pain: The Architecture of Synthetic Scars & The Rapid-Regret Miner) 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-inAUTH001 + GraphvizDOT001 ,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'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:
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*/ * 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):
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:
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 ( 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 (@randalschwartz) for the Synthetic Scars series, and particularly Giving AI Pain, 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 and howcani, whose sharp questions on Field Notes #1 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. 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.