Skip to content

Commit 525d02e

Browse files
Gavin Williamsclaude
andcommitted
feat(web): customisable AI code review agent configs
Adds a per-org/connection/repo agent config system for the code review agent. Database: - New `AgentConfig` model with `AgentType`, `AgentScope`, and `PromptMode` enums - Junction tables `AgentConfigToRepo` and `AgentConfigToConnection` for scope targeting - Migration: `20260421203635_add_agent_config` Review agent: - Priority resolver: REPO > CONNECTION > ORG > system defaults - `resolveRules()`: APPEND or REPLACE custom prompt against built-in rules - `parseAgentConfigSettings()`: validates per-config JSON settings - `webhookUtils`: `isAutoReviewEnabled` and `getReviewCommand` respect per-config overrides - `invokeDiffReviewLlm`: accepts per-config model override - `fetchContextFile`: fetches repo-level instruction files (e.g. AGENTS.md) once per PR and injects as context for every diff hunk; supports comma/space-separated list of paths; missing files silently ignored - Webhook route wires resolved config through to all processing functions API: - `GET/POST /api/agents` — list and create agent configs - `GET/PATCH/DELETE /api/agents/[agentId]` — manage individual configs - Full OpenAPI registration with request/response schemas UI: - Agents list page shows all configs with scope/type/enabled badges - Create and edit forms with scope picker, repo/connection checkbox lists with filter inputs, custom prompt with APPEND/REPLACE mode, and settings overrides (model dropdown, review command, auto-review, context files) Tests: - 60 unit tests across resolveAgentConfig, app, webhookUtils, and the agents API route Docs: - Updated review-agent.mdx with agent configs, custom prompt, and context files sections - Env var table updated to note per-config override support Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent f49932d commit 525d02e

25 files changed

Lines changed: 3214 additions & 90 deletions

File tree

docs/api-reference/sourcebot-public.openapi.json

Lines changed: 656 additions & 0 deletions
Large diffs are not rendered by default.

docs/docs/features/agents/review-agent.mdx

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,36 @@ By default, the agent does not review PRs and MRs automatically. To enable autom
137137

138138
You can also trigger a review manually by commenting `/review` on any PR or MR. To use a different command, set `REVIEW_AGENT_REVIEW_COMMAND` to your preferred value (without the leading slash).
139139

140+
# Agent configs
141+
142+
Agent configs let you customise how the review agent behaves per repository, connection, or your whole org. You can override the model, review command, auto-review behaviour, custom prompt, and context files — all without changing environment variables.
143+
144+
Configs are managed on the **Agents** page in the Sourcebot UI. Each config has a **scope**:
145+
146+
- **Repo** — applies to specific repositories (highest priority)
147+
- **Connection** — applies to all repos in a specific connection
148+
- **Org** — applies to all repositories in your org (lowest priority, catch-all)
149+
150+
When a PR or MR arrives, Sourcebot selects the most specific matching config. If no config exists, the agent falls back to global environment variable defaults.
151+
152+
## Custom prompt
153+
154+
Each config can include a custom prompt. Two modes are available:
155+
156+
- **Append** (default) — your instructions are added after the built-in review rules.
157+
- **Replace** — your instructions entirely replace the built-in rules. Use this when you want full control over what the agent looks for.
158+
159+
## Context files
160+
161+
You can configure one or more repository files to be fetched at review time and injected as additional context for the model. This is useful for encoding project-specific conventions that the model should be aware of when reviewing diffs — for example, preferred error handling patterns, style rules, or areas of the codebase that need extra scrutiny.
162+
163+
Set **Context files** in the agent config form to a comma or space separated list of paths relative to the repository root (e.g. `AGENTS.md .sourcebot/review.md`). Files that do not exist in the repository are silently ignored. The files are fetched once per PR from the head commit and included in the context for every diff hunk.
164+
140165
# Environment variable reference
141166

