DEV Community

Avery Li
Avery Li

Posted on

Separate Probe and Patch Windows Before an Agent Touches the Tree

Agent pairing remains unreviewable when chat text is treated as the plan and the working tree is treated as the proof. The session described below keeps one surviving decision: the agent may probe freely, but it may not edit until a structured decision record exists. That record must name a local oracle command the senior already trusts, and the command must fail closed when evidence is missing. The protocol stays useful after product names are removed, because the review gate lives in files and tests.

The pairing setup this protocol assumes

The following write-up is a proposed pairing protocol, labeled as such because it is not a production postmortem. It assumes a brownfield repository, a senior reviewer who rejects chat summaries, and an agent that can read files. It also assumes the pair can copy the repo into a disposable workspace that can be deleted after the session. Private credentials stay off that workspace, and the laptop remains the only place where a patch may be applied.

What the senior blocked

The junior wanted the agent to start editing a flaky checkout service after a short verbal brief. The senior stopped the share-screen session before the first write, because the agent had no named verification command. The working tree already contained unrelated local changes, so a successful chat answer could not be distinguished from an accidental edit. The pairing would have produced a diff that nobody could replay without the original conversation.

Questions the senior asked before any tool write

The senior did not request architecture slogans, fashionable agent terms, or a demonstration of autonomous looping. The senior instead demanded evidence that could be rerun after the chat window had closed. The answers were written into a file before the agent received permission to touch tracked sources.

  1. Name the exact command that proves the current failure on a clean checkout.
  2. Name the files the agent is allowed to read during the probe window.
  3. Name the files the agent is forbidden to write until the decision record is accepted.
  4. Name the unknown that should abort the session instead of inviting a guess.

Those four answers became the header of the decision record used later in the session. Anything the agent could not answer remained listed as an unknown with an abort rule. Unknowns were treated as abort conditions rather than as invitations to improvise a plausible patch.

Dead ends that consumed the first hour

The first dead end was pasting a stack trace into chat and requesting a patch immediately. The model returned a confident diff against a helper that no longer existed on the main branch. The junior branch was twelve commits behind, so the generated hunk could not apply on a clean tree. The pair deleted that diff instead of negotiating with it.

The second dead end was allowing the agent to run formatters across the entire repository during exploration. The later review could not separate the intended fix from a storm of unrelated whitespace changes. The senior reverted the sandbox and marked repository-wide formatters as a deny-list action during probe. Formatting could return only after the functional oracle was green.

The third dead end was trusting a generated summary after the sandbox had already rewritten a migration file. The summary omitted the migration, so a reviewer who read only the chat would have merged a silent schema change. Each dead end shared one shape: generation was allowed to outrun evidence the senior could rerun. The senior froze writes at that point and demanded a record a later reviewer could reject without the transcript.

The decision the pair kept

After those loops, the pair kept one decision and discarded the rest of the earlier improvisation. Probe work may run in a disposable environment so the primary clone never receives experimental writes. The agent must emit decision-record.json before any write to tracked source files is permitted.

The senior reviews that file, runs the named oracle, and only then opens a bounded patch window. If the oracle is missing or exits nonzero, the protocol fails closed and the session produces no diff. Chat logs can remain as scratch paper, but they are not an input to merge review.

A probe window that does not own the repository

A disposable probe host can be a local container or a short-lived remote box with a throwaway clone. MonkeyCode's free model access and free server option can host that probe window when the laptop must stay clean. Disclosure: This article was prepared as part of MonkeyCode's product outreach.

The laptop still runs the oracle and the final git apply step, because a remote workspace is not the source of truth. Free remote capacity is relevant only for reading the tree, running probes, and drafting the decision record. The mergeable artifact remains a patch that a clean laptop worktree can recreate.

Workflow: probe, record, oracle, then patch

The steps below are meant to be copied into a pairing checklist rather than performed from memory. Each step produces a file or an exit code, which keeps the next person from depending on the original call.

1. Freeze the laptop clone

The pair creates a clean worktree so the oracle is not lying about unrelated dirty files.

git fetch origin
git worktree add /tmp/checkout-oracle origin/main
cd /tmp/checkout-oracle
git status --porcelain
test -z "$(git status --porcelain)"
Enter fullscreen mode Exit fullscreen mode

The empty porcelain output is the baseline, and any later path must be explained in the decision record. If porcelain is not empty, the pair stops and moves those files aside before any agent command runs. A dirty worktree lets accidental local edits masquerade as successful repairs when the later review begins.

2. Copy a probe clone onto the disposable host

The pair copies a shallow clone to the probe host without env files, deploy keys, or session tokens.

git clone --depth 1 --branch main "$REPO_URL" /tmp/probe-src
rsync -a --delete \
  --exclude '.env*' \
  --exclude '*.pem' \
  --exclude '.git/config' \
  /tmp/probe-src/ probe-host:~/probe-src/
