Build a GitHub agent with eve
eve is Vercel's filesystem-first agent framework: an agent is a folder with instructions, a model config, and tools. With @github-tools/sdk/eve, that folder becomes a complete GitHub agent in 3 files — all 42 tools registered from a single file, with durable human-in-the-loop approval that actually pauses the session until a person approves.
@github-tools/sdk/eve directly (below), or mounting them as an eve extension via @github-tools/eve-extension. Both are supported today — the extension is the direction this integration is moving toward and will become the recommended way to add GitHub tools to an eve agent.Add GitHub tools to an eve agent
The whole agent
Three files. That's the entire thing:
You are a GitHub assistant. Use the GitHub tools to inspect repos, PRs, and issues.
Ask before merging or closing anything destructive.
import { defineAgent } from 'eve'
export default defineAgent({
model: 'anthropic/claude-sonnet-5',
})
import { createGithubTools } from '@github-tools/sdk/eve'
export default createGithubTools({
preset: 'maintainer',
})
Run it:
npx eve dev
You now have a GitHub agent that can read repos, review PRs, triage issues, and manage CI — with every write operation gated behind durable approval by default. The full example lives in examples/eve-agent/ (or pnpm dev:eve-agent from the monorepo root).
Install
Add eve and ai v7 (required peer of eve v0.19+) alongside the SDK:
pnpm add @github-tools/sdk eve ai zod
npm install @github-tools/sdk eve ai zod
yarn add @github-tools/sdk eve ai zod
bun add @github-tools/sdk eve ai zod
Ensure ai resolves to v7 — eve v0.19+ is not compatible with AI SDK v6. You still need GITHUB_TOKEN (or pass token explicitly). See Installation.
For Vercel Connect, use connectGithubTools from @github-tools/sdk/connect/eve — it mints the token lazily. Do not top-level await getToken(...) in agent/tools/ modules.
One file, all tools
createGithubTools() returns a defineDynamic value: eve resolves the tool set on session.started, so a single default export registers every tool. Scope and gate as needed:
import { createGithubTools } from '@github-tools/sdk/eve'
export default createGithubTools({
preset: ['code-review', 'issue-triage'],
requireApproval: {
mergePullRequest: true,
createIssue: 'once',
addPullRequestComment: false,
createOrUpdateFile: ({ toolInput }) => toolInput?.owner !== 'vercel-labs',
},
})
Map keys become tool names — the model sees listPullRequests, createIssue, and so on (same names as the AI SDK package). There is no automatic file-slug prefix when returning a tool map.
Durable approval, done right
This is eve's headline advantage over the boolean needsApproval on the AI SDK and Workflow paths: approval pauses the session durably until a human responds, and policies are expressive:
| Value | Maps to | Behavior |
|---|---|---|
true / 'always' | always() | Require approval on every call |
false / 'never' | never() | Skip approval |
'once' | once() | Approve once per session, then auto-allow |
| predicate | custom Approval | Input-dependent gate (toolInput, session context) |
always() / once() / never() | passthrough | Import helpers from eve/tools/approval |
Default (no requireApproval): all write tools → always(). Unlisted write tools keep the always() fail-safe default. Read tools never require approval.
For durable HITL with the standard boolean/per-tool config, use durable Workflow agents with WorkflowAgent. See also Control write safety for the AI SDK surface.
Cherry-pick one tool per file
For the filesystem-native eve layout, import individual factories:
import { listPullRequests } from '@github-tools/sdk/eve'
export default listPullRequests()
Each factory returns a defineTool value. The filename slug becomes the tool name when exported alone.
Presets and options
All presets (code-review, issue-triage, repo-explorer, ci-ops, maintainer) work with createGithubTools. Options mirror the AI SDK surface:
| Option | Description |
|---|---|
token | GitHub PAT (defaults to GITHUB_TOKEN) |
preset | Single preset or array to merge |
requireApproval | Global, per-tool, or predicate approval (eve) |
overrides | Per-tool description, approval, toModelOutput, outputSchema |
author / committer / coAuthors | Commit attribution for file/merge tools |
High-volume read tools (listPullRequestFiles, getCommit, getFileContent) include conservative default toModelOutput projections — full payloads still reach channels; the model sees trimmed diffs/content.
Idempotency
eve replays completed steps but re-runs steps interrupted mid-execution:
| Tool | Idempotency |
|---|---|
createOrUpdateFile | Natural when content + sha unchanged |
closeIssue | Natural when already closed |
createBranch | Natural when branch exists at same SHA |
addIssueComment, createIssue, mergePullRequest, … | Not idempotent |
Gate non-idempotent writes behind always() or once() where replay safety matters.
eve vs AI SDK vs Workflow SDK
| AI SDK | eve | Workflow SDK | |
|---|---|---|---|
| Import | @github-tools/sdk | @github-tools/sdk/eve | @github-tools/sdk/workflow |
| Tool registration | createGithubTools() object | defineDynamic in agent/tools/ | createGithubTools() in workflow |
| Approval | boolean needsApproval | always / once / predicates | boolean needsApproval (durable pause) |
| Durability | in-process | eve session (filesystem-first) | "use workflow" steps |
Vercel Connect
Skip GITHUB_TOKEN entirely and mint the token from a Connect connector — connectGithubTools derives scopes from preset and fetches the token lazily inside each tool call:
import { defineAgent } from 'eve'
export default defineAgent({
model: 'anthropic/claude-sonnet-5',
// TODO(eve-connect-bundle): remove when eve externalizes transitive @vercel/connect
build: {
externalDependencies: ['@vercel/connect'],
},
})
import { connectGithubTools } from '@github-tools/sdk/connect/eve'
export default connectGithubTools('github/my-connector', {
preset: 'maintainer',
})
See Vercel Connect for the connector setup checklist and multi-tenant scoping.
eve extension
@github-tools/eve-extension packages the same tools as a mountable eve extension instead of importing @github-tools/sdk/eve directly into agent/tools/. This is the recommended direction going forward — the direct @github-tools/sdk/eve import above keeps working and isn't going away, but new agents should prefer the extension:
import githubExtension from '@github-tools/eve-extension'
export default githubExtension({
connector: 'github/my-connector', // or token: process.env.GITHUB_TOKEN
preset: 'code-review',
requireApproval: {
addPullRequestComment: ({ toolInput }) => toolInput?.owner !== 'vercel-labs',
},
})
code-review is used here (rather than maintainer) because it pairs cleanly with a Connect connector — maintainer and repo-explorer include gist tools, and GitHub only grants gist access to user access tokens, never the installation tokens Connect mints, so gist calls 403 over Connect (see Tokens & Auth). The requireApproval predicate above is also a real gate, not a no-op: write tools already require approval by always() by default, so { mergePullRequest: true } would change nothing — a predicate is what actually narrows or loosens the default.
Tools are exposed to the model as <namespace>__<toolName>, where <namespace> comes from the mount file's name — agent/extensions/github.ts yields github__listPullRequests, github__createIssue, and so on. Not yet published to npm; see examples/eve-extension-agent and packages/github-tools-eve-extension to build and consume it from the workspace.
Once published, this extension will become the primary, recommended way to add GitHub tools to an eve agent; @github-tools/sdk/eve remains supported for direct imports.
External references
- How to build a GitHub agent with eve and GitHub tools
- eve documentation
- Dynamic capabilities (bundled with the
evepackage) - Human-in-the-loop