GitHub Tools
Frameworks

Build GitHub bots with Chat SDK

Connect GitHub tools to Chat SDK for durable GitHub, Slack, and Discord bots — a complete PR review agent in about 60 lines of code.

Chat SDK is a multi-platform agent framework: one agent definition, adapters for GitHub, Slack, and Discord. Pair it with @github-tools/sdk and Vercel Workflow and you get a durable platform bot — @mention it on a PR, it reviews the changes and answers follow-ups, surviving crashes and timeouts along the way.

Build a durable GitHub PR review bot

How it works

When someone @mentions the bot on a PR, the GitHub adapter receives the webhook, the agent reacts, and a durable workflow runs the review — reading files, commits, and diffs with GitHub tools — then posts the result as a comment. Follow-up messages resume the same workflow via a hook.

@my-agent review this PR
        │
        ▼
   Chat SDK receives webhook
        │
        ▼
   Reaction added ──► starts durable workflow
        │
        ▼
   Agent reads PR (files, diffs, commits)
        │
        ▼
   Posts structured review comment
        │
        ▼
   Listens for follow-up messages

The complete implementation is about 60 lines across two files, in examples/pr-review-agent/.

Install

pnpm add @github-tools/sdk chat @chat-adapter/github @chat-adapter/state-memory workflow ai zod

The agent

Chat SDK handles webhooks, thread state, and @mention detection. onNewMention starts a durable workflow; onSubscribedMessage resumes it:

server/lib/agent.ts
import { Chat, emoji, type Message, type Thread } from 'chat'
import { createGitHubAdapter } from '@chat-adapter/github'
import { createMemoryState } from '@chat-adapter/state-memory'
import { resumeHook, start } from 'workflow/api'
import { reviewWorkflow } from '../workflows/review'

export interface ThreadState { runId?: string }
export type ChatTurnPayload = { text: string }

const adapters = { github: createGitHubAdapter() }

export const agent = new Chat<typeof adapters, ThreadState>({
  userName: process.env.GITHUB_AGENT_USERNAME || 'pr-review-agent',
  adapters,
  state: createMemoryState(),
}).registerSingleton()

agent.onNewMention(async (thread: Thread<ThreadState>, message: Message) => {
  const sent = thread.createSentMessageFromMessage(message)
  await sent.addReaction(emoji.eyes)

  await thread.subscribe()
  const run = await start(reviewWorkflow, [message.text])
  await thread.setState({ runId: run.runId })
})

agent.onSubscribedMessage(async (thread: Thread<ThreadState>, message: Message) => {
  const state = await thread.state
  if (!state?.runId) return
  await resumeHook<ChatTurnPayload>(state.runId, { text: message.text })
})

The workflow

The review itself is a "use workflow" function. Each turn runs createGithubAgent with the code-review preset inside a "use step" — durable, retryable, observable. A createHook keeps the workflow alive for follow-up messages:

server/workflows/review.ts
import { createHook, getWorkflowMetadata } from 'workflow'
import { createGithubAgent } from '@github-tools/sdk'
import type { ChatTurnPayload } from '../lib/agent'

async function runAgentTurn(prompt: string) {
  'use step'
  const agent = createGithubAgent({
    model: 'anthropic/claude-sonnet-4.6',
    preset: 'code-review',
    requireApproval: false,
    additionalInstructions: 'Always post your review as a comment on the PR.',
  })
  const { text } = await agent.generate({ prompt })
  return text
}

export async function reviewWorkflow(prompt: string) {
  'use workflow'
  const { workflowRunId } = getWorkflowMetadata()

  using hook = createHook<ChatTurnPayload>({ token: workflowRunId })

  await runAgentTurn(prompt)

  for await (const event of hook) {
    await runAgentTurn(event.text)
  }
}

The webhook route

One route hands GitHub webhooks to the adapter:

server/routes/webhooks/github.post.ts
import { defineHandler } from 'nitro/h3'
import { agent } from '../../lib/agent'

export default defineHandler(async (event) => {
  const handler = agent.webhooks.github
  if (!handler) {
    return new Response('GitHub adapter not configured', { status: 404 })
  }
  return handler(event.req, {
    waitUntil: (task: Promise<unknown>) => event.waitUntil(task),
  })
})

Configure the webhook in your repository settings (Settings → Webhooks) with the route URL, a shared secret (GITHUB_WEBHOOK_SECRET), and the Issue comments and Pull request review comments events.

Use a different GitHub account for the agent than the one commenting — Chat SDK filters self-messages to prevent loops. Set GITHUB_AGENT_USERNAME to the bot account's username.

Add observability with evlog

The example wires evlog into the agent turn for token usage, tool calls, cost, and timing:

with-evlog.ts
import { createLogger } from 'evlog'
import { createAILogger } from 'evlog/ai'

async function runAgentTurn(prompt: string) {
  'use step'
  const log = createLogger()
  const ai = createAILogger(log, { toolInputs: { maxLength: 500 } })

  const agent = createGithubAgent({
    model: ai.wrap('anthropic/claude-sonnet-4.6'),
    preset: 'code-review',
    requireApproval: false,
  })

  const { text } = await agent.generate({ prompt })
  log.emit()
  return text
}

Beyond GitHub

Chat SDK adapters share the same agent definition — swap or add @chat-adapter/slack or @chat-adapter/discord and the same GitHub-tools-powered agent answers in Slack channels or Discord threads.

External references