DEV Community

Morgan Xu
Morgan Xu

Posted on

Sign a Loop Card Before the Agent Iterates

Shared coding agents fail at the stop line. The model keeps iterating after the owner leaves. A signed wiki loop card is the real control plane.

Teams treat the chat transcript as durable memory. That habit dies on a shared engineering desk. The next engineer inherits a loop with no bound.

A loop is a repeated tool call on a moving tree. It is not a chat and not a design review. The conveyor moves until a human cuts power.

Community posts still argue about raw model skill. The operational failure is quieter and more expensive. An unbounded loop rewrites tests to match a guess.

The same loop then opens a hurried pull request. Reviewers argue with a story instead of a diff. The wiki never recorded who could stop the run.

This playbook proposes a one-page team SOP. No production benchmark is claimed in these pages. The artifact is a card, a gate, and a stop judge.

Four roles keep the conveyor honest during a shift. The Loop Captain starts the session and kills it. The Bound Clerk writes the stop condition in plain text.

The Reliever accepts the desk only with a signed card. The Patch Reader reviews the diff and ignores chat. One person may hold several roles on quiet days.

Four signatures still belong on every card. A shared free server makes the split visible. Anyone with the session can continue the loop.

Treat the card like a lockout tag on a mill. Factory floors hang tags before anyone services a motor. Software desks skip the tag and hope for restraint.

The agent does not practice restraint on its own. It iterates while the browser tab stays open. Hope is not a stop condition on a conveyor.

Paste the following block into the team wiki. Keep the page short enough to read aloud. Do not grow the runbook into a novel.

# Loop Card — one page, paste above the session

ticket: TICKET-ID
repo: org/name
branch: loop/TICKET-ID
captain: @handle
bound_clerk: @handle
reliever: @handle or none
patch_reader: @handle
goal: one present-tense sentence
stop_when: test command or time box
forbidden: paths the agent must not touch
budget: max tool rounds as an integer
started_at: ISO-8601
freeze_sha: output of git rev-parse HEAD
status: open | handed | stopped
Enter fullscreen mode Exit fullscreen mode

The card is boring on purpose for a reason. Boring cards get filled before the run. Clever cards get skipped under heavy calendar pressure.

The Bound Clerk writes stop_when before the first tool call. A vague make-it-work line is not a stop. A green pytest on tests/test_billing.py is a stop.

The Loop Captain pins budget as a positive integer. Eight rounds is a reasonable local team choice. An unset budget remains an unattended fire.

The Reliever refuses a desk with status open. A missing freeze_sha is an automatic refusal too. Courtesy does not replace a signed card.

Gate every session with a small shell script. The script reads the card and exits non-zero. Incomplete cards must not launch the agent.

#!/usr/bin/env bash
set -euo pipefail
CARD="${1:-.loop-card.yml}"

if [[ ! -f "$CARD" ]]; then
  echo "loop card missing: $CARD" >&2
  exit 2
fi

need() {
  local key="$1"
  local val
  val="$(awk -F': ' -v k="$key" '$1==k {print $2}' "$CARD" | tail -n1)"
  if [[ -z "${val// }" || "$val" == "TBD" ]]; then
    echo "loop card missing $key" >&2
    exit 3
  fi
}

need ticket
need repo
need branch
need captain
need bound_clerk
need patch_reader
need goal
need stop_when
need budget
need freeze_sha
need status

budget="$(awk -F': ' '$1=="budget" {print $2}' "$CARD" | tail -n1)"
if ! [[ "$budget" =~ ^[0-9]+$ ]] || [[ "$budget" -lt 1 ]]; then
  echo "budget must be a positive integer" >&2
  exit 4
fi

status="$(awk -F': ' '$1=="status" {print $2}' "$CARD" | tail -n1)"
if [[ "$status" != "open" && "$status" != "handed" && "$status" != "stopped" ]]; then
  echo "status must be open, handed, or stopped" >&2
  exit 5
fi

echo "loop card ok: $CARD"
Enter fullscreen mode Exit fullscreen mode

Make the script executable on the shared desk. Capture HEAD before the agent starts writing files. Run the gate as the first command.

chmod +x scripts/check-loop-card.sh
git rev-parse HEAD
git status --porcelain
./scripts/check-loop-card.sh .loop-card.yml
Enter fullscreen mode Exit fullscreen mode

A YAML twin sits beside the human wiki page. The wiki is for the handoff conversation. The YAML file is for the gate script.

ticket: BILL-214
repo: acme/billing
branch: loop/BILL-214
captain: alex
bound_clerk: rina
reliever: none
patch_reader: sam
goal: reject expired coupons in checkout
stop_when: pytest tests/test_coupon.py exits 0
forbidden: migrations/, secrets.env
budget: 8
started_at: 2026-09-11T09:00:00Z
freeze_sha: 4f2c1aa
status: open
Enter fullscreen mode Exit fullscreen mode

Add a stop judge in Python next. Label this helper as a proposed local tool. It was not executed against a production fleet.

"""Proposed stop judge. Unexecuted example for a local desk."""
from __future__ import annotations

from pathlib import Path
import subprocess
import sys
import yaml

MAX_ROUNDS_HARD_CAP = 20


def load_card(path: Path) -> dict:
    data = yaml.safe_load(path.read_text())
    if not isinstance(data, dict):
        raise SystemExit("loop card is not a mapping")
    return data


