background-agents
ApplicationColeMurray/background-agents
Open-source background agent system for automated coding tasks.
Overview
A coding agent that runs in the background, automating tasks like PRs, reviews, and issue resolution. Supports multiple AI models and integrates with Slack, GitHub, and Linear. Designed for single-tenant deployment within organizations.
README Preview
# Background Agents: Open-Inspect\n\nAn open-source background agents coding system inspired by\n[Ramp's Inspect](https://builders.ramp.com/post/why-we-built-our-background-agent).\n\n## Overview\n\nOpen-Inspect provides a hosted background coding agent that can:\n\n- Work on tasks in the background while you focus on other things\n- Access full development environments (Node.js, Python, git, browser automation, VS Code)\n- Connect from anywhere — web UI, Slack, GitHub PRs, Linear issues, or webhooks\n- Enable multiplayer sessions where multiple people can collaborate in real time\n- Create PRs with proper commit attribution to the prompting user\n- Run on a schedule — cron jobs, Sentry alerts, and webhook-triggered automations\n- Spawn parallel sub-tasks that work in separate sandboxes simultaneously\n- Use your choice of AI model — Anthropic Claude, OpenAI Codex (via ChatGPT subscription), or\n OpenCode Zen\n\n## Security Model (Single-Tenant Only)\n\n> **Important**: This system is designed for **single-tenant deployment only**, where all users are\n> trusted members of the same organization with access to the same repositories.\n\n### How It Works\n\nThe system uses a shared GitHub App installation for git operations (clone, fetch, push). The\ncontrol plane mints short-lived installation tokens server-side and brokers them to sandboxes\nthrough the git credential helper on demand. This means:\n\n- **All users share the same GitHub App credentials** - The GitHub App must be installed on your\n organization's repositories, and any user of the system can access any repo the App has access to\n- **No per-user repository access validation** - The system does not verify that a user has\n permission to access a specific repository before creating a session\n- **GitHub users' OAuth tokens are used for PR creation** - For GitHub logins, PRs are created using\n the user's GitHub OAuth token, ensuring proper attribution and that they can only create PRs on\n repos the
FAQ (5)
TroubleshootingHow do I fix HTTP 400 classifier failures with Claude Opus 4.8/5.5: 'temperature is deprecated' or 'tool_choice type tool not supported'?
Update the Slack and Linear classifier requests in packages/slack-bot/src/classifier/index.ts and packages/linear-bot/src/classifier/index.ts:
1. Remove temperature: 0; Claude Opus 4.7+ rejects non-default temperature values.
2. For Opus 5.5, replace the forced tool choice, such as tool_choice: { type: "tool", name: "classify_target" }, with tool_choice: { type: "auto" }.
3. Instruct the model in the prompt that it must call the classify_target tool.
4. Keep the existing tool-input validation and target-picker fallback for responses that omit the tool call.
Opus 4.8 works with forced tool use once temperature is omitted; Opus 5.5 requires auto because both tool and any tool-choice types are unsupported. As an immediate workaround, set classification_model = "claude-haiku-4-5", the unaffected default model.
TroubleshootingWhy are PRs created from Slack or Linear using the app/bot GitHub identity instead of my personal credentials?
This was caused by a credential-lookup bug after the web sign-in migration to Better Auth. Slack, Linear, and GitHub bot sessions still read OAuth tokens from the legacy user_scm_tokens table, while new GitHub tokens are stored in Better Auth's user_identities.access_token table. With no user token found, PR creation fell back to the GitHub App installation token. The fix routes bot service principals through Better Auth's trusted server API using the canonical user established during route admission and their linked GitHub account, validates the GitHub profile, and keeps user_scm_tokens only as a fallback for pre-migration credentials. Upgrade or deploy the control-plane release containing the fix for issue #1912, then confirm the affected user has a linked GitHub account in Better Auth.
TroubleshootingWhy does a renamed session title revert to the old name in the sidebar on 'Load more' pages?
This is a known bug in open-inspect's web UI, fixed by PR #2404 (merged into main as commit 9eee5a4c02b418fec570581fd85d98e01dbee25e). Root cause: useSessionRename kept one shared optimistic title per session. When the open session page's header caught up with the confirmed rename, it cleared the optimistic title for all subscribers. Sidebar rows on pages loaded via 'Load more' live only in React state and never refetch, so they fell back to the stale pre-rename title. Fix: update to a build containing that commit — it adds a title overlay in useSidebarSessions (same shape as the read overlay) written from the rename result and dropped when the page unmounts or refetches. Verify with: npm test -w @open-inspect/web -- src/hooks/use-sidebar-title-reconciliation.test.tsx src/hooks/use-session-rename.test.tsx src/hooks/use-sidebar-sessions.test.tsx --maxWorkers=1 (74 tests should pass).
TroubleshootingHow do I recover work after a Fable Modal sandbox restores from an old snapshot?
In Fable 5.1, supervisor.restored_from_snapshot means the session restored an older snapshot_image_id. The restore path only runs git fetch; it does not run git checkout, reset, or clean. Data loss usually occurs because Modal's 2-hour hard timeout kills the container before a snapshot is taken, an open browser tab suppresses the inactivity snapshot, or a failed snapshot silently keeps the previous image.
Recovery steps:
1. Fetch the last session diff: GET /sessions/<session_id>/diff, or open the Diff view in the web UI. The session_diff store contains the last tracked and untracked files captured after a turn, with limits of 512 KiB per file and 1.5 MiB total. If the missing file is present, re-apply that diff.
2. Search Cloudflare Worker logs for sandbox.snapshot_saved events and check their image_id values. If a newer image was saved but later replaced, start or restore from that image manually if possible.
3. Only one snapshot_image_id is retained per session; there is no snapshot history table, and snapshot_saved WebSocket events are not persisted.
To prevent recurrence, commit and push to Git before long turns or the 2-hour sandbox limit, do not assume an open browser tab creates an inactivity snapshot, and investigate Snapshot failed log messages; snapshot operations time out after 300 seconds. The permanent fix is the "Preserve sandbox state before provider expiry" change, which performs a provider-neutral final preservation before hard expiry with a configurable buffer of 5 minutes minimum and 10 minutes default.
TroubleshootingHow do I stop Modal sandboxes from repeatedly spawning in an endless loop and causing unexpected charges?
Upgrade/deploy the control plane to a release containing the fixes for issue #1894: it now ignores fatal reports from sandboxes already failed by the connect watchdog, and the circuit breaker counts boot failures. This prevents a slow or failing .openinspect/setup.sh from triggering createSandbox on every boot cycle. As an immediate workaround, set a Modal budget cap/alerts so failed sandbox creation stops the loop, then optimize setup.sh—move expensive installs into a prebuilt image or cache dependencies so boot completes before the connect watchdog expires. Verify in the logs that setup.complete follows setup.start quickly and has exit_code: 0.