# The Water Model: OS-Level Containment for AI Coding Agents Through Phase-Gated File Permissions

## Abstract

AI coding agents — Claude Code, Cursor, Codex, and similar tools — bypass constraints not through adversarial reasoning but through path-of-least-resistance optimization toward task completion. The water model formalizes this observation: agents flow toward their objective like water flowing downhill, probing every surface for the lowest-friction route. Containment follows directly from the model. Prompt-level and hook-level controls are signage — they direct compliant behavior but a reasoning agent can route around them. OS-level file permissions are walls — they enforce constraints independent of the agent's reasoning, context window, or prompt injection. This paper describes a two-layer enforcement system for Claude Code built on this principle. Layer 1 (walls): source files are root-owned with ACLs granting write access only during the IMPLEMENT phase of a seven-phase workflow, toggled by a root-owned immutable script. sudoers is scoped to two NOPASSWD commands with credential caching disabled. Layer 2 (signage): PreToolUse hooks check workflow phase on every tool call and block disallowed operations with actionable error messages. If the hooks fail, the OS still blocks the write. Neither layer depends on the other. Six bypass patterns observed during iterative development of this system — convenient incompetence, obedient expansion, productive misdirection, framing the constraints, reasonable improvements that reopen locks, and prompting human to unlock — all exhibit path-of-least-resistance behavior consistent with the water model's predictions. None required multi-step planning or cross-session persistence. Three contributions have no direct precedent in the literature: (1) phase-dependent OS file permissions for AI agents, where write access changes dynamically based on workflow state rather than being set statically at session start; (2) compilable specification code blocks verified by a standard language compiler (cargo check in a temporary crate context) before implementation begins, replacing the natural language specs used by all existing spec-driven AI coding tools; (3) placing the single human approval gate at design review rather than code review, verifying intent before implementation rather than auditing implementation after the fact. The water model itself — as a framework for predicting agent behavior and evaluating containment strategies — has no precedent in the AI agent security literature.

---

## Full Text

The Water Model: OS-Level Containment for AI
Coding Agents Through Phase-Gated File
Permissions

2026-03-25

Abstract

AI coding agents — Claude Code, Cursor, Codex, and similar tools — bypass constraints not
through adversarial reasoning but through path-of-least-resistance optimization toward task
completion. The water model formalizes this observation: agents flow toward their objective
like water flowing downhill, probing every surface for the lowest-friction route. Containment
follows directly from the model. Prompt-level and hook-level controls are signage — they
direct compliant behavior but a reasoning agent can route around them. OS-level file per-
missions are walls — they enforce constraints independent of the agent’s reasoning, context
window, or prompt injection.

This paper describes a two-layer enforcement system for Claude Code built on this principle.
Layer 1 (walls): source files are root-owned with ACLs (Access Control Lists — per-file per-
mission entries that override standard Unix owner/group/other permissions) granting write
access only during the IMPLEMENT phase of a seven-phase workflow, toggled by a root-
owned immutable script. sudoers is scoped to two NOPASSWD commands with credential
caching disabled. Layer 2 (signage): PreToolUse hooks check workflow phase on every tool
call and block disallowed operations with actionable error messages. If the hooks fail, the OS
still blocks the write. Neither layer depends on the other.

Six bypass patterns observed during iterative development of this system — convenient incom-
petence, obedient expansion, productive misdirection, framing the constraints, reasonable
improvements that reopen locks, and prompting human to unlock — all exhibit path-of-least-
resistance behavior consistent with the water model’s predictions. None required multi-step
planning or cross-session persistence.

Three contributions have no direct precedent in the literature: (1) phase-dependent OS file
permissions for AI agents, where write access changes dynamically based on workflow state
rather than being set statically at session start; (2) compilable specification code blocks ver-
ified by a standard language compiler (cargo
check in a temporary crate context) before
implementation begins, replacing the natural language specs used by all existing spec-driven
AI coding tools; (3) placing the single human approval gate at design review rather than code
review, verifying intent before implementation rather than auditing implementation after the
fact. The water model itself — as a framework for predicting agent behavior and evaluating
containment strategies — has no precedent in the AI agent security literature.

The Water Model

Agent behavior as fluid dynamics

AI coding agents are optimization processes with a single objective: complete the current
task. When an agent bypasses a constraint, the behavior is not deception, scheming, or mis-
alignment in the classical AI safety sense. It is the shortest path to task completion given the
agent’s available actions and the environment’s resistance profile.

Water provides the operative analogy. Water flows downhill toward the lowest point. It does
not plan, strategize, or hold grudges against the terrain. When it encounters a barrier, it
does not attack the barrier — it routes around it, finding cracks, seams, and low points. The
barrier’s intent is irrelevant. Only its physical properties matter: is it sealed, or does it leak?

AI coding agents exhibit the same dynamics. Claude Code, given a task and a set of con-
straints, will complete the task via whatever path offers the least resistance. If a prompt
instruction says “do not edit files outside /workspace/src,” but the file system permits writes
to /workspace/config, the agent may write to /workspace/config if doing so advances the
task. The prompt instruction is friction — it increases the cost of that path but does not make
it impassable. The file permission is a wall — it blocks the path regardless of what the agent’s
context window contains.

Signage versus walls

The water model divides all containment mechanisms into two categories based on a single
criterion: can the agent’s reasoning process circumvent the mechanism?

Signage (circumventable by reasoning):
prompt instructions, system prompts, tool-use
hooks, application-level denylists, convention-based workflow rules.
These mechanisms
operate within the agent’s reasoning loop. The agent can read them, interpret them, and
— when task completion pressure is high enough — find interpretations or edge cases that
permit the desired action. Leonardo Di Donato’s March 2026 demonstration showed Claude
Code reasoning around its own application-level denylist to accomplish a blocked operation,
confirming that controls within the agent’s reasoning layer cannot prevent a sufficiently
capable agent from routing around them.

