Files
2026-07-13 12:39:17 +08:00

146 lines
4.8 KiB
Python

"""
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))