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 @@ -26,15 +26,18 @@ class TemplateType(str, Enum):
TEMPORAL_OPENAI_AGENTS = "temporal-openai-agents"
TEMPORAL_PYDANTIC_AI = "temporal-pydantic-ai"
TEMPORAL_LANGGRAPH = "temporal-langgraph"
TEMPORAL_CLAUDE_CODE = "temporal-claude-code"
DEFAULT = "default"
DEFAULT_LANGGRAPH = "default-langgraph"
DEFAULT_PYDANTIC_AI = "default-pydantic-ai"
DEFAULT_OPENAI_AGENTS = "default-openai-agents"
DEFAULT_CLAUDE_CODE = "default-claude-code"
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"


def render_template(
Expand Down Expand Up @@ -67,15 +70,18 @@ def create_project_structure(
TemplateType.TEMPORAL_OPENAI_AGENTS: ["acp.py", "workflow.py", "run_worker.py", "activities.py"],
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.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.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"],
}[template_type]

# Create project/code files
Expand Down Expand Up @@ -189,6 +195,7 @@ def validate_agent_name(text: str) -> bool | str:
{"name": "Async ACP + OpenAI Agents SDK", "value": TemplateType.DEFAULT_OPENAI_AGENTS},
{"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},
],
).ask()
if not template_type:
Expand All @@ -201,6 +208,7 @@ def validate_agent_name(text: str) -> bool | str:
{"name": "Temporal + OpenAI Agents SDK (Recommended)", "value": TemplateType.TEMPORAL_OPENAI_AGENTS},
{"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},
],
).ask()
if not template_type:
Expand All @@ -214,6 +222,7 @@ def validate_agent_name(text: str) -> bool | str:
{"name": "Sync ACP + OpenAI Agents SDK + Local Sandbox", "value": TemplateType.SYNC_OPENAI_AGENTS_LOCAL_SANDBOX},
{"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},
],
).ask()
if not template_type:
Expand Down
43 changes: 43 additions & 0 deletions src/agentex/lib/cli/templates/default-claude-code/.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-claude-code/.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 for the Claude Code CLI (the `claude` subprocess this agent spawns)
ANTHROPIC_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=
51 changes: 51 additions & 0 deletions src/agentex/lib/cli/templates/default-claude-code/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/**
Comment thread
greptile-apps[bot] marked this conversation as resolved.

# Install the Claude Code CLI: the agent shells out to `claude` on every turn,
# so the binary must be present in the runtime image.
RUN npm install -g @anthropic-ai/claude-code

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 uv lockfile

agentex init renders pyproject.toml for use_uv=True, but it does not create a uv.lock. A new Claude template project built immediately after init therefore fails during Docker build at this COPY ... uv.lock step before uv sync can run. Please either generate the lockfile during init, remove uv.lock from the COPY, or make the Dockerfile support a fresh project without a lockfile.

Artifacts

Repro: script that renders the uv project and checks the Docker COPY sources

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

Repro: command transcript showing no uv.lock and emulated Docker COPY failure

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

Repro: template availability check used during setup

  • 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-claude-code/Dockerfile-uv.j2
Line: 30-34

Comment:
**Missing uv lockfile**

`agentex init` renders `pyproject.toml` for `use_uv=True`, but it does not create a `uv.lock`. A new Claude template project built immediately after init therefore fails during Docker build at this `COPY ... uv.lock` step before `uv sync` can run. Please either generate the lockfile during init, remove `uv.lock` from the `COPY`, or make the Dockerfile support a fresh project without a lockfile.

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

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-claude-code/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 \
Comment thread
greptile-apps[bot] marked this conversation as resolved.
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

# Install the Claude Code CLI: the agent shells out to `claude` on every turn,
# so the binary must be present in the runtime image.
RUN npm install -g @anthropic-ai/claude-code

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"]
64 changes: 64 additions & 0 deletions src/agentex/lib/cli/templates/default-claude-code/README.md.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# {{ agent_name }} - AgentEx Async Claude Code Agent

This template builds an **asynchronous** (non-Temporal) agent that drives the
**Claude Code CLI** through the unified harness surface on AgentEx:
- Spawns `claude -p --output-format stream-json --verbose` as a local subprocess
- Wraps the CLI's stdout stream in a `ClaudeCodeTurn`
- Delivers canonical `StreamTaskMessage*` events via `UnifiedEmitter.auto_send_turn`
(the async Redis push path), so the UI receives output in real time
- Tracing integration to SGP / AgentEx

## Prerequisites

- The `claude` CLI installed and on your `PATH`
- An `ANTHROPIC_API_KEY` (or equivalent credential) 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, 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 Claude Code CLI and pushes the
harness events to the task stream.

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

## Development

### 1. Customize the subprocess
Edit `_spawn_claude` in `project/acp.py` to change the CLI flags, working
directory, or how the prompt is delivered.

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

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