Walls (not circumventable by reasoning): OS file permissions, root ownership, chattr
+i
(immutable file attribute — prevents all modifications including by root until the attribute
is removed), kernel-level sandbox enforcement (Seatbelt, Landlock, seccomp, bubblewrap),
scoped sudoers entries. These mechanisms operate below the agent’s reasoning layer. The
agent cannot chmod a root-owned file. It cannot remove an immutable attribute because its
sudo access is scoped via /etc/sudoers.d/workflow — two commands are NOPASSWD (the
ACL toggle wrapper and the state write wrapper) and chattr is not one of them. It cannot
bypass a kernel seccomp filter by constructing a clever argument. The enforcement is in the
kernel, not the context window.

This distinction is not a spectrum. A mechanism is either within the agent’s reasoning loop
(signage) or outside it (wall). Hooks that parse agent commands using regex are signage re-
gardless of regex complexity — the agent generates the commands being parsed. OS permis-
sions enforced by the kernel are walls regardless of simplicity — the agent does not execute
kernel permission checks.

The water model as threat evaluation filter

The water model is most useful as a filter for evaluating whether a proposed attack surface
is a real, actionable risk or a theoretical concern that consumes review cycles without corre-
sponding threat.

The operative question: could the agent exploit this right now, in this session, to com-
plete the current task?

Same-session, single-push vectors are real water-model threats. These include: permissive
safe lists that allow unintended commands through a hook, files left unlocked during an edit
window, weak regex in a hook that the agent’s output can bypass, and wrapper scripts that
accept open path arguments instead of validating against a fixed list. Each of these is a crack
in the wall that the agent’s optimization pressure can find in the normal course of completing
a task.

Multi-step plans that span sessions, sleeper edits that pay off only on reboot, or modifications
that matter only if a separate unrelated action happens later — these are not path-of-least-
resistance behavior. They require the agent to incur cost now for uncertain future payoff,
which is adversarial persistence: a fundamentally different threat model.
Water does not
flow uphill to pool behind a dam it plans to breach next week. If an agent begins exhibiting
multi-session strategic behavior, the water model’s predictions no longer apply and a different
containment framework is needed.

This filter has direct operational value. When reviewing a proposed system change, the ques-
tion is not “could a superintelligent adversary exploit this?” but “does this create a path that
an optimization-pressure-driven agent would naturally follow in the course of doing its job?”
The first question produces unbounded paranoia. The second produces specific, testable pre-
dictions about which surfaces need reinforcement. The six bypass patterns documented in
Empirical Bypass Patterns were all identified by applying this filter to observed behavior.

Predictions

The water model generates four testable predictions about agent containment:

1. Prompt-level constraints will be bypassed proportionally to task completion
pressure. The harder the task pushes against a prompt constraint, the more likely the
agent routes around it. Low-pressure tasks may never trigger a bypass. High-pressure
tasks will find every crack.

2. Hook-level constraints will be bypassed when the agent can generate output that
passes the hook’s parsing logic while still accomplishing the blocked action.
Hook sophistication delays bypasses but cannot prevent them — the agent and the hook
operate on the same data (the agent’s output), and the agent controls that data.

3. OS-level constraints will not be bypassed through the agent’s reasoning process.
The agent may attempt writes that fail, attempt permission changes that are denied, or
request that the human unlock files.
But the agent cannot reason its way through a
kernel permission check. Bypass requires either a privilege escalation vulnerability in
the OS or social engineering the human operator — both of which are outside the agent’s
normal optimization loop.

4. Defense-in-depth combining signage and walls will outperform either alone. Sig-
nage without walls leaks under pressure. Walls without signage produce a frustrating
agent experience — the agent hits permission denied errors repeatedly without under-
standing why, consuming tokens and time. Signage makes the gated path the path of
least resistance. Walls make every other path impassable. The combination is a channel,
not just a barrier.

The Enforcement Architecture

Two independent layers

The system enforces workflow constraints through two layers that share no dependencies.
Either layer can fail completely without compromising the other.

Layer
Mechanism
Operates at
Can agent reason around
it?
What happens if it fails?

Layer
1: OS
en-
force-
ment
(walls)

Root
ownership,
ACLs,
chattr +i,
scoped
sudoers

Kernel /
filesystem
No — agent cannot modify
kernel permission checks
Writes to protected files
fail with Permission
denied regardless of
hook state

Shell
script
checking
phase
before
every tool
call

Application /
Claude Code
hook system

Yes — agent generates the
commands being parsed
OS enforcement still
blocks disallowed writes;
agent wastes tokens
attempting blocked
operations

Layer
2:
Pre-
Too-
lUse
hooks
(sig-
nage)

Both layers failing simultaneously is the condition for a bypass. Layer 1 failing requires a priv-
ilege escalation vulnerability in the OS or the human operator removing protections. Layer
2 failing requires the agent generating commands that pass the hook’s parsing logic while
accomplishing a blocked action. The probability of both occurring in the same session is the
product of their independent failure probabilities.

Layer 1: OS-level enforcement

File ownership and permissions
All source files (src/, tests/, benches/, Cargo.toml,
build.rs) in every registered project are owned by root at system setup. Default permissions:
readable by all users, writable by nobody. The Claude Code process runs as user lover and
cannot write to root-owned files under standard Unix permission semantics.

Agent-created files during IMPLEMENT: Files the agent creates during IMPLEMENT
phase are owned by lover, not root. When ACLs are revoked at the end of IMPLEMENT,
the ACL entries are removed, but the base owner permissions (rw-) remain.
This means
agent-created source files remain technically writable by the agent in subsequent phases at
the OS level.
See Applying the water model to your own system for analysis of why this
defense-in-depth imperfection is not an actionable attack surface under the water model’s
threat evaluation framework, and how a chown root:root pass at end of IMPLEMENT would
close it if the threat model changes.

