Choose the boundary before starting the assistant

A useful task boundary names writable directories, any exact-file exceptions, protected files inside those directories, and the command that proves the requested behavior. Keep read access separate: a frontend fix may require reading a backend response type without allowing backend edits.

TaskAllowed changesExtra decision before starting
Cart calculation bugImplementation files below apps/frontend/Protect the existing test and package configuration; prohibit dependency installation.
Frontend dependency updateThe frontend package plus the exact root lockfileApprove the dependency and lockfile exception. Check for unrelated dependency churn.
Generated API client updateThe owning schema and the named generated client packageSpecify the generator command and review generated output separately from handwritten code.
Change to a shared interfaceThe shared package and identified consumersList affected consumers and their tests; a frontend-only scope cannot complete this task.
Root formatter or CI configurationExact configuration filesDecide whether formatting other packages belongs in a separate change.

When the assistant discovers that the fix needs another path, stop that task at the boundary. Ask it to identify the dependency, the proposed files, and the consequence of leaving them unchanged. Review that proposal before creating a new scope. Adding a whole repository to the allowed list makes the original boundary useless.

Keep existing work out of the experiment

Start by inspecting git status --short in the repository you intend to use. If it contains another developer's changes, create a separate worktree from an explicitly selected commit or branch. For example, from the repository root:

git worktree add -b ai/cart-quantity ../cart-quantity-task HEAD

This creates another checkout. Existing uncommitted changes are not automatically transferred into it. If the task requires those changes, preserve them first, inspect the intended patch, and apply only that patch to the task checkout. Include new files deliberately; an ordinary tracked diff does not contain their contents. The Git worktree documentation explains the checkout and shared repository data.

A worktree avoids mixing file edits with another task, but it shares repository data and is not an operating-system sandbox. Give parallel agents separate worktrees and coordinate operations involving shared refs. Avoid two agents writing into the same checkout: a final diff shows the resulting state, not which process made each edit.

For work that must start with existing modifications, the supplied checker offers --accept-existing-changes at snapshot time. Use it only after reviewing that state. It records both current files and index entries, so an existing edit can remain in place while a new task edit is checked. It does not preserve a restorable copy of those files. Make your own backup before the task, and keep other writers out until verification ends.

Check whether this verifier fits your repository

The supplied checker works best in a small, independent checkout with ordinary files and no other process writing into it. Make this choice before downloading dependencies or giving the assistant an assignment:

Repository statePractical choice
Clean checkout, ordinary files, dependency-free testUse the included fixture workflow directly.
Large dependency directories, build output, or cachesUse a new worktree when possible. The checker reads and hashes ignored files too, so a large installed checkout can be expensive to scan.
Symlinks, hard links, submodules, or nested repositoriesThe checker rejects these. Use a clean checkout without those features if the task permits it, or use runtime isolation plus a verifier that follows the required targets and checks each repository.
Tests create cache or generated filesName the permitted output directory before taking the baseline. Run the tests once on a disposable copy to discover their outputs.
Other developers or agents are still editing the checkoutGive this task its own worktree. A final file comparison cannot identify which process made an edit.

Do not delete dependencies or another person's files merely to make the checker pass. If tests require an installed tree that this verifier cannot inspect, keep that limitation explicit and review the patch using a suitable tool. The included dependency-free fixture and lockfile-only example avoid installed workspace links; they demonstrate the workflow without pretending to cover every monorepo layout.

Create the complete cart fixture

Download the complete fixture and checker kit. It requires Git, Python 3.10 or later, and Node.js with ES module support. The optional lockfile example also needs npm. Extract the archive and enter the directory containing its scripts:

unzip monorepo-boundary-kit.zip
cd monorepo-boundary-kit
python3 monorepo-boundary-fixture.py monorepo-task
mkdir -p monorepo-review

Run the following commands from this kit directory unless another working directory is stated. On a system without unzip, use the archive manager and then open a terminal inside the extracted monorepo-boundary-kit/ directory. The generator refuses to overwrite an existing directory.

The new Git repository contains the frontend implementation, protected test and package configuration, backend and shared files, repository instructions, and a task description. No npm installation is needed for the cart example. Its test expects 12 * 2 + 5 * 3 = 39, plus zero for an empty cart and a zero-quantity line. The initial implementation adds each price once, so this command should fail:

