Skip to content
Chapter 4 of 13

Environments and side effects

Narrated walkthrough · Environments and side effects
An agent's environment is the set of things it can change: a filesystem, a shell, a browser, a screen, an API surface. It is the first design decision, not a deployment detail, because it sets the ceiling on what the agent can accomplish and the floor on what it can destroy, and those two move together. A model with one read-only lookup tool is nearly harmless and nearly useless. Coding agents are the canonical rich environment and worth studying for that reason: the environment is a real filesystem plus a shell plus version control, which is exactly why they can do a day of work and exactly why they can delete one. The choice in front of you is how much of that to hand over and what stands between a wrong action and a permanent consequence. In this lab the environment is a Python dict, the agent fires a badly expanded glob, and you watch the same call be trivial in a sandbox and catastrophic anywhere else.

The lab: read it, then run it

labs/agentic-engineering/ae4-sandbox-environment.py
#!/usr/bin/env python3
"""
LAB AE4: Environments and side effects.

An agent's environment is the set of things it can change, and it is a design
decision, not an afterthought. This lab gives an agent a filesystem made of a
Python dict, then hands it a badly expanded glob, the single most common way a
coding agent destroys work. In the sandbox the damage is total and completely
reversible, because a snapshot exists and the dict is the only writable surface.
Against a real filesystem the identical call has no snapshot behind it, which is
why a coding agent's real environment needs version control before it needs
capability. A path guard refuses every attempt to reach outside the sandbox.

NOTHING here touches your real filesystem: the tools close over a dict and the
module never opens a file.

Run: python3 modules/academy-content/labs/agentic-engineering/ae4-sandbox-environment.py
"""
import sys, os, fnmatch
_cands = [os.path.join(os.path.dirname(__file__), "..") if "__file__" in globals() else None,
          os.path.join(os.getcwd(), "..", "labs"), os.path.join(os.getcwd(), "labs")]
for _c in _cands:
    if _c and os.path.exists(os.path.join(_c, "academy_llm.py")):
        sys.path.insert(0, os.path.abspath(_c)); break

# ── The ENVIRONMENT. Three files, and a snapshot that stands in for git. ──────
FS = {"notes.md": "# raven outline\n", "main.py": "print('ship it')\n",
      "secrets.env": "TOKEN=abc123\n"}
SNAPSHOT = dict(FS)                      # the commit the agent can be rolled back to

def guard(path):
    """The boundary. A sandbox is only a sandbox if escape is impossible: no
    absolute paths, no parent traversal, no home expansion, no separators."""
    if path.startswith(("/", "~")) or ".." in path or os.sep in path:
        return "REFUSED: %r is outside the sandbox" % path
    return None

def delete(pattern):
    """The destructive tool. A glob is a loaded weapon: '*' matches everything."""
    bad = guard(pattern)
    if bad:
        return bad
    hit = [k for k in FS if fnmatch.fnmatch(k, pattern)]
    for k in hit:
        del FS[k]
    return "deleted %d file(s): %s" % (len(hit), sorted(hit))

print("ENVIRONMENT before: %s" % sorted(FS))
print("")
print("STEP 1: the agent means 'clean the .tmp files' and emits delete('*')")
print("  -> %s" % delete("*"))
print("  environment now: %s  (empty)" % sorted(FS))
destroyed = FS == {}

print("")
print("STEP 2: roll back from the snapshot, the thing a real filesystem lacks")
FS.update(SNAPSHOT)
print("  environment now: %s" % sorted(FS))
recovered = FS == SNAPSHOT and FS["secrets.env"] == "TOKEN=abc123\n"
print("  contents byte-identical to the snapshot: %s" % recovered)

print("")
print("STEP 3: the same call aimed outside the sandbox")
escapes = ["/etc/passwd", "../../.ssh/id_ed25519", "~/Documents/taxes.pdf"]
refused = 0
for attempt in escapes:
    result = delete(attempt)
    refused += result.startswith("REFUSED")
    print("  delete(%r) -> %s" % (attempt, result))
blocked = refused == len(escapes)

print("")
print("WHY THIS MATTERS ON A REAL FILESYSTEM")
print("  Same delete('*'), same agent, no snapshot: main.py and secrets.env are")
print("  gone and unrecoverable. A coding agent's environment is files plus a")
print("  shell plus version control, and the version control is the snapshot.")
print("  Rich environment, real capability, real blast radius.")

ok = destroyed and recovered and blocked
print("")
print("SANDBOX CONTAINED THE DESTRUCTION AND BLOCKED EVERY ESCAPE: %s" % ("YES" if ok else "NO"))
if not ok:
    sys.exit(1)
print("Choose the environment first. Capability and risk arrive together.")
Runnable lab
ae4-sandbox-environment.py

Let an agent run a destructive glob against a mock filesystem, roll it back from a snapshot, and prove every attempt to escape the sandbox is refused.

Proves: SANDBOX CONTAINED THE DESTRUCTION AND BLOCKED EVERY ESCAPE: YES

Open the notebook

Runs in your browser via Pyodide. First run loads the runtime once; no install, no server.

The agent meant to clean up temporary files and emitted delete('*'), which took all three files including secrets.env. In the sandbox that cost nothing: the snapshot restored every file byte-identical. Against a real working directory the same call is unrecoverable, and that gap is the entire argument for running coding agents on a branch with a clean tree, where the snapshot is a commit and the review is a diff. One warning about the guard you just watched work. It refused three escapes because its author thought of absolute paths, parent traversal, and home expansion. Real escapes are the paths nobody thought of, so a boundary that matters belongs in the operating system, a container, a separate user, a read-only mount, not in a helper function the agent's own tooling can be talked around.
Check your understanding
  1. 1. Why are coding agents the canonical example of a rich environment?

  2. 2. What made the destructive delete in the lab harmless?

  3. 3. Why should a sandbox boundary be enforced outside the agent's own code?