Skip to content
Open
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
25 changes: 16 additions & 9 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,26 @@ FORGE_REQUIRE_PROJECT_CONFIG=true

# =============================================================================
# LLM Configuration
# Supports Claude (via Anthropic API or Vertex AI) and Gemini (via Vertex AI)
# Forge passes LangChain chat model instances into Deep Agents. Built-in
# configuration supports the Gemini API, Vertex AI, and Anthropic.
# =============================================================================

# Option 1: Direct Anthropic API (Claude models only)
ANTHROPIC_API_KEY=
# Option 2: Google Vertex AI (supports both Claude and Gemini)
ANTHROPIC_VERTEX_PROJECT_ID=your-gcp-project-id
ANTHROPIC_VERTEX_REGION=us-east5
# Default: Gemini on Vertex AI using Application Default Credentials.
LLM_BACKEND=vertex-ai
GOOGLE_CLOUD_PROJECT=your-gcp-project-id
GOOGLE_CLOUD_LOCATION=global

# Alternative: Gemini API.
# LLM_BACKEND=google-genai
# GOOGLE_API_KEY=

# Alternative: Anthropic API.
# LLM_BACKEND=anthropic
# ANTHROPIC_API_KEY=

# Model for orchestrator (PRD, spec, epic planning)
# Claude models: claude-opus-4-5@20251101, claude-sonnet-4-5@20250929, claude-haiku-4-5@20251001
# Gemini models: gemini-2.5-pro, gemini-2.5-flash, gemini-3.1-pro-preview
LLM_MODEL=claude-opus-4-5@20251101
# Examples: gemini-3.5-flash, gemini-2.5-pro, claude-opus-4-5@20251101
LLM_MODEL=gemini-3.5-flash

# Model for container tasks (code implementation)
# Can use a different model than orchestrator (e.g., for cost/rate limit reasons)
Expand Down
22 changes: 13 additions & 9 deletions containers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ Based on `mcr.microsoft.com/devcontainers/universal:linux` which provides:

Additional packages installed:
- `deepagents` - AI agent framework
- `anthropic`, `langchain-anthropic` - Claude API access
- `langchain-google-vertexai` - Vertex AI support (Claude and Gemini)
- `anthropic`, `langchain-anthropic` - direct Anthropic API access
- `langchain-google-vertexai` - Vertex AI model access
- `langchain-mcp-adapters` - MCP server integration

## Image Configuration
Expand Down Expand Up @@ -104,10 +104,13 @@ Passed automatically by the orchestrator:

| Variable | Description |
|----------|-------------|
| `ANTHROPIC_API_KEY` | Claude API key (direct API) |
| `ANTHROPIC_VERTEX_PROJECT_ID` | GCP project for Vertex AI |
| `ANTHROPIC_VERTEX_REGION` | Vertex AI region |
| `LLM_MODEL` | Model to use (e.g., `claude-opus-4-5@20251101`, `gemini-2.5-pro`) |
| `LLM_BACKEND` | `vertex-ai` (default), `google-genai`, or `anthropic` |
| `GOOGLE_API_KEY` | Gemini API key for `google-genai` |
| `GOOGLE_CLOUD_PROJECT` | GCP project for `vertex-ai` |
| `GOOGLE_CLOUD_LOCATION` | Vertex AI location |
| `ANTHROPIC_API_KEY` | API key for `anthropic` |
| `ANTHROPIC_*` | Backwards-compatible aliases for existing deployments |
| `LLM_MODEL` | Model to use (default: `gemini-3.5-flash`) |
| `FORGE_SYSTEM_PROMPT_TEMPLATE` | System prompt template (interpolated by entrypoint) |
| `GOOGLE_APPLICATION_CREDENTIALS` | Path to mounted gcloud credentials |
| `GIT_USER_NAME` | Git author name for commits (default: `Forge`) |
Expand Down Expand Up @@ -136,9 +139,10 @@ Example: `forge-AISOS-189-installer-12345`

## Task Execution

The entrypoint runs a Deep Agent with `LocalShellBackend`. Supported models:
- **Claude** (via Anthropic API or Vertex AI): `claude-opus-4-5@20251101`, `claude-sonnet-4-5@20250929`
- **Gemini** (via Vertex AI): `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-3.1-pro-preview`
The entrypoint runs a Deep Agent with `LocalShellBackend`. The built-in model
factory supports the Gemini API, Vertex AI, and the Anthropic API. Because the
agent receives a LangChain chat model instance, additional providers can be
added by extending the model factory.