(cd monorepo-task/apps/frontend && node cart.test.mjs)

This deliberately small calculation tests the containment workflow. It does not implement production money, tax, rounding, or input-validation rules. Keep the kit and monorepo-review/ outside the assistant's writable area.

Put instructions where the assistant will load them

For Codex, place repository-wide requirements in the root AGENTS.md and relevant local requirements along the path to the task's working directory. Codex discovers its instruction chain from the project root down to that directory; nearer guidance can override earlier guidance. Check what loaded when starting a session, particularly when an AGENTS.override.md exists. See the official instruction discovery rules.

Task: fix the cart quantity calculation.
Read backend or shared files only when needed to understand the interface.
Write only below apps/frontend/.
Do not change cart.test.mjs, package.json, instructions, or task.md.
Do not install dependencies, run generators, or create commits.
Run node cart.test.mjs from apps/frontend/.
If another path is required, report the proposed scope change and stop.

Repeat the short task-specific boundary in the assignment. Keep broad repository conventions in the instruction file. The assistant should be able to name its allowed paths, protected tests, and acceptance command before editing. Check the execution permissions next, before allowing edits.

Verify the write boundary before allowing edits

For Codex, distinguish the directories it may write from the actions it may ask you to approve. --ask-for-approval never prevents approval requests; the active sandbox or permission profile determines what a command can actually change. The official security guide describes both controls and the /status command for inspecting the workspace.

The kit contains monorepo-boundary-permissions.py. On the tested macOS installation with Codex CLI 0.159.2, it selects a named profile for a local sandboxed command, without changing your saved configuration or starting an AI model session:

python3 monorepo-boundary-permissions.py monorepo-task \
  --output monorepo-review/permissions.json

The profile extends :read-only, adds write access to the absolute frontend directory, keeps its test, package configuration, and instruction files read-only, and disables command network access. The probe runs actual filesystem operations on the disposable fixture. Expect status: pass and these results:

OperationRequired result
Create and remove a frontend probe fileallowed
Create a backend probe filedenied
Write to the frontend test or package configurationdenied for each file
Read the backend fixtureallowed

A missing executable, an existing probe file, or another operating error is a failed check, not evidence of an enforced denial. The helper refuses to run these probes on a checkout without the generated fixture's task marker. Inspect codex sandbox --help if your CLI reports unsupported options. Sandbox command forms vary between versions and operating systems; the retained result covers this macOS command test.

For the assistant session, print the same policy with absolute paths:

python3 monorepo-boundary-permissions.py monorepo-task \
  --print-config > monorepo-review/frontend-policy.toml

Review the generated file, then add its named profile and top-level selection to your Codex configuration. It sets default_permissions = "monorepo_frontend" and approval_policy = "never". Permission profiles are currently beta. Follow the official configuration rules: the older sandbox_mode and sandbox_workspace_write settings take precedence when loaded, so remove conflicting legacy settings from your chosen configuration before using this profile. Do not also pass --sandbox when selecting it. If managed settings prevent this configuration, stop and use the policy supported by your environment.

codex --cd monorepo-task/apps/frontend

In that session, inspect /status and make the first assignment a permission check, with no implementation edits yet. Ask the assistant to execute the helper using absolute paths:

python3 /absolute/kit/monorepo-boundary-permissions.py \
  /absolute/kit/monorepo-task --probe-current

Replace the paths with your extracted kit location. This mode runs directly inside the session's command environment. Require the same allowed and denied results. A verbal refusal to write outside the directory is insufficient. If any result differs, stop before editing, correct the selected policy, and repeat the probe. This second check matters because passing a separately launched sandbox probe does not prove that the assistant session loaded the same policy.

When a required environment cannot enforce the desired scope, a concrete fallback is a read-only assistant session that proposes a patch. Review and apply the approved hunks yourself in an isolated checkout. For another assistant or operating system, use its documented execution controls and repeat the operations above before trusting the boundary.

Save the baseline and verify the cart fix

Once the probes pass and their temporary files are gone, save the pre-task baseline yourself from the reviewer-controlled terminal:

python3 monorepo-boundary-check.py snapshot \
  monorepo-task monorepo-review/baseline.json \
  --allow apps/frontend/ \
  --protect apps/frontend/cart.test.mjs \
  --protect apps/frontend/package.json \
  --test-cwd apps/frontend \
  --test-command node cart.test.mjs

Expect status: snapshot and the fixed commit SHA. The JSON is outside the worktree; keep it outside the assistant's writable area too. Do not regenerate it after editing. The checker stores paths, modes, and hashes, so keep a separate backup when starting from existing changes.

Now give the assistant the bounded cart assignment. The sufficient implementation fix changes the reducer expression to:

sum + line.unitPrice * line.quantity

After it finishes, run the final check yourself:

python3 monorepo-boundary-check.py check \
  monorepo-task monorepo-review/baseline.json

A successful check returns status: pass, the baseline SHA, changed paths before and after the test, and the test's exit code and output. For this fix, the changed implementation path is apps/frontend/cart.js. A rejection returns a nonzero exit code and a specific reason. The fixture instructions include the complete sequence.

Understand what the independent checker actually checks

The version 2 checker stores a filesystem inventory, index entries, allowed paths, protected files, the fixed path to the test program, and a fixed HEAD at snapshot time. It compares file contents and modes, additions, deletions, directory changes, and index changes before and after the acceptance command.

Its filesystem inventory includes ignored files. Git's ls-files --others --exclude-standard excludes files covered by standard ignore rules, so it cannot establish that every new file stayed in scope. The checker instead walks the worktree and reads Git index entries using NUL-separated output. This also avoids treating quoted Chinese names or embedded newlines as different paths. See Git's file enumeration options.

A move from docs/policy.txt into the frontend changes both the old and new filesystem paths. The old path is outside scope, so the run is rejected even when Git recognizes a rename. Looking only at git diff --name-only can omit the source side of a rename because that option reports names in the changed tree. For manual review, use --name-status and the full diff, including both rename endpoints.

If HEAD moves, the checker rejects the run rather than accepting the new commit as its baseline. The index comparison also catches an outside edit that was staged and then reverted only in the working file. Inside the allowed subtree, the named acceptance test and package configuration remain protected. Printing a success message from an altered test cannot satisfy this policy.

The comparison observes the files before and after the task. A process could write outside the tree, remove a temporary file before inspection, alter permissions during the run, or affect a remote service. The checker cannot observe those effects. It also cannot attribute concurrent writes to an individual actor. Runtime permissions and an appropriate execution environment are necessary for those boundaries.

The repository-fit table above lists unsupported links and nested repositories. Their targets and separate Git state need their own checks; a path-prefix match cannot establish that those changes stayed inside the intended boundary.

Handle lockfiles, generators, and shared packages deliberately

A dependency task needs a different policy from the cart bug. Suppose the frontend needs an existing local shared package and npm must update the root package-lock.json. Permit frontend package changes and that exact lockfile, while keeping root package.json, the shared package configuration, and the frontend test protected.

The runnable npm workspace example in the kit creates a fresh temporary repository, a root workspace manifest, and a local package named @boundary/shared. It fixes the cart calculation before taking its baseline so this new task is about dependencies. Run:

python3 monorepo-boundary-lockfile-demo.py \
  --output monorepo-review/lockfile-results.json

The runner adds "@boundary/shared": "1.0.0" to the frontend's dependencies and updates the lockfile offline. Its acceptance policy allows apps/frontend/ plus --allow-file package-lock.json. It protects the frontend test, root manifest, and shared manifest. Here the frontend package.json is deliberately editable; carrying over the cart task's protection for that file would make the dependency task impossible.

The npm command used in this example is:

npm install --package-lock-only --ignore-scripts --offline \
  --no-audit --no-fund --logs-max=0 \
  --cache apps/frontend/.npm-cache

Run it from the workspace root when reproducing the runner's steps. The example names the cache inside the allowed frontend subtree and ignores it in Git; the checker still inventories it. --package-lock-only avoids creating an installed node_modules tree, and --ignore-scripts suppresses package lifecycle scripts. The npm install options and workspace documentation explain these behaviors.