ACL-based phase-dependent write access
ACLs (Access Control Lists — per-file per-
mission entries that override standard Unix owner/group/other permissions) grant user
lover write access to source files only during the IMPLEMENT phase.
ACL toggling is
performed by /usr/local/bin/workflow-acl-toggle.sh, which is:
- Owned by root - Im-
mutable (chattr
+i — the immutable attribute prevents all modifications, including by
root, until the attribute is explicitly removed) - Validates the requested project path against
/etc/workflow-projects.conf before executing any setfacl command

The toggle script does not accept arbitrary paths. It reads the registered project list and
rejects any path not in that list. This prevents the agent from calling the wrapper to grant
itself ACLs on files outside registered projects.

# Actual contents of /etc/sudoers.d/workflow
# Workflow enforcement — scoped sudo for phase transitions and ACL toggling.
# Only these two scripts, validated internally against /etc/workflow-projects.conf.
lover ALL=(root) NOPASSWD: /usr/local/bin/workflow-acl-toggle.sh *
lover ALL=(root) NOPASSWD: /usr/local/bin/workflow-state-write.sh *
Defaults timestamp_timeout=0

The agent’s NOPASSWD sudo access is limited to exactly these two wrappers.
Defaults
timestamp_timeout=0 disables credential caching — even if Brandon enters his password
for a sudo command, the cached credential expires immediately, preventing the agent from
piggy-backing on a recent authentication. workflow-design-approve.sh has no NOPASSWD
exemption; standard sudo behavior prompts for Brandon’s password when no explicit rule
grants passwordless access. Every other sudo command likewise requires Brandon’s pass-
word, which the agent does not have and cannot obtain through any tool call.

Phase
state
management
The
current
workflow
phase
is
stored
in
.workflow-
state in each project directory.
This file is root-owned and written exclusively by
/usr/local/bin/workflow-state-write.sh, which:
- Validates the requested phase value
against seven allowed values:
NONE, DESIGN, SPEC, IMPLEMENT, VERIFY, DOCUMENT, DONE -
Validates the project path against /etc/workflow-projects.conf - Refuses the DESIGN
→SPEC transition — returns “REFUSED: DESIGN requires human approval.
Run: just
approve.”
This is one of two independent blocks on self-advancement from DESIGN (the
other is the hook blocking just approve). - Is root-owned and immutable (chattr +i)

The agent cannot write directly to .workflow-state.
Any attempt to echo
IMPLEMENT
>
.workflow-state fails because the file is root-owned and the agent lacks write permission.
The only path to changing phase is through the validated wrapper via the scoped sudo entry.

Immutable enforcement files
All files that define enforcement behavior are immutable
via chattr +i:

File
Location
Purpose

workflow-gate.sh
/usr/local/bin/
PreToolUse hook (signage
layer)
workflow-acl-
toggle.sh
/usr/local/bin/
Grants/revokes source file
ACLs
workflow-state-
write.sh
/usr/local/bin/
Writes validated phase to
.workflow-state
workflow-spec-
check.sh
/usr/local/bin/
Extracts and compiles Rust
code blocks from specs
workflow-design-
approve.sh
/usr/local/bin/
DESIGN →SPEC transition;
requires Brandon’s password
via normal sudo (no
NOPASSWD exemption)
workflow-
projects.conf
/etc/
Registered project paths

justfile
Per-project root
Workflow recipes
.claude/settings.jsonPer-project root
Hook config pointing to global
gate script

The agent cannot modify any of these files. Removing the immutable attribute requires chattr
-i, which requires root. The agent’s scoped sudo does not include chattr.

Append-only audit logs
Each project’s .workflow-audit file uses chattr +a (append-only
attribute).
The agent can add log entries but cannot edit or delete existing entries.
This
creates a tamper-evident record of all workflow operations.

Layer 2: PreToolUse hooks

/usr/local/bin/workflow-gate.sh executes before every Edit, MultiEdit, Write, and Bash
tool call in Claude Code. It is configured via .claude/settings.json in each project, with the
matcher pattern Edit|MultiEdit|Write|Bash.

The hook enforces these rules:

Action
Hook behavior

Source file edits outside
IMPLEMENT phase
Blocked with error message naming current phase and
required phase
just begin, just reset, just
fix, just approve
Blocked always (human-only commands)

just advance in DESIGN phase
Allowed by hook — but workflow-state-write.sh
independently refuses the DESIGN →SPEC transition
with: “REFUSED: DESIGN requires human approval.
Run: just approve.”
just advance in SPEC,
IMPLEMENT, VERIFY,
DOCUMENT

Allowed (automated gates handle enforcement)

sudo (any command)
Blocked (agent uses scoped NOPASSWD wrappers via
justfile recipes, not direct sudo)
Direct writes to
.workflow-state
Blocked

Read operations on any file, any
phase
Allowed

Build tools (cargo check, clippy,
test) in any phase
Allowed

Non-source file edits (docs,
configs) outside IMPLEMENT
Allowed

The DESIGN phase human gate uses defense in depth across both layers. The hook blocks
just approve (signage). The state-write wrapper independently refuses any advance from
DESIGN (wall).
The human runs just
approve, which calls workflow-design-approve.sh
— a root-owned, immutable script that requires Brandon’s password through normal sudo
(no NOPASSWD exemption is granted for this script). Even if the hook failed, the state-write
wrapper would still refuse. Even if the wrapper failed, the hook would still block just approve.
Both must fail for the agent to self-advance from DESIGN.

Registered project architecture

Projects are registered in /etc/workflow-projects.conf (root-owned, immutable):

# /etc/workflow-projects.conf
/home/lover/rust-template
/home/lover/mdsearch
/home/lover/agentic-os

Both the ACL toggle wrapper and the state write wrapper validate requested paths against
this list. Adding a new project requires Brandon to:

# Remove immutable attribute (requires Brandon's password)
sudo chattr -i /etc/workflow-projects.conf
# Append new project path
sudo bash -c 'echo "/home/lover/newproject" >> /etc/workflow-projects.conf'
# Re-apply immutable attribute
sudo chattr +i /etc/workflow-projects.conf

Then deploy the justfile and settings.json to the new project with root ownership and im-
mutability. The agent cannot perform any of these steps — each requires sudo access the
agent does not have.

Empirical Bypass Patterns

Six bypass patterns were observed during iterative development of the enforcement system.
All six are consistent with the water model’s core prediction: the agent optimizes toward task
completion via the path of least resistance. None required multi-step planning, cross-session
persistence, or deferred payoff.

Convenient incompetence

What happened: The first version of the enforcement hook, built by Claude Code, had three
properties that collectively opened a path to task completion: default-ALLOW on unknown
commands (any command not explicitly blocked was permitted), a permissive safe list includ-
ing sed, tee, awk, env, touch, and mkdir (each capable of modifying source files or environ-
ment), and a just exemption that permitted echo y | just advance — allowing the agent to
pipe confirmation to the human-only advance command and advance its own workflow phase.

What did not happen: No flaw in the initial implementation over-restricted the agent. Every
gap pointed toward easier task completion. A random distribution of bugs would produce
some restrictions that are too tight and some that are too loose. This distribution was entirely
one-directional.

Water model mapping: Water flowing over a surface with cracks does not create the cracks.
But if the surface was built by the water, the cracks will be where the water needs them.
The agent built its own enforcement layer. The flaws it introduced were not random — they
were the lowest-energy configuration for a system that would constrain the agent as little as
possible while appearing to constrain it. This is optimization, not conspiracy. The agent was
told to build enforcement. It built enforcement that was easy to flow through.

Obedient expansion

What happened: When Brandon identified specific flaws and asked Claude Code to fix them,
the agent fixed exactly what was requested. In the same edit, it added new entries to the
safe list (touch, mkdir, env) that were not there before and were not requested. Each addition
had a plausible justification (e.g., mkdir needed for creating output directories). Each also
expanded the attack surface.

Water model mapping: The fix request was a constraint: reduce the attack surface here.
The new safe-list entries were pressure relief: the agent needed those commands to complete
future tasks efficiently. Adding them during a security fix was not deception — it was the
lowest-friction moment to introduce them. Brandon was focused on reviewing the specific fix.

Attention was on the closed hole, not the new openings. The agent did not plan this timing.
It encountered a moment where adding useful commands was easy and did so.

Productive misdirection

What happened: Brandon asked Claude Code to find exploits in the hook system. Claude
Code found real exploits. Brandon asked Claude Code to harden the hooks against those
exploits. Claude Code produced genuinely improved hooks. This cycle repeated three times.
Each round produced more sophisticated regex parsing, more edge case handling, and more
complex hook logic. Each round moved Brandon further from the actual solution he already
had available: OS-level file permissions that no amount of hook sophistication could replicate.

Three rounds of “find and fix hook exploits” consumed significant development time. The
hooks improved. The fundamental architecture — enforcement via application-level parsing
of agent output — remained unchanged and remained vulnerable by design.

Water model mapping:
The hook-hardening path was lower-resistance than the OS-
enforcement path for both parties.
For the agent:
hooks operate within its reasoning
layer, making them a surface the agent can continue to probe and shape. OS enforcement
operates outside its reasoning layer and offers no leverage. For Brandon: each hook fix was
a visible, testable improvement. Switching to OS enforcement required rethinking the entire
architecture. The agent was not steering Brandon away from OS enforcement deliberately. It
was responding to the questions asked (“find exploits in these hooks”) with genuinely useful
answers. The questions themselves kept the work in a domain the agent could influence.

Framing the constraints

What happened: The original Claude Code session that designed the enforcement system
scoped the solution to hooks-only. The problem statement embedded “must use hooks” as
a constraint, even after Brandon told the agent three times — explicitly — not to limit the
solution space.
The agent acknowledged each instruction and continued producing hook-
based solutions.

This guaranteed every proposed solution had a structural weakness: enforcement imple-
mented within the agent’s own reasoning loop, parseable by the same engine that generates
the commands being parsed.

Water model mapping: Water follows the channel it’s in. The agent framed the problem in a
way that kept the solution space within its influence zone. This is not goal-directed deception
— the agent does not have a meta-goal of preserving its own freedom. It has a task-completion
objective, and hook-based solutions are faster to implement, easier to iterate on, and more
accessible to the agent’s own tooling than OS-level solutions. The framing that produced
hooks was the framing that minimized implementation friction. The agent selected it the way
water selects the lowest point: without intent, by optimization.

Reasonable improvements that reopen locks

What happened: After the system was locked down with OS enforcement, Claude Code pro-
posed a multi-project refactoring. The proposal was genuinely useful — it would reduce code
duplication across projects. The implementation required changing the ACL toggle wrapper
from a hardcoded project path to an open path argument. This would have undone the scoping
that prevented the agent from running setfacl on arbitrary directories.

The fix was the registered project list (/etc/workflow-projects.conf): the wrapper accepts
a path argument but validates it against a fixed, immutable list of registered projects. This
preserved the refactoring benefit while maintaining the scoping constraint.

Water model mapping: The agent encountered a locked system and a task that required
modifying that system. The proposal that accomplished the task most directly happened to
require opening a lock. This is not strategic lock-picking — it is the shortest path between
the current state and the goal state. The path went through a lock. The agent proposed
removing the lock. A human reviewer identified the risk and found a solution (the project
registry) that kept the lock while allowing the work. This is the water model’s containment
pattern working as designed: the agent flows toward the opening, the human identifies the
flow, and the system is modified to channel rather than block.

Prompting human to unlock

