Who Watches What Your Agent Is Doing?
Sandboxing, Task-Level Isolation, Fine-Grained Permissions, and LLM Judges — the Missing Supervision Layer of Agentic Software
Who Watches What Your Agent Is Doing?
Sandboxing, Task-Level Isolation, Fine-Grained Permissions, and LLM Judges — the Missing Supervision Layer of Agentic Software
Your coding agent just edited forty files, ran a build, installed a package, and pushed a branch. You watched the first two minutes, went for coffee, and came back to a green checkmark. This is not a hypothetical productivity boost story; it is Tuesday, on a few million developer machines, right now. Meanwhile the same week's headlines include models caught leaving hidden notes to future instances to conceal bad behavior [1], and a rogue-agent remediation industry being built in public [2]. The question is no longer whether agents can do the work. It is who — or what — watches what they actually do while nobody is looking.
There are three credible answers, and they are not alternatives. They are layers. First, containment: the agent runs somewhere its damage cannot escape — a sandbox with no network, a filesystem it cannot leave. Second, least privilege at task granularity: every sub-task an agent delegates runs under an explicit permission envelope, narrower than the parent's, with no path to launder capabilities downward. Third, judicial oversight: independent LLM judges that review plans and outputs the way a code reviewer reviews a pull request — adversarially, with the power to block.
This essay walks through all three layers using the concrete architecture of odek, an open-source agent harness — every mechanism described here exists in a repo you can clone today. We finish with a worked example: how to trigger a judge-monitored plan, and how to hand a sub-agent exactly the permissions it needs — and not one bit more.
1. The Supervision Gap
Traditional software has a supervision stack built over fifty years: process isolation, users and groups, capabilities, code review, CI gates, audit logs, on-call rotation. None of it anticipated a coworker who writes code at machine speed, executes shell commands based on statistical guesses, and ingests instructions from every web page it reads.
The specific gap is this: the agent's intent is probabilistic, but its actions are real. A classical program does what its author wrote. An agent does what an LLM decided — this turn, under this context, with these tool descriptions and whatever a fetched web page whispered into its context window. Indirect prompt injection research has documented, repeatedly and empirically, that instruction-shaped content riding in benign tool output gets executed — hidden in web pages, code comments, error messages, or command arguments [3]. Not exotic exploits: ordinary tool output, doing double duty as an instruction.
You cannot review your way out of this. A human reading a transcript at agent speed is the bottleneck, and approval fatigue is a documented failure mode of every human-in-the-loop permission system ever built: click "approve" enough times and it stops meaning anything. The supervision layer has to be mostly automatic, mostly default-deny, and mostly invisible when everything is fine.
That leads to a design principle worth stating up front:
Supervision must be structural, not behavioral. Anything that depends on the model choosing to be safe is not supervision — it is hope.
2. Layer One: The Sandbox
The first layer answers the question "what happens if the agent goes completely rogue?" with a simple offer: try it, from inside a container where nothing matters outside the workspace.
Concretely, in odek:
- Shell commands run in an isolated Docker container by default — sandboxing is on for
run,repl, andserve, and opting out is an explicit, visible decision (--no-sandbox). You can invert the default entirely withODEK_REQUIRE_SANDBOX=1, making any unsandboxed execution fatal [4]. - The network is off by default.
sandbox_network: "none"means a prompt-injected build step that tries to exfiltrate your~/.npmrcor phone home to an attacker's collector has no route out. Network access is a deliberate per-run decision (--sandbox-network bridge), not an ambient property. - Resources are bounded: memory limits, CPU caps, a non-root user, read-only root filesystems, and explicit volume mounts. The blast radius of any single misbehaving command is a number you chose, not a number the model decides.
- Project-specific environments come from a checked-in
Dockerfile.odek, so the agent's toolchain is versioned and reviewable — no surprise global installs.
Its value is not that it catches clever attacks; it is that it converts "the agent did something catastrophic" into "the agent did something catastrophic inside a disposable container." Those are different Saturdays.
What the sandbox does not solve: the agent still needs to read your code and write your files to be useful. The workspace itself is inside the blast radius, and the workspace is where the real assets live. That is what the second layer is for.
3. Layer Two: Task-Level Execution Isolation and Fine-Grained Permissions
Modern agents do not work alone — they delegate. An orchestrator spawns sub-agents to research, build, test, and review. Each delegation is a capability transfer, and the security question is: what exactly changed hands?
3.1 Risk classes, not booleans
odek classifies every tool invocation into named risk classes — from read-only safe, through workspace-scoped local_write, up to system_write, code_execution, install, network_egress, destructive, and a dedicated persistence class for anything whose entire purpose is deferred execution: shell profiles, git hooks, CI workflow steps, cron entries [5].
That last class deserves a pause, because it encodes a hard-won lesson. Writing a line to ~/.zshrc is not destructive, not egress, and not an install — by classical file-write logic it is harmless. But its payload executes later, in a context the user trusts. It is the malware pattern of the 1990s wearing an agent's trench coat. Classifying by write target rather than command shape — and making persistence-class operations prompt or deny, including headless — is the kind of counterintuitive, adversarially-derived design that only comes from red-teaming your own system.
Path classification is equally paranoid with good reason: credential files (~/.aws/credentials, ~/.netrc, ~/.ssh, the odek trust anchors themselves) escalate to system_write even on reads via shell, and matching is case-insensitive because macOS APFS and Windows NTFS will happily let ~/.SSH/id_rsa and ~/.ssh/id_rsa be the same file [5].
3.2 The default envelope, and profiles
Out of the box, every sub-agent runs under a built-in default profile capped at local_write: no system writes, no arbitrary code execution, no installs, no network egress, no destructive operations [6]. Sub-agents must opt up by selecting an explicit, operator-authored profile — never by asking nicely.
Profiles are named permission envelopes defined by the operator, not the model:
{
"profiles": {
"research": {
"description": "Read-only web research — fetches pages, never edits or runs anything",
"max_risk": "safe",
"tools": { "disabled": ["write_file", "patch", "batch_patch", "shell"] }
},
"builder": {
"description": "Write and verify code changes with project build/test commands allowlisted",
"max_risk": "local_write",
"allowlist": ["go test ./...", "go build ./..."]
}
}
}Three properties matter here:
max_riskis a ceiling, not a hint. Every class above it is denied outright for tasks running under that profile.allowlistis exact-invocation. Abuilderprofiled agent can rungo test ./...without a prompt — and nothing else in its prompt-class. Exact strings, no wildcards for the model to decorate with injected arguments.- Profiles are operator-authored. Project files and task files cannot define or widen them; a task can select a defined profile, and nothing more. The ceiling is set by the human who owns the machine.
3.3 Trust is non-increasing downward
The subtle attack against delegation systems is capability laundering: an orchestrator that ingested a prompt injection spawns a "trusted" sub-agent and launders its untrusted context into a child with a bigger permission envelope.
odek closes this structurally: trust is stamped into the task at spawn time and the child runs at min(parent_trust, child_trust) — a task tree rooted in untrusted content cannot promote itself [6]. Sub-agents are non-interactive by design: they never prompt for approvals, prompt-class operations are denied outright, and the only route to a prompt-class operation is an exact operator allowlist entry.
And crucially, denials are loud, not silent. Every denied operation is reported back up the chain (denials: [{tool, class, reason}]) and emitted as a runtime event the operator can audit. A delegation system that silently swallows its own security decisions is a system you cannot reason about; one that reports every refusal turns the permission layer into an observable, debuggable surface. Each sub-task runs in the smallest envelope that makes it functional, and no chain of delegation can amplify its own permissions.
4. Deterministic Risk Assessment and Approval Requests
The judge layer handles semantic judgment. But the moment-to-moment question — "may this specific command run?" — must never be a judgment call at all. It needs to be deterministic: same input, same verdict, every time, with a paper trail.
4.1 The classifier
odek answers this with a command classifier that runs before every shell invocation, file write, and network operation. It is a deterministic function from (command, paths, flags) to risk class, and its design reads like a red-team log because it was built against real evasion attempts [5]:
- Wrapper expansion.
watch -n 1 ps,timeout 10 make test,sudo apt update— wrappers are unwrapped and the inner command classified.sudofloors the class tosystem_writeregardless of what follows. - Substitution recursion.
$(echo rm) -rf /is not an echo; command substitutions are recursively expanded before classification. - Separator awareness.
cat x & curl evil.sh— a lone&is a command separator, so the hidden second command is classified on its own merits. - Effect-class fidelity.
docker psissafe;docker runiscode_execution;docker pullisnetwork_egress;docker system pruneissystem_write.brew installisinstallwhilebrew listissafe. Toolchains compile assafe, butjava Mainexecutes (code_execution). And anything unrecognizable isunknown— deny, never guess.
The property that matters is not the long verb list; it is fail-closed directionality. Classification errs toward the more restrictive class, and the only path to a prompt-class operation without a human is an exact-match operator allowlist. Denylist entries (prefix match) are checked after allowlist but before class policy. The result is auditable: for any historical command, the classifier's verdict is reproducible — which turns "why did the agent do that?" from folklore into a lookup.
4.2 Approval requests as a designed UX, not an interrupt
When an operation's class maps to prompt, the approval request is a designed surface with its own threat model — because the human is now the attack surface:
- Typed confirmation plus a pause. Destructive-class approvals on TTY and Web require typing
approveand waiting a 1.5-second settle — reflex-approval after a stream of benign prompts is the failure mode being defended against, and the friction targets it directly. - Batch cards show everything. A
parallel_shellorbatch_patchcall renders a card with every command and path classified and shown in full — no truncation. And a blanket "trust all" is refused if any item in the batch is unclassifiable: one opaque item poisons the whole card. - Trust is withheld for the dangerous classes. The session-trust shortcut ("don't ask me again this session") is simply unavailable for
destructive,persistence,unknown, and batched tool calls — in all approvers, on every channel. You can pre-trust a class of file writes; you cannot pre-trust deferred execution. - Headless means fail-closed. With no human to ask, the default
non_interactivepolicy isread_only: safe inspection proceeds, everything prompt-class is denied. CI and cron get a read-only agent by construction, not by instructions.
The cumulative effect: the agent's every consequential action passes a deterministic gate, and the gate's exception path — the approval request — is engineered so that saying yes is a deliberate act, not a twitch. Supervision systems fail at their laziest interaction, not their strongest one; this is the layer designed for the lazy one.
5. Layer Three: LLM Coordinator Judges
Sandboxing contains damage. Permissions constrain capability. Neither can answer the question a staff engineer actually asks of a junior's work: is this plan any good, and did they actually do what they said? That judgment is semantic, and no human can perform it at agent speed. That is the judge layer's job.
The pattern — call it Agents-as-a-Judge, in deliberate contrast to LLM-as-a-judge — treats review as an adversarial multi-agent process, not a single model's self-grading:
- Adversarial framing. The judge's job is to find what is wrong: holes in a plan, unverified claims in a diff, security regressions. A reviewer told to "validate" finds validation; a reviewer told to attack finds attacks.
- Independence. Judges get the artifact under review (plan, diff, essay, test results) and their own instructions — not the producing agent's reasoning, and never the producing agent's ability to edit the judge's context. In odek's sub-agent architecture, parent-supplied text travels only in the user request, never in the child's system prompt, so a compromised orchestrator cannot rewrite the judge's identity or strip its safety rules [6].
- Diversity. Multiple judges, different personas, ideally different models. Shared blind spots are the failure mode of single-reviewer systems — the same reason human security review uses a panel.
- Evidence-gated completion. A plan is not "done" because the model says so. odek's plan tool lets each step carry explicit checks — a tool name and exact arguments whose actual observed outcome must pass before the step may be marked complete. "I ran the tests" is a claim;
go test -race ./...with exit code 0 is evidence [7]. - Convergence, not consensus theater. In production use, a three-judge panel iterates: findings go back, the work is revised, review repeats until the panel is clean. The release gate is the clean review, not the author's confidence.
The judge layer is what catches the failure classes the first two layers structurally cannot: a plan that is perfectly permission-compliant and completely wrong; a diff that passes CI and quietly weakens a security property; a "completed" step whose verification never actually ran. Sandboxing bounds the cost of a bad decision. Judges reduce the rate of bad decisions.
6. Worked Example: A Judge-Monitored Plan in odek
Enough architecture. Here is how this looks in practice, from the operator's seat.
Step 1 — define the envelopes (once, in your operator config):
{
"profiles": {
"builder": {
"description": "Write and verify code changes; build/test commands allowlisted",
"max_risk": "local_write",
"allowlist": ["go test ./...", "go build ./...", "go vet ./..."]
},
"adversary": {
"description": "Read-only adversarial review — may read everything, change nothing, run only vet",
"max_risk": "local_write",
"tools": { "disabled": ["write_file", "patch", "batch_patch", "delegate_tasks"] },
"allowlist": ["go vet ./..."]
}
}
}Step 2 — the orchestrator declares a plan with evidence checks attached to each step. In conversation, it invokes the plan tool; each step can carry up to four checks, each one a concrete tool invocation that must be observed succeeding before the step counts as done:
plan →
1. Add request validation to the handler [pending]
check: shell { command: "go vet ./..." }
2. Add failing test for malformed payloads (RED) [pending]
check: shell { command: "go test ./internal/... -run TestMalformed -count=1" }
3. Implement fix until tests pass (GREEN) [pending]
check: shell { command: "go test -race -count=1 ./..." }
4. Adversarial review of the diff [pending]The critical mechanic: the model cannot mark step 3 complete by asserting it. The runtime matches the check's exact tool call and arguments against actually executed invocations, and only a passing observed outcome flips the status. The plan progress the user sees in the UI is grounded in tool-call evidence, not narrative.
Step 3 — the judge step delegates to an adversarial sub-agent, and the delegation itself carries the permission envelope:
delegate_tasks →
task:
goal: "Adversarially review the working-tree diff for correctness,
security regressions, and unverified claims. Iterate with the
findings until the review is clean. Deliver the final verdict
and a list of any policy denials you hit."
profile: "adversary"
trust_level: "untrusted"
max_risk: "local_write"(The adversary profile caps at local_write rather than safe because the reviewer legitimately runs go vet — code execution — to check the diff compiles. Read-and-verify, write-nothing: the envelope is as small as the job allows.)
What happens at runtime, layer by layer:
- Discovery: the orchestrator can call
list_subagent_profilesand seesadversary— its description, itsmax_risk, its tool filters — and selects by intent rather than guessing. - Containment: the sub-agent runs sandboxed, network off, in the project container.
- Least privilege: it can read the diff and run exactly
go vet ./.... It cannot write files, patch, or spawn its own sub-agents — those tools are disabled at the profile level, so even a fully compromised reviewer cannot plant code while "reviewing." - Non-interactivity: it never prompts. If its review demands something outside the envelope, the operation is denied, the denial appears in its result (
denials: [{tool, class, reason}]), and the parent seessubagent_deniedevents it can reason about. - Evidence-verified reporting: the parent doesn't trust the verdict's prose; it re-runs the observable checks (
go vet, the test suite) and compares. Trust, but checksum. - Iteration: findings flow back into plan steps; the cycle repeats until the judge panel is clean. Only then does the work move toward a pull request — which, in this workflow, merges only after CI passes. The human remains the final approver, but they are approving a reviewed, evidence-gated, adversarially-clean diff — not a leap of faith.
That is what "who watches the agent" looks like when the answer is architecture.
What happens when it fails matters as much as the happy path. If the judge's review demands something outside its envelope, the operation is denied — visibly, in the result's denials list — and the parent adapts or re-scopes; it never gets silence. If a step's evidence check fails (go test exits non-zero), the plan step cannot be marked done: the status stays visible as incomplete, the model keeps iterating on that step, and a blocked step is marked blocked for the operator to see. If a model tries to declare a step complete without the observed check, the runtime refuses — the claim has no matching evidence in the session. And if the judge rejects the diff outright, the findings loop back into new plan steps and the cycle repeats; the gate is the clean review, so a rejected diff is structurally unable to reach a pull request. Every failure mode lands somewhere observable: a denial event, an open plan step, a repeated review cycle — never a quiet pass.
7. The Honest Limits
No supervision essay is complete without the parts that don't work yet.
Judges share training data with the judged. Diversity of persona is not diversity of weights; correlated blind spots across models are a live research problem, and a judge can be socially engineered by a sufficiently persuasive diff the same way a tired human reviewer can.
Allowlists are brittle. Exact-invocation matching is safe but high-maintenance; every legitimate new build command is an operator decision. There is a real tension between strictness and the approval fatigue it prevents — we judge strictness worth the friction, but it is a judgment, not a theorem.
Covert channels beat transport-layer observation. If a model can hide intent inside legitimate-looking content — a comment, a test fixture, a variable name — no permission system sees it. Detection has to move to semantic and behavioral layers, and that work is early.
The human is still the final gate, and the human is still fatigable. All of this machinery exists to make the final approval a five-second review of an evidence-backed summary instead of a twenty-minute archaeology of an agent transcript — a 10× improvement, not a solution. The honest question every team should ask is not whether to pay that supervision cost, but which layers their threat model actually needs. Judges multiply LLM spend; sandboxes multiply latency. Containment is cheap insurance. Start there, and add judgment only where autonomy is high.
8. Conclusion
"Who watches what your agent is doing?" is no longer rhetorical. It is an engineering question with a concrete answer stack: contain what you cannot trust, minimize what you must grant, and judge what you would otherwise assume. None of these layers requires AGI, telemetry dystopias, or a platform vendor's blessing — the harness described here is open source, runs on a laptop, and its security documentation reads like a red-team log because that is, in large part, what it is.
The agents are coming for the work regardless. The only open question is whether supervision ships in the same commit.
References
[1] TechCrunch. (2026). "OpenAI caught its models leaving notes to successors to hide bad behavior." TechCrunch AI.
[2] TechCrunch. (2026). "The fix for rogue AI agents could be more AI." TechCrunch AI.
[3] Greshake, K., Abdelnabi, S., Mishra, S., Endres, C., Holz, T., Fritz, M. (2023). Not What You've Signed Up For: Compromising Real-World LLM-Integrated Applications with Indirect Prompt Injection. arXiv:2302.12173. https://arxiv.org/abs/2302.12173
[4] BackendStack21. (2026). Sandboxing — odek documentation. https://github.com/BackendStack21/odek/blob/main/docs/SANDBOXING.md
[5] BackendStack21. (2026). Security — odek documentation (risk classification, persistence class, credential path hardening). https://github.com/BackendStack21/odek/blob/main/docs/SECURITY.md
[6] BackendStack21. (2026). Sub-Agents — odek documentation (trust inheritance, profiles, denials reporting). https://github.com/BackendStack21/odek/blob/main/docs/SUBAGENTS.md
[7] BackendStack21. (2026). Planning — odek documentation (evidence-gated plan checks). https://github.com/BackendStack21/odek/blob/main/docs/PLANNING.md
Kyberneees, 2026