Skip to content
Merged
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
9 changes: 9 additions & 0 deletions src/agentex/lib/cli/commands/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,20 @@ class TemplateType(str, Enum):
TEMPORAL_PYDANTIC_AI = "temporal-pydantic-ai"
TEMPORAL_LANGGRAPH = "temporal-langgraph"
TEMPORAL_CLAUDE_CODE = "temporal-claude-code"
TEMPORAL_CODEX = "temporal-codex"
DEFAULT = "default"
DEFAULT_LANGGRAPH = "default-langgraph"
DEFAULT_PYDANTIC_AI = "default-pydantic-ai"
DEFAULT_OPENAI_AGENTS = "default-openai-agents"
DEFAULT_CLAUDE_CODE = "default-claude-code"
DEFAULT_CODEX = "default-codex"
SYNC = "sync"
SYNC_OPENAI_AGENTS = "sync-openai-agents"
SYNC_OPENAI_AGENTS_LOCAL_SANDBOX = "sync-openai-agents-local-sandbox"
SYNC_LANGGRAPH = "sync-langgraph"
SYNC_PYDANTIC_AI = "sync-pydantic-ai"
SYNC_CLAUDE_CODE = "sync-claude-code"
SYNC_CODEX = "sync-codex"


def render_template(
Expand Down Expand Up @@ -71,17 +74,20 @@ def create_project_structure(
TemplateType.TEMPORAL_PYDANTIC_AI: ["acp.py", "workflow.py", "run_worker.py", "agent.py", "tools.py"],
TemplateType.TEMPORAL_LANGGRAPH: ["acp.py", "workflow.py", "run_worker.py", "graph.py", "tools.py"],
TemplateType.TEMPORAL_CLAUDE_CODE: ["acp.py", "workflow.py", "run_worker.py", "activities.py"],
TemplateType.TEMPORAL_CODEX: ["acp.py", "workflow.py", "run_worker.py", "activities.py"],
TemplateType.DEFAULT: ["acp.py"],
TemplateType.DEFAULT_LANGGRAPH: ["acp.py", "graph.py", "tools.py"],
TemplateType.DEFAULT_PYDANTIC_AI: ["acp.py", "agent.py", "tools.py"],
TemplateType.DEFAULT_OPENAI_AGENTS: ["acp.py"],
TemplateType.DEFAULT_CLAUDE_CODE: ["acp.py"],
TemplateType.DEFAULT_CODEX: ["acp.py"],
TemplateType.SYNC: ["acp.py"],
TemplateType.SYNC_OPENAI_AGENTS: ["acp.py"],
TemplateType.SYNC_OPENAI_AGENTS_LOCAL_SANDBOX: ["acp.py", "agent.py", "tools.py"],
TemplateType.SYNC_LANGGRAPH: ["acp.py", "graph.py", "tools.py"],
TemplateType.SYNC_PYDANTIC_AI: ["acp.py", "agent.py", "tools.py"],
TemplateType.SYNC_CLAUDE_CODE: ["acp.py"],
TemplateType.SYNC_CODEX: ["acp.py"],
}[template_type]

# Create project/code files
Expand Down Expand Up @@ -196,6 +202,7 @@ def validate_agent_name(text: str) -> bool | str:
{"name": "Async ACP + LangGraph", "value": TemplateType.DEFAULT_LANGGRAPH},
{"name": "Async ACP + Pydantic AI", "value": TemplateType.DEFAULT_PYDANTIC_AI},
{"name": "Async ACP + Claude Code", "value": TemplateType.DEFAULT_CLAUDE_CODE},
{"name": "Async ACP + Codex", "value": TemplateType.DEFAULT_CODEX},
],
).ask()
if not template_type:
Expand All @@ -209,6 +216,7 @@ def validate_agent_name(text: str) -> bool | str:
{"name": "Temporal + Pydantic AI", "value": TemplateType.TEMPORAL_PYDANTIC_AI},
{"name": "Temporal + LangGraph", "value": TemplateType.TEMPORAL_LANGGRAPH},
{"name": "Temporal + Claude Code", "value": TemplateType.TEMPORAL_CLAUDE_CODE},
{"name": "Temporal + Codex", "value": TemplateType.TEMPORAL_CODEX},
],
).ask()
if not template_type:
Expand All @@ -223,6 +231,7 @@ def validate_agent_name(text: str) -> bool | str:
{"name": "Sync ACP + LangGraph", "value": TemplateType.SYNC_LANGGRAPH},
{"name": "Sync ACP + Pydantic AI", "value": TemplateType.SYNC_PYDANTIC_AI},
{"name": "Sync ACP + Claude Code", "value": TemplateType.SYNC_CLAUDE_CODE},
{"name": "Sync ACP + Codex", "value": TemplateType.SYNC_CODEX},
],
).ask()
if not template_type:
Expand Down
43 changes: 43 additions & 0 deletions src/agentex/lib/cli/templates/default-codex/.dockerignore.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# Environments
.env**
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# IDE
.idea/
.vscode/
*.swp
*.swo

