AutoDev supports two platforms: Claude Code (claude -p) and Cursor (cursor agent --print). Both are wrapped behind a uniform PlatformAdapter protocol so the orchestrator and tournament engine are platform-agnostic.
class PlatformAdapter(Protocol):
async def init_workspace(self, cwd: Path, agents: list[AgentSpec]) -> None:
"""Write platform-native agent files (called by autodev init)."""
...
async def execute(self, inv: AgentInvocation) -> AgentResult:
"""Invoke a single agent and return its result."""
...
async def parallel(
self, invs: list[AgentInvocation], max_concurrent: int = 3
) -> list[AgentResult]:
"""Invoke multiple agents concurrently (used for tournament judges)."""
...
async def healthcheck(self) -> bool:
"""Return True if the platform CLI is available and logged in."""
...class AgentInvocation(BaseModel):
role: str # "developer", "critic_t", "judge", etc.
prompt: str # fully rendered prompt (no templating inside adapter)
cwd: Path # working directory for the subprocess
model: str | None = None # Claude Code: "sonnet" | "opus" | "haiku" | None (use default) | Cursor: "auto" | "sonnet" | "opus"
timeout_s: int = 600 # subprocess timeout
allowed_tools: list[str] | None = None # passed as --allowed-tools
max_turns: int = 1 # most roles = single-turn; developer can be multi-turnclass AgentResult(BaseModel):
success: bool
text: str # final message text
tool_calls: list[ToolCall]
files_changed: list[Path]
diff: str | None
duration_s: float
error: str | None
raw_stdout: str # for debuggingFile: autodev/adapters/claude_code.py
claude -p "<rendered_prompt>" \
--output-format json \
--model <model> \
--permission-mode acceptEdits \
--allowed-tools Read,Edit,Bash,Glob,GrepThe subprocess is launched with cwd=<repo_path> (Python's subprocess.run parameter).
autodev init writes .claude/agents/<role>.md with YAML frontmatter:
---
name: developer
description: Writes code; anti-hallucination protocol
tools: Read, Edit, Write, Bash, Glob, Grep
model: sonnet
---
<prompt content>| Deviation | Details |
|---|---|
No --cwd flag |
The claude CLI does not accept a --cwd argument. AutoDev uses Python's subprocess.run(cwd=...) parameter instead. |
| No temperature control | claude -p does not expose per-call temperature. The tournament relies on fresh-context-per-call (subprocess isolation) for stochasticity instead of temperature variation. |
--continue not used |
Each call is a fresh context. Continuity lives in .autodev/ state, not in the LLM session. |
--permission-mode acceptEdits |
Required for the developer and test_engineer roles to write files. |
The adapter parses the JSON output from claude -p --output-format json:
resultfield →AgentResult.texttoolUseBlocks→AgentResult.tool_calls- File change detection via git diff after the call
File: autodev/adapters/cursor.py
cursor agent "<rendered_prompt>" --print --output-format json --model <model>Falls back to cursor-agent binary name if cursor is not found on PATH.
autodev init writes .cursor/rules/<role>.mdc:
---
description: Coder agent — writes code with anti-hallucination protocol
alwaysApply: false
---
<prompt content>| Deviation | Details |
|---|---|
| No native tool restriction | Cursor has no --allowed-tools equivalent. AutoDev relies on .cursor/rules/*.mdc prompt-level constraints to guide tool use. |
| No temperature control | Same as Claude Code — fresh-context-per-call provides stochasticity. |
| Rate limit fallback | For explicit opus or sonnet models, the adapter automatically falls back to auto when rate limited (429 error or "rate limit" in stderr). This ensures high-reasoning roles gracefully degrade to auto-select mode. |
File: autodev/adapters/detect.py
Adapter selection follows this precedence:
- CLI flag
--platform claude|cursor|auto - Environment variable
AUTODEV_PLATFORM=claude_code|cursor|auto - Config file
config.json.platform(if not"auto") - Auto-detect:
- Run
claude --version→ if exit 0, useClaudeCodeAdapter - Else run
cursor --version→ if exit 0, useCursorAdapter - Else fail with diagnostic: "No supported CLI found. Run
autodev doctor."
- Run
async def get_adapter(platform: str) -> PlatformAdapter:
"""Resolve and return the appropriate platform adapter."""
...All subprocess calls share these properties:
- Stateless: each call is a fresh subprocess with no session continuity
- Async:
asyncio.create_subprocess_execfor non-blocking execution - Timeout: configurable per-invocation via
AgentInvocation.timeout_s(default 600s) - Retry:
AdapterLLMClientwraps the adapter with tenacity retry onTransientError(rate limits, transient failures) - Parallel:
adapter.parallel()usesasyncio.gathercapped atmax_parallel_subprocesses
# In autodev/tournament/llm.py
class AdapterLLMClient:
def __init__(
self,
adapter: PlatformAdapter,
cwd: Path,
timeout_s: int = 600,
max_attempts: int = 3,
): ...Retries use exponential backoff on TransientError. Permanent errors (e.g., invalid prompt, auth failure) are not retried.
To add support for a new platform:
- Create
autodev/adapters/<platform>.pyimplementing thePlatformAdapterprotocol. - Add detection logic to
autodev/adapters/detect.py. - Add agent file rendering to
autodev/agents/render_<platform>.py. - Register the platform in
autodev/config/schema.py(platformliteral type). - Add tests in
tests/test_adapter_<platform>.py.