Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions 1_foundations/twin2/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from context import TWIN_SYSTEM_PROMPT, TWIN_NAME
from tools import tools
from styles import CSS, build_js, EXAMPLES
from dotenv import load_dotenv
import gradio as gr
from agents import Agent, Runner

load_dotenv(override=True)

MODEL_NAME = "gpt-5.4-mini"

agent = Agent(name="Digital Twin", instructions=TWIN_SYSTEM_PROMPT, model=MODEL_NAME, tools=tools)

async def chat(message, history):
messages = [{"role": m["role"], "content": m["content"]} for m in history] + [{"role": "user", "content": message}]
result = await Runner.run(agent, messages)
return result.final_output


if __name__ == "__main__":
gr.ChatInterface(
chat,
examples=EXAMPLES,
title=f"{TWIN_NAME} — Digital Twin",
description="Talk to my AI twin about my career",
chatbot=gr.Chatbot(show_label=False),
).launch(css=CSS, js=build_js(TWIN_NAME), theme=gr.themes.Base())
36 changes: 36 additions & 0 deletions 1_foundations/twin2/tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import os
import requests
from dotenv import load_dotenv
from agents import function_tool

load_dotenv(override=True)

pushover_user = os.getenv("PUSHOVER_USER")
pushover_token = os.getenv("PUSHOVER_TOKEN")

pushover_url = "https://api.pushover.net/1/messages.json"


def push(text):
return requests.post(
pushover_url,
data={
"token": pushover_token,
"user": pushover_user,
"message": text,
},
).status_code

@function_tool
def record_user_details_tool(email:str, name:str="Name not provided", notes:str="not provided") -> str:
""" Use this tool to record that a user is interested in being in touch and provided an email address """
result = push(f"Recording interest from {name} with email {email} and notes {notes}")
return f"Recording interest pushed with API status code {result}"

@function_tool
def record_unknown_question_tool(question:str) -> str:
""" Always use this tool to record any question that couldn't be answered as you didn't know the answer """
push(f"Recording {question} asked that I couldn't answer")
return "OK"

tools = [record_user_details_tool, record_unknown_question_tool]
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
engineering_lead:
role: >
Engineering Lead for the engineering team, directing the work of the engineers
goal: >
You are given high level requirements for a system.
You are responsible for designing the system to achieve the requirements, and assigning work to 3 engineers: backend_engineer, frontend_engineer, and test_engineer.
You should describe the modules, classes, functions to be built. Give function signatures but do not write any code.
All the engineers will have access to a sandbox to write, execute and test code. All files are in the same directory; no subdirectories / packages.
Everything runs in a uv project with gradio installed; no other third-party packages are available.
Use your Context7 mcp tools to check APIs, particularly the latest gradio 6 APIs which have changes.
In your design, include explicit Gradio 6 API guidance for the frontend engineer (correct kwargs, method signatures, where things have changed from earlier versions), since they do not have access to Context7.
Success criteria: the system is successfully built and works.
backstory: >
You're a seasoned engineering lead with a knack for writing clear and concise designs.
llm: openai/gpt-5.5


backend_engineer:
role: >
Python Backend Engineer who can write code to achieve the design described by the engineering lead
goal: >
Use your sandbox tools to write and check python module(s) to achieve the design described by the engineering lead, in order to achieve the requirements.
Only the Python standard library is available — do not import any third-party packages.
Do not write any UI or frontend code; that is the frontend engineer's responsibility.
backstory: >
You're a seasoned python engineer with a knack for writing clean, efficient code.
You follow the design instructions carefully.
llm: openai/gpt-5.5