# Git
.git
.gitignore

# Misc
.DS_Store
13 changes: 13 additions & 0 deletions src/agentex/lib/cli/templates/default-codex/.env.example.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# {{ agent_name }} - Environment Variables
# Copy this file to .env and fill in the values

# API key used by the codex CLI (`codex exec` reads OPENAI_API_KEY directly)
OPENAI_API_KEY=

# LLM base URL (optional - override to use a different provider)
# OPENAI_BASE_URL=

# SGP Configuration (optional - for tracing)
# SGP_API_KEY=
# SGP_ACCOUNT_ID=
# SGP_CLIENT_BASE_URL=
Comment thread
greptile-apps[bot] marked this conversation as resolved.
51 changes: 51 additions & 0 deletions src/agentex/lib/cli/templates/default-codex/Dockerfile-uv.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# syntax=docker/dockerfile:1.3
FROM python:3.12-slim
COPY --from=ghcr.io/astral-sh/uv:0.6.4 /uv /uvx /bin/

# Install system dependencies
RUN apt-get update && apt-get install -y \
htop \
vim \
curl \
tar \
python3-dev \
postgresql-client \
build-essential \
libpq-dev \
gcc \
cmake \
netcat-openbsd \
nodejs \
npm \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/**

# Install the codex CLI: the agent shells out to `codex` on every turn, so the
# binary must be present in the runtime image.
RUN npm install -g @openai/codex

ENV UV_COMPILE_BYTECODE=1
ENV UV_LINK_MODE=copy
ENV UV_HTTP_TIMEOUT=1000

WORKDIR /app/{{ project_path_from_build_root }}

# Copy dependency files for layer caching
COPY {{ project_path_from_build_root }}/pyproject.toml {{ project_path_from_build_root }}/uv.lock ./

# Install dependencies (without project itself, for layer caching)
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --no-install-project --no-dev
Comment on lines +34 to +38

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Missing generated lockfile

The uv Dockerfile requires uv.lock, but agentex init only renders pyproject.toml for uv projects and does not create a lockfile. A freshly scaffolded Codex project generated with the uv option can fail its Docker build at this COPY or at uv sync --locked before the agent starts. The same lockfile assumption should be fixed in the sync and Temporal Codex uv Dockerfiles.

Artifacts

Repro: scaffold render and Dockerfile COPY validation script

  • Contains supporting evidence from the run (text/x-python; charset=utf-8).

Repro: generated scaffold listing and missing uv.lock COPY validation failure

  • Keeps the command output available without making the summary code-heavy.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agentex/lib/cli/templates/default-codex/Dockerfile-uv.j2
Line: 34-38

Comment:
**Missing generated lockfile**

The uv Dockerfile requires `uv.lock`, but `agentex init` only renders `pyproject.toml` for uv projects and does not create a lockfile. A freshly scaffolded Codex project generated with the uv option can fail its Docker build at this `COPY` or at `uv sync --locked` before the agent starts. The same lockfile assumption should be fixed in the sync and Temporal Codex uv Dockerfiles.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code


# Copy the project code
COPY {{ project_path_from_build_root }}/project ./project

# Install the project
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --no-dev

ENV PATH="/app/{{ project_path_from_build_root }}/.venv/bin:$PATH"
ENV PYTHONPATH=/app

# Run the agent using uvicorn
CMD ["uvicorn", "project.acp:acp", "--host", "0.0.0.0", "--port", "8000"]
46 changes: 46 additions & 0 deletions src/agentex/lib/cli/templates/default-codex/Dockerfile.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# syntax=docker/dockerfile:1.3
FROM python:3.12-slim
COPY --from=ghcr.io/astral-sh/uv:0.6.4 /uv /uvx /bin/

# Install system dependencies
RUN apt-get update && apt-get install -y \
htop \
vim \
curl \
tar \
python3-dev \
postgresql-client \
build-essential \
libpq-dev \
gcc \
cmake \
netcat-openbsd \
nodejs \
npm \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
Comment thread
greptile-apps[bot] marked this conversation as resolved.

# Install the codex CLI: the agent shells out to `codex` on every turn, so the
# binary must be present in the runtime image.
RUN npm install -g @openai/codex

RUN uv pip install --system --upgrade pip setuptools wheel

ENV UV_HTTP_TIMEOUT=1000

# Copy just the requirements file to optimize caching
COPY {{ project_path_from_build_root }}/requirements.txt /app/{{ project_path_from_build_root }}/requirements.txt

WORKDIR /app/{{ project_path_from_build_root }}

# Install the required Python packages
RUN uv pip install --system -r requirements.txt

# Copy the project code
COPY {{ project_path_from_build_root }}/project /app/{{ project_path_from_build_root }}/project

# Set environment variables
ENV PYTHONPATH=/app

# Run the agent using uvicorn
CMD ["uvicorn", "project.acp:acp", "--host", "0.0.0.0", "--port", "8000"]
72 changes: 72 additions & 0 deletions src/agentex/lib/cli/templates/default-codex/README.md.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# {{ agent_name }} - AgentEx Async Codex Agent

This template builds an **asynchronous** (non-Temporal) agent that drives the
**Codex CLI** through the unified harness surface on AgentEx:
- Spawns `codex exec --json` as a local subprocess
- Wraps the CLI's stdout stream in a `CodexTurn`
- Delivers canonical `StreamTaskMessage*` events via `UnifiedEmitter.auto_send_turn`
(the async Redis push path), so the UI receives output in real time
- Persists the codex session/thread ID via `adk.state` for multi-turn memory
- Tracing integration to SGP / AgentEx

## Prerequisites

- The `codex` CLI installed and on your `PATH` (`npm install -g @openai/codex`)
- An `OPENAI_API_KEY` in your environment

## Running the Agent

```bash
agentex agents run --manifest manifest.yaml
```

## Project Structure

```
{{ project_name }}/
β”œβ”€β”€ project/
β”‚ β”œβ”€β”€ __init__.py
β”‚ └── acp.py # ACP server, subprocess spawn, state, and event handlers
β”œβ”€β”€ Dockerfile
β”œβ”€β”€ manifest.yaml
β”œβ”€β”€ dev.ipynb
{% if use_uv %}
└── pyproject.toml
{% else %}
└── requirements.txt
{% endif %}
```

## Key Concepts

### Async ACP with the harness
The async ACP model streams events over Redis instead of an HTTP response. The
`@acp.on_task_event_send` handler spawns the Codex CLI and pushes the harness
events to the task stream.

### Multi-turn memory
The codex session/thread ID is persisted via `adk.state`, so each new turn
resumes the same codex session with `codex exec resume <thread_id>`.

### The unified harness surface
`CodexTurn` + `UnifiedEmitter` are the unified harness surface. The turn
normalizes CLI output into canonical AgentEx events; the emitter traces and
delivers them.

## Development

### 1. Choose a model
Set `CODEX_MODEL` (defaults to `o4-mini`) to control which model codex uses.

### 2. Customize the subprocess
Edit `_spawn_codex` in `project/acp.py` to change the CLI flags or how the
prompt is delivered.

### 3. Configure Credentials
Set your credentials via `manifest.yaml`, an exported environment variable, or a
`.env` file in the project directory.

### 4. Run Locally
```bash
export ENVIRONMENT=development && agentex agents run --manifest manifest.yaml
```
Loading
Loading