mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 09:13:14 +00:00
cr
This commit is contained in:
parent
5c493ea1c7
commit
da5c0f36f6
2 changed files with 753 additions and 0 deletions
437
CUSTOMIZATION.md
Normal file
437
CUSTOMIZATION.md
Normal file
|
|
@ -0,0 +1,437 @@
|
|||
# Customization Guide
|
||||
|
||||
Open SWE is designed to be forked and customized for your org. The core agent is assembled in a single function — `get_agent()` in `agent/server.py` — where you can swap out the sandbox, model, tools, and triggers.
|
||||
|
||||
```python
|
||||
# agent/server.py — the key lines
|
||||
return create_deep_agent(
|
||||
model=make_model("anthropic:claude-opus-4-6", temperature=0, max_tokens=20_000),
|
||||
system_prompt=construct_system_prompt(repo_dir, ...),
|
||||
tools=[http_request, fetch_url, commit_and_open_pr, linear_comment, slack_thread_reply],
|
||||
backend=sandbox_backend,
|
||||
middleware=[
|
||||
ToolErrorMiddleware(),
|
||||
check_message_queue_before_model,
|
||||
ensure_no_empty_msg,
|
||||
open_pr_if_needed,
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. Sandbox
|
||||
|
||||
By default, Open SWE runs each task in a [LangSmith cloud sandbox](https://docs.smith.langchain.com/) — an isolated Linux environment where the agent clones the repo and executes commands. Sandbox creation and connection is handled in `agent/integrations/langsmith.py`.
|
||||
|
||||
### Using a custom sandbox template
|
||||
|
||||
Set environment variables to use a custom Docker image:
|
||||
|
||||
```bash
|
||||
DEFAULT_SANDBOX_TEMPLATE_NAME="my-template" # Template registered in LangSmith
|
||||
DEFAULT_SANDBOX_TEMPLATE_IMAGE="my-org/my-image:latest" # Docker image
|
||||
```
|
||||
|
||||
This is useful for pre-installing languages, frameworks, or internal tools that your repos depend on — reducing setup time per agent run.
|
||||
|
||||
### Using a different sandbox provider
|
||||
|
||||
The `deepagents` ecosystem includes several sandbox providers out of the box. To swap providers, replace the `create_langsmith_sandbox()` call in `agent/server.py` with one of the following:
|
||||
|
||||
#### Modal
|
||||
|
||||
```bash
|
||||
pip install langchain-modal
|
||||
```
|
||||
|
||||
```python
|
||||
import modal
|
||||
from langchain_modal import ModalSandbox
|
||||
|
||||
app = modal.App.lookup("open-swe")
|
||||
sandbox_backend = ModalSandbox(sandbox=modal.Sandbox.create(app=app))
|
||||
```
|
||||
|
||||
This is what Ramp uses for their Inspect agent — container-based isolation with fast spin-up.
|
||||
|
||||
#### Daytona
|
||||
|
||||
```bash
|
||||
pip install langchain-daytona
|
||||
```
|
||||
|
||||
```python
|
||||
from daytona import Daytona
|
||||
from langchain_daytona import DaytonaSandbox
|
||||
|
||||
sandbox = Daytona().create()
|
||||
sandbox_backend = DaytonaSandbox(sandbox=sandbox)
|
||||
```
|
||||
|
||||
#### Runloop
|
||||
|
||||
```bash
|
||||
pip install langchain-runloop
|
||||
```
|
||||
|
||||
```python
|
||||
import os
|
||||
from runloop_api_client import RunloopSDK
|
||||
from langchain_runloop import RunloopSandbox
|
||||
|
||||
client = RunloopSDK(bearer_token=os.environ["RUNLOOP_API_KEY"])
|
||||
devbox = client.devbox.create()
|
||||
sandbox_backend = RunloopSandbox(devbox=devbox)
|
||||
```
|
||||
|
||||
#### Local shell (no isolation — development only)
|
||||
|
||||
```python
|
||||
from deepagents.backends import LocalShellBackend
|
||||
|
||||
sandbox_backend = LocalShellBackend(
|
||||
root_dir="/path/to/repo",
|
||||
inherit_env=True,
|
||||
)
|
||||
```
|
||||
|
||||
> **Warning**: `LocalShellBackend` runs commands directly on your host machine with no sandboxing. Only use for local development with human-in-the-loop enabled.
|
||||
|
||||
#### Wiring it up
|
||||
|
||||
All providers implement `SandboxBackendProtocol` and are interchangeable. Replace the sandbox creation in `agent/server.py`:
|
||||
|
||||
```python
|
||||
# Before (LangSmith)
|
||||
sandbox_backend = await asyncio.to_thread(create_langsmith_sandbox)
|
||||
|
||||
# After (any provider)
|
||||
sandbox_backend = await asyncio.to_thread(create_my_sandbox)
|
||||
```
|
||||
|
||||
### Building a custom sandbox provider
|
||||
|
||||
If none of the built-in providers fit, you can build your own. The agent accepts any backend that implements `SandboxBackendProtocol` from `deepagents`. The protocol requires:
|
||||
|
||||
- **File operations**: `ls_info()`, `read()`, `write()`, `edit()`, `glob_info()`, `grep_raw()`
|
||||
- **Shell execution**: `execute(command, timeout=None) -> ExecuteResponse`
|
||||
- **Identity**: `id` property returning a unique sandbox identifier
|
||||
|
||||
The easiest approach is to extend `BaseSandbox` from `deepagents.backends.sandbox` — it implements all file operations by delegating to `execute()`, so you only need to implement the shell execution layer:
|
||||
|
||||
```python
|
||||
from deepagents.backends.sandbox import BaseSandbox
|
||||
from deepagents.backends.protocol import ExecuteResponse
|
||||
|
||||
class MySandbox(BaseSandbox):
|
||||
def __init__(self, connection):
|
||||
self._conn = connection
|
||||
|
||||
@property
|
||||
def id(self) -> str:
|
||||
return self._conn.id
|
||||
|
||||
def execute(self, command: str, *, timeout: int | None = None) -> ExecuteResponse:
|
||||
result = self._conn.run(command, timeout=timeout or 300)
|
||||
return ExecuteResponse(
|
||||
output=result.stdout + result.stderr,
|
||||
exit_code=result.exit_code,
|
||||
truncated=False,
|
||||
)
|
||||
```
|
||||
|
||||
See `agent/integrations/langsmith.py` (`LangSmithBackend` class) for a full reference implementation.
|
||||
|
||||
---
|
||||
|
||||
## 2. Model
|
||||
|
||||
The model is configured in the `get_agent()` function in `agent/server.py`:
|
||||
|
||||
```python
|
||||
model=make_model("anthropic:claude-opus-4-6", temperature=0, max_tokens=20_000)
|
||||
```
|
||||
|
||||
### Switching models
|
||||
|
||||
Use the `provider:model` format:
|
||||
|
||||
```python
|
||||
# Anthropic
|
||||
model=make_model("anthropic:claude-sonnet-4-6", temperature=0, max_tokens=16_000)
|
||||
|
||||
# OpenAI (uses Responses API by default)
|
||||
model=make_model("openai:gpt-4o", temperature=0, max_tokens=16_000)
|
||||
|
||||
# Google
|
||||
model=make_model("google_genai:gemini-2.5-pro", temperature=0, max_tokens=16_000)
|
||||
```
|
||||
|
||||
The `make_model()` helper in `agent/utils/model.py` wraps `langchain.chat_models.init_chat_model`. For OpenAI models, it automatically enables the Responses API. For full control, pass a pre-configured model instance directly:
|
||||
|
||||
```python
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
|
||||
model = ChatAnthropic(model_name="claude-sonnet-4-6", temperature=0, max_tokens=16_000)
|
||||
|
||||
return create_deep_agent(
|
||||
model=model,
|
||||
...
|
||||
)
|
||||
```
|
||||
|
||||
### Using different models per context
|
||||
|
||||
You can route to different models based on task complexity, repo, or trigger source:
|
||||
|
||||
```python
|
||||
async def get_agent(config: RunnableConfig) -> Pregel:
|
||||
source = config["configurable"].get("source")
|
||||
|
||||
if source == "slack":
|
||||
# Faster model for Slack Q&A
|
||||
model = make_model("anthropic:claude-sonnet-4-6", temperature=0, max_tokens=16_000)
|
||||
else:
|
||||
# Full model for code changes from Linear
|
||||
model = make_model("anthropic:claude-opus-4-6", temperature=0, max_tokens=20_000)
|
||||
|
||||
return create_deep_agent(model=model, ...)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Tools
|
||||
|
||||
Open SWE ships with five custom tools on top of the built-in Deep Agents tools (file operations, shell execution, subagents, todos):
|
||||
|
||||
| Tool | File | Purpose |
|
||||
|---|---|---|
|
||||
| `commit_and_open_pr` | `agent/tools/commit_and_open_pr.py` | Git commit + GitHub draft PR |
|
||||
| `fetch_url` | `agent/tools/fetch_url.py` | Fetch web pages as markdown |
|
||||
| `http_request` | `agent/tools/http_request.py` | HTTP API calls |
|
||||
| `linear_comment` | `agent/tools/linear_comment.py` | Post comments on Linear tickets |
|
||||
| `slack_thread_reply` | `agent/tools/slack_thread_reply.py` | Reply in Slack threads |
|
||||
|
||||
### Adding a tool
|
||||
|
||||
Create a new file in `agent/tools/`, define a function, and add it to the tools list.
|
||||
|
||||
**Example — adding a Datadog search tool:**
|
||||
|
||||
```python
|
||||
# agent/tools/datadog_search.py
|
||||
import requests
|
||||
from typing import Any
|
||||
|
||||
def datadog_search(query: str, time_range: str = "1h") -> dict[str, Any]:
|
||||
"""Search Datadog logs for debugging context.
|
||||
|
||||
Args:
|
||||
query: Datadog log query string
|
||||
time_range: Time range to search (e.g. "1h", "24h", "7d")
|
||||
|
||||
Returns:
|
||||
Dictionary with matching log entries
|
||||
"""
|
||||
# Your Datadog API integration here
|
||||
...
|
||||
```
|
||||
|
||||
Then register it in `agent/server.py`:
|
||||
|
||||
```python
|
||||
from .tools import commit_and_open_pr, fetch_url, http_request, linear_comment, slack_thread_reply
|
||||
from .tools.datadog_search import datadog_search
|
||||
|
||||
return create_deep_agent(
|
||||
...
|
||||
tools=[
|
||||
http_request, fetch_url, commit_and_open_pr,
|
||||
linear_comment, slack_thread_reply,
|
||||
datadog_search, # new tool
|
||||
],
|
||||
...
|
||||
)
|
||||
```
|
||||
|
||||
The agent will automatically see the tool's name, docstring, and parameter types — the docstring serves as the tool description, so write it clearly.
|
||||
|
||||
### Removing tools
|
||||
|
||||
If you only use Linear (not Slack), remove `slack_thread_reply` from the tools list and vice versa. If you don't need web fetching, remove `fetch_url`. The only tool that's essential to the core workflow is `commit_and_open_pr`.
|
||||
|
||||
### Conditional tools
|
||||
|
||||
You can vary the toolset based on the trigger source:
|
||||
|
||||
```python
|
||||
base_tools = [http_request, fetch_url, commit_and_open_pr]
|
||||
source = config["configurable"].get("source")
|
||||
|
||||
if source == "linear":
|
||||
tools = [*base_tools, linear_comment]
|
||||
elif source == "slack":
|
||||
tools = [*base_tools, slack_thread_reply]
|
||||
else:
|
||||
tools = [*base_tools, linear_comment, slack_thread_reply]
|
||||
|
||||
return create_deep_agent(tools=tools, ...)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Triggers
|
||||
|
||||
Open SWE supports three invocation surfaces: Linear, Slack, and GitHub. Each is implemented as a webhook endpoint in `agent/webapp.py`. You can add, remove, or modify triggers independently.
|
||||
|
||||
### Removing a trigger
|
||||
|
||||
If you don't use Linear, simply don't configure the Linear webhook and remove the env vars. Same for Slack. The webhook endpoints still exist but won't receive events.
|
||||
|
||||
To fully remove a trigger's code, delete the corresponding endpoint from `agent/webapp.py`:
|
||||
|
||||
- **Linear**: `linear_webhook()` and `process_linear_issue()`
|
||||
- **Slack**: `slack_webhook()` and `process_slack_mention()`
|
||||
|
||||
### Customizing Linear routing
|
||||
|
||||
The `LINEAR_TEAM_TO_REPO` dict in `agent/webapp.py` maps Linear teams and projects to GitHub repos:
|
||||
|
||||
```python
|
||||
LINEAR_TEAM_TO_REPO = {
|
||||
"Engineering": {
|
||||
"projects": {
|
||||
"backend": {"owner": "my-org", "name": "backend"},
|
||||
"frontend": {"owner": "my-org", "name": "frontend"},
|
||||
},
|
||||
"default": {"owner": "my-org", "name": "monorepo"},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Customizing Slack routing
|
||||
|
||||
Slack uses env vars for default routing:
|
||||
|
||||
```bash
|
||||
SLACK_REPO_OWNER="my-org"
|
||||
SLACK_REPO_NAME="my-repo"
|
||||
```
|
||||
|
||||
Users can override per-message with `repo:owner/name` syntax in their Slack message.
|
||||
|
||||
### Adding a new trigger
|
||||
|
||||
To add a new invocation surface (e.g. Jira, Discord, a custom API):
|
||||
|
||||
1. **Add a webhook endpoint** in `agent/webapp.py`:
|
||||
|
||||
```python
|
||||
@app.post("/webhooks/my-trigger")
|
||||
async def my_trigger_webhook(request: Request, background_tasks: BackgroundTasks):
|
||||
# Parse the incoming event
|
||||
payload = await request.json()
|
||||
|
||||
# Extract task description and repo info
|
||||
task_description = payload["description"]
|
||||
repo_config = {"owner": "my-org", "name": "my-repo"}
|
||||
|
||||
# Create a LangGraph run
|
||||
background_tasks.add_task(process_my_trigger, task_description, repo_config)
|
||||
return {"status": "accepted"}
|
||||
```
|
||||
|
||||
2. **Create a processing function** that builds the prompt and starts an agent run:
|
||||
|
||||
```python
|
||||
async def process_my_trigger(task_description: str, repo_config: dict):
|
||||
thread_id = generate_deterministic_id(task_description)
|
||||
langgraph_client = get_client(url=LANGGRAPH_URL)
|
||||
|
||||
await langgraph_client.runs.create(
|
||||
thread_id,
|
||||
"agent",
|
||||
input={"messages": [{"role": "user", "content": task_description}]},
|
||||
config={"configurable": {
|
||||
"repo": repo_config,
|
||||
"source": "my-trigger",
|
||||
"user_email": "user@example.com",
|
||||
}},
|
||||
if_not_exists="create",
|
||||
)
|
||||
```
|
||||
|
||||
3. **Add a communication tool** (optional) so the agent can report back:
|
||||
|
||||
```python
|
||||
# agent/tools/my_trigger_reply.py
|
||||
def my_trigger_reply(message: str) -> dict:
|
||||
"""Post a reply to the triggering service."""
|
||||
# Your API call here
|
||||
...
|
||||
```
|
||||
|
||||
The key fields in `config.configurable` are:
|
||||
- `repo`: `{"owner": "...", "name": "..."}` — which GitHub repo to work on
|
||||
- `source`: string identifying the trigger (used for auth routing and communication)
|
||||
- `user_email`: the triggering user's email (for GitHub OAuth resolution)
|
||||
|
||||
---
|
||||
|
||||
## 5. System prompt
|
||||
|
||||
The system prompt is assembled in `agent/prompt.py` from modular sections. You can customize behavior by editing individual sections:
|
||||
|
||||
| Section | What it controls |
|
||||
|---|---|
|
||||
| `WORKING_ENV_SECTION` | Sandbox paths and execution constraints |
|
||||
| `TASK_EXECUTION_SECTION` | Workflow steps (understand → implement → verify → submit) |
|
||||
| `CODING_STANDARDS_SECTION` | Code style, testing, and quality rules |
|
||||
| `COMMIT_PR_SECTION` | PR title/body format and commit conventions |
|
||||
| `CODE_REVIEW_GUIDELINES_SECTION` | How the agent reviews code changes |
|
||||
| `COMMUNICATION_SECTION` | Formatting and messaging guidelines |
|
||||
|
||||
### Using AGENTS.md
|
||||
|
||||
Drop an `AGENTS.md` file in the root of any repository to add repo-specific instructions. The agent reads it from the sandbox at startup and appends it to the system prompt. This is the easiest way to encode conventions per-repo without modifying Open SWE's code.
|
||||
|
||||
---
|
||||
|
||||
## 6. Middleware
|
||||
|
||||
Middleware hooks run around the agent loop. Open SWE includes four:
|
||||
|
||||
| Middleware | Type | Purpose |
|
||||
|---|---|---|
|
||||
| `ToolErrorMiddleware` | Tool error handler | Catches and formats tool errors |
|
||||
| `check_message_queue_before_model` | Before model | Injects follow-up messages that arrived mid-run |
|
||||
| `ensure_no_empty_msg` | Before model | Prevents empty messages from reaching the model |
|
||||
| `open_pr_if_needed` | After agent | Safety net — opens a PR if the agent didn't |
|
||||
|
||||
Add custom middleware by appending to the middleware list in `get_agent()`. See the [LangChain middleware docs](https://python.langchain.com/docs/concepts/agents/#middleware) for the `@before_model` and `@after_agent` decorators.
|
||||
|
||||
**Example — adding a CI check after agent completion:**
|
||||
|
||||
```python
|
||||
from langchain.agents.middleware import AgentState, after_agent
|
||||
from langgraph.runtime import Runtime
|
||||
|
||||
@after_agent
|
||||
async def run_ci_check(state: AgentState, runtime: Runtime):
|
||||
"""Run CI checks after the agent finishes."""
|
||||
# Trigger your CI pipeline here
|
||||
...
|
||||
```
|
||||
|
||||
Then add it to the middleware list:
|
||||
|
||||
```python
|
||||
middleware=[
|
||||
ToolErrorMiddleware(),
|
||||
check_message_queue_before_model,
|
||||
ensure_no_empty_msg,
|
||||
open_pr_if_needed,
|
||||
run_ci_check, # new middleware
|
||||
],
|
||||
```
|
||||
316
INSTALLATION.md
Normal file
316
INSTALLATION.md
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
# Installation Guide
|
||||
|
||||
This guide walks you through setting up Open SWE end-to-end: local development, GitHub App creation, Linear and Slack webhooks, and production deployment.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.11+
|
||||
- [uv](https://docs.astral.sh/uv/) package manager
|
||||
- [LangGraph CLI](https://langchain-ai.github.io/langgraph/cloud/reference/cli/)
|
||||
- [ngrok](https://ngrok.com/) (for local development — exposes webhook endpoints to the internet)
|
||||
|
||||
## 1. Clone and install
|
||||
|
||||
```bash
|
||||
git clone https://github.com/langchain-ai/open-swe.git
|
||||
cd open-swe
|
||||
uv sync
|
||||
```
|
||||
|
||||
## 2. Create a GitHub App
|
||||
|
||||
Open SWE authenticates as a [GitHub App](https://docs.github.com/en/apps/creating-github-apps) to clone repos, push branches, and open PRs.
|
||||
|
||||
1. Go to **GitHub Settings** → **Developer settings** → **GitHub Apps** → **New GitHub App**
|
||||
2. Fill in:
|
||||
- **App name**: `open-swe` (or your preferred name)
|
||||
- **Homepage URL**: any valid URL
|
||||
- **Webhook URL**: `https://<your-ngrok-url>/webhooks/github` (you'll set this up in step 4)
|
||||
- **Webhook secret**: generate with `openssl rand -hex 32` — save this for `GITHUB_WEBHOOK_SECRET`
|
||||
3. Set permissions:
|
||||
- **Repository permissions**:
|
||||
- Contents: Read & write
|
||||
- Pull requests: Read & write
|
||||
- Issues: Read
|
||||
- Metadata: Read-only
|
||||
4. Under **Subscribe to events**, enable:
|
||||
- Pull request review comment
|
||||
- Issue comment
|
||||
5. Click **Create GitHub App**
|
||||
6. Note the **App ID** from the app settings page
|
||||
7. Generate a **private key** (scroll down on the app page → **Generate a private key**). Save the `.pem` file contents.
|
||||
8. **Install the app** on the repositories you want Open SWE to access:
|
||||
- Go to your app's page → **Install App** → select your org/account → choose repositories
|
||||
- Note the **Installation ID** from the URL after installation (e.g. `https://github.com/settings/installations/12345678` → `12345678`)
|
||||
|
||||
## 3. Set up LangSmith
|
||||
|
||||
Open SWE uses [LangSmith](https://smith.langchain.com/) for two things:
|
||||
- **Tracing**: all agent runs are logged for debugging and observability
|
||||
- **Sandboxes**: each task runs in an isolated LangSmith cloud sandbox
|
||||
|
||||
1. Create a [LangSmith account](https://smith.langchain.com/) if you don't have one
|
||||
2. Go to **Settings** → **API Keys** → create a new API key
|
||||
3. Save it as `LANGSMITH_API_KEY_PROD`
|
||||
|
||||
### GitHub OAuth (for user authentication)
|
||||
|
||||
Open SWE resolves GitHub tokens per-user via LangSmith's OAuth integration. This lets each user authenticate with their own GitHub account rather than sharing a single bot token.
|
||||
|
||||
You'll need these from your LangSmith workspace settings:
|
||||
- `GITHUB_OAUTH_PROVIDER_ID` — the OAuth provider ID configured in LangSmith
|
||||
- `X_SERVICE_AUTH_JWT_SECRET` — the service JWT secret for user token resolution
|
||||
|
||||
> **Note**: If these aren't configured, the agent will fall back to the GitHub App's installation token for all operations.
|
||||
|
||||
### Sandbox templates (optional)
|
||||
|
||||
You can configure a custom sandbox template for the agent's execution environment:
|
||||
|
||||
- `DEFAULT_SANDBOX_TEMPLATE_NAME` — name of a LangSmith sandbox template
|
||||
- `DEFAULT_SANDBOX_TEMPLATE_IMAGE` — Docker image for the sandbox
|
||||
|
||||
If not set, the default LangSmith sandbox image is used.
|
||||
|
||||
## 4. Set up triggers
|
||||
|
||||
Open SWE can be triggered from Linear, Slack, or GitHub. Configure whichever invocation surfaces your team uses — you don't need all of them.
|
||||
|
||||
### Linear
|
||||
|
||||
Open SWE listens for Linear comments that mention `@openswe`.
|
||||
|
||||
**Create a webhook:**
|
||||
|
||||
1. In Linear, go to **Settings** → **API** → **Webhooks** → **New webhook**
|
||||
2. Fill in:
|
||||
- **Label**: `open-swe`
|
||||
- **URL**: `https://<your-ngrok-url>/webhooks/linear`
|
||||
- **Secret**: generate with `openssl rand -hex 32` — save this for `LINEAR_WEBHOOK_SECRET`
|
||||
3. Under **Data change events**, enable **Comments** → `Create` only
|
||||
4. Click **Create webhook**
|
||||
|
||||
**Get your API key:**
|
||||
|
||||
1. Go to **Settings** → **API** → **Personal API keys** → **New API key**
|
||||
2. Name it `open-swe`, select **All access**, and copy the key
|
||||
3. Save it as `LINEAR_API_KEY`
|
||||
|
||||
**Configure team-to-repo mapping:**
|
||||
|
||||
Open SWE routes Linear issues to GitHub repos based on the Linear team and project. The mapping is defined in `agent/webapp.py` in the `LINEAR_TEAM_TO_REPO` dict:
|
||||
|
||||
```python
|
||||
LINEAR_TEAM_TO_REPO = {
|
||||
"My Team": {"owner": "my-org", "name": "my-repo"},
|
||||
"Engineering": {
|
||||
"projects": {
|
||||
"backend": {"owner": "my-org", "name": "backend"},
|
||||
"frontend": {"owner": "my-org", "name": "frontend"},
|
||||
},
|
||||
"default": {"owner": "my-org", "name": "monorepo"},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
- **Flat mapping**: team name → single repo
|
||||
- **Nested mapping**: team name → project name → repo, with an optional `default` fallback
|
||||
|
||||
Update this to match your Linear workspace structure.
|
||||
|
||||
### Slack
|
||||
|
||||
**Create a Slack App:**
|
||||
|
||||
1. Go to [api.slack.com/apps](https://api.slack.com/apps) → **Create New App** → **From scratch**
|
||||
2. Name it `open-swe` and select your workspace
|
||||
|
||||
**Configure OAuth & permissions:**
|
||||
|
||||
Under **OAuth & Permissions**, add these Bot Token Scopes:
|
||||
- `app_mentions:read`
|
||||
- `channels:history`
|
||||
- `channels:read`
|
||||
- `chat:write`
|
||||
- `reactions:write`
|
||||
- `users:read`
|
||||
- `users:read.email`
|
||||
|
||||
Install the app to your workspace and copy the **Bot User OAuth Token** (`xoxb-...`).
|
||||
|
||||
**Configure event subscriptions:**
|
||||
|
||||
1. Under **Event Subscriptions**, enable events
|
||||
2. Set the **Request URL** to `https://<your-ngrok-url>/webhooks/slack`
|
||||
3. Subscribe to bot events:
|
||||
- `app_mention`
|
||||
- `message.channels` (if you want non-@ mentions to work with username matching)
|
||||
4. Save changes
|
||||
|
||||
**Credentials you'll need:**
|
||||
|
||||
- `SLACK_BOT_TOKEN`: the Bot User OAuth Token (`xoxb-...`)
|
||||
- `SLACK_SIGNING_SECRET`: found under **Basic Information** → **App Credentials**
|
||||
- `SLACK_BOT_USER_ID`: the bot's user ID (find it in Slack by clicking the bot's profile)
|
||||
- `SLACK_BOT_USERNAME`: the bot's display name (e.g. `open-swe`)
|
||||
|
||||
**Configure default repo:**
|
||||
|
||||
Slack messages are routed to a default repo unless the user specifies one with `repo:owner/name`:
|
||||
|
||||
```bash
|
||||
SLACK_REPO_OWNER="my-org" # Default GitHub org
|
||||
SLACK_REPO_NAME="my-repo" # Default GitHub repo
|
||||
```
|
||||
|
||||
### GitHub
|
||||
|
||||
GitHub triggering works automatically once your GitHub App is set up (step 2). Tag `@openswe` in PR comments on agent-created PRs to have it address review feedback and push fixes to the same branch.
|
||||
|
||||
## 5. Environment variables
|
||||
|
||||
Create a `.env` file in the project root:
|
||||
|
||||
```bash
|
||||
# === LangSmith ===
|
||||
LANGSMITH_API_KEY_PROD="" # LangSmith API key
|
||||
LANGCHAIN_TRACING_V2="true"
|
||||
LANGCHAIN_PROJECT="" # LangSmith project name for traces
|
||||
|
||||
# === LLM ===
|
||||
ANTHROPIC_API_KEY="" # Anthropic API key (default provider)
|
||||
|
||||
# === GitHub App ===
|
||||
GITHUB_APP_ID="" # From step 2
|
||||
GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
|
||||
...
|
||||
-----END RSA PRIVATE KEY-----
|
||||
"
|
||||
GITHUB_APP_INSTALLATION_ID="" # From step 2
|
||||
|
||||
# === GitHub Webhook ===
|
||||
GITHUB_WEBHOOK_SECRET="" # openssl rand -hex 32
|
||||
|
||||
# === GitHub OAuth (via LangSmith) ===
|
||||
GITHUB_OAUTH_PROVIDER_ID="" # Optional — LangSmith OAuth provider
|
||||
X_SERVICE_AUTH_JWT_SECRET="" # Optional — service JWT secret
|
||||
|
||||
# === Linear ===
|
||||
LINEAR_API_KEY="" # From step 4
|
||||
LINEAR_WEBHOOK_SECRET="" # From step 4
|
||||
|
||||
# === Slack (optional) ===
|
||||
SLACK_BOT_TOKEN="" # From step 4
|
||||
SLACK_BOT_USER_ID=""
|
||||
SLACK_BOT_USERNAME=""
|
||||
SLACK_SIGNING_SECRET=""
|
||||
SLACK_REPO_OWNER="" # Default org for Slack-triggered tasks
|
||||
SLACK_REPO_NAME="" # Default repo for Slack-triggered tasks
|
||||
|
||||
# === Sandbox ===
|
||||
DEFAULT_SANDBOX_TEMPLATE_NAME="" # Optional — custom sandbox template
|
||||
DEFAULT_SANDBOX_TEMPLATE_IMAGE="" # Optional — custom Docker image
|
||||
|
||||
# === Token Encryption ===
|
||||
TOKEN_ENCRYPTION_KEY="" # openssl rand -base64 32
|
||||
```
|
||||
|
||||
## 6. Start the server (local development)
|
||||
|
||||
Start ngrok in one terminal to expose your local server:
|
||||
|
||||
In one terminal, expose your local server:
|
||||
|
||||
```bash
|
||||
ngrok http 2024
|
||||
```
|
||||
|
||||
Copy the HTTPS URL (e.g. `https://xxxx.ngrok.io`) and update your webhook URLs from step 4.
|
||||
|
||||
Then start the LangGraph server in another terminal:
|
||||
|
||||
```bash
|
||||
uv run langgraph dev --no-browser
|
||||
```
|
||||
|
||||
The server runs on `http://localhost:2024` with these endpoints:
|
||||
|
||||
| Endpoint | Purpose |
|
||||
|---|---|
|
||||
| `POST /webhooks/linear` | Linear comment webhooks |
|
||||
| `GET /webhooks/linear` | Linear webhook verification |
|
||||
| `POST /webhooks/slack` | Slack event webhooks |
|
||||
| `GET /webhooks/slack` | Slack webhook verification |
|
||||
| `GET /health` | Health check |
|
||||
|
||||
## 7. Verify it works
|
||||
|
||||
### Linear
|
||||
|
||||
1. Go to any Linear issue in a team you configured in `LINEAR_TEAM_TO_REPO`
|
||||
2. Add a comment: `@openswe what files are in this repo?`
|
||||
3. You should see:
|
||||
- A 👀 reaction on your comment within a few seconds
|
||||
- A new run in your LangSmith project
|
||||
- The agent replies with a comment on the issue
|
||||
|
||||
### Slack
|
||||
|
||||
1. In any channel where the bot is invited, start a thread
|
||||
2. Mention the bot: `@open-swe what's in the repo?`
|
||||
3. You should see:
|
||||
- An 👀 reaction on your message
|
||||
- A reply in the thread with the agent's response
|
||||
|
||||
## 8. Production deployment
|
||||
|
||||
For production, deploy the agent on [LangGraph Cloud](https://langchain-ai.github.io/langgraph/cloud/) instead of running locally:
|
||||
|
||||
1. Push your code to a GitHub repository
|
||||
2. Connect the repo to LangGraph Cloud
|
||||
3. Set all environment variables from step 5 in the deployment config
|
||||
4. Update your Linear and Slack webhook URLs to point to your production URL (replace the ngrok URL)
|
||||
|
||||
The `langgraph.json` at the project root already defines the graph entry point and HTTP app:
|
||||
|
||||
```json
|
||||
{
|
||||
"graphs": {
|
||||
"agent": "agent.server:get_agent"
|
||||
},
|
||||
"http": {
|
||||
"app": "agent.webapp:app"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Webhook not receiving events
|
||||
|
||||
- Verify ngrok is running and the URL matches what's configured in Linear/Slack
|
||||
- Check the ngrok web inspector at `http://localhost:4040` for incoming requests
|
||||
- Ensure you enabled the correct event types (Comments → Create for Linear, `app_mention` for Slack)
|
||||
|
||||
### GitHub authentication errors
|
||||
|
||||
- Verify `GITHUB_APP_ID`, `GITHUB_APP_PRIVATE_KEY`, and `GITHUB_APP_INSTALLATION_ID` are set correctly
|
||||
- Ensure the GitHub App is installed on the target repositories
|
||||
- Check that the private key includes the full `-----BEGIN RSA PRIVATE KEY-----` and `-----END RSA PRIVATE KEY-----` lines
|
||||
|
||||
### Sandbox creation failures
|
||||
|
||||
- Verify `LANGSMITH_API_KEY_PROD` is set and valid
|
||||
- Check LangSmith sandbox quotas in your workspace settings
|
||||
- If using a custom template, verify `DEFAULT_SANDBOX_TEMPLATE_NAME` matches an existing template
|
||||
|
||||
### Agent not responding to comments
|
||||
|
||||
- For Linear: ensure the comment contains `@openswe` (case-insensitive)
|
||||
- For Slack: ensure the bot is invited to the channel and the message is an `@mention`
|
||||
- Check server logs for webhook processing errors
|
||||
|
||||
### Token encryption errors
|
||||
|
||||
- Ensure `TOKEN_ENCRYPTION_KEY` is set (generate with `openssl rand -base64 32`)
|
||||
- The key must be a valid 32-byte Fernet-compatible base64 string
|
||||
Loading…
Add table
Reference in a new issue