Skip to main content

Sandbox & Tools for Coding Agents

Coding agents execute code they generate. Without isolation, a bad generation could corrupt the host, leak credentials, or break shared state. AgentCore provides sandboxed execution at every level.


Execution Options​

OptionWhat It IsBest For
Runtime shellFull shell inside the agent's microVM (BYOC container)Production agents with custom toolchains
Runtime InstancesAWS-managed EC2 in your account, GPU support, persistent EBSLong-running or GPU-bound coding tasks that exceed micro-VM limits
Code InterpreterManaged sandbox (Python, JS, TS) with pre-installed libsData analysis, prototyping, no container to manage
Docker SandboxLocal Docker container via Strands SDKLocal development and testing before deploy

Persistent Storage Options​

The runtime supports three mount types under filesystemConfigurations. Pick by persistence and sharing needs:

StoragePersistenceSharingBest For
Session Storage (Preview)Survives stop/resume; 14-day idle expiryIsolated per sessionAgent workspace: cloned repos, build artifacts
S3 FilesPermanent (S3 lifecycle rules apply)Shared across sessions and agentsShared skills, templates, reference code
EFSPermanent (until deleted)Shared across sessions and agentsLarge repos, shared dependency caches, ML models

Session Storage​

The default choice for a coding agent's workspace at /mnt/workspace:

PropertyValue
StatusPublic Preview
Mount pathConfigurable under /mnt/ (e.g., /mnt/workspace)
Max size1 GB per session
PersistenceSurvives stop/resume; 14-day idle expiry
Filesystem opsFull POSIX — git, npm, pip, cargo all work
IsolationPer-session (one session cannot see another's data)

Standard developer workflows work naturally: git clone into /mnt/workspace, install dependencies, build, and test. The next invocation finds everything still there.

S3 Files and EFS​

Use these when the agent needs data that outlives sessions or is shared across agents:

"filesystemConfigurations": [
{"sessionStorage": {"mountPath": "/mnt/workspace"}},
{"s3Files": {"mountPath": "/mnt/skills", "accessPointArn": "arn:aws:s3:...:accesspoint/skills"}},
{"efsStorage": {"mountPath": "/mnt/shared", "fileSystemId": "fs-abc123", "accessPointId": "fsap-def456"}}
]
  • S3 Files works well for read-mostly shared assets: agent skills, prompt libraries, code templates. The autonomous coding samples mount skills this way so every agent instance sees the same playbooks.
  • EFS suits large or frequently-written shared state: monorepos too big for the 1 GB session limit, warm dependency caches (node_modules, venv), or models. Standard NFS semantics apply.
Choosing storage

Start with session storage alone — it covers the common case (one agent, one task, one workspace). Add S3 Files when you find yourself copying the same reference material into every session. Add EFS when repo size or shared write access becomes the constraint.


Strands SDK Tools​

ToolPackageWhat It Does
bashstrands (vended)Shell commands with persistent state (env, cwd)
file_editorstrands (vended)View, create, and edit files via string replace
shellstrands-agents-toolsShell/subprocess execution
file_readstrands-agents-toolsRead file contents
code_interpreterstrands-agents-toolsAgentCore Code Interpreter integration

Example: Agent with File and Shell Tools​

from strands import Agent, tool
from bedrock_agentcore.runtime import BedrockAgentCoreApp

app = BedrockAgentCoreApp()

SYSTEM_PROMPT = """You are a coding agent. Your workspace is /mnt/workspace.
Use shell to run commands, file_read to inspect files, and file_write to make changes.
Always run tests after making changes to verify correctness."""


@tool
def shell(command: str) -> dict:
"""Execute a shell command in the workspace.

Args:
command: The shell command to run.
"""
import subprocess

try:
result = subprocess.run(
command, shell=True, capture_output=True, text=True,
timeout=60, cwd="/mnt/workspace",
)
return {
"stdout": result.stdout[-3000:],
"stderr": result.stderr[-1000:],
"exit_code": result.returncode,
}
except subprocess.TimeoutExpired:
return {"error": "Command timed out after 60 seconds"}


@tool
def file_read(path: str) -> dict:
"""Read the contents of a file.

Args:
path: Absolute path to the file.
"""
from pathlib import Path

target = Path(path)
if not target.exists():
return {"error": f"File not found: {path}"}
return {"content": target.read_text()[:10000]}


@tool
def file_write(path: str, content: str) -> dict:
"""Write content to a file, creating parent directories as needed.

Args:
path: Absolute path to the file.
content: The content to write.
"""
from pathlib import Path

target = Path(path)
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(content)
return {"written": str(target), "bytes": len(content)}


agent = Agent(
model="global.anthropic.claude-sonnet-4-6",
system_prompt=SYSTEM_PROMPT,
tools=[shell, file_read, file_write],
)


@app.entrypoint
async def invoke(payload, context):
"""Stream responses token-by-token to the caller."""
async for event in agent.stream_async(payload.get("prompt", "")):
if "data" in event:
yield event["data"]


if __name__ == "__main__":
app.run()
Streaming vs. sync entrypoints

The Quick Start uses a simpler sync pattern (result = agent(prompt); return {"response": result.message}) for clarity. Production agents use async def + agent.stream_async() + yield so responses render as they are generated, rather than waiting for the full answer. Both patterns work on AgentCore Runtime.


Common Shell Commands​

# Run tests
cd /mnt/workspace && python -m pytest tests/ -v

# Install dependencies
cd /mnt/workspace && pip install -r requirements.txt

# Build
cd /mnt/workspace && npm run build

# Lint
cd /mnt/workspace && npx eslint src/ --format json

# Git status
cd /mnt/workspace && git status --short

# Environment check
python --version && node --version && git --version

Tool Design Principles​

PrincipleWhy
One tool = one actionCompound tools confuse the LLM. Split "read and edit" into two tools.
Descriptive names and docstringsThe LLM decides tool selection based on name + description. Be specific.
Validate all inputsThe LLM sends arbitrary values. Check paths, formats, and bounds.
Return errors as valuesDon't raise exceptions. Return {"error": "..."} so the agent can adapt.
Typed schemasEvery tool gets a JSON schema defining inputs. No "catch-all" parameters.
Idempotent where possibleAgent might retry on failure. Make tools safe to call multiple times.

Safety Guardrails for Coding Agents​

Coding agents can write files and execute shell commands. Constrain them to prevent damage:

RiskMitigation
Write outside project dirValidate all paths with os.path.realpath start with /mnt/workspace
Destructive shell commandsBlock patterns like rm -rf /, chmod 777, force-push to main
Credential exfiltrationDeny writes to .env, .pem, id_rsa; strip env vars from output
Infinite loopsSet timeouts on all subprocess calls; bound retry attempts
Push to protected branchesRestrict git push targets in the tool or via Cedar policies
import os

WORKSPACE = "/mnt/workspace"

def validate_path(path: str) -> str:
"""Ensure the path is within the workspace. Raise if not."""
real = os.path.realpath(path)
if not real.startswith(os.path.realpath(WORKSPACE)):
raise ValueError(f"Path {path} is outside workspace")
return real

For stronger enforcement, use AgentCore Cedar policies on the Gateway to restrict which tools the agent can call. See Bounded Autonomy for the Cedar policy model (applicable to coding agents too).