Skip to content

Commit ff359de

Browse files
declan-scaleclaude
andcommitted
feat(cli): add claude-code init templates (sync / async / temporal)
Add default-claude-code, sync-claude-code and temporal-claude-code templates across all three tiers, wiring the new TemplateType entries into the init flow. Scaffolded code imports ClaudeCodeTurn from the agentex.lib.adk facade. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ab23d6d commit ff359de

37 files changed

Lines changed: 2388 additions & 0 deletions

src/agentex/lib/cli/commands/init.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,18 @@ class TemplateType(str, Enum):
2626
TEMPORAL_OPENAI_AGENTS = "temporal-openai-agents"
2727
TEMPORAL_PYDANTIC_AI = "temporal-pydantic-ai"
2828
TEMPORAL_LANGGRAPH = "temporal-langgraph"
29+
TEMPORAL_CLAUDE_CODE = "temporal-claude-code"
2930
DEFAULT = "default"
3031
DEFAULT_LANGGRAPH = "default-langgraph"
3132
DEFAULT_PYDANTIC_AI = "default-pydantic-ai"
3233
DEFAULT_OPENAI_AGENTS = "default-openai-agents"
34+
DEFAULT_CLAUDE_CODE = "default-claude-code"
3335
SYNC = "sync"
3436
SYNC_OPENAI_AGENTS = "sync-openai-agents"
3537
SYNC_OPENAI_AGENTS_LOCAL_SANDBOX = "sync-openai-agents-local-sandbox"
3638
SYNC_LANGGRAPH = "sync-langgraph"
3739
SYNC_PYDANTIC_AI = "sync-pydantic-ai"
40+
SYNC_CLAUDE_CODE = "sync-claude-code"
3841

3942