def git_sha() -> str:
    out = subprocess.check_output(["git", "rev-parse", "HEAD"], text=True)
    return out.strip()


def judge(card: dict, rounds_used: int) -> str:
    if card.get("status") == "stopped":
        return "halt: status is stopped"
    if rounds_used >= int(card["budget"]):
        return "halt: budget exhausted"
    if rounds_used >= MAX_ROUNDS_HARD_CAP:
        return "halt: hard cap"
    freeze = card.get("freeze_sha")
    if freeze:
        # SHA drift after start is expected; re-sign the card on handoff.
        _ = git_sha()
    return "continue"


def main() -> None:
    card_path = Path(sys.argv[1] if len(sys.argv) > 1 else ".loop-card.yml")
    rounds_used = int(sys.argv[2] if len(sys.argv) > 2 else "0")
    card = load_card(card_path)
    decision = judge(card, rounds_used)
    print(decision)
    if decision.startswith("halt"):
        sys.exit(10)


if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Install PyYAML inside the desk virtualenv if needed. Then invoke the judge with the card path. A halt exit code means the Captain kills the process.

python -m pip install pyyaml
python scripts/stop_judge.py .loop-card.yml 0
Enter fullscreen mode Exit fullscreen mode

The handoff is a short ceremony, not a chat dump. The Captain sets status to handed. The Captain pastes the card under today's wiki date.

The Reliever reads stop_when aloud before typing a handle. Then the Reliever becomes the new Loop Captain. Silent assumptions are how shared loops drift overnight.

Do not hand off the chat transcript as truth. The transcript remains a rumor with loose timestamps. The freeze_sha, forbidden paths, and test command are the record.

A shared free coding server makes this ceremony easy to skip. The tab stays open and the model stays reachable. The next person continues just one more round.

That extra round is how tests get rewritten. The Patch Reader then inherits a story-shaped diff. The card would have stopped the run at budget.

Some teams park the coding agent on a practice desk. Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode's free model access and free server option can host that desk.

Treat that server like a borrowed laptop in a lab. Do not store secrets on the shared machine. Do not point the agent at production credentials.

Rotate the session when the Reliever signs the card. Kill leftover processes before the next ticket starts. Stale sessions are how forbidden paths get touched.

A simple wrapper keeps secrets off the desk. The wrapper only exports the workdir and card path. Production tokens do not belong in this file.

#!/usr/bin/env bash
set -euo pipefail
# Proposed wrapper. Do not export production tokens here.
export AGENT_WORKDIR="${PWD}"
export LOOP_CARD="${PWD}/.loop-card.yml"
./scripts/check-loop-card.sh "$LOOP_CARD"
echo "gate passed; start the agent in $AGENT_WORKDIR"
Enter fullscreen mode Exit fullscreen mode

Test the SOP like any other interface. The plan below is a local rehearsal. Use a throwaway branch and a fake ticket id.

First omit stop_when and confirm the shell gate exits 3. Next set budget to zero and confirm exit 4. Then run the judge at budget and confirm halt.

Hand the card to a colleague on the same desk. Watch them refuse an unsigned freeze_sha. That social check is the real acceptance test.

Those four checks take only a few minutes. They catch a social failure before a mechanical one. The script cannot save a team that never fills cards.

Limitations stay sharp on purpose. This SOP does not measure model quality. It does not prove a patch is correct.

It does not replace a human Patch Reader. It does not survive a team that skips the card. It does not send SIGTERM by itself.

Do not use this approach on regulated production changes. Do not use it as a reason to skip review. Do not use it when the agent must touch secrets.

Incident work under legal hold needs a different protocol. Migrations need a human at the keyboard. Coupon copy is a better first drill.

Solo developers who never share a desk can skip Reliever. They still need a written stop_when line. An unbounded personal loop wastes a morning the same way.

Time boxes belong in stop_when as well. Stop at 10:30 local is a valid bound. The agent cannot see a clock unless the runner injects one.

The Captain must kill the process at the time box. The card does not send signals by itself. A wall clock plus a human is the timer.

Forbidden paths deserve a second mechanical gate. A tiny diff guard helps after every round. The Bound Clerk owns the grep pattern.

#!/usr/bin/env bash
set -euo pipefail
# Proposed forbidden-path guard. Unexecuted example.
FORBIDDEN="${1:-migrations/|secrets.env}"
if git diff --name-only | grep -E "$FORBIDDEN"; then
  echo "agent touched a forbidden path" >&2
  exit 6
fi
Enter fullscreen mode Exit fullscreen mode

Run that guard after every agent round. The Loop Captain owns the kill when it fires. Debating the model after a forbidden write is too late.

Wiki hygiene keeps the conveyor from eating history. Keep one card per ticket on the page. Archive yesterday's cards under a dated heading.

Never edit a stopped card in place. Copy it, then mark the copy open. In-place edits hide who changed the bound.

Print the card if the room is noisy. A paper tag on a monitor beats a buried doc. The YAML remains the machine-readable copy.

The core conclusion does not change with model fashion. Intelligence does not name an owner. Intelligence does not write a stop line.

Intelligence does not refuse a dirty handoff. A loop without a card is a mill without an emergency stop. Sign the card, then let the agent iterate.

Top comments (0)