142167
| Variable | Default | Description |
143168
|---|---|---|
144-
| `REVIEW_AGENT_AUTO_REVIEW_ENABLED` | `false` | Automatically review new and updated PRs/MRs |
145-
| `REVIEW_AGENT_REVIEW_COMMAND` | `review` | Comment command that triggers a manual review (without the `/`) |
146-
| `REVIEW_AGENT_MODEL` | first configured model | `displayName` of the language model to use for reviews |
147-
| `REVIEW_AGENT_LOGGING_ENABLED` | unset | Write prompt and response logs to disk for debugging |
169+
| `REVIEW_AGENT_AUTO_REVIEW_ENABLED` | `false` | Automatically review new and updated PRs/MRs. Can be overridden per agent config. |
170+
| `REVIEW_AGENT_REVIEW_COMMAND` | `review` | Comment command that triggers a manual review (without the `/`). Can be overridden per agent config. |
171+
| `REVIEW_AGENT_MODEL` | first configured model | `displayName` of the language model to use for reviews. Can be overridden per agent config. |
172+
| `REVIEW_AGENT_LOGGING_ENABLED` | unset | Write prompt and response logs to disk for debugging. |
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
-- CreateEnum
2+
CREATE TYPE "AgentType" AS ENUM ('CODE_REVIEW');
3+
4+
-- CreateEnum
5+
CREATE TYPE "AgentScope" AS ENUM ('ORG', 'CONNECTION', 'REPO');
6+
7+
-- CreateEnum
8+
CREATE TYPE "PromptMode" AS ENUM ('REPLACE', 'APPEND');
9+
10+
-- CreateTable
11+
CREATE TABLE "AgentConfig" (
12+
"id" TEXT NOT NULL,
13+
"orgId" INTEGER NOT NULL,
14+
"name" TEXT NOT NULL,
15+
"description" TEXT,
16+
"type" "AgentType" NOT NULL,
17+
"enabled" BOOLEAN NOT NULL DEFAULT true,
18+
"prompt" TEXT,
19+
"promptMode" "PromptMode" NOT NULL DEFAULT 'APPEND',
20+
"scope" "AgentScope" NOT NULL,
21+
"settings" JSONB NOT NULL DEFAULT '{}',
22+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
23+
"updatedAt" TIMESTAMP(3) NOT NULL,
24+
25+
CONSTRAINT "AgentConfig_pkey" PRIMARY KEY ("id")
26+
);
27+
28+
-- CreateTable
29+
CREATE TABLE "AgentConfigToRepo" (
30+
"agentConfigId" TEXT NOT NULL,
31+
"repoId" INTEGER NOT NULL,
32+
33+
CONSTRAINT "AgentConfigToRepo_pkey" PRIMARY KEY ("agentConfigId","repoId")
34+
);
35+
36+
-- CreateTable
37+
CREATE TABLE "AgentConfigToConnection" (
38+
"agentConfigId" TEXT NOT NULL,
39+
"connectionId" INTEGER NOT NULL,
40+
41+
CONSTRAINT "AgentConfigToConnection_pkey" PRIMARY KEY ("agentConfigId","connectionId")
42+
);
43+
44+
-- CreateIndex
45+
CREATE INDEX "AgentConfig_orgId_type_enabled_idx" ON "AgentConfig"("orgId", "type", "enabled");
46+
47+
-- CreateIndex
48+
CREATE UNIQUE INDEX "AgentConfig_orgId_name_key" ON "AgentConfig"("orgId", "name");
49+
50+
-- AddForeignKey
51+
ALTER TABLE "AgentConfig" ADD CONSTRAINT "AgentConfig_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "Org"("id") ON DELETE CASCADE ON UPDATE CASCADE;
52+
53+
-- AddForeignKey
54+
ALTER TABLE "AgentConfigToRepo" ADD CONSTRAINT "AgentConfigToRepo_agentConfigId_fkey" FOREIGN KEY ("agentConfigId") REFERENCES "AgentConfig"("id") ON DELETE CASCADE ON UPDATE CASCADE;
55+
56+
-- AddForeignKey
57+
ALTER TABLE "AgentConfigToRepo" ADD CONSTRAINT "AgentConfigToRepo_repoId_fkey" FOREIGN KEY ("repoId") REFERENCES "Repo"("id") ON DELETE CASCADE ON UPDATE CASCADE;
58+
59+
-- AddForeignKey
60+
ALTER TABLE "AgentConfigToConnection" ADD CONSTRAINT "AgentConfigToConnection_agentConfigId_fkey" FOREIGN KEY ("agentConfigId") REFERENCES "AgentConfig"("id") ON DELETE CASCADE ON UPDATE CASCADE;
61+
62+
-- AddForeignKey
63+
ALTER TABLE "AgentConfigToConnection" ADD CONSTRAINT "AgentConfigToConnection_connectionId_fkey" FOREIGN KEY ("connectionId") REFERENCES "Connection"("id") ON DELETE CASCADE ON UPDATE CASCADE;

