chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
# Sandbox resume
|
||||
|
||||
This example shows a small sandbox resume flow with `AGENTS.md` mounted in the sandbox and loaded into the agent instructions. It runs in two
|
||||
steps: first it builds the app and smoke tests it, then it serializes the
|
||||
sandbox session state, resumes the sandbox, and adds pytest coverage.
|
||||
|
||||
By default the agent builds a tiny warehouse-robot status API, smoke-tests it, then resumes the same sandbox to add tests. The sandbox workspace starts with
|
||||
one instruction file:
|
||||
|
||||
- `AGENTS.md` with instructions to build FastAPI apps, use type hints and Pydantic, install dependencies with `uv`, run Python commands through `uv run python`, and test locally before finishing.
|
||||
|
||||
Run the example from the repository root:
|
||||
|
||||
```bash
|
||||
uv run python examples/sandbox/tutorials/sandbox_resume/main.py
|
||||
```
|
||||
|
||||
This demo exits after the scripted resume flow so the serialized session state and resume step stay easy to follow.
|
||||
|
||||
You can override the model or prompt:
|
||||
|
||||
```bash
|
||||
uv run python examples/sandbox/tutorials/sandbox_resume/main.py --model gpt-5.6-sol --question "Build a FastAPI service that exposes a warehouse robot's maintenance status."
|
||||
```
|
||||
|
||||
To run the same flow in Docker, build the shared tutorial image once and pass
|
||||
`--docker`:
|
||||
|
||||
```bash
|
||||
docker build --tag sandbox-tutorials:latest examples/sandbox/tutorials
|
||||
uv run python examples/sandbox/tutorials/sandbox_resume/main.py --docker
|
||||
```
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
"""
|
||||
Show the smallest Unix-local sandbox flow with workspace instructions.
|
||||
|
||||
The manifest includes an AGENTS.md file that tells the agent how to build the
|
||||
app, and the prompt asks for a tiny FastAPI operations status API with a health
|
||||
check.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from textwrap import dedent
|
||||
|
||||
from agents import Runner, RunResultStreaming, TResponseInputItem
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import Manifest, SandboxAgent, SandboxRunConfig
|
||||
from agents.sandbox.capabilities import Filesystem, Shell
|
||||
from agents.sandbox.entries import File
|
||||
|
||||
if __package__ is None or __package__ == "":
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[4]))
|
||||
|
||||
from examples.sandbox.tutorials.misc import (
|
||||
DEFAULT_SANDBOX_IMAGE,
|
||||
create_sandbox_client_and_session,
|
||||
load_env_defaults,
|
||||
print_event,
|
||||
)
|
||||
|
||||
DEFAULT_QUESTION = (
|
||||
"Build a small warehouse-robot operations status API with FastAPI. Include a health "
|
||||
"check, a typed `/robots/{robot_id}/status` endpoint backed by a tiny in-memory "
|
||||
"fixture, and clear 404 behavior. Install dependencies with uv, smoke test it locally "
|
||||
"with `uv run python` and `urllib.request`, and summarize what you built."
|
||||
)
|
||||
DEMO_DIR = Path(__file__).resolve().parent
|
||||
RESUME_QUESTION = (
|
||||
"Now add pytest coverage for the health check, robot status success case, and unknown "
|
||||
"robot 404 case. Install any missing dependencies with uv, run the tests locally, and "
|
||||
"summarize the files you changed."
|
||||
)
|
||||
AGENTS_MD = dedent(
|
||||
"""\
|
||||
# AGENTS.md
|
||||
|
||||
- When asked to build an app, make it a FastAPI app.
|
||||
- Use type hints and Pydantic models.
|
||||
- Use `uv` when installing dependencies.
|
||||
- Run Python commands as `uv run python ...`, not bare `python`.
|
||||
- Smoke test local HTTP endpoints with `uv run python` and `urllib.request`, not `curl`.
|
||||
- Test the app locally before finishing.
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
async def run_step(result: RunResultStreaming) -> list[TResponseInputItem]:
|
||||
async for event in result.stream_events():
|
||||
print_event(event)
|
||||
print_event(str(result.final_output).strip())
|
||||
return result.to_input_list()
|
||||
|
||||
|
||||
async def main(model: str, question: str, use_docker: bool, image: str) -> None:
|
||||
manifest = Manifest(entries={"AGENTS.md": File(content=AGENTS_MD.encode("utf-8"))})
|
||||
agent = SandboxAgent(
|
||||
name="Vibe Coder",
|
||||
model=model,
|
||||
instructions=AGENTS_MD,
|
||||
capabilities=[Shell(), Filesystem()],
|
||||
)
|
||||
|
||||
client, sandbox = await create_sandbox_client_and_session(
|
||||
manifest=manifest,
|
||||
use_docker=use_docker,
|
||||
image=image,
|
||||
)
|
||||
conversation: list[TResponseInputItem] = [{"role": "user", "content": question}]
|
||||
|
||||
try:
|
||||
async with sandbox:
|
||||
result = Runner.run_streamed(
|
||||
agent,
|
||||
conversation,
|
||||
max_turns=20,
|
||||
run_config=RunConfig(
|
||||
sandbox=SandboxRunConfig(session=sandbox),
|
||||
tracing_disabled=True,
|
||||
workflow_name="Sandbox resume example",
|
||||
),
|
||||
)
|
||||
conversation = await run_step(result)
|
||||
|
||||
frozen_session_state = client.deserialize_session_state(
|
||||
client.serialize_session_state(sandbox.state)
|
||||
)
|
||||
conversation.append({"role": "user", "content": RESUME_QUESTION})
|
||||
|
||||
resumed_sandbox = await client.resume(frozen_session_state)
|
||||
try:
|
||||
async with resumed_sandbox:
|
||||
resumed_result = Runner.run_streamed(
|
||||
agent,
|
||||
conversation,
|
||||
max_turns=20,
|
||||
run_config=RunConfig(
|
||||
sandbox=SandboxRunConfig(session=resumed_sandbox),
|
||||
tracing_disabled=True,
|
||||
workflow_name="Sandbox resume example",
|
||||
),
|
||||
)
|
||||
conversation = await run_step(resumed_result)
|
||||
finally:
|
||||
await client.delete(resumed_sandbox)
|
||||
finally:
|
||||
await client.delete(sandbox)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
load_env_defaults(DEMO_DIR / ".env")
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--model",
|
||||
default="gpt-5.4-mini",
|
||||
help="Model name to use.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--question",
|
||||
default=DEFAULT_QUESTION,
|
||||
help="Prompt to send to the agent.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--docker",
|
||||
action="store_true",
|
||||
help="Run this example in Docker instead of Unix-local.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--image",
|
||||
default=DEFAULT_SANDBOX_IMAGE,
|
||||
help="Docker image to use when --docker is set.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
asyncio.run(main(args.model, args.question, args.docker, args.image))
|
||||
Reference in New Issue
Block a user