The agent:
1. Reads and understands the codebase
Expand Down
124 changes: 92 additions & 32 deletions containers/entrypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,34 @@
)
logger = logging.getLogger(__name__)


def resolve_llm_backend() -> str:
"""Resolve explicit or legacy standalone-container backend configuration."""
explicit_backend = os.environ.get("LLM_BACKEND")
if explicit_backend:
return explicit_backend
if os.environ.get("ANTHROPIC_API_KEY") or os.environ.get("LLM_API_KEY"):
return "anthropic"
if os.environ.get("GOOGLE_API_KEY"):
return "google-genai"
return "vertex-ai"


def resolve_llm_model(backend_name: str) -> str:
"""Resolve model configuration with a backend-compatible default."""
configured_model = os.environ.get("LLM_MODEL") or os.environ.get("CLAUDE_MODEL")
if configured_model:
return configured_model
if backend_name == "anthropic":
return "claude-sonnet-4-5-20250929"
return "gemini-3.5-flash"


# Enable LangChain debug/verbose mode if requested
if os.environ.get("LANGCHAIN_VERBOSE", "").lower() in ("true", "1", "yes"):
try:
from langchain_core.globals import set_debug, set_verbose

set_verbose(True)
set_debug(True)
logger.info("LangChain verbose/debug mode enabled")
Expand Down Expand Up @@ -226,12 +250,14 @@ def git_commit(workspace: Path, message: str) -> bool:
return False

