Skip to content

Latest commit

 

History

History
149 lines (116 loc) · 7.49 KB

File metadata and controls

149 lines (116 loc) · 7.49 KB

External Coding Agents

For development tasks we use production-ready tools instead of writing our own agents.

Three are implemented and interchangeable: Claude Code, Factory.ai Droid and OpenAI Codex. A project picks one at creation time; when it does not, DEFAULT_AGENT_TYPE decides, and that default is claude.

Claude Code

The default. A CLI tool from Anthropic for agentic coding.

# Installation (native installer)
curl -fsSL https://claude.ai/install.sh | sh

# Usage
claude -p "Implement user registration endpoint"

# Pipe
cat error.log | claude -p "Fix this error"

Context: natively uses CLAUDE.md files. Worker-manager automatically maps INSTRUCTIONS.mdCLAUDE.md.

Price: a Pro/Max subscription, cheaper than the API. Workers authenticate through their own session, separate from the operator's.

Factory.ai Droid

An autonomous coding agent with autonomy levels: low (many confirmations), medium, high (full autonomy). The worker runs it non-interactively:

droid exec --prompt-file TASK.md --skip-permissions-unsafe

OpenAI Codex CLI

Codex is available for developer workers and is the default central exploratory-QA executor. The image pins Codex CLI 0.144.6; the wrapper runs it non-interactively:

codex exec --sandbox danger-full-access \
  "Read TASK.md and AGENTS.md, then complete the task described in TASK.md."

The task is in /workspace/TASK.md, and the shared developer instructions are in /workspace/AGENTS.md. The agent must report success or failure through POST http://localhost:9090/result. CLI stdout and stderr are diagnostics and are neither accepted as the business result nor persisted for Codex workers. Codex's own sandbox is off because the container already is one, and the two cannot nest: workspace-write puts every file operation through Codex's bwrap helper, which needs a user namespace the worker container does not have. Codex then fails to read TASK.md, reports itself blocked and exits without a result. danger-full-access also removes the need for the per-run network override that workspace-write required for the localhost result call, dependency access and Git push. The Docker worker network and the container's own cap_drop: ALL, no-new-privileges and resource limits remain the isolation boundary.

A central QA worker is intentionally different: it receives an empty ephemeral non-Git workspace, injected AGENTS.md and TASK.md, and invokes Codex with --skip-git-repo-check. Its deployment access is the QA capability endpoint only; the target never receives the mounted Codex profile or an API key.

Dedicated ChatGPT session profile

Do not mount the operator's live ~/.codex. Create a separate profile on the Docker host and log in once with device authentication:

install -d -m 0700 "$HOME/.codex-worker"
printf 'cli_auth_credentials_store = "file"\n' > "$HOME/.codex-worker/config.toml"
chmod 0600 "$HOME/.codex-worker/config.toml"
CODEX_HOME="$HOME/.codex-worker" codex login --device-auth
chmod 0600 "$HOME/.codex-worker/auth.json"

Set HOST_CODEX_HOME=/home/youruser/.codex-worker in .env, then rebuild the worker images. Worker-manager requires directory mode 0700, file modes 0600, access and refresh tokens in a valid auth.json, and cli_auth_credentials_store = "file". A missing or unsuitable profile stops Codex worker creation before image resolution. The profile is mounted read-write only into Codex containers at /home/worker/.codex so refreshed tokens persist. Claude, Factory, and noop workers do not receive this mount.

See the official authentication and non-interactive mode documentation for the upstream behavior.


Integration into the project

The Developer node in the Engineering Subgraph uses coding agents through the worker-manager service (the PO does not use containers, it is a LangGraph ReactAgent):

When creating a project the PO passes the chosen developer worker in create_project(agent_type="claude" | "factory" | "codex"). The value is stored in project.config.agent_type and applies to the engineering tasks of that project. If no choice is given, the API resolves the current runtime DEFAULT_AGENT_TYPE when the project is created. An unknown value is rejected before the project is created.

  1. Worker-manager creates a container from a worker-base image
  2. Mounts the pre-scaffolded workspace (/data/workspaces/{repo_id}/) — the code is already in place
  3. Worker-manager creates/checks out story feature branch (story/{story_id})
  4. Injects the static instructions from services/langgraph/src/prompts/developer_worker/INSTRUCTIONS.md → an agent-specific file (CLAUDE.md / AGENTS.md)
  5. Injects a dynamic TASK.md into /workspace/TASK.md with the project-specific task. Previous tasks archived in .story/old_tasks/
  6. Starts the coding agent (Claude Code, Droid or Codex) in non-interactive mode
  7. The agent commits and pushes to the feature branch. Worker-wrapper pulls from the current branch (not a hardcoded main)
  8. The agent reports the result over HTTP: curl -X POST localhost:9090/result -d '{"success":true,"commit":"<sha>","summary":"..."}'
  9. If the task cannot be completed: curl -X POST localhost:9090/result -d '{"success":false,"reason":"..."}'

The worker-wrapper HTTP server (localhost:9090):

  • POST /result — a single endpoint for results (success/failure). Auto-resume: if the agent exits without calling /result, the wrapper restarts it once automatically.
  • POST /infra/compose — a compose proxy for managing the sidecar infrastructure (db, redis). Proxied to worker-manager.
  • The Makefile override targets (make migrate, make dev-start) inside the worker use curl localhost:9090/infra/compose.

Agent subprocess environment

The wrapper does not pass its complete container environment to Claude, Codex, Factory or noop subprocesses. Both the normal launch and Claude auto-resume use the same allowlist: process basics (HOME, PATH, locale, terminal, temporary directory, timezone and PYTHONPATH without /app); PYTHONNOUSERSITE, which keeps agent Python commands from loading user-site packages that can shadow the wrapper's shared package; Claude authentication and session settings (ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_BASE_URL, CLAUDE_CONFIG_DIR) plus the container runtime settings DISABLE_AUTOUPDATER and DISABLE_TELEMETRY; Codex authentication and session settings (CODEX_API_KEY, CODEX_HOME); FACTORY_API_KEY; and the repository-scoped GITHUB_TOKEN and GH_TOKEN credentials. The cosmetic interpreter settings PYTHONUNBUFFERED and PYTHONDONTWRITEBYTECODE remain wrapper-only. The agent uses localhost:9090 for result reporting and Compose operations, so the child env= mapping does not pass wrapper Redis/API/manager URLs, encryption keys, Docker/Compose sockets, host paths or arbitrary task command environment variables. Saved transcripts still redact secrets from the full wrapper environment rather than the reduced child environment.


Mapping onto the graph nodes

Node Tool Status
Scaffolder Copier template ✅ Implemented
Developer Claude Code / Factory.ai Droid / OpenAI Codex ✅ Implemented (Native execution, Flat Dev Environment)
Tester ❌ Removed. The Developer runs the tests through make; CI checks run after the subgraph
DevOps GitHub Actions (deploy.yml) ✅ Implemented