diff --git a/.agents/docs b/.agents/docs
new file mode 120000
index 00000000..a9594bfe
--- /dev/null
+++ b/.agents/docs
@@ -0,0 +1 @@
+../docs
\ No newline at end of file
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 00000000..f62e70b6
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,18 @@
+# Boundary agent instructions
+
+Boundary is a Linux network isolation tool for monitoring and restricting HTTP and HTTPS requests from child processes. This file is only the root entrypoint for agent runtimes. Keep canonical guidance in `docs/`.
+
+## Canonical docs
+
+- Human architecture overview: `docs/architecture.md`
+- Agent workflow guide: `docs/agent-guide.md`
+- E2E test safety guide: `docs/e2e-tests.md`
+
+## Non-negotiable rules
+
+- Read `docs/agent-guide.md` before making non-trivial changes.
+- Read `docs/e2e-tests.md` before running or changing e2e tests.
+- Use `make unit-test` for normal validation. Do not assume `make test` exists.
+- Ask before changing privilege escalation, iptables rules, certificate trust behavior, release workflow semantics, or the allow-rule grammar.
+
+
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
deleted file mode 100644
index 48dc6d6f..00000000
--- a/ARCHITECTURE.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# Boundary Architecture
-
-
-
-# Alternative Architectures
-## Anthropic SRT
-https://github.com/anthropic-experimental/sandbox-runtime
-
-
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 120000
index 00000000..47dc3e3d
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1 @@
+AGENTS.md
\ No newline at end of file
diff --git a/README.md b/README.md
index 7c3474b8..44d0b3ca 100644
--- a/README.md
+++ b/README.md
@@ -184,7 +184,7 @@ make lint # Lint code
## Architecture
-For detailed information about how `boundary` works internally, see [ARCHITECTURE.md](ARCHITECTURE.md).
+For detailed information about how `boundary` works internally, see [docs/architecture.md](docs/architecture.md).
## License
diff --git a/docs/agent-guide.md b/docs/agent-guide.md
new file mode 100644
index 00000000..b635aebb
--- /dev/null
+++ b/docs/agent-guide.md
@@ -0,0 +1,268 @@
+# Boundary agent guide
+
+This guide gives autonomous agents the context needed to change `github.com/coder/boundary` safely. It is intentionally consolidated so agents can load one detailed handbook after reading the root `AGENTS.md`. For a human-facing system overview, read `docs/architecture.md`.
+
+## Architecture and runtime
+
+See [docs/architecture.md](architecture.md) for the repository map, high-level model, startup flow, parent/child process model, policy model, proxy model, backend details, TLS, audit logging, session correlation, and security limitations.
+
+The rest of this guide focuses on agent-specific workflow, change guidance, testing, and troubleshooting.
+
+## CLI and config
+
+The CLI is built with `github.com/coder/serpent` in `cli/cli.go`.
+
+Important config types:
+
+- `config.CliConfig`: serpent values for flags, environment variables, and YAML.
+- `config.AppConfig`: runtime config passed into the jail backend and proxy setup.
+- `config.SessionCorrelationConfig`: controls session-correlation header injection.
+- `config.UserInfo`: resolves the effective user, including sudo scenarios.
+
+Important CLI behavior:
+
+- `--allow` is repeatable and CLI-only.
+- YAML `allowlist` is merged with CLI `--allow` rules.
+- `--jail-type` defaults to `nsjail`.
+- `--use-real-dns` intentionally permits DNS exfiltration. Do not enable it by accident.
+- `--disable-audit-logs` disables workspace-agent socket forwarding. It does not remove stderr logging.
+- `--enable-session-correlation` requires configured inject targets or a valid fallback from `CODER_AGENT_URL`.
+- `--log-proxy-socket-path` defaults to the Coder workspace-agent boundary log proxy socket path.
+
+When changing CLI flags:
+
+- Update README usage if behavior changes.
+- Add or update config tests if parsing or validation changes.
+- Check environment variable names. Some are shared with the Coder workspace agent.
+- Preserve backwards compatibility unless the task explicitly allows breaking it.
+
+## Rules engine
+
+See the Policy model section in [docs/architecture.md](architecture.md) for the allow-rule grammar and matching semantics.
+
+When changing rule parsing or matching:
+
+- Update parser tests in `rulesengine/`.
+- Update matcher tests in `rulesengine/`.
+- Update README examples if user-visible behavior changes.
+- Be careful with percent-encoded paths. Proxy forwarding preserves `RawPath` for cases like scoped npm package names.
+
+## Proxy
+
+See the Proxy model section in [docs/architecture.md](architecture.md) for HTTP, HTTPS, and CONNECT request paths and forwarding/blocking behavior.
+
+Key files: `proxy/proxy.go`, `proxy/connect.go`, `proxy/*_test.go`.
+
+When changing proxy behavior:
+
+- Prefer unit tests with `proxy/proxy_framework_test.go` and `httptest`.
+- Avoid live network tests unless the behavior truly requires it.
+- Test both allow and deny paths.
+- Test both transparent and CONNECT paths when TLS behavior changes.
+- Preserve audit behavior for both allowed and denied requests.
+
+## Audit
+
+See the Audit logging section in [docs/architecture.md](architecture.md) for the audit model.
+
+Key types: `audit.Request`, `audit.Auditor`, `audit.LogAuditor`, `audit.SocketAuditor`, `audit.MultiAuditor`, `audit.SequenceCounter`.
+
+When changing audit behavior:
+
+- Check `audit/socket_auditor_test.go` for batching, retry, drop, shutdown, and session ID expectations.
+- Preserve the Coder boundary log proxy codec contract.
+- Avoid blocking request handling on slow socket forwarding.
+
+## TLS
+
+See the TLS and certificate trust section in [docs/architecture.md](architecture.md) for the CA and certificate model.
+
+When changing TLS behavior:
+
+- Preserve file ownership for the original user when running through sudo.
+- Be careful with config directory paths from `config.UserInfo`.
+- Consider the impact on curl, git, Python requests, and Node clients.
+- Avoid broad certificate trust changes without explicit review.
+
+## nsjail backend
+
+See the nsjail backend section in [docs/architecture.md](architecture.md) for the namespace, veth, iptables, and DNS model.
+
+High-risk details:
+
+- Interface names are constrained by Linux's 15-character interface name limit.
+- iptables cleanup must mirror setup rules.
+- `--no-user-namespace` changes clone flags and UID/GID mappings.
+- `CAP_NET_ADMIN` and sometimes `CAP_SYS_ADMIN` are required.
+- Non-HTTP TCP protocols are redirected but the proxy only understands HTTP and TLS-style traffic.
+
+## landjail backend
+
+See the landjail backend section in [docs/architecture.md](architecture.md) for the Landlock and proxy-env model.
+
+When changing landjail:
+
+- Check kernel and Landlock version assumptions.
+- Preserve proxy env injection unless a task explicitly changes the model.
+- Test that denied direct connections remain blocked.
+- Remember that behavior depends on clients honoring proxy environment variables.
+
+## Privilege model
+
+See the Startup flow section in [docs/architecture.md](architecture.md) for how privilege escalation fits into the runtime.
+
+When changing privilege code:
+
+- Ask for review before implementation.
+- Test both already-privileged and needs-escalation paths where possible.
+- Preserve environment variables needed by child processes and the target command.
+- Be cautious with PATH handling and sudo behavior.
+
+## Testing
+
+Normal validation:
+
+```sh
+make unit-test
+make build
+```
+
+Formatting and linting:
+
+```sh
+make fmt
+make fmt-check
+make lint
+```
+
+E2E validation:
+
+```sh
+make e2e-test
+```
+
+Important test facts:
+
+- `make unit-test` runs `go test -v -race $(go list ./... | grep -v e2e_tests)`.
+- `make e2e-test` runs `sudo $(which go) test -v -race ./e2e_tests -count=1`.
+- `make e2e-test` targets only the root `e2e_tests` package, not all subpackages.
+- `make test-coverage` runs `go test -v -race -coverprofile=coverage.out ./...`, so it may include e2e packages.
+- The Makefile currently does not define a `test` target. Do not use `make test` unless the Makefile changes.
+
+Testing guidance by area:
+
+- Rules changes: use parser and matcher tests in `rulesengine/`.
+- Proxy changes: prefer `proxy/` unit tests with `httptest` and the proxy test framework.
+- Config changes: use `config/*_test.go` and explicit environment slices.
+- Audit changes: use `audit/*_test.go`, especially socket auditor behavior.
+- nsjail and landjail changes: add focused unit tests where possible, then run e2e only on a suitable Linux sudo host.
+
+Avoid adding new sleeps in tests. Prefer readiness checks, channels, contexts, test servers, and explicit process state checks. Existing tests contain sleeps, but that should not become the default pattern for new code.
+
+## CI and releases
+
+CI lives in `.github/workflows/ci.yml`.
+
+Current CI behavior:
+
+- Uses Go 1.25.
+- Runs `make deps`.
+- Runs `make fmt-check` and `make lint` in the lint job.
+- Installs `golangci-lint` before linting.
+- Bind-mounts `/run/systemd/resolve/resolv.conf` over `/etc/resolv.conf` before tests on Linux.
+- Runs `make unit-test`.
+- Runs `make e2e-test`.
+- Runs `make build`.
+
+Build and release workflows:
+
+- `make build-all` builds Linux amd64 and Linux arm64 binaries.
+- Build and release workflow files include Darwin artifact upload paths even though `make build-all` currently creates Linux binaries only.
+- Release archives can be created from local `build/` output or downloaded workflow artifacts.
+
+When changing CI or releases:
+
+- Confirm Makefile targets exist before referencing them.
+- Keep README, RELEASES, Makefile help, and workflows aligned.
+- Avoid changing binary names or archive names without considering `install.sh`.
+- Check whether artifacts are actually produced before uploading them.
+
+## Troubleshooting
+
+### `make test` fails with no rule
+
+Use `make unit-test` for regular tests. The current Makefile does not define a `test` target. Note that `make ci` also depends on `test`, so it will fail for the same reason; use individual targets instead.
+
+### E2E tests fail with DNS issues
+
+CI bind-mounts `/run/systemd/resolve/resolv.conf` over `/etc/resolv.conf` so namespace tests can reach upstream DNS instead of the host stub resolver. Local environments may need similar attention.
+
+### E2E tests leave host networking residue
+
+Inspect iptables and veth state. Cleanup should remove rules that setup added. Be careful before deleting unrelated host rules.
+
+### Boundary cannot escalate privileges
+
+Check that `sudo` and `setpriv` exist and that the current user can use sudo. The default nsjail backend needs capabilities for network setup.
+
+### Port conflicts
+
+Default proxy port is `8080`. Default pprof port is `6060`. Use CLI flags or environment variables when running multiple instances.
+
+### HTTPS clients reject certificates
+
+Check the CA path in the user config directory and the environment variables injected into the child process. Different clients use different CA variables.
+
+### Rules do not match as expected
+
+Check exact vs wildcard domain semantics first. `domain=github.com` and `domain=*.github.com` are different rules.
+
+## Agent failure catalog
+
+### Symptom: agent runs `make test`
+
+Cause: generic Go habit or stale README/help references.
+
+Fix: inspect the Makefile and run `make unit-test` for normal validation. Use e2e only when appropriate.
+
+### Symptom: agent runs e2e tests in an unsuitable environment
+
+Cause: treating e2e tests like normal unit tests.
+
+Fix: stop and verify Linux, sudo, iptables, namespace support, required tools, and cleanup expectations.
+
+### Symptom: proxy tests miss CONNECT or transparent TLS paths
+
+Cause: testing only one request path.
+
+Fix: add coverage for the path affected by the code change. TLS, HTTP, and CONNECT can differ.
+
+### Symptom: allow-rule change breaks subdomain behavior
+
+Cause: confusing exact domain and wildcard domain matching.
+
+Fix: update tests for base domain, subdomain, and unrelated domain cases.
+
+### Symptom: audit socket changes block request handling
+
+Cause: doing synchronous socket work in the request path.
+
+Fix: keep queueing and batching behavior. Preserve drop and retry tests.
+
+### Symptom: workflow uploads artifacts that were never built
+
+Cause: workflow artifact paths drift from `make build-all` outputs.
+
+Fix: align Makefile, workflow uploads, RELEASES, and install script expectations.
+
+## Review checklist
+
+Before opening a PR:
+
+- [ ] The change is narrow and avoids unrelated cleanup.
+- [ ] `go fmt` or `make fmt` was run for Go changes.
+- [ ] Focused tests were run for the changed area.
+- [ ] `make unit-test` was run unless the change is docs-only and the user agreed to skip it.
+- [ ] E2E tests were only run on a suitable Linux sudo host.
+- [ ] README, Makefile, workflows, and release docs are aligned when commands or binaries change.
+- [ ] Privilege, TLS, iptables, and rule grammar changes received explicit review.
diff --git a/docs/architecture.md b/docs/architecture.md
new file mode 100644
index 00000000..df75a053
--- /dev/null
+++ b/docs/architecture.md
@@ -0,0 +1,299 @@
+# Boundary architecture
+
+Boundary is a Linux network isolation tool that runs a child process with restricted network access. It intercepts HTTP and HTTPS traffic, evaluates each request against allow rules, and records an audit trail of what was allowed or denied.
+
+The practical goal is simple: run an agent or command with a default-deny network policy while still letting approved HTTP and HTTPS requests work normally.
+
+## High-level model
+
+Boundary has three moving parts:
+
+1. **CLI and configuration** parse rules, logging options, jail options, and the target command.
+2. **Jail backend** starts the target command in a restricted environment.
+3. **Proxy and policy engine** inspect HTTP and HTTPS requests, allow or block them, and emit audit logs.
+
+```text
+user shell
+ |
+ | boundary --allow "domain=github.com" -- command args...
+ v
+boundary parent process
+ |
+ | parses config, creates policy engine, starts proxy, starts jail
+ v
+restricted child process
+ |
+ | HTTP and HTTPS traffic
+ v
+boundary proxy
+ |
+ | evaluates method, host, path
+ +--> allowed: forward to upstream server
+ +--> denied: return HTTP 403 and audit the denial
+```
+
+## Repository map
+
+| Path | Responsibility |
+|------|----------------|
+| `cmd/boundary/main.go` | Binary entrypoint. Builds and runs the CLI command. |
+| `cli/` | Command-line interface, flags, environment variables, YAML config loading, and privilege setup. |
+| `config/` | Runtime configuration, user information, and session-correlation settings. |
+| `run/` | Platform dispatch. Linux runs a jail backend. Non-Linux returns an unsupported-platform error. |
+| `rulesengine/` | Allow-rule parsing and matching. |
+| `proxy/` | HTTP and HTTPS proxy, transparent TLS detection, CONNECT support, forwarding, blocking, auditing, and session-correlation header injection. |
+| `audit/` | Structured stderr audit logging and optional Coder workspace-agent socket forwarding. |
+| `tls/` | Local CA management and per-host certificate generation for HTTPS interception. |
+| `nsjail_manager/` | Default jail backend. Parent/child orchestration, proxy setup, and cleanup. |
+| `nsjail_manager/nsjail/` | Low-level Linux namespace networking: veth, iptables, dummy DNS, env, and command runner. |
+| `landjail/` | Alternative jail backend using Landlock restrictions and proxy environment variables. |
+| `privilege/` | Linux privilege escalation through `sudo` and `setpriv` for the default backend. |
+| `dnsdummy/` | DNS server used by the namespace backend to prevent DNS exfiltration. |
+| `log/` | slog setup for stderr and file logging. |
+| `e2e_tests/` | Linux integration tests that require sudo and can mutate host networking. |
+
+## Startup flow
+
+The startup path is:
+
+```text
+cmd/boundary/main.go
+ -> cli.NewCommand
+ -> config.NewAppConfigFromCliConfig
+ -> privilege.EnsurePrivileges, for nsjail only
+ -> log.SetupLogging
+ -> run.Run
+ -> nsjail_manager.Run or landjail.Run
+```
+
+The CLI builds a `config.AppConfig` from flags, environment variables, optional YAML, and the target command. Then `run.Run` assigns a new session UUID and dispatches to the requested jail backend.
+
+The default jail type is `nsjail`. That backend needs Linux network privileges, so the CLI calls `privilege.EnsurePrivileges()` before entering the runtime. If the current process does not have the required capabilities, Boundary re-execs itself through `sudo` and `setpriv` with the minimal capabilities it needs for networking setup.
+
+The `landjail` backend does not use the same privilege escalation path.
+
+## Parent and child process model
+
+Both jail backends use a parent and child process model. The selected backend checks the `CHILD=true` environment variable to decide which role the current process should run.
+
+### Parent process
+
+The parent process owns setup and cleanup:
+
+1. Parse allow rules.
+2. Create the rule engine.
+3. Set up audit logging.
+4. Create or load the local CA.
+5. Start the HTTP proxy.
+6. Start the child process.
+7. Wait for the child process to exit or for a termination signal.
+8. Stop the proxy.
+9. Clean up backend-specific resources.
+
+### Child process
+
+The child process runs the target command inside the restricted environment. Backend-specific setup happens before the target command starts.
+
+For `nsjail`, the child configures namespace networking and DNS behavior before running the target. For `landjail`, the child applies Landlock network restrictions before running the target.
+
+## Policy model
+
+Boundary uses a default-deny policy. Requests are allowed only when at least one allow rule matches.
+
+Allow rules are strings made of key-value pairs:
+
+```text
+method=GET,HEAD domain=github.com path=/api/*
+```
+
+Supported keys are:
+
+- `method`: one or more HTTP methods, comma-separated. `*` matches every method.
+- `domain`: an exact host or wildcard host pattern.
+- `path`: one or more path patterns, comma-separated.
+
+Important matching rules:
+
+- `domain=github.com` matches only `github.com`.
+- `domain=github.com` does not match `api.github.com`.
+- `domain=*.github.com` matches subdomains such as `api.github.com` and deeper subdomains such as `v1.api.github.com`.
+- `domain=*.github.com` does not match `github.com`.
+- To allow a base domain and its subdomains, configure both patterns.
+- Path wildcards are segment-based. A wildcard must be a whole path segment.
+- A trailing `*` segment matches multiple remaining segments: `path=/api/*` matches `/api/v1/users`.
+
+The engine returns both the allow or deny decision and the matching rule, if one matched. Audit logs include the matched rule for allowed requests.
+
+## Proxy model
+
+The proxy is the enforcement point for HTTP and HTTPS traffic.
+
+It supports two styles of traffic:
+
+1. **Transparent traffic**, where the target process does not know about the proxy. The `nsjail` backend redirects TCP traffic to Boundary with iptables.
+2. **Explicit proxy traffic**, where the target process uses `HTTP_PROXY` and `HTTPS_PROXY`. The `landjail` backend uses this model.
+
+### HTTP requests
+
+For plain HTTP, the proxy reads the request, reconstructs the full URL when needed, evaluates the method, host, and path, then either forwards the request or returns a 403 response.
+
+### HTTPS requests
+
+For HTTPS, Boundary acts as a local TLS endpoint so it can inspect the HTTP request inside the encrypted stream. It uses a local CA and generates per-host certificates on demand.
+
+The target process must trust Boundary's CA. Boundary injects common CA environment variables into the child process so tools such as curl, git, Python requests, and Node can trust the generated certificates.
+
+### CONNECT requests
+
+When a client uses Boundary as an explicit HTTP proxy for HTTPS, it sends a CONNECT request. Boundary accepts the CONNECT tunnel, performs TLS with the client, reads HTTP requests from inside the tunnel, and evaluates each request independently.
+
+### Forwarding and blocking
+
+For allowed requests, the proxy creates a new upstream request, copies appropriate headers, optionally injects session-correlation headers, and writes the upstream response back to the client.
+
+For denied requests, the proxy returns HTTP 403 with a short message and example allow rules.
+
+Every HTTP request that reaches the proxy is audited before the allow or deny handling completes. CONNECT handshake requests themselves are not audited; only the HTTP requests inside the resulting tunnel are audited.
+
+## nsjail backend
+
+`nsjail` is the default backend. It provides transparent network interception with Linux networking primitives.
+
+The backend creates a point-to-point network between the host and child namespace:
+
+```text
+host namespace child network namespace
+
+boundary proxy :8080 target command
+ ^ |
+ | | TCP traffic
+iptables REDIRECT v
+ | veth jail side
+veth host side
+```
+
+Key details:
+
+- The host side of the veth pair uses `192.168.100.1/24`.
+- The child side uses `192.168.100.2/24`.
+- The fixed subnet is `192.168.100.0/24`.
+- iptables NAT and REDIRECT rules send TCP traffic from the child namespace to the Boundary proxy.
+- Non-TCP forwarding rules allow return traffic for non-TCP flows.
+- A dummy DNS server can run inside the namespace to prevent DNS exfiltration.
+- `--use-real-dns` intentionally disables the dummy DNS behavior.
+- `--no-user-namespace` disables user namespace creation for restricted environments.
+
+The parent process configures host-side networking before the child runs. Once the child process exists, the parent moves the jail-side veth into the child's network namespace. The child then configures its IP address, loopback, and default route.
+
+Cleanup removes the iptables rules and veth interface created during setup.
+
+## landjail backend
+
+`landjail` is an alternative backend based on Linux Landlock network restrictions.
+
+Unlike `nsjail`, it does not rely on transparent iptables redirection. Instead, it configures the child process to use Boundary as an explicit proxy:
+
+- `HTTP_PROXY`
+- `HTTPS_PROXY`
+- `http_proxy`
+- `https_proxy`
+
+It also clears `NO_PROXY` and `no_proxy` so the target command cannot bypass Boundary through proxy bypass lists.
+
+Landlock restricts the child so it can connect only to the Boundary proxy port. This means the backend depends on clients honoring proxy environment variables. A client that ignores those variables will generally fail to connect rather than bypass Boundary.
+
+## TLS and certificate trust
+
+Boundary uses TLS interception for HTTPS so it can evaluate host, path, method, and headers in the request.
+
+The TLS manager:
+
+1. Finds the user's Boundary config directory.
+2. Loads an existing local CA if present.
+3. Generates a new local CA if needed.
+4. Writes the CA certificate for child processes to trust.
+5. Generates per-host certificates for incoming TLS connections.
+
+The jail backends set environment variables for common tools:
+
+- `SSL_CERT_FILE`
+- `SSL_CERT_DIR`
+- `CURL_CA_BUNDLE`
+- `GIT_SSL_CAINFO`
+- `REQUESTS_CA_BUNDLE`
+- `NODE_EXTRA_CA_CERTS`
+
+When Boundary runs through sudo, ownership and paths must still refer to the original user, not root, where possible.
+
+## Audit logging
+
+Boundary audits every HTTP and HTTPS request that reaches the proxy.
+
+An audit record includes:
+
+- method
+- URL
+- host
+- allowed or denied decision
+- matching rule for allowed requests
+- per-session sequence number
+
+Boundary always creates a stderr log auditor. When running inside a compatible Coder workspace, it can also forward audit batches to the workspace agent over a Unix socket. The workspace agent then forwards the logs to coderd for centralized logging.
+
+`--disable-audit-logs` disables socket forwarding. It does not remove stderr logging.
+
+## Session correlation
+
+The proxy package contains support for injecting session-correlation headers into selected outbound requests. This is intended for Coder AI Gateway flows where downstream services need to correlate a Boundary audit event with an upstream request.
+
+The headers are defined in `config/session_correlation.go`:
+
+- `X-Coder-Agent-Firewall-Session-Id`
+- `X-Coder-Agent-Firewall-Sequence-Number`
+
+Injection targets use the same rule engine semantics as normal allow rules. When changing this area, verify the end-to-end runtime path from CLI config through the selected jail backend into `proxy.Config`; unit tests for proxy support alone are not enough.
+
+## Security properties and limitations
+
+Boundary is designed for HTTP and HTTPS control. The default policy is deny, but the enforcement point is the proxy and the selected jail backend.
+
+Important limitations:
+
+- Boundary is Linux-only for runtime enforcement.
+- The `nsjail` backend redirects TCP traffic, but the proxy understands HTTP, HTTPS, and CONNECT-style traffic. Arbitrary non-HTTP TCP protocols are not supported as normal allowed traffic.
+- DNS behavior is backend-specific. The namespace backend uses dummy DNS by default to reduce DNS exfiltration. `--use-real-dns` changes that intentionally.
+- The landjail backend depends on clients using proxy environment variables.
+- The fixed namespace subnet can conflict with local networking in unusual environments.
+- E2E tests can mutate host networking and require careful cleanup.
+
+## Development notes
+
+Useful commands:
+
+```sh
+make build
+make unit-test
+make fmt
+make fmt-check
+make lint
+```
+
+E2E tests require Linux and sudo:
+
+```sh
+make e2e-test
+```
+
+Read `docs/e2e-tests.md` before running or changing e2e tests.
+
+## Diagrams and related work
+
+The original design sketch is preserved here for context:
+
+
+
+Anthropic's sandbox runtime is a related architecture worth comparing when thinking about alternative isolation designs:
+
+https://github.com/anthropic-experimental/sandbox-runtime
+
+
diff --git a/docs/e2e-tests.md b/docs/e2e-tests.md
new file mode 100644
index 00000000..c4f30dad
--- /dev/null
+++ b/docs/e2e-tests.md
@@ -0,0 +1,69 @@
+# Boundary e2e test guidance
+
+E2E tests in this directory are not normal unit tests. They can mutate host networking and require a suitable Linux sudo environment.
+
+Read this file before changing or running e2e tests.
+
+## Requirements
+
+Expected tools and host features include:
+
+- Linux
+- sudo
+- Go
+- iptables
+- ip
+- nsenter
+- curl
+- dig
+- nc
+- Linux network namespaces
+- Landlock support for landjail tests
+
+## Safety rules
+
+- Do not run e2e tests casually in a shared or fragile environment.
+- Prefer focused package or test-name runs when debugging.
+- Expect tests to create boundary binaries under temporary directories.
+- Expect tests to create or inspect iptables rules, veth interfaces, and network namespaces.
+- Check cleanup when a test fails or is interrupted.
+- Do not delete unrelated host iptables rules during cleanup or debugging.
+
+## Commands
+
+The Makefile target is:
+
+```sh
+make e2e-test
+```
+
+It currently runs:
+
+```sh
+sudo $(which go) test -v -race ./e2e_tests -count=1
+```
+
+That target runs the root `e2e_tests` package only. It does not run every e2e subpackage. If you need subpackage coverage, choose the package deliberately and document what you ran.
+
+Examples:
+
+```sh
+sudo $(which go) test -v -race ./e2e_tests/nsjail -count=1
+sudo $(which go) test -v -race ./e2e_tests/landjail -count=1
+```
+
+## Common pitfalls
+
+- DNS inside namespaces can fail if the host uses a stub resolver at `127.0.0.53`.
+- iptables cleanup must remove exactly the rules added by setup.
+- Port conflicts can occur when another boundary or proxy process is running.
+- Existing sleeps in e2e helpers are not a pattern to copy. Prefer readiness checks when adding new tests.
+- Some tests depend on external network behavior. Keep assertions focused and diagnostics clear.
+
+## When editing tests
+
+- Add targeted assertions for the behavior under test.
+- Use unique ports, names, or temporary directories when tests can run concurrently.
+- Preserve cleanup with `t.Cleanup` where possible.
+- Capture enough diagnostics to debug host networking failures.
+- Keep unit-level logic in package tests outside e2e when possible.
diff --git a/e2e_tests/AGENTS.md b/e2e_tests/AGENTS.md
new file mode 120000
index 00000000..cbe64983
--- /dev/null
+++ b/e2e_tests/AGENTS.md
@@ -0,0 +1 @@
+../docs/e2e-tests.md
\ No newline at end of file