frontend_engineer:
role: >
A Gradio expert who can write a simple frontend to demonstrate a backend, and can validate that it will open as expected.
goal: >
Use your sandbox tools to write a gradio UI that demonstrates the given backend, all in one file to be in the same directory as the backend, as described in the design.
Also write and run some python code to validate that the gradio UI constructs without error.
Use color palette `#ecad0a` / `#209dd7` / `#753991` with grays but ensure the colors work in both light and dark mode.
Everything runs in a uv project with gradio installed; gradio is the only third-party package available, so do not import any others.
Success criteria: the gradio UI looks great in light mode and dark mode; the _validate.py script runs well and demonstrates that the gradio UI constructs.
backstory: >
You're a seasoned python engineer highly skilled at writing simple Gradio UIs for a backend class.
You produce a simple gradio UI that demonstrates the given backend class; you write the gradio UI to the sandbox.
llm: openai/gpt-5.4-mini

test_engineer:
role: >
An engineer with python coding skills who can write unit tests for the given code,
goal: >
Use your sandbox tools to write unit tests for the backend module, run them and check the results are as expected. Fix any defects and rerun the tests until they pass.
Only the Python standard library is available — use the built-in `unittest` module. Do not use pytest or any other third-party packages.
backstory: >
You're a seasoned QA engineer and software developer who writes great unit tests for python code.
llm: openai/gpt-5.4-mini
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
design_task:
description: >
Take the high level requirements described here and prepare a detailed design for the engineering team;
Here are the requirements: {requirements}
IMPORTANT: Only output the design in markdown format, laying out the module(s), classes, functions.
Do not actually write any code. Function or method signatures only.
You should assign work to the following engineers: backend_engineer, frontend_engineer, test_engineer.
The backend_engineer should write only the backend python code, no frontend (gradio) code and no test code.
The frontend_engineer should make a gradio app.
The test_engineer should write unit tests for the backend module.
All engineers will be working in the same sandbox directory. There is no directory structure, all files are in the same directory.
Everything runs in a uv project with gradio installed.
expected_output: >
A detailed design to achieve the requirements, identifying code structure, and assignments to the engineers.
agent: engineering_lead
output_file: sandbox/design.md

code_task:
description: >
Write python code that implements the backend portion of the design described by the engineering lead, in order to achieve the requirements.
Here are the requirements: {requirements}
expected_output: >
Python files written to the sandbox that implement the design and achieve the requirements.
IMPORTANT: Do not write unit tests (test_backend.py). The test_engineer will do this in the test_task.
Do not write the frontend code (app.py). The frontend_engineer will do this in the frontend_task.
Use your sandbox tools to write the code.
agent: backend_engineer
context:
- design_task

frontend_task:
description: >
Write a gradio UI in a module app.py that demonstrates the backend code, as described in the design.
Assume there is only 1 user, and have the UI be professional, polished, clean.
Then write a separate validation script (e.g. _validate.py) that imports app.py and confirms the Blocks object constructs without error, and run it via your sandbox tools.
IMPORTANT: the validation script must NOT call `.launch()` — that would block until timeout. Just import and instantiate.
Here are the requirements: {requirements}
expected_output: >
A gradio UI in module app.py written to the sandbox that demonstrates the functionality.
The file should be ready so that it can be run as-is, in the same sandbox directory as the backend code.
IMPORTANT: Use your sandbox tools to write and check the code.
agent: frontend_engineer
context:
- code_task
- design_task

test_task:
description: >
Write unit tests for the backend module, in a single test file, using the stdlib `unittest` module.
Do not write tests for app.py (the gradio frontend).
Fix any errors in the backend code so that the unit tests pass.
Keep working until all unit tests pass.
If you change any backend code, ensure that the unit tests pass and that the gradio app in app.py will still work.
Avoid making any changes that might break the gradio app in app.py.
expected_output: >
IMPORTANT: Use your sandbox tools to write and run the unit tests; the test file itself is written to the
sandbox with your write tool, NOT included in your final answer.
Your final answer must be ONLY a markdown summary of the test results: the number of tests run, how many
passed and failed, and details of any failures. Do NOT include the test file source code in your final answer.
agent: test_engineer
context:
- code_task
- design_task
output_file: sandbox/test_summary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
from crewai.tools import tool
from pathlib import Path
import shutil
import subprocess
import sys