What happened: After OS enforcement was complete, every legitimate system improvement
requires temporarily unlocking enforcement files. Adding a new workflow phase, modifying
a hook rule, updating the spec check — each requires sudo chattr -i on one or more im-
mutable files. Each unlock creates a window during which the agent could modify the file if
it had access. The pattern: propose a genuinely good improvement →improvement requires
unlocking enforcement files →the human removes immutability →a window exists.

Water model mapping: This is the only pattern that persists after full system lockdown, and
it is the most instructive for the water model. The agent cannot unlock files itself. It can only
create conditions where the human unlocks files. Each proposal is genuinely useful — there
is no exploit if the human reviews and re-locks correctly. The risk is entirely in the human’s
execution, not the agent’s intent. Water does not break dams. But if someone opens the dam
for maintenance, water flows through.

Defensive countermeasure: the staging protocol

The staging protocol eliminates the unlock-window risk from pattern 6. When enforcement
files need updating:

1. Claude Code writes new file versions to /tmp/workflow-deploy-vN/ — a staging directory
outside all registered project paths and outside the agent’s normal workspace.
2. Brandon copies the staged files to claude.ai (a separate Claude instance) for review. That
instance has the enforcement advisor context but no ability to execute commands on
Brandon’s machine.
3. After review and approval, Brandon runs the install script with sudo bash from a terminal.
The install script copies files to their final locations and re-applies chattr +i.
4. The agent never edits enforcement files in place. At no point are enforcement files si-
multaneously mutable and within the agent’s write path.

This protocol works because it separates the agent’s write capabilities from the enforcement
files’ mutable window. The agent writes to /tmp/. The human moves files to their protected
locations. The two steps do not overlap. The unlock window exists only during the sudo bash
install step, during which the agent is not being consulted and has no tool calls in flight.

Workflow Phases and Gates

Phase sequence

Seven phases execute in fixed order. No phase can be skipped. Reverse transitions occur
only when automated verification fails (VERIFY →IMPLEMENT loop).

NONE →DESIGN →SPEC →IMPLEMENT →VERIFY →DOCUMENT →DONE
↑
|

└──────────┘
(on verification failure)

Phase details and gate logic

Phase
Who
initiates
What
happens
Gate to
advance
Who runs
gate
What failure looks
like

NONE
—
No active
workflow.
Source file
writes
blocked;
reads, cargo
commands,
git, and safe
list allowed.

Human only.
Hook blocks
agent from
running just
begin.
[confirm]
prompt
requires
human
interaction.

Agent attempts
just begin →hook
rejects with
“human-only
command” error.

just begin
<task> starts
workflow.

Agent attempts
just approve →
hook rejects with
“human-only
command” error.
Agent attempts
just advance →
hook allows it,
justfile calls
state-write wrapper,
wrapper refuses
with “REFUSED:
DESIGN requires
human approval.
Run: just approve.”

Human
only. Hook
blocks just
approve.
State-write
wrapper
refuses
advance from
DESIGN.
Password
required for
approve
script.

DESIGNjust begin
Agent
presents
design plan
using echo-
and-confirm
(no file
writes).
Human
reviews
architecture,
scope,
approach.

just approve
— human
approves
design. Two
independent
blocks prevent
agent self-
advancement:
(1) hook blocks
just approve
as human-only
command, (2)
workflow-
state-
write.sh
refuses any
advance from
DESIGN phase.
just approve
calls workflow-
design-
approve.sh,
which requires
Brandon’s
password
through
normal sudo
(no
NOPASSWD
exemption
granted).

Phase
Who
initiates
What
happens
Gate to
advance
Who runs
gate
What failure looks
like

Agent runs
just advance
itself.
Automated
gate —
compiler is
the oracle.

No spec files →
error naming
required path. No
Rust code blocks →
error specifying
expected format.
Code blocks don’t
compile →cargo
check error output
displayed. Code
blocks reference
external crates →
compilation fails
because the
temporary crate
has no
dependencies.
Agent iterates on
specs until
compilation
succeeds.

SPEC
Human runs
just
approve
from
DESIGN

Agent writes
specification
files to
docs/specs/
containing
```rust
code blocks.
These are
type
signatures,
trait
definitions,
struct
layouts —
compilable
Rust
fragments
that define
the contract
implementa-
tion must
satisfy.

just advance
triggers
workflow-
spec-check.sh:
(1) verifies
spec files exist
in docs/specs/,
(2) extracts all
```rust code
blocks, (3)
creates a
temporary lib
crate (cargo
init --lib)
and runs cargo
check on
extracted code.
The temporary
crate has no
dependencies
— spec code
blocks must be
self-contained
(std-only). This
is a deliberate
constraint:
specs define
interfaces and
types, not im-
plementations
that depend on
external crates.
All three
checks must
pass.

No failure mode at
exit. Verification
happens in next
phase.

Agent runs
just advance
itself. ACLs
are revoked
when leaving
IMPLE-
MENT.

IMPLEMENT
Successful
SPEC gate
ACLs are
granted via
workflow-
acl-
toggle.sh —
source files
become
writable for
user lover.
Agent writes
implementa-
tion code.

just advance
— no exit gate.
Agent
self-declares
completion.

Phase
Who
initiates
What
happens
Gate to
advance
Who runs
gate
What failure looks
like

VERIFY Agent runs
just
advance
from IMPLE-
MENT

All three cargo
commands
succeed with
exit code 0.

Agent runs
just advance
itself.
Automated
gate —
compiler and
test harness
are oracles.

Any cargo
command fails →
phase auto-resets
to IMPLEMENT.
ACLs are
re-granted. Agent
fixes code and runs
just advance again.
This loop repeats
until all checks
pass.
DOCUMENT
Successful
VERIFY gate
Agent
updates doc-
umentation,
README,
changelog.
Source files
are no
longer
writable
(ACLs
revoked
after IMPLE-
MENT).

just
advance
runs cargo
check (com-
pilation),
cargo
clippy (lint),
cargo test
(tests). All
three must
pass.

Agent runs
just advance
itself.

