← Back to sungyongcho.com

PetV3 Agent Factory 2: Safe Landing Before Parallelism

#petv3#git#worktree#agents

PetV3 automation system, part 2 of 4

In the previous post, I defined the goal as preventing false success rather than maximizing the number of agents. Three Git mechanisms implement that goal:

isolated worktree → visible overlap ledger → central landing gate

Give every worker its own workshop

The first temptation in a shared working tree is to use git stash. In a mixed repository, however, a broad stash can hide work that belongs to the user or another agent. PetV3 rejected that approach and assigned each worker a branch and worktree instead.

work/command-split     /var/tmp/petv3-w-cmd
work/reply-p0          /var/tmp/petv3-w-p0
work/dirty-guard       /var/tmp/petv3-w-dirty

A worker may commit on its own branch, but it cannot land on main. It also receives a closed task that does not require waiting for another worker. If completion depends on a file outside the assignment, the task was divided incorrectly.

This separates two questions: is the implementation correct, and is the merge order correct?

The ledger is radar, not a lock

Before editing, a worker declares the files it expects to touch.

scripts/ledger.sh claim work/reply-p0 \
  crates/petv3-native/src/connection.rs \
  crates/petv3-overlay/src/reply.rs

The ledger does not reject a second worker. It records and warns about the overlap. A hard lock would encourage the second worker to stop reporting, which would make the collision invisible to the controller.

The question is not “who owns this file?” It is:

Which intentions are currently meeting in the same file?

When a merge conflict appears, the controller reads both intentions. No script silently chooses one side.

Landing is a transaction

PetV3 uses land.sh as the only path to main. Its core sequence is:

  1. Run the scoped gate at the worker branch tip.
  2. Recheck the starting main HEAD and clean state.
  3. Merge without unresolved conflicts.
  4. Run the gate again on the merged result.
  5. Verify that the main HEAD links on its own.

The last gate came from a real incident. A narrow package gate passed, but another crate could not handle a new enum variant. I reproduced the defect deliberately: the branch and merged gates passed, the standalone HEAD link failed, and the script restored the safe starting point.

A gate existing is less important than proving that it catches the failure it claims to catch.

Build caches belong to the isolation boundary

Separating source directories was not enough. Two archived source trees once shared a Cargo target. A reused test binary contained the previous CARGO_MANIFEST_DIR and looked for a backlog file inside an archive that had already been deleted.

A landing run now isolates all of these together:

  • branch source archive;
  • merged source archive;
  • branch and merged Cargo targets;
  • a rustc TMPDIR;
  • stage logs and retained failure diagnostics.

Successful runs remove only their own run root. Failed runs keep diagnostics. When retained failures reach a limit, the system refuses a new landing instead of deleting evidence automatically.

Documentation can collide too

The first history design let every worker append one index line. The event files were separate, but the shared index still conflicted. Sequence numbers also collided when multiple workers selected the “next” number.

History now uses one YYYY-MM-DD-slug.md file per event, and a script generates the index. Landing commits are derived from Git instead of copied by hand.

The lesson was simple: documentation has the same parallelism problems as code.

The second conclusion

Multi-agent throughput should be measured by safely landed closed tasks, not worker count. One pipeline that preserves a verified snapshot is more valuable than ten fast workers producing ambiguous branches.

Next: PetV3 Agent Factory 3: Turning a Terminal Dashboard into a Control Plane