Mains foragents.
Run AI coding agents in secure workspaces. Inspect changes, collaborate with agents, and ship with confidence.
Built for agent workflows
Run agents in isolated workspaces
Spin up Git-backed workspaces linked to your repos. Run AI coding agents like Claude Code, Copilot or Codex in secure, sandboxed environments.
Review every change before committing
Browse files, inspect diffs, and track changes across tabs. See exactly what the agent modified before you commit or open a pull request.
Link tasks from your tools
Connect issues from GitHub, Gitlab, Linear, Jira, and Asana directly to a workspace. Give agents the right context to start working immediately.
Review every change before it ships
Inspect diffs, catch issues with inline annotations, and approve with confidence — before any code reaches your main branch.
export async function dispatchRun(request: DispatchRunRequest): Promise<DispatchRunResult> { const runId = generateRunId(); - // Load provider- const provider = await providersRepo.findById(request.accountId);+ // 1. Load and validate provider+ const provider = await providersRepo.findById(request.providerId); if (!provider) {- throw new Error(`Provider not found`);+ throw new Error(`Provider "${request.providerId}" not found`);+ }+ if (!provider.isEnabled) {+ throw new Error(`Provider "${provider.displayName}" is not enabled`);+ }+ if (!isSupportedWorkProvider(provider.id)) {+ throw new Error(`Provider "${provider.id}" is not a supported work provider`);`isSupportedWorkProvider` validates the provider ID but doesn't verify `provider.kind === "agent_runtime"`. A provider could pass the ID check but have an incompatible kind, causing unexpected behavior in the adapter.
Suggestion: Add `if (provider.kind !== "agent_runtime") throw new Error(...)` before the `isSupportedWorkProvider` check
} let result: WorkRunResult; try {- result = await adapter.startRun(adapterRequest);+ result = await adapter.startRun(adapterRequest, async (event) => {+ await writeback.handleEvent(event);Passing the async callback to `adapter.startRun` streams each event through `writeback.handleEvent`, persisting tool calls and messages as they arrive instead of batching at the end. This is a solid pattern for long-running agent sessions.
+ }); - const finalStatus = result.status === "succeeded" ? "succeeded" : "failed";+ const finalStatus: RunStatus =+ result.status === "succeeded"+ ? "succeeded"+ : result.status === "canceled"+ ? "canceled"The ternary maps `result.status === "canceled"` to `"canceled"`, but `WorkRunResult` may not always emit a canceled status — some adapters resolve with `"failed"` and a cancellation message instead, causing user-canceled runs to show as failed.
Suggestion: Check `result.canceledByUser` flag or inspect `result.summary` for cancellation signals as a fallback
+ : "failed"; await runsService.updateRun(runId, { status: finalStatus, endedAt: new Date(),+ lastError: result.status === "failed" ? result.summary : undefined, });Turn Sentry signals into pull requests
Connect your Sentry project and let Mains prioritize unresolved issues, analyze stack traces, and generate fix PRs — automatically.
Block risky dependencies before they land
Mains checks every package install against Socket.dev before it executes — across npm, pip, cargo, go, and gems. Malware, typosquats, and vulnerable packages are denied automatically.
Full observability for every agent run
Monitor agent sessions, model costs, and tool usage across all your workspaces — so you always know where time and money are going.
Powerful integrations
Powered by MCP servers and APIs. Mains connects to your tools natively, so your agents can read, write, and act across services without leaving the app.




















Mains for AI coding agents
Run autonomous agents in secure workspaces, connect your repositories, and move from task to reviewed pull request without leaving Mains.