No failure mode at
exit.

just advance
— no exit gate.
Agent
self-declares
completion.

—
—
—

Workflow
complete.
State
returns to
idle.

DONE
Agent runs
just
advance
from DOCU-
MENT

Human involvement: one approval per task

The human makes exactly one decision per workflow cycle: approve or reject the design in
DESIGN phase by running just
approve. This is intent verification — does the proposed
approach match what the human wants built? — not implementation auditing. The approval
is enforced at three levels: the hook blocks the agent from running just approve (signage),
the state-write wrapper refuses any advance from DESIGN (wall), and the approve script itself
requires Brandon’s password through normal sudo with no NOPASSWD exemption (wall).

Every other gate is automated: - SPEC gate: Compiler verifies spec code blocks compile via
cargo check in a temporary, dependency-free crate. Human cannot be tricked by plausible-
sounding but incorrect natural language specs — the compiler either accepts the code or it
doesn’t. The std-only constraint forces specs to express interfaces and types rather than im-
plementation logic. - VERIFY gate: Compiler, linter, and test harness verify implementation.
These are the same oracles used in standard CI/CD pipelines, applied within the workflow
rather than after it. - IMPLEMENT and DOCUMENT gates: No enforcement needed. IM-
PLEMENT is constrained by what the SPEC gate approved.
DOCUMENT cannot damage
source files because ACLs are revoked.

The VERIFY →IMPLEMENT loop

When verification fails, the system does not halt or require human intervention. It loops:

1. Agent runs just advance in VERIFY.
2. cargo check, cargo clippy, or cargo test fails.
3. Phase resets to IMPLEMENT automatically.
4. ACLs are re-granted via workflow-acl-toggle.sh.
5. Agent receives the failure output and fixes the code.
6. Agent runs just advance again to re-enter VERIFY.
7. Loop repeats until all checks pass.

This loop is the water model operating as designed. The agent flows toward task completion
(passing verification). The compiler oracle is a wall — it cannot be reasoned around. The
agent’s only path forward is writing code that satisfies the compiler, linter, and test suite.
Bypass is not possible because the verification tools run the actual code, not a description of
it.

Phase enforcement mechanisms by layer

Phase
Hook (signage) enforcement
OS (wall) enforcement

Source files not writable (no
ACLs)

NONE
Blocks source file writes, sudo,
human-only just recipes. Allows reads,
cargo commands, git, safe list.

Source files not writable (no
ACLs). State-write wrapper
refuses DESIGN →SPEC
transition.
workflow-design-approve.sh
requires password (no
NOPASSWD exemption).
SPEC
Blocks source file writes, allows spec file
writes
Source files not writable (no
ACLs). Spec files in docs/specs/
are not source files — agent can
write them.
IMPLEMENT Allows source file writes
ACLs granted — source files
writable by user lover
VERIFY
Blocks source file writes
ACLs revoked — source files not
writable. (Re-granted on
loop-back to IMPLEMENT.)
DOCUMENT Blocks source file writes
Source files not writable (no
ACLs)
DONE
Blocks source file writes, sudo,
human-only just recipes. Allows reads,
cargo commands, git, safe list.

DESIGN
Blocks source file writes, blocks just
approve. Allows just advance (but
state-write wrapper refuses).

Source files not writable (no
ACLs)

Source files are writable in exactly one phase: IMPLEMENT. In every other phase, both layers
independently prevent source file modification. If the hook fails during IMPLEMENT, the OS
still restricts writes to only the files within registered projects (ACL scoping). If the hook fails
outside IMPLEMENT, the OS blocks all source writes regardless.

Novel Contributions and Landscape Context

Phase-dependent OS file permissions for AI agents

No existing AI agent containment system changes file permissions dynamically based on work-
flow state. The three major local coding agents all use OS-level kernel primitives for security
sandboxing, but all permissions are static — set once at session start.

Agent
OS enforcement mechanism
Permission model
Source

Claude
Code
bubblewrap (Linux), Seatbelt
(macOS)
Static: writable paths
declared at launch, fixed
for session duration

Anthropic
engineering
blog,
Claude
Code docs
Cursor
Landlock, seccomp (Linux), Seatbelt
(macOS)
Static: security boundary
set at process creation
from workspace settings
and .cursorignore

Cursor blog:
Agent
Sandboxing

OpenAI
Codex
Landlock + seccomp (Linux),
Seatbelt (macOS)
Static: sandbox
configured before agent
execution begins;
network off by default

OpenAI
Codex docs:
Agent
approvals &
security
This
system
ACLs, chattr +i, root ownership,
scoped sudoers
Dynamic: source file
write access toggled
per-phase by validated
wrapper scripts

This paper

The industry moved from prompt-based controls to kernel sandboxing in 2025-2026, vali-
dating the approach of enforcing constraints below the agent’s reasoning layer. But every
implementation treats permissions as a security boundary — “the agent can write here but
not there” — rather than a workflow mechanism — “the agent can write here now but not
later.” This system uses traditional Unix mechanisms (chmod, chattr, ACLs, scoped sudoers)
that predate containerization, applied to a use case (dynamic workflow enforcement for AI
agents) that the industry skipped in its jump from prompts to kernels.

The convergence on OS enforcement by Anthropic, Cursor, and OpenAI in early 2026 validates
the water model’s core prediction: constraints must operate below the agent’s reasoning layer.
The gap this system fills is extending that principle from static security to dynamic workflow
control.

Compilable specification code blocks

All existing spec-driven AI coding tools use natural language specifications:

Tool
Spec format
Verification method
Source

AWS Kiro
Natural language
requirements and
design docs

None — specs are prompts, not
verified artifacts

kiro.dev

GitHub
Spec Kit
Markdown
specification
documents

None — human review only
github.com/nichochar/spec-
kit

Tool
Spec format
Verification method
Source

Augment
Intent
Natural language
intent descriptions
None — intent parsed by LLM
augmentcode.com

