From da5c0f36f6c160c70513167c8054ad6c5cc3c737 Mon Sep 17 00:00:00 2001 From: Harrison Chase Date: Sat, 7 Mar 2026 13:24:22 -0800 Subject: [PATCH] cr --- CUSTOMIZATION.md | 437 +++++++++++++++++++++++++++++++++++++++++++++++ INSTALLATION.md | 316 ++++++++++++++++++++++++++++++++++ 2 files changed, 753 insertions(+) create mode 100644 CUSTOMIZATION.md create mode 100644 INSTALLATION.md diff --git a/CUSTOMIZATION.md b/CUSTOMIZATION.md new file mode 100644 index 00000000..d17bbab9 --- /dev/null +++ b/CUSTOMIZATION.md @@ -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 +], +``` diff --git a/INSTALLATION.md b/INSTALLATION.md new file mode 100644 index 00000000..045562d6 --- /dev/null +++ b/INSTALLATION.md @@ -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:///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:///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:///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