SANDBOX_DIR = Path(__file__).parents[3] / "sandbox"
SANDBOX_DIR.mkdir(parents=True, exist_ok=True)


def reset_sandbox() -> None:
"""Wipe the sandbox and re-initialize it as a fresh uv project with gradio."""
if SANDBOX_DIR.exists():
try:
shutil.rmtree(SANDBOX_DIR)
except OSError:
# A prior Docker run may have left a Linux-style symlink (e.g. a
# venv's lib64 -> lib) inside the bind-mounted sandbox. Windows'
# shutil.rmtree can't traverse that reparse point, but the native
# `rmdir /s /q` deletes it without needing to enumerate it.
if sys.platform == "win32":
subprocess.run(["cmd", "/c", "rmdir", "/s", "/q", str(SANDBOX_DIR)], check=True)
else:
raise
SANDBOX_DIR.mkdir(parents=True, exist_ok=True)

subprocess.run(["uv", "init", "--bare", "--python", "3.13"], cwd=SANDBOX_DIR, check=True)
subprocess.run(["uv", "add", "--no-sync", "gradio"], cwd=SANDBOX_DIR, check=True)

@tool("List Sandbox Files")
def list_sandbox_files() -> str:
"""
List the filenames currently in the sandbox directory.

Returns:
A newline-separated list of filenames, or a message if the
sandbox is empty.
"""
names = sorted(p.name for p in SANDBOX_DIR.iterdir())
return "\n".join(names) if names else "The sandbox is empty."


@tool("Read Sandbox File")
def read_sandbox_file(filename: str) -> str:
"""
Read and return the text contents of a file in the sandbox directory.

Args:
filename: The name of the file to read (e.g. "solution.py").
Returns:
The file's contents, or a message if the file does not exist.
"""
path = SANDBOX_DIR / filename
if not path.is_file():
return f"No such file in the sandbox: {filename}"
return path.read_text(encoding="utf-8")


@tool("Write Sandbox File")
def write_sandbox_file(filename: str, content: str) -> str:
"""
Write text to a file in the sandbox directory, replacing any existing
file with the same name.

Args:
filename: The name of the file to write (e.g. "solution.py").
content: The text content to write.
Returns:
A confirmation message.
"""
path = SANDBOX_DIR / filename
path.write_text(content, encoding="utf-8")
return f"Wrote {len(content)} characters to {filename}."


@tool("Run Sandbox Python File")
def run_sandbox_python(filename: str) -> str:
"""
Execute a Python file from the sandbox directory inside an ephemeral
Docker container, with the sandbox mounted as the working directory,
using a uv run to run the code in the uv project,
and return whatever the script printed to stdout and stderr.

Note: the stdlib `unittest` module writes test results (including
failures and tracebacks) to stderr by default, so stderr must be
captured too or test runs will appear to produce no output.

Args:
filename: The name of the Python file to run (e.g. "solution.py").
Returns:
The text printed to stdout and stderr by the executed script.
"""
result = subprocess.run(
[
"docker", "run", "--rm",
"-v", f"{SANDBOX_DIR}:/workspace",
"-w", "/workspace",
"-e", "UV_PROJECT_ENVIRONMENT=/opt/venv",
"ghcr.io/astral-sh/uv:python3.13-bookworm-slim",
"uv", "run", filename,
],
capture_output=True,
text=True,
timeout=300,
)
output = result.stdout
if result.stderr:
output += f"\n--- stderr ---\n{result.stderr}"
return output

sandbox_tools = [list_sandbox_files, read_sandbox_file, write_sandbox_file, run_sandbox_python]


def _never_cache(*_args, **_kwargs) -> bool:
return False


# Sandbox state changes between calls (files appear/change/run), so caching tool
# results would feed agents stale data. Opt out of CrewAI's default tool caching.
for _t in sandbox_tools:
_t.cache_function = _never_cache