Enter fullscreen mode Exit fullscreen mode

The probe host may use free model access for reading files and proposing the record, not for merging. The copy is disposable on purpose, and it should be destroyed when the pairing session ends. Any secret that would make the probe host more convenient should stay on the laptop instead.

3. Constrain the agent to probe commands

The agent receives an allowlist that can list, read, search, and run tests, but cannot overwrite tracked files.

# probe-allowlist.txt
allow: ls, cat, sed -n, rg, git log, git blame, git show, pytest, npm test
deny: git commit, git push, git apply, tee, cp, mv, rm, chmod
Enter fullscreen mode Exit fullscreen mode
#!/usr/bin/env bash
# proposed example: probe-shell.sh as the agent's command runner
set -euo pipefail
cmd="$*"
deny_re='(git[[:space:]]+(commit|push|apply|rebase)|[[:space:]]tee[[:space:]]| rm | mv | cp )'
if [[ "$cmd" =~ $deny_re ]]; then
  printf 'blocked by probe-shell: %s\n' "$cmd" >&2
  exit 78
fi
bash -lc "$cmd"
Enter fullscreen mode Exit fullscreen mode

This wrapper is a proposed example, not production sandboxing, and a determined process can still bypass it. The point is social and mechanical friction, not a security boundary against a hostile agent runtime. Keep the wrapper in the repo so the next pairing session does not reinvent the deny list from chat.

4. Emit a rejectable decision record

The agent writes only decision-record.json during the probe window, using the schema in the next section. The senior should be able to reject the file in a normal review comment without opening the chat log. If the agent also wants to narrate, that narration stays out of the repository and out of the pull request body.

5. Validate the record before human review

A tiny validator checks required keys, abort unknowns, and the presence of an oracle command string. Validation runs on the laptop against the file copied back from the probe host. A record that fails validation never reaches the patch window, which is cheaper than arguing with a speculative diff.

6. Run the oracle on the laptop worktree

The oracle runs against /tmp/checkout-oracle, not against the probe host, so remote side effects cannot fake green tests.

python3 validate_record.py decision-record.json
ORACLE="$(python3 -c 'import json; print(json.load(open("decision-record.json"))["oracle"]["command"])')"
bash -lc "$ORACLE"
Enter fullscreen mode Exit fullscreen mode

The oracle command must already exist in the project, such as a focused pytest node or a language-native test runner. A sentence that claims tests should pass is not an oracle, and the validator below rejects that shape. The senior watches the exit code rather than the model's summary of the exit code.

7. Open a bounded patch window

Only after a zero exit from the oracle may the agent propose a patch file, still not a live edit. The pair applies the patch on the laptop, reruns the oracle, and then continues ordinary review.

git apply --check agent.patch
git apply agent.patch
bash -lc "$ORACLE"
Enter fullscreen mode Exit fullscreen mode

If git apply --check needs fuzz, the patch returns to the probe window instead of being massaged by hand. Hand massaging reintroduces the original problem, which was an edit that could not be replayed from evidence. The pull request then contains the record, the patch, and the oracle command, not a chat export.

Artifact: schema, sample record, validator, and decision table