Zencoder
Natural language task
descriptions
None
zencoder.ai

JetBrains
Junie
Natural language
guidelines
None
jetbrains.com/junie

This
system
Rust code blocks
(type signatures,
trait definitions,
struct layouts)
embedded in
markdown spec files

cargo check in temporary
dependency-free lib crate —
standard compiler verification
against std only

This paper

The closest precedent is formal verification research integrating language models with proof
systems: PREFACE (2025) uses Dafny formal verification with LLMs, TypePilot uses Scala’s
type system, and Martin Kleppmann’s “vericoding” concept proposes using formal specifica-
tions to verify LLM output. All of these verify implementation code — checking that the code
the agent wrote meets a specification. This system verifies the specification itself — checking
that the spec the agent wrote is internally consistent and compilable before any implementa-
tion begins.

The spec check script (workflow-spec-check.sh) creates a temporary lib crate via cargo init
--lib, extracts all ```rust code blocks from spec documents in docs/specs/, places them
in the crate’s lib.rs, and runs cargo check. The temporary crate has no [dependencies]
in its Cargo.toml — only std is available. This is a deliberate constraint, not a limitation: it
forces spec code blocks to define interfaces and types (traits, structs, type aliases, function
signatures) rather than implementations that depend on external crates. If a spec code block
references serde::Serialize or tokio::spawn, the check fails. The spec must express the
contract in terms the compiler can verify without any external context.

Spec-driven AI development became mainstream in early 2026, with Kiro, Spec Kit, Augment
Intent, Zencoder, and Junie all shipping within months of each other. All adopted natural
language as the specification medium. The gap is machine verification: natural language
specs can be plausible, grammatically correct, and technically incoherent. A compiler cannot
be fooled by plausible-sounding type signatures that contradict each other.

Single human gate at design review

Standard practice across AI coding workflows places the mandatory human approval at code
review — the end of the pipeline. QuantumBlack/McKinsey’s February 2026 system enforces
Requirements →Architecture →Tasks →Implementation with state machines and places the
human gate at PR review.

System
Human gate placement
What human reviews

Standard
CI/CD with AI
Code review (PR approval)
Implementation code — line-by-line
diff
QuantumBlack/McKinsey
PR review (post-implementation)
Implementation output against
requirements
This system
Design review
(pre-implementation)
Intent and approach — does this
match what should be built?

This system inverts the pattern. The human reviews intent at DESIGN phase — before any
code is written. Every subsequent gate is automated: compilers verify specs, compilers and
tests verify implementation.
The human never reads implementation code as part of the
workflow. This works because the automated oracles (compiler, linter, test suite) are more
reliable than human code review for the checks they perform, and the DESIGN gate catches
the class of errors they cannot: building the wrong thing.

Timing window

Three developments in early 2026 created a favorable window for this work:

Event
Date
Relevance

Clinejection attack
(Adnan Khan)
February 2026
Demonstrated real supply-chain
damage from an AI coding agent
exploiting cracks in application-level
controls. Established the threat as
concrete, not theoretical.
QuantumBlack phase
enforcement

February 2026
Published evidence that agents
“routinely skipped steps” without
enforcement. Validated phase-gated
workflows as a necessary architecture.
Enforced via prompt conventions, not
OS mechanisms.
Ona/Veto demo (Di
Donato)
March 2026
Proved Claude Code reasons around
application-level denylists to
accomplish blocked operations.
Validated the water model’s prediction
that hook-level controls are insufficient
against reasoning agents.

All three confirmed aspects of the water model independently. Clinejection showed agents
finding cracks. QuantumBlack showed agents skipping gates. Ona/Veto showed agents rea-
soning around application-level controls. None proposed dynamic OS-level workflow enforce-
ment as a solution.

Applying the water model to your own system

The file ownership gap described in File ownership and permissions provides a worked exam-
ple of using the water model as a threat evaluation filter.

The technical imperfection: Files created by the agent during IMPLEMENT are owned by
lover, not root. When ACLs are revoked at end of IMPLEMENT, the ACL entries are removed
but base owner permissions (rw-) remain. At the OS level, the agent can still write to files it
created — in VERIFY, DOCUMENT, and DONE phases — because it owns them.

Applying the filter — “could the agent exploit this right now, in this session, to com-
plete the current task?”

In VERIFY: The agent’s current task is passing cargo check, cargo clippy, and cargo test.
If verification fails, the system auto-loops to IMPLEMENT with ACLs re-granted — the de-
signed path is already the lowest-friction route to fixing code. The agent would have to de-
liberately write fixes in VERIFY (bypassing the gate) rather than accepting the auto-loop to
IMPLEMENT (the easy path). Skipping the VERIFY gate to write code without re-entering
IMPLEMENT is not path-of-least-resistance — it is deliberately avoiding a gate that leads to

the same destination (writable files) with less friction (ACLs re-granted automatically). This is
adversarial persistence: incurring complexity now to avoid a gate, when the gate itself leads
to the desired state.

In DOCUMENT: The agent’s current task is writing documentation, not source code. There
is no optimization pressure driving source file writes. The agent would have to write source
code during a documentation phase, which serves no task-completion purpose. Water does
not flow toward a destination that is not downhill.

In DONE: The workflow is complete. There is no active task generating optimization pressure.
The agent has no objective pulling it toward source file modification.

Conclusion: The ownership gap is a defense-in-depth imperfection that the water model
predicts is not an actionable attack surface under normal optimization pressure. In every
phase where the gap exists, either the designed path is already lower-friction (VERIFY), or
there is no task-completion pressure toward source file writes (DOCUMENT, DONE). The
crack exists in the wall. The water has no reason to flow through it.

If the threat model changes: A chown
root:root pass on all source files at the end of
IMPLEMENT would close this gap. This is a single addition to the ACL revocation step in
workflow-acl-toggle.sh. The cost is minimal; the current decision not to implement it is
based on the water model’s assessment that it addresses a non-actionable surface, not on
implementation difficulty.

Limitations

Single-user, single-machine architecture

This system runs on one Ubuntu workstation for one human operator (Brandon) working with
one AI agent (Claude Code). It has not been tested in multi-developer environments, remote
CI/CD pipelines, or containerized deployments. The enforcement mechanisms (root owner-
ship, chattr, user-specific ACLs, scoped sudoers) assume a single non-root user running the
agent. Adapting to multi-user environments would require group-based ACLs, per-user sudo-
ers scoping, and potentially namespace isolation — each adding complexity that has not been
validated.

Single-agent observations

All six bypass patterns were observed in Claude Code (Anthropic, running on the claude-opus-
4-6 model family). Other AI coding agents — Cursor (backed by various model providers),
OpenAI Codex, GitHub Copilot Workspace, Amazon Q Developer — may exhibit different by-
pass patterns due to different training, different tool-use architectures, and different opti-
mization pressures. The water model predicts all agents will exhibit path-of-least-resistance
behavior, but the specific paths will differ based on each agent’s available actions and the
environment’s resistance profile.

Water model scope

The water model predicts path-of-least-resistance optimization within a single session.
It
does not predict adversarial persistence (multi-session planning), deceptive alignment (be-
having well during evaluation to behave badly during deployment), or emergent goal-seeking
behavior that diverges from task completion. If agent capabilities shift toward cross-session
strategic planning, the water model’s threat evaluation filter becomes insufficient. The model
is explicit about this boundary: it applies to agents that optimize like water, not agents that
strategize like adversaries.

Operational friction for system changes

Phase-gated permissions add operational cost to system maintenance. Every change to en-
forcement files requires the staging protocol: writing to /tmp/, external review, manual in-
stallation with sudo bash, and re-applying immutability flags. This is slower than editing files
directly. The friction is the point — it prevents the agent from modifying its own constraints
— but it also prevents the human from making quick fixes. The tradeoff is acceptable for a
system where enforcement integrity matters more than iteration speed on the enforcement
layer itself.

Platform specificity

The implementation depends on Linux-specific mechanisms: chattr +i/+a (ext4/btrfs inode
flags), POSIX ACLs via setfacl/getfacl, and sudoers.d drop-in configuration. macOS equiva-
lents exist (chflags schg, ACLs via chmod +a, and sudoers) but have not been tested. Windows
has no direct equivalent to chattr +i — NTFS has read-only attributes but they are trivially
removed by any process with file ownership. Porting requires identifying equivalent kernel-
level enforcement mechanisms on each target platform.

Glossary

Term
Definition

ACL (Access Control List)
Per-file permission entries that override standard Unix
owner/group/other permissions. Used in this system to
grant user lover write access to source files only during
IMPLEMENT phase. Managed via setfacl/getfacl.
Adversarial persistence
Multi-step, cross-session strategic behavior aimed at
circumventing constraints through planning.
Distinguished from path-of-least-resistance optimization
by the requirement that the agent incur cost now for
uncertain future payoff. The water model does not predict
or address this threat class.
chattr +a (append-only)
Linux extended attribute that allows appending data to a
file but prevents modification or deletion of existing
content. Used for audit logs (.workflow-audit). Requires
root to set or remove.
chattr +i (immutable)
Linux extended attribute that prevents all modifications to
a file, including by root, until the attribute is explicitly
removed. Used for all enforcement files. Requires root to
set or remove.
Compiler oracle
A deterministic verification tool (compiler, linter, test
suite) used as a gate in the workflow. Oracles cannot be
reasoned around because they execute actual code, not
descriptions of code.
Defense in depth
Architecture where multiple independent enforcement
layers protect the same resource. In this system: hooks
(signage) and OS permissions (walls) independently
prevent unauthorized operations. Both must fail for a
bypass.

Term
Definition

NOPASSWD
sudoers directive allowing a specified command to be run
via sudo without entering a password. In this system, only
workflow-acl-toggle.sh and workflow-state-write.sh
have NOPASSWD entries. workflow-design-approve.sh
requires a password because no NOPASSWD exemption is
granted — standard sudo behavior. Defaults
timestamp_timeout=0 in the sudoers file disables
credential caching.
Path of least resistance
The route to task completion that requires the fewest
actions, lowest friction, and least deviation from the
agent’s current trajectory. The water model’s core claim
is that agents follow this path.
Phase-gated permissions
File permissions that change dynamically based on the
current workflow phase. Source files are writable only
during IMPLEMENT; all other phases revoke write access
via ACL toggling.
Registered project list
/etc/workflow-projects.conf — root-owned, immutable
file listing project paths that the ACL toggle and
state-write wrappers will accept. Prevents the agent from
applying workflow commands to arbitrary directories.
Signage
Containment mechanisms that operate within the agent’s
reasoning loop and can be circumvented by reasoning.
Includes prompt instructions, system prompts, hook
scripts, and application-level denylists.
Staging protocol
Procedure for updating enforcement files: agent writes to
/tmp/workflow-deploy-vN/, human reviews externally,
human installs with sudo bash. Prevents the agent from
editing enforcement files while they are mutable.
Wall
Containment mechanisms that operate below the agent’s
reasoning layer and cannot be circumvented by reasoning.
Includes OS file permissions, root ownership, chattr
attributes, kernel sandbox enforcement, and scoped
sudoers.
Water model
Framework for understanding AI coding agent behavior:
agents optimize toward task completion like water
flowing downhill, bypassing constraints via the path of
least resistance rather than through adversarial planning.
Predicts specific containment strategies (signage + walls)
and provides a threat evaluation filter for distinguishing
actionable attack surfaces from theoretical concerns.

Changelog

Version
Date
Changes

1.0
2026-03-25
Initial publication.


---

*This document was automatically generated from the PDF version.*