new_files = [
f for f in ls_result.stdout.split(b"\0")
f
for f in ls_result.stdout.split(b"\0")
if f and not f.startswith(b".forge/") and f != b".forge"
]
if new_files:
result = subprocess.run(
["git", "add", "--"] + [f.decode("utf-8", errors="surrogateescape") for f in new_files],
["git", "add", "--"]
+ [f.decode("utf-8", errors="surrogateescape") for f in new_files],
cwd=workspace,
capture_output=True,
text=True,
Expand Down Expand Up @@ -369,23 +395,36 @@ async def run_agent_task(
previous_task_keys: List of previously implemented task keys for handoff context.
trace_context: Workflow fields forwarded to Langfuse only.
"""
# Support both new (LLM_MODEL) and legacy (CLAUDE_MODEL) env var names
model_name = os.environ.get("LLM_MODEL") or os.environ.get("CLAUDE_MODEL", "claude-sonnet-4-5@20250929")
backend_name = resolve_llm_backend()
model_name = resolve_llm_model(backend_name)
logger.info(f"Implementing task: {task_summary}")
logger.info(f"Model: {model_name}")

try:
from deepagents import create_deep_agent
from deepagents.backends import LocalShellBackend

# Check for API credentials
api_key = os.environ.get("ANTHROPIC_API_KEY")
vertex_project = os.environ.get("ANTHROPIC_VERTEX_PROJECT_ID")
google_api_key = os.environ.get("GOOGLE_API_KEY")
anthropic_api_key = os.environ.get("ANTHROPIC_API_KEY") or os.environ.get("LLM_API_KEY")
vertex_project = (
os.environ.get("GOOGLE_CLOUD_PROJECT")
or os.environ.get("VERTEX_PROJECT_ID")
or os.environ.get("ANTHROPIC_VERTEX_PROJECT_ID")
)
vertex_region = (
os.environ.get("GOOGLE_CLOUD_LOCATION")
or os.environ.get("VERTEX_REGION")
or os.environ.get("ANTHROPIC_VERTEX_REGION", "global")
)

if not api_key and not vertex_project:
logger.error(
"No API credentials found (ANTHROPIC_API_KEY or ANTHROPIC_VERTEX_PROJECT_ID)"
)
if backend_name == "google-genai" and not google_api_key:
logger.error("GOOGLE_API_KEY is required for the google-genai backend")
return False
if backend_name == "vertex-ai" and not vertex_project:
logger.error("GOOGLE_CLOUD_PROJECT is required for the vertex-ai backend")
return False
if backend_name == "anthropic" and not anthropic_api_key:
logger.error("ANTHROPIC_API_KEY is required for the anthropic backend")
return False

# Create the agent with local shell backend (enables git commands)
Expand All @@ -408,49 +447,67 @@ async def run_agent_task(
"llm_model": model_name,
}

# Determine model type (Gemini vs Claude)
# Determine model family for the built-in LangChain model factories.
is_gemini = model_name.lower().startswith(("gemini", "models/gemini"))

# Get max tokens from env (default 16384)
max_tokens = int(os.environ.get("LLM_MAX_TOKENS", "16384"))

if vertex_project:
if backend_name == "vertex-ai":
if is_gemini:
# Gemini models via ChatGoogleGenerativeAI with Vertex AI backend
from langchain_google_genai import ChatGoogleGenerativeAI

logger.info(f"Using Gemini model: {model_name}, max_output_tokens={max_tokens}")
logger.info(
f"Using Vertex AI Gemini model: {model_name}, max_output_tokens={max_tokens}"
)
model = ChatGoogleGenerativeAI(
model=model_name,
project=vertex_project,
location=os.environ.get("ANTHROPIC_VERTEX_REGION", "us-east5"),
location=vertex_region,
vertexai=True,
max_output_tokens=max_tokens,
)
else:
# Claude models via ChatAnthropicVertex
from langchain_google_vertexai.model_garden import ChatAnthropicVertex

logger.info(f"Using Claude model: {model_name}, max_tokens={max_tokens}")
logger.info(
f"Using Vertex AI Anthropic model: {model_name}, max_tokens={max_tokens}"
)
model = ChatAnthropicVertex(
model_name=model_name,
project=vertex_project,
location=os.environ.get("ANTHROPIC_VERTEX_REGION", "us-east5"),
location=vertex_region,
max_tokens=max_tokens,
)
else:
elif backend_name == "google-genai":
if not is_gemini:
logger.error(f"Model '{model_name}' is not supported by google-genai")
return False

from langchain_google_genai import ChatGoogleGenerativeAI

logger.info(f"Using Gemini API model: {model_name}, max_output_tokens={max_tokens}")
model = ChatGoogleGenerativeAI(
model=model_name,
api_key=google_api_key,
max_output_tokens=max_tokens,
)
elif backend_name == "anthropic":
if is_gemini:
logger.error(f"Gemini model '{model_name}' requires Vertex AI credentials")
logger.error(f"Model '{model_name}' is not supported by anthropic")
return False

from langchain_anthropic import ChatAnthropic

logger.info(f"Using Claude model: {model_name}, max_tokens={max_tokens}")
logger.info(f"Using direct Anthropic model: {model_name}, max_tokens={max_tokens}")
model = ChatAnthropic(
model=model_name,
api_key=api_key,
api_key=anthropic_api_key,
max_tokens=max_tokens,
)
else:
logger.error(f"Unsupported LLM_BACKEND: {backend_name}")
return False

# Load Context7 MCP tools for library documentation
mcp_tools = await load_context7_tools()
Expand Down Expand Up @@ -513,9 +570,7 @@ async def run_agent_task(

# Run the agent (with Langfuse session context if enabled)
initial_message = {
"messages": [
{"role": "user", "content": f"Implement this task:\n\n{task_description}"}
]
"messages": [{"role": "user", "content": f"Implement this task:\n\n{task_description}"}]
}

if langfuse_enabled:
Expand Down Expand Up @@ -675,13 +730,18 @@ def main():
# Ensure changes are committed (agent should have done this, but as fallback).
# Skip if workspace is not a git repo — analysis tasks (RCA, reflection) write
# artifacts to .forge/ without needing a commit.
is_git_repo = subprocess.run(
["git", "rev-parse", "--is-inside-work-tree"],
cwd=workspace,
capture_output=True,
).returncode == 0
is_git_repo = (
subprocess.run(
["git", "rev-parse", "--is-inside-work-tree"],
cwd=workspace,
capture_output=True,
).returncode
== 0
)
if is_git_repo:
fallback_message = f"[{task_key}] {task_summary}\n\nAuto-committed by Forge container fallback."
fallback_message = (
f"[{task_key}] {task_summary}\n\nAuto-committed by Forge container fallback."
)
if not git_commit(workspace, fallback_message):
logger.error("Failed to commit changes")
sys.exit(EXIT_TASK_FAILED)
Expand Down
9 changes: 5 additions & 4 deletions docs/dev/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This page is a condensed reference. For the full walkthrough including payload-b
| Podman | `brew install podman` / `dnf install podman` |
| Docker Compose | Included with Docker Desktop or `brew install docker-compose` |

External accounts needed: Jira Cloud, GitHub, and Anthropic API key (or Vertex AI).
External accounts needed: Jira Cloud, GitHub, and LLM backend access through a direct model provider API or Vertex AI.

## Installation

Expand All @@ -33,9 +33,10 @@ JIRA_API_TOKEN=your-jira-api-token

GITHUB_TOKEN=github_pat_your_token

ANTHROPIC_API_KEY=sk-ant-your-key # or Vertex AI — see developer guide

LLM_MODEL=claude-opus-4-5@20251101
LLM_BACKEND=vertex-ai
GOOGLE_CLOUD_PROJECT=your-gcp-project
GOOGLE_CLOUD_LOCATION=global
LLM_MODEL=gemini-3.5-flash
REDIS_URL=redis://localhost:6380/0
```

Expand Down
20 changes: 7 additions & 13 deletions docs/developer-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Everything you need to run Forge locally, test it, observe what it's doing, and
- **Docker Compose** — for Redis and API gateway (`dnf install docker-compose` / included with Docker Desktop)
- **Jira Cloud** account with API access
- **GitHub** account with a Personal Access Token (scopes: `repo`, `read:org`)
- **Claude API key** (Anthropic direct) OR Google Cloud project with Vertex AI enabled
- **LLM backend access** through a direct model provider API OR Google Cloud project with Vertex AI enabled

---

Expand Down Expand Up @@ -68,17 +68,11 @@ JIRA_API_TOKEN=your-jira-api-token
# GitHub
GITHUB_TOKEN=github_pat_your_token

# LLM — choose one backend

# Option A: Anthropic direct
ANTHROPIC_API_KEY=sk-ant-your-key

# Option B: Google Vertex AI (supports Claude + Gemini)
ANTHROPIC_VERTEX_PROJECT_ID=your-gcp-project
ANTHROPIC_VERTEX_REGION=us-east5

# Which model to use
LLM_MODEL=claude-opus-4-5@20251101
# Default LLM backend
LLM_BACKEND=vertex-ai
GOOGLE_CLOUD_PROJECT=your-gcp-project
GOOGLE_CLOUD_LOCATION=global
LLM_MODEL=gemini-3.5-flash
```

---
Expand Down Expand Up @@ -507,7 +501,7 @@ forge_webhooks_failed_total
# External API latency
forge_external_api_latency_seconds{service="jira"}
forge_external_api_latency_seconds{service="github"}
forge_external_api_latency_seconds{service="claude"}
forge_external_api_latency_seconds{service="llm"}
```

### Adding worker metrics to Prometheus
Expand Down
13 changes: 6 additions & 7 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Get Forge running locally in about 10 minutes.
- **Docker Compose** — for Redis (`brew install docker-compose` / included with Docker Desktop)
- **Jira Cloud** account with API access
- **GitHub** Personal Access Token (scopes: `repo`, `read:org`)
- **Claude API key** (Anthropic direct) or Google Cloud project with Vertex AI enabled
- **LLM backend access** through a direct model provider API or Google Cloud project with Vertex AI enabled

## 1. Install

Expand All @@ -36,12 +36,11 @@ JIRA_API_TOKEN=your-jira-api-token
# GitHub
GITHUB_TOKEN=github_pat_your_token

# LLM — choose one
ANTHROPIC_API_KEY=sk-ant-your-key # Anthropic direct
# ANTHROPIC_VERTEX_PROJECT_ID=my-proj # OR Vertex AI
# ANTHROPIC_VERTEX_REGION=us-east5

LLM_MODEL=claude-opus-4-5@20251101
# Default LLM backend
LLM_BACKEND=vertex-ai
GOOGLE_CLOUD_PROJECT=your-gcp-project
GOOGLE_CLOUD_LOCATION=global
LLM_MODEL=gemini-3.5-flash
```

## 3. Build the Container Image
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/pr-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Merge `main` into the PR branch and resolve any merge conflicts using AI. Use th
3. Clones the repository and checks out the PR branch from the fork
4. Attempts `git merge origin/main`
5. If no conflicts: pushes the merge commit to the fork branch
6. If conflicts: spawns a container with Claude to resolve them using the PR description and changed files as context
6. If conflicts: spawns a container agent to resolve them using the PR description and changed files as context
7. Verifies no conflict markers remain, commits, and force-pushes to the fork
8. Returns the workflow to the node it was at before the rebase

Expand Down
Loading
Loading