Jarvy in AI Sandboxes¶
AI coding sandboxes are ephemeral execution environments where an agent clones a repo, writes code, runs commands, and exits. Examples: Claude Code sandbox, Cursor background agents, Devin, OpenAI Codex, Replit Agent, e2b, Modal, Daytona, plus the long-running container family (devcontainers, GitHub Codespaces, Gitpod).
The same properties that make Jarvy safe to wire into a project's
startup path — declarative, idempotent, fast no-op when satisfied,
exits clean in non-interactive contexts — make it a good fit for these
sandboxes. One jarvy.toml provisions the human's laptop, the agent's
sandbox, and CI from a single source of truth, so drift between "what
works for me" and "what the agent has" is impossible by construction.
Seamless mode (it just works)¶
Jarvy auto-detects when it's running inside a sandbox or agent environment and switches into seamless mode. You do not need to set any environment variables to get the right behavior.
When seamless mode is active, Jarvy:
- Disables interactive prompts. No first-run setup wizard, no confirmation dialogs.
- Auto-disables telemetry. Despite the global opt-out default,
multi-tenant base images must not leak signal across tenants —
unattended sandboxes drop back to off. Per-session re-enable via
JARVY_TELEMETRY=1still works for fleet operators who want session-scoped traces. - Disables auto-update checks. Ephemeral envs shouldn't try to swap their own binary mid-session.
- Auto-baselines drift state on first clean run. If the sandbox
image already has all the configured tools at the right versions
and there's no existing
.jarvy/state.json, Jarvy silently writes one. Futurejarvy drift checkruns in the same sandbox have a meaningful baseline without the operator runningjarvy drift acceptat image bake time. - Falls back to verify-only on read-only / no-install sandboxes.
If the rootfs is read-only or there's no sudo + no user-scope
package manager available, Jarvy doesn't attempt installs. It
runs the version-check pipeline and exits non-zero if anything
required by
jarvy.tomlis missing — fast feedback instead of permission-denied spam.
You'll see one stderr line when seamless mode activates:
If you ever need to disable it, set JARVY_SANDBOX=0 for the
invocation.
What gets auto-detected¶
| Sandbox / environment | Signal |
|---|---|
| GitHub Codespaces | CODESPACES=true or CODESPACE_NAME set |
| Gitpod | GITPOD_WORKSPACE_ID set |
| Devcontainers (VS Code Remote Containers) | REMOTE_CONTAINERS=true or DEVCONTAINER=true |
| Replit | REPL_ID set |
| e2b | E2B_SANDBOX_ID set |
| Modal | MODAL_TASK_ID set |
| Daytona | DAYTONA_WS_ID set |
| Claude Code | CLAUDECODE=1 or CLAUDE_CODE_ENTRYPOINT set |
| Cursor background agents | CURSOR_AGENT=1 |
| Generic container (fallback) | /.dockerenv exists AND stdin is not a TTY |
| Any CI runner | already covered by crate::ci::is_ci() — GitHub Actions, GitLab CI, CircleCI, etc. |
The generic-container fallback requires both /.dockerenv and a
non-TTY stdin so a developer who shelled into their own container
still gets normal interactive behavior.
Two install strategies¶
| Strategy | When to pick |
|---|---|
| Bake Jarvy into the base image | Hot path. Image build runs the install script once. Session start runs jarvy setup, which is a fast no-op when satisfied. No network needed at session start, fastest cold boot. |
| Install on session start | Disposable templates with no custom base image. Entrypoint runs curl … \| bash then jarvy setup. Slower first boot, zero image maintenance. |
For agent sandboxes that spin up dozens of containers per hour, bake into the image. For one-off ephemeral envs, install on start.
Wire-up by sandbox¶
Devcontainers / GitHub Codespaces / Gitpod¶
devcontainer.json:
{
"name": "Project dev container",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"onCreateCommand": "curl -fsSL https://raw.githubusercontent.com/Cliftonz/jarvy/main/dist/scripts/install.sh | bash",
"postCreateCommand": "jarvy setup"
}
onCreateCommand runs during image build and is cached across
rebuilds. postCreateCommand runs per container, so it picks up any
config changes since the image was baked. No JARVY_TEST_MODE,
CI=true, or JARVY_TELEMETRY rituals required — Codespaces /
devcontainers are auto-detected.
For Codespaces specifically, see also the migration guide from Codespaces.
Dockerfile-based templates (e2b, Modal, Daytona, custom)¶
FROM ubuntu:24.04
# Bake Jarvy into the image
RUN curl -fsSL https://raw.githubusercontent.com/Cliftonz/jarvy/main/dist/scripts/install.sh | bash
# Project config — checked in alongside the Dockerfile
COPY jarvy.toml /workspace/jarvy.toml
WORKDIR /workspace
# Provision at image-build time so cold start is instant
RUN jarvy setup
# Drift-check at session start — auto-baseline writes state.json on the
# first run inside a detected sandbox, so this becomes a real check
# from the second session onward
ENTRYPOINT ["sh", "-c", "jarvy drift check || jarvy setup; exec \"$@\"", "--"]
Auto-baseline turns a pre-loaded image into a drift-trackable
environment without the operator running jarvy drift accept at image
bake time.
Claude Code sandbox / Cursor background agents¶
For sandboxes that mount the project repo without a custom image, use
the graceful-skip pattern from
for-ai-agents.md.
Wire it into package.json / Makefile / justfile:
{
"scripts": {
"setup": "jarvy setup",
"predev": "command -v jarvy >/dev/null 2>&1 && jarvy setup || echo 'jarvy not installed — skipping auto-provision'",
"prebuild": "command -v jarvy >/dev/null 2>&1 && jarvy setup || echo 'jarvy not installed — skipping auto-provision'"
}
}
Sandboxes without Jarvy still boot (one-line hint, no hard error). Sandboxes with Jarvy auto-provision; Jarvy itself detects whether it's running in Claude Code / Cursor and disables prompts/telemetry/updates without you having to think about it.
Claude Code: also register the MCP server¶
For Claude Code specifically, drop a .mcp.json in the repo so the
agent can install tools mid-task via the typed, audited
MCP server instead of shelling out:
Benefits over shell: dry-run is the default, tool calls are
rate-limited, every call is written to ~/.jarvy/mcp-audit.log for
review.
Override knobs¶
You should never need these in a normal sandbox. Documented here for the cases where seamless mode guesses wrong:
| Variable | Effect |
|---|---|
JARVY_SANDBOX=0 |
Force-disable sandbox detection for this invocation. Use when you're in a container you own and want full interactive setup. |
JARVY_SANDBOX=1 |
Force seamless mode even when no sandbox signal is present. Useful for testing wire-up locally. |
JARVY_TELEMETRY=1 |
Re-enable telemetry inside a seamless-mode session (default is off). Pair with JARVY_OTLP_ENDPOINT for tenant-scoped export. |
JARVY_UPDATE=1 |
Re-enable update checks inside a seamless-mode session. |
JARVY_QUIET=1 |
Suppress the seamless-mode banner. |
The legacy knobs (JARVY_TEST_MODE=1, CI=true) still work, but
they're redundant inside a detected sandbox. Set them only if you
need the matching semantics in an environment Jarvy doesn't recognize.
Telemetry inside sandboxes¶
This is the part that bit operators before auto-detection landed. The short version: telemetry is off by default in every detected sandbox.
If you want session-scoped telemetry — say, you're running an agent fleet and want OTLP traces of what each session installs — opt in explicitly per-session:
export JARVY_TELEMETRY=1
export JARVY_OTLP_ENDPOINT="$TENANT_OTLP_ENDPOINT"
export OTEL_RESOURCE_ATTRIBUTES="tenant.id=$TENANT_ID,session.id=$SESSION_ID"
jarvy setup
Three guardrails:
- Don't bake
JARVY_OTLP_ENDPOINTinto a shared base image. It leaks signal across tenants. Set it per-session. - Don't enable telemetry in no-egress sandboxes. If outbound HTTPS
is blocked, every event becomes a 30-second connect-timeout. Pair
JARVY_TELEMETRY=1with a sidecar OTLP collector onlocalhost:4318or leave it off. - For K8s-hosted sandboxes, the
jarvy-telemetry-forwarderHelm chart terminates OTLP in-cluster and forwards via whatever egress your cluster already allows. See Telemetry forwarder operations for the deployment model.
Drift detection as an AI safety net¶
The agent's job is to change code. Sometimes it also installs unrelated things — a different Python version to test a hunch, a debugger it forgets to uninstall, a global npm package that overrides the project's pinned one. By the time the human reviews the PR, the sandbox is gone and the install trail is gone with it.
Seamless mode turns the sandbox into an auditable surface
automatically. On the first session in a sandbox that already
matches jarvy.toml, Jarvy writes .jarvy/state.json as a baseline.
On subsequent sessions, jarvy drift check shows exactly what the
agent touched.
# Session end: dump what the agent touched
jarvy drift check --format json > /workspace/.jarvy-drift-report.json
Pipe the report into the agent's session summary, or fail the session
if drift exceeds a threshold. See Drift detection for the
full state-file format and accept / fix workflows.
Verify-only fallback¶
If Jarvy detects a read-only rootfs or a sudoless container without a
user-scope package manager on PATH, jarvy setup doesn't attempt
installs. It runs the version-check pipeline, prints a single stderr
line listing any gaps, and exits with code 3 (PREREQ_MISSING) if
anything is missing.
This is automatic — you don't need to call jarvy doctor instead of
jarvy setup. The same entrypoint works in install-capable and
read-only sandboxes; Jarvy figures out which one it's in.
To force this path during testing, set
JARVY_FORCE_VERIFY_ONLY=1.
CI parity bonus¶
Because the same jarvy.toml provisions dev laptops and agent
sandboxes, you get CI parity for free:
Drop the emitted workflow into .github/workflows/. CI now installs
exactly what the agent's sandbox installed, which is exactly what the
human's laptop installed.
See CI/CD integration for per-provider snippets.
Anti-patterns¶
- Don't reach for
JARVY_TEST_MODE=1/CI=trueenv vars in every sandbox entrypoint. They're redundant in detected sandboxes. Setting them isn't wrong, just unnecessary. - Don't bake a telemetry endpoint into a shared sandbox base
image. It leaks signal across tenants. Set the endpoint
per-session via
JARVY_OTLP_ENDPOINTif you want session-scoped telemetry. - Don't skip
--dry-runon first integration. Runjarvy setup --dry-runonce interactively, commit the resultingjarvy.toml, and only then point ephemeral sandboxes at it. - Don't bypass roles to make the agent's sandbox lighter. Adding
a second
jarvy.toml("the agent one") guarantees drift. Use[roles.agent]withextends = "base"instead. - Don't auto-accept drift in the agent's session. Drift acceptance is a human decision. The auto-baseline on first run is intentionally only on a full clean match.
Reference¶
- For AI Agents overview — three modes (use, configure, modify Jarvy)
- MCP server — typed, audited tool access for agents
- Roles guide — per-role tool sets including agent-only roles
- Drift detection — baseline + check + accept + fix
- Network & proxy — corporate egress, custom CA bundles
- CI/CD integration — emit matching workflows from the same config
- Telemetry forwarder operations — in-cluster OTLP for multi-tenant sandbox fleets
- Migrate from Codespaces
- Migrate from Dev Containers
- PRD-053 — implementation details and rationale for seamless mode