packages/db/prisma/schema.prisma

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,28 @@ enum CodeHostType {
3939
azuredevops
4040
}
4141

42+
enum AgentType {
43+
CODE_REVIEW
44+
}
45+
46+
/// Determines which repositories an AgentConfig applies to.
47+
enum AgentScope {
48+
/// Applies to all repositories in the organization.
49+
ORG
50+
/// Applies to all repositories under specific connections.
51+
CONNECTION
52+
/// Applies to specific repositories only.
53+
REPO
54+
}
55+
56+
/// Controls how a custom prompt is combined with the built-in system rules.
57+
enum PromptMode {
58+
/// Custom prompt replaces the built-in rules entirely.
59+
REPLACE
60+
/// Custom prompt is appended after the built-in rules.
61+
APPEND
62+
}
63+
4264
model Repo {
4365
id Int @id @default(autoincrement())
4466
name String /// Full repo name, including the vcs hostname (ex. github.com/sourcebot-dev/sourcebot)
@@ -75,6 +97,8 @@ model Repo {
7597
7698
searchContexts SearchContext[]
7799
100+
agentConfigMappings AgentConfigToRepo[]
101+
78102
@@unique([external_id, external_codeHostUrl, orgId])
79103
@@index([orgId])
80104
@@index([indexedAt])
@@ -170,6 +194,8 @@ model Connection {
170194
/// When the connection was last synced successfully.
171195
syncedAt DateTime?
172196
197+
agentConfigMappings AgentConfigToConnection[]
198+
173199
/// Controls whether repository permissions are enforced for this connection.
174200
/// When `PERMISSION_SYNC_ENABLED` is false, this setting has no effect.
175201
/// Defaults to the value of `PERMISSION_SYNC_ENABLED`.
@@ -291,6 +317,8 @@ model Org {
291317
searchContexts SearchContext[]
292318
293319
chats Chat[]
320+
321+
agentConfigs AgentConfig[]
294322
}
295323

296324
enum OrgRole {
@@ -569,3 +597,64 @@ model OAuthToken {
569597
createdAt DateTime @default(now())
570598
lastUsedAt DateTime?
571599
}
600+
601+
/// Configures a customisable AI agent (e.g. code review) scoped to an org, connection, or specific repos.
602+
model AgentConfig {
603+
id String @id @default(cuid())
604+
605+
org Org @relation(fields: [orgId], references: [id], onDelete: Cascade)
606+
orgId Int
607+
608+
name String
609+
description String?
610+
611+
type AgentType
612+
enabled Boolean @default(true)
613+
614+
/// Custom prompt instructions. Null means the agent uses its built-in rules only.
615+
prompt String?
616+
617+
/// Controls whether the custom prompt replaces or appends to the built-in rules.
618+
promptMode PromptMode @default(APPEND)
619+
620+
/// Determines what this config is scoped to: the whole org, specific connections, or specific repos.
621+
scope AgentScope
622+
623+
/// Repo-level scope mappings (populated when scope = REPO).
624+
repos AgentConfigToRepo[]
625+
626+
/// Connection-level scope mappings (populated when scope = CONNECTION).
627+
connections AgentConfigToConnection[]
628+
629+
/// Extensible per-agent settings stored as JSON.
630+
/// Shape: { autoReviewEnabled?: boolean, reviewCommand?: string, model?: string }
631+
settings Json @default("{}")
632+
633+
createdAt DateTime @default(now())
634+
updatedAt DateTime @updatedAt
635+
636+
@@unique([orgId, name])
637+
@@index([orgId, type, enabled])
638+
}
639+
640+
/// Maps an AgentConfig to specific repositories (used when scope = REPO).
641+
model AgentConfigToRepo {
642+
agentConfig AgentConfig @relation(fields: [agentConfigId], references: [id], onDelete: Cascade)
643+
agentConfigId String
644+
645+
repo Repo @relation(fields: [repoId], references: [id], onDelete: Cascade)
646+
repoId Int
647+
648+
@@id([agentConfigId, repoId])
649+
}
650+
651+
/// Maps an AgentConfig to specific connections (used when scope = CONNECTION).
652+
model AgentConfigToConnection {
653+
agentConfig AgentConfig @relation(fields: [agentConfigId], references: [id], onDelete: Cascade)
654+
agentConfigId String
655+
656+
connection Connection @relation(fields: [connectionId], references: [id], onDelete: Cascade)
657+
connectionId Int
658+
659+
@@id([agentConfigId, connectionId])
660+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { authenticatedPage } from "@/middleware/authenticatedPage";
2+
import { NavigationMenu } from "@/app/(app)/components/navigationMenu";
3+
import { AgentConfigForm } from "../agentConfigForm";
4+
import { notFound } from "next/navigation";
5+
6+
type Props = {
7+
params: Promise<{ agentId: string }>;
8+
};
9+
10+
export default authenticatedPage(async ({ prisma, org }, { params }: Props) => {
11+
const { agentId } = await params;
12+
13+
const [config, connections, repos] = await Promise.all([
14+
prisma.agentConfig.findFirst({
15+
where: { id: agentId, orgId: org.id },
16+
include: {
17+
repos: { select: { repoId: true } },
18+
connections: { select: { connectionId: true } },
19+
},
20+
}),
21+
prisma.connection.findMany({
22+
where: { orgId: org.id },
23+
select: { id: true, name: true, connectionType: true },
24+
orderBy: { name: "asc" },
25+
}),
26+
prisma.repo.findMany({
27+
where: { orgId: org.id },
28+
select: { id: true, displayName: true, external_id: true, external_codeHostType: true },
29+
orderBy: { displayName: "asc" },
30+
}),
31+
]);
32+
33+
if (!config) {
34+
notFound();
35+
}
36+
37+
return (
38+
<div className="flex flex-col items-center overflow-hidden min-h-screen">
39+
<NavigationMenu />
40+
<div className="w-full max-w-3xl px-4 mt-12 mb-24">
41+
<h1 className="text-2xl font-semibold text-foreground mb-8">Edit agent config</h1>
42+
<AgentConfigForm
43+
initialValues={{
44+
id: config.id,
45+
name: config.name,
46+
description: config.description ?? "",
47+
type: config.type,
48+
enabled: config.enabled,
49+
prompt: config.prompt ?? "",
50+
promptMode: config.promptMode,
51+
scope: config.scope,
52+
repoIds: config.repos.map((r) => r.repoId),
53+
connectionIds: config.connections.map((c) => c.connectionId),
54+
settings: config.settings as Record<string, unknown>,
55+
}}
56+
connections={connections}
57+
repos={repos}
58+
/>
59+
</div>
60+
</div>
61+
);
62+
});

0 commit comments

Comments
 (0)