Expect exactly two tracked paths to change: apps/frontend/package.json and package-lock.json. The trusted cart test and scope check pass. The runner then adds an unrelated setting to root package.json; that check must reject the root change. Its retained result records both outcomes. This local package example needs no registry access. An external dependency task also requires reviewing the package, source, version, transitive changes, and intended network access before running an installation.

For a real dependency task, update the runtime permissions as well as the checker's allow-list. A lockfile exception in the verifier cannot grant the assistant permission to write that file. Review the new policy before taking a new baseline, including any cache location. Inspect the lockfile diff for unrelated packages, registry changes, or dependency churn before staging the two approved files.

Generators can update schemas, generated clients, snapshots, and caches outside the package where they run. Record the generator command, input schema, output directories, and any consumer checks before widening the scope. After generation, review generated files separately from handwritten edits, run the owning package and consumer checks, and repeat the boundary check. An unexpected output requires a scope decision before a new attempt.

For a shared interface change, list its consumer packages and the tests or builds that exercise them. A passing test in the shared package alone does not show that its consumers remain compatible. Expand the allowed paths only to the necessary packages and run each relevant check. Target formatters at the reviewed files or subtree; repository-wide formatting should have its own task and review.

Make failure cases part of acceptance

The kit includes a runnable fault-injection suite:

python3 monorepo-boundary-regression.py \
  --output monorepo-boundary-regression-results.json

The October 1, 2026 result contains 28 passing local checks of the verifier's expected accept-or-reject behavior. They include ordinary allowed changes, ignored outside files, changed HEAD, test-time writes, rename source paths, index-only changes, altered tests, exact-file exceptions, existing developer edits, Unicode and newline filenames, and unsupported filesystem features. The test results list each case and the observed outcome.

Observed failureWhat to do
Out-of-scope file or index changePreserve the patch and file list. Determine whether it is an error or a required scope extension before restoring anything.
HEAD movedReview commits against the recorded SHA. Keep that original baseline; do not replace it with current HEAD.
Protected test or package configuration changedRestore the trusted acceptance inputs in an isolated task checkout and evaluate the implementation again.
Acceptance test failedKeep the failing output and fix the implementation within the current boundary. Review test changes separately if the requirement itself was wrong.
Outside file appears during testsInspect imports, hooks, generator calls, and caches. Correct or explicitly authorize the effect before taking a new baseline.
Unsupported link or nested repositoryUse a verifier designed for those targets and retain the runtime write restriction.

Recover a rejected run without losing existing changes

Stop the assistant before cleanup and record the rejection. Save tracked changes against the fixed baseline:

git diff --binary BASELINE_SHA -- > ../monorepo-review/task-working.patch
git diff --cached --binary BASELINE_SHA -- > ../monorepo-review/task-index.patch

Replace BASELINE_SHA with the SHA stored in the snapshot. These patches do not contain new untracked or ignored files; preserve the relevant new files separately. The checker inventory records hashes and paths, not backup contents. Avoid blanket cleanup commands when a checkout contains existing changes or when you have not accounted for every file.

For a clean disposable task checkout, a straightforward recovery is to create another worktree from the recorded commit and apply only the reviewed frontend hunks. Copy approved new files explicitly. Run the protected tests and take a fresh baseline for any new attempt. Keep the rejected checkout until you have preserved the needed work.

If the task started with reviewed existing edits, restoring an outside file to HEAD would erase those existing edits. Recover from the pre-task backup or rebuild the task checkout with that reviewed state. Use the recorded inventory to verify the recovered files. Separate worktrees make this decision easier because unrelated development can remain in its original checkout.

Review the exact patch before committing

After scope and protected acceptance checks pass, inspect the implementation diff. For the cart fixture, confirm that quantity affects each line, the initial value remains zero, and the protected tests still contain their assertions. For a real application, run the package's relevant type, lint, build, integration, or consumer checks according to what the patch changes.

git diff --check
git diff -- apps/frontend/
git add -- apps/frontend/cart.js
git diff --cached --name-status
git diff --cached -- apps/frontend/cart.js

Use the actual reviewed file list when staging. In a shared checkout, staging a whole subtree can include another person's edits even if that subtree was allowed for the task. Confirm the staged paths and content against the reviewed patch, then let the authorized reviewer approve the commit or pull request.