4043
def render_template(
@@ -67,15 +70,18 @@ def create_project_structure(
6770
TemplateType.TEMPORAL_OPENAI_AGENTS: ["acp.py", "workflow.py", "run_worker.py", "activities.py"],
6871
TemplateType.TEMPORAL_PYDANTIC_AI: ["acp.py", "workflow.py", "run_worker.py", "agent.py", "tools.py"],
6972
TemplateType.TEMPORAL_LANGGRAPH: ["acp.py", "workflow.py", "run_worker.py", "graph.py", "tools.py"],
73+
TemplateType.TEMPORAL_CLAUDE_CODE: ["acp.py", "workflow.py", "run_worker.py", "activities.py"],
7074
TemplateType.DEFAULT: ["acp.py"],
7175
TemplateType.DEFAULT_LANGGRAPH: ["acp.py", "graph.py", "tools.py"],
7276
TemplateType.DEFAULT_PYDANTIC_AI: ["acp.py", "agent.py", "tools.py"],
7377
TemplateType.DEFAULT_OPENAI_AGENTS: ["acp.py"],
78+
TemplateType.DEFAULT_CLAUDE_CODE: ["acp.py"],
7479
TemplateType.SYNC: ["acp.py"],
7580
TemplateType.SYNC_OPENAI_AGENTS: ["acp.py"],
7681
TemplateType.SYNC_OPENAI_AGENTS_LOCAL_SANDBOX: ["acp.py", "agent.py", "tools.py"],
7782
TemplateType.SYNC_LANGGRAPH: ["acp.py", "graph.py", "tools.py"],
7883
TemplateType.SYNC_PYDANTIC_AI: ["acp.py", "agent.py", "tools.py"],
84+
TemplateType.SYNC_CLAUDE_CODE: ["acp.py"],
7985
}[template_type]
8086

8187
# Create project/code files
@@ -189,6 +195,7 @@ def validate_agent_name(text: str) -> bool | str:
189195
{"name": "Async ACP + OpenAI Agents SDK", "value": TemplateType.DEFAULT_OPENAI_AGENTS},
190196
{"name": "Async ACP + LangGraph", "value": TemplateType.DEFAULT_LANGGRAPH},
191197
{"name": "Async ACP + Pydantic AI", "value": TemplateType.DEFAULT_PYDANTIC_AI},
198+
{"name": "Async ACP + Claude Code", "value": TemplateType.DEFAULT_CLAUDE_CODE},
192199
],
193200
).ask()
194201
if not template_type:
@@ -201,6 +208,7 @@ def validate_agent_name(text: str) -> bool | str:
201208
{"name": "Temporal + OpenAI Agents SDK (Recommended)", "value": TemplateType.TEMPORAL_OPENAI_AGENTS},
202209
{"name": "Temporal + Pydantic AI", "value": TemplateType.TEMPORAL_PYDANTIC_AI},
203210
{"name": "Temporal + LangGraph", "value": TemplateType.TEMPORAL_LANGGRAPH},
211+
{"name": "Temporal + Claude Code", "value": TemplateType.TEMPORAL_CLAUDE_CODE},
204212
],
205213
).ask()
206214
if not template_type:
@@ -214,6 +222,7 @@ def validate_agent_name(text: str) -> bool | str:
214222
{"name": "Sync ACP + OpenAI Agents SDK + Local Sandbox", "value": TemplateType.SYNC_OPENAI_AGENTS_LOCAL_SANDBOX},
215223
{"name": "Sync ACP + LangGraph", "value": TemplateType.SYNC_LANGGRAPH},
216224
{"name": "Sync ACP + Pydantic AI", "value": TemplateType.SYNC_PYDANTIC_AI},
225+
{"name": "Sync ACP + Claude Code", "value": TemplateType.SYNC_CLAUDE_CODE},
217226
],
218227
).ask()
219228
if not template_type:
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Python
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
*.so
6+
.Python
7+
build/
8+
develop-eggs/
9+
dist/
10+
downloads/
11+
eggs/
12+
.eggs/
13+
lib/
14+
lib64/
15+
parts/
16+
sdist/
17+
var/
18+
wheels/
19+
*.egg-info/
20+
.installed.cfg
21+
*.egg
22+
23+
# Environments
24+
.env**
25+
.venv
26+
env/
27+
venv/
28+
ENV/
29+
env.bak/
30+
venv.bak/
31+
32+
# IDE
33+
.idea/
34+
.vscode/
35+
*.swp
36+
*.swo
37+
38+
# Git
39+
.git
40+
.gitignore
41+
42+
# Misc
43+
.DS_Store
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# {{ agent_name }} - Environment Variables
2+
# Copy this file to .env and fill in the values
3+
4+
# API key for your LLM provider
5+
LITELLM_API_KEY=
6+
7+
# LLM base URL (optional - override to use a different provider)
8+
# OPENAI_BASE_URL=
9+
10+
# SGP Configuration (optional - for tracing)
11+
# SGP_API_KEY=
12+
# SGP_ACCOUNT_ID=
13+
# SGP_CLIENT_BASE_URL=
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# syntax=docker/dockerfile:1.3
2+
FROM python:3.12-slim
3+
COPY --from=ghcr.io/astral-sh/uv:0.6.4 /uv /uvx /bin/
4+
5+
# Install system dependencies
6+
RUN apt-get update && apt-get install -y \
7+
htop \
8+
vim \
9+
curl \
10+
tar \
11+
python3-dev \
12+
postgresql-client \
13+
build-essential \
14+
libpq-dev \
15+
gcc \
16+
cmake \
17+
netcat-openbsd \
18+
nodejs \
19+
npm \
20+
&& apt-get clean \
21+
&& rm -rf /var/lib/apt/lists/**
22+
23+
ENV UV_COMPILE_BYTECODE=1
24+
ENV UV_LINK_MODE=copy
25+
ENV UV_HTTP_TIMEOUT=1000
26+
27+
WORKDIR /app/{{ project_path_from_build_root }}
28+
29+
# Copy dependency files for layer caching
30+
COPY {{ project_path_from_build_root }}/pyproject.toml {{ project_path_from_build_root }}/uv.lock ./
31+
32+
# Install dependencies (without project itself, for layer caching)
33+
RUN --mount=type=cache,target=/root/.cache/uv \
34+
uv sync --locked --no-install-project --no-dev
35+
36+
# Copy the project code
37+
COPY {{ project_path_from_build_root }}/project ./project
38+
39+
# Install the project
40+
RUN --mount=type=cache,target=/root/.cache/uv \
41+
uv sync --locked --no-dev
42+
43+
ENV PATH="/app/{{ project_path_from_build_root }}/.venv/bin:$PATH"
44+
ENV PYTHONPATH=/app
45+
46+
# Run the agent using uvicorn
47+
CMD ["uvicorn", "project.acp:acp", "--host", "0.0.0.0", "--port", "8000"]
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# syntax=docker/dockerfile:1.3
2+
FROM python:3.12-slim
3+
COPY --from=ghcr.io/astral-sh/uv:0.6.4 /uv /uvx /bin/
4+
5+
# Install system dependencies
6+
RUN apt-get update && apt-get install -y \
7+
htop \
8+
vim \
9+
curl \
10+
tar \
11+
python3-dev \
12+
postgresql-client \
13+
build-essential \
14+
libpq-dev \
15+
gcc \
16+
cmake \
17+
netcat-openbsd \
18+
node \
19+
npm \
20+
&& apt-get clean \
21+
&& rm -rf /var/lib/apt/lists/*
22+
23+
RUN uv pip install --system --upgrade pip setuptools wheel
24+
25+
ENV UV_HTTP_TIMEOUT=1000
26+
27+
# Copy just the requirements file to optimize caching
28+
COPY {{ project_path_from_build_root }}/requirements.txt /app/{{ project_path_from_build_root }}/requirements.txt
29+
30+
WORKDIR /app/{{ project_path_from_build_root }}
31+
32+
# Install the required Python packages
33+
RUN uv pip install --system -r requirements.txt
34+
35+
# Copy the project code
36+
COPY {{ project_path_from_build_root }}/project /app/{{ project_path_from_build_root }}/project
37+
38+
# Set environment variables
39+
ENV PYTHONPATH=/app
40+
41+
# Run the agent using uvicorn
42+
CMD ["uvicorn", "project.acp:acp", "--host", "0.0.0.0", "--port", "8000"]
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# {{ agent_name }} - AgentEx Async Claude Code Agent
2+
3+
This template builds an **asynchronous** (non-Temporal) agent that drives the
4+
**Claude Code CLI** through the unified harness surface on AgentEx:
5+
- Spawns `claude -p --output-format stream-json --verbose` as a local subprocess
6+
- Wraps the CLI's stdout stream in a `ClaudeCodeTurn`
7+
- Delivers canonical `StreamTaskMessage*` events via `UnifiedEmitter.auto_send_turn`
8+
(the async Redis push path), so the UI receives output in real time
9+
- Tracing integration to SGP / AgentEx
10+
11+
## Prerequisites
12+
13+
- The `claude` CLI installed and on your `PATH`
14+
- An `ANTHROPIC_API_KEY` (or equivalent credential) in your environment
15+
16+
## Running the Agent
17+
18+
```bash
19+
agentex agents run --manifest manifest.yaml
20+
```
21+
22+
## Project Structure
23+
24+
```
25+
{{ project_name }}/
26+
├── project/
27+
│ ├── __init__.py
28+
│ └── acp.py # ACP server, subprocess spawn, and event handlers
29+
├── Dockerfile
30+
├── manifest.yaml
31+
├── dev.ipynb
32+
{% if use_uv %}
33+
└── pyproject.toml
34+
{% else %}
35+
└── requirements.txt
36+
{% endif %}
37+
```
38+
39+
## Key Concepts
40+
41+
### Async ACP with the harness
42+
The async ACP model streams events over Redis instead of an HTTP response. The
43+
`@acp.on_task_event_send` handler spawns the Claude Code CLI and pushes the
44+
harness events to the task stream.
45+
46+
### The unified harness surface
47+
`ClaudeCodeTurn` + `UnifiedEmitter` are the unified harness surface. The turn
48+
normalizes CLI output into canonical AgentEx events; the emitter traces and
49+
delivers them.
50+
51+
## Development
52+
53+
### 1. Customize the subprocess
54+
Edit `_spawn_claude` in `project/acp.py` to change the CLI flags, working
55+
directory, or how the prompt is delivered.
56+
57+
### 2. Configure Credentials
58+
Set your credentials via `manifest.yaml`, an exported environment variable, or a
59+
`.env` file in the project directory.
60+
61+
### 3. Run Locally
62+
```bash
63+
export ENVIRONMENT=development && agentex agents run --manifest manifest.yaml
64+
```

0 commit comments

Comments
 (0)