The artifact is intentionally small so a pairing session can adopt it without a platform migration. Copy the schema and validator into the repo, then treat a missing record as a failed CI check for agent-authored branches.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "AgentDecisionRecord",
  "type": "object",
  "required": ["problem", "probe_evidence", "unknowns", "write_targets", "oracle", "abort_rules"],
  "properties": {
    "problem": { "type": "string", "minLength": 24 },
    "probe_evidence": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "required": ["path", "command", "observation"],
        "properties": {
          "path": { "type": "string" },
          "command": { "type": "string" },
          "observation": { "type": "string", "minLength": 8 }
        }
      }
    },
    "unknowns": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["text", "abort_if_unknown"],
        "properties": {
          "text": { "type": "string" },
          "abort_if_unknown": { "type": "boolean" }
        }
      }
    },
    "write_targets": {
      "type": "array",
      "minItems": 1,
      "items": { "type": "string" }
    },
    "oracle": {
      "type": "object",
      "required": ["command", "expected_exit"],
      "properties": {
        "command": { "type": "string", "minLength": 3 },
        "expected_exit": { "type": "integer" }
      }
    },
    "abort_rules": {
      "type": "array",
      "minItems": 1,
      "items": { "type": "string" }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The sample below is a proposed fixture that the validator should reject until the historical invoice unknown is resolved. That rejection is the point of the pairing rule, not a defect in the fixture.

{
  "problem": "Checkout total drifts by one cent when tax is computed on a rounded line item.",
  "probe_evidence": [
    {
      "path": "services/checkout/tax.py",
      "command": "rg -n 'round(' services/checkout",
      "observation": "round() is applied per line before tax, then applied again on the sum."
    }
  ],
  "unknowns": [
    {
      "text": "Whether historical invoices must keep the old rounding.",
      "abort_if_unknown": true
    }
  ],
  "write_targets": ["services/checkout/tax.py", "services/checkout/test_tax.py"],
  "oracle": {
    "command": "pytest -q services/checkout/test_tax.py",
    "expected_exit": 0
  },
  "abort_rules": [
    "Do not patch if abort_if_unknown is true.",
    "Do not format files outside write_targets."
  ]
}
Enter fullscreen mode Exit fullscreen mode
#!/usr/bin/env python3
"""validate_record.py — proposed checker for agent decision records."""
from __future__ import annotations

import json
import re
import sys
from pathlib import Path

REQUIRED = (
    "problem",
    "probe_evidence",
    "unknowns",
    "write_targets",
    "oracle",
    "abort_rules",
)


def fail(msg: str) -> None:
    print(f"invalid decision-record: {msg}", file=sys.stderr)
    raise SystemExit(2)


def main(path: str) -> None:
    data = json.loads(Path(path).read_text(encoding="utf-8"))
    missing = [key for key in REQUIRED if key not in data]
    if missing:
        fail(f"missing keys: {missing}")
    if len(data["problem"]) < 24:
        fail("problem statement is too short to review")
    if not data["probe_evidence"]:
        fail("probe_evidence must contain at least one observation")
    oracle = data["oracle"].get("command", "")
    if not re.search(r"(pytest|npm test|go test|cargo test|make test)", oracle):
        fail("oracle.command must be a concrete test runner, not a narrative")
    if any(item.get("abort_if_unknown") for item in data["unknowns"]):
        fail("an aborting unknown is still open; patch window stays closed")
    print("decision-record accepted for human review")


if __name__ == "__main__":
    if len(sys.argv) != 2:
        fail("usage: validate_record.py decision-record.json")
    main(sys.argv[1])
Enter fullscreen mode Exit fullscreen mode

The validator above is deliberately harsh: an open aborting unknown fails the file before a human spends time on the prose. Teams that want a warning can move aborting unknowns into an advisory list, but this pairing kept the hard stop. The checkout session did not open a patch window until the invoice unknown was either answered from a product owner or rewritten as an explicit non-goal.

Decision table used during the session

Condition Probe window Patch window
Oracle command missing or narrative Keep probing Blocked
Unknown with abort_if_unknown: true Record it and stop guessing Blocked
Formatter or rewrite outside write_targets Deny the command Blocked
Oracle exits 0 on the clean laptop worktree Not used Patch may be applied
Patch applies only with fuzz or manual edits Return to probe Blocked
Secrets or .env files appear in the probe copy Abort the host Blocked

A short test plan for the protocol itself

  1. Feed the validator a record with an empty probe_evidence array and expect exit status 2.
  2. Feed a record whose oracle is tests should pass and expect the same failure.
  3. Feed a record with abort_if_unknown: true and confirm the patch window never opens.
  4. On a clean worktree, run a known failing oracle and confirm the pair does not apply agent.patch.
  5. After a valid record and a green oracle, apply the patch with git apply --check before git apply.

Those checks exercise the protocol, not the checkout tax logic, and they should be committed beside the validator. A pairing team that cannot fail the validator on purpose does not yet have a gate. The senior in this session treated a green validator as permission to read, not as permission to merge.

How the senior rejected records

The senior rejected the record when any unknown had abort_if_unknown set true and a guess appeared in the proposed patch. The senior also rejected the record when the oracle command was a narrative sentence instead of a shell line. A third rejection reason was a file path in write_targets that never appeared in the probe allowlist. Those rejections were cheaper than reviewing a speculative diff, because they happened before the tree moved.

Limitations

The decision record does not prove that the patch is correct; it only proves that the pair named evidence and a rerunnable command. The probe-shell wrapper is not a sandbox, and it will not contain an agent that can execute arbitrary binaries already on the host. A free remote server may lack private packages, so a green probe test can still fail on the laptop oracle.

Time-sensitive model quality, quotas, and hardware details are omitted here because they change and were not independently measured for this article. The protocol adds latency on purpose, and that delay will annoy pairs who optimize for message count. Teams that need a guaranteed hosted runtime, private dependency caches, or production credentials on the probe host will need a different isolation design.

Who should not use this approach

Incident responders who must patch a live failure in minutes should not insert this record gate in front of a hotfix. Teams whose repositories are saturated with secrets should not copy a tree onto a shared or free remote host at all. Regulated environments that forbid third-party model inference should keep probe work on approved machines and ignore any hosted option. Developers who want the agent to own the merge button should pick another workflow rather than weaken the oracle.

The pair kept a narrow surviving decision after discarding an hour of speculative diffs and chat summaries. The agent may probe and draft a rejectable record, then propose a patch only after the local oracle runs. Chat text never becomes the plan, and the disposable host never becomes the repository of record. Teams that need a throwaway probe box can try MonkeyCode's free model access and free server option while keeping the oracle local.

Top comments (0)