Manage your Sanity project without leaving Slack. You @mention the bot, and it queries and edits content with GROQ, inspects and shapes schemas, creates and edits drafts, manages releases, and drafts long-form pieces into Notion, all as you. Destructive operations like patching, publishing, and deploying schemas pause for your approval before they run, rendered as buttons in the thread.
The bot is built on eve, a filesystem-first framework for durable backend agents from Vercel. Every credential is brokered at runtime: Slack, Sanity, and Notion authenticate through Vercel Connect, and Vercel Blob and AI Gateway authenticate with your project's OpenID Connect (OIDC) token. There are no API keys or client secrets to set.
Deploy the template now, or read on for a deeper look at how it all works.
Sanity Copilot eve Template
A Slack bot that manages your Sanity project: GROQ queries, schema work, drafts, releases, and Notion drafting, with approvals for destructive operations.
Copy link to headingQuick start with an AI coding agent
If you're working with an AI coding agent like Claude Code or Cursor, you can use this prompt to have it help you with building your agent:
I want to build a Slack agent with the eve framework, using the Sanity copilot template. Read the setup instructions at https://agent-resources.dev/sanity-copilot-eve-template.md and follow them. They will cover deploying the template, building with eve, how everything works overall, and more.
Copy link to headingVercel Plugin
The Vercel Plugin turns your AI coding agent (e.g., OpenAI Codex, Claude Code, or Cursor) into a Vercel expert. It adds skills, slash commands, and current knowledge of the tools this template uses, including Vercel Connect, Vercel Blob, and AI Gateway. The plugin is optional; it isn't required to use eve or to follow this guide.
Copy link to headingSetup and deployment
Copy link to headingWhat you need before deploying
You need four accounts to deploy and run the copilot:
- A Vercel account.
- A Slack workspace where you can install an app.
- A Sanity project the copilot will manage.
- A Notion workspace for source material and drafts.
For local development, you also need Node.js 24 or newer, pnpm, and the Vercel CLI.
Copy link to headingDeploy to Vercel
Deploying with the one-click flow provisions everything the agent needs:
- A Slack connector, with its event trigger pointed at the route eve serves (
/eve/v1/slack) and the connector UID stored inSLACK_CONNECTOR. - A Sanity connector, with its UID stored in
SANITY_CONNECTOR. - A Notion connector, with its UID stored in
NOTION_CONNECTOR. - A public Vercel Blob store for the asset and user-preference tools.
When the deployment finishes, your agent is live and wired up. All that's left is to invite it to a Slack channel and have team members sign in to Sanity and Notion on first use.
Copy link to headingConnect Slack
The one-click flow installs the Slack app and points its event trigger at the route the agent serves (/eve/v1/slack), so there's nothing to wire up. After the deploy finishes, invite the bot to a channel in your workspace and @mention it to start a thread.
You can't test the Slack surface locally, because Slack forwards events to Vercel Connect, which verifies them and delivers them to your deployed project rather than to a local URL. Test the inbound Slack path against a preview or production deployment; everything else runs in the local dev terminal UI.
Copy link to headingAuthorize Sanity and Notion
The first time a team member asks the agent to touch Sanity or Notion, Vercel Connect will prompt them to sign in to that service in the browser. That sign-in is per user, so the agent acts as the signed-in person with their own permissions. A query runs against the datasets they can see, a draft is created by the real author, and there's no shared integration token to manage.
Because the connections are user-scoped, the agent reads and writes only what each person can already access. On top of that, destructive operations are gated on approval: the call pauses and renders approve/deny buttons in Slack before it runs.
| Connection | Operations that require approval |
|---|---|
| Sanity | Patching documents, publishing, unpublishing, discarding drafts, discarding versions, updating datasets, deploying schemas, deploying the Studio |
| Notion | Updating pages, moving pages, updating data sources, updating views |
| Blob tools | Deleting an asset, clearing a user's preferences |
Reads, GROQ queries, and draft creation flow freely. Creating a new Notion page is also not gated, because drafting into Notion is the normal flow.
Copy link to headingLocal development
You can run the agent locally in a terminal UI (TUI).
First, clone the repository that the one-click flow created under your GitHub account:
Then link it to the Vercel project you deployed and pull the environment variables:
vercel env pull writes a short-lived OIDC token into .env.local, which authenticates Vercel Blob and the model locally. Re-run it when the token expires. Then start the dev server and link a model provider with /model in the TUI:
In the TUI you can exercise the Sanity, Notion, Blob, and preference flows directly.
The template ships a dev-only auth shim (agent/channels/eve.ts) that presents your local session as a signed-in user, so the user-scoped Sanity and Notion connections work from the TUI without affecting production.
Push changes to production with eve deploy:
The eve deploy command is the same thing as running vercel deploy --prod.
The template also uses Ultracite (a Biome preset) for linting and formatting.
pnpm check checks formatting and lint rules, pnpm fix auto-fixes what it can, and pnpm validate runs lint, typecheck, and eve's discovery diagnostics together.
Copy link to headingHow the Sanity copilot works
The agent runs one loop: load the user's preferences and the right skill, ground the work in the real project, work in drafts, and run anything destructive past you first.
- @mention in Slack. A team member @mentions the bot or DMs it with a Sanity task, like a GROQ query, a new draft, or a release to manage. The Slack channel turns the message into a session and threads every reply.
- Load preferences and the matching skill. The agent calls
get_user_preferencesto apply that user's standing notes (a preferred dataset, tone, or workflow), then loads the skill that matches the task. Skills load on demand, so a skill enters the agent’s context only when the task calls for it. - Ground everything in the real project. The agent inspects the schema before creating or editing documents and queries with GROQ to see what actually exists, so it never invents document IDs, field names, or content. It pulls briefs and source material from Notion when you point it there. When a piece needs an outside fact, it delegates to the
researchersubagent, which runs web research in a fresh session and returns cited findings. - Work in drafts. Sanity content is created and edited as drafts. The agent treats content as ready to publish only when you explicitly say so, and the publish call itself still pauses for an approval button.
- Draft long pieces into Notion. When the destination is Notion, or the piece is long-form, the agent creates it as a new Notion page and replies with the link. A page is easier to read than a long in-thread message.
- Get a fresh-eyes review. On the final draft of a piece, the agent delegates to the
reviewersubagent, which judges the draft against a writing-quality rubric in a fresh session and returns a verdict with concrete issues. The agent addresses them before proposing the draft in the thread.
Along the way, the Vercel Blob tools store any durable files (e.g., exported drafts or images) and return URLs that the agent can reuse, and the preference tools persist each user's standing preferences across sessions.
Every credential in this loop is brokered at runtime:
- Slack, Sanity, and Notion through Vercel Connect
- Vercel Blob and AI Gateway through the OIDC token
No static keys live in the code.
Copy link to headingCode walkthrough
The whole agent is defined under agent/, and eve discovers each capability from the filesystem. A tool's, connection's, or channel's filename is its name, so there's no central registry to wire up. These are the pieces that matter.
Copy link to headingThe Slack surface
The Slack surface is one file:
connectSlackCredentials reads the connector UID from SLACK_CONNECTOR and lets Vercel Connect handle token rotation, multi-workspace tenancy, and webhook verification, so none of that lives in your code. Because the file is slack.ts, eve registers it as the slack channel and serves its events at /eve/v1/slack. Each human Slack user becomes a per-user principal, allowing the agent to act in Sanity and Notion as that specific person and to link their preferences to them.
Copy link to headingManaging Sanity
Sanity is a Model Context Protocol (MCP) connection. This connection is the copilot's core: GROQ queries, schema inspection, drafts, releases, and media generation.
Two details carry the weight here:
-
connect()makes it user-scoped: each person signs in once, the per-user token is resolved before every call, and the model never sees the URL or the token. - The
approvalpolicy gates the destructive tools: any call whose name matches an entry inAPPROVAL_REQUIRED_TOOLSpauses for an approve/deny decision, rendered as a Slack button, before it runs. Reads and draft creation flow freely; patching, publishing, and deploying wait for a human.
The model discovers the available Sanity tools at runtime through the built-in connection__search and calls them by their qualified name, such as sanity__patch_documents. The description field is the hint the model uses to decide when to reach for Sanity.
Copy link to headingReading and writing Notion
Notion follows the same pattern:
Page updates, moves, and data-source changes are gated; page creation (notion-create-pages) is left ungated on purpose, because drafting a new page into Notion is the copilot's normal flow. To gate creation too, add it to the list.
Copy link to headingSanity expertise as skills
Seven skills sit under agent/skills/, each a folder with a SKILL.md and reference files. Six of them come from Sanity's Agent Toolkit, and the sanity-best-practices references are the same canonical content behind the Sanity MCP server's rules tools:
sanity-best-practices: schemas, GROQ, TypeGen, Visual Editing, Sanity Functions, Blueprints, and framework integrations from Next.js to Hydrogen.content-modeling-best-practices: designing and refactoring content types, references versus embedding, taxonomies.portable-text-conversionandportable-text-serialization: moving rich text into Portable Text and rendering it back out to React, HTML, Markdown, and other formats.seo-aeo-best-practices: metadata, structured data, EEAT, and more.content-experimentation-best-practices: A/B tests and variants.writing-quality: prose rules for anything written for humans, from AI-tells to avoid through web-content specs.
The description in each skill's frontmatter is a routing hint, not a label. eve exposes it to the model alongside a built-in load_skill tool, and the model loads a skill only when a request matches the description. Loading a skill only adds instructions to the turn, it never adds a new action the agent can take. The instructions tell the agent to load the matching skill before acting, not after something goes wrong.
Copy link to headingFresh-context subagents
Two subagents live under agent/subagents/, and each runs in its own fresh child session: it inherits none of the root's skills, connections, or tools, so the root packs everything the subagent needs into the call message, and the result comes back as a structured tool result.
The researcher handles outside facts. When a piece needs a statistic, a competitor detail, or a claim verified, the root delegates rather than reaching from memory. The researcher uses the framework's web_search and web_fetch tools and returns an outputSchema-typed result: findings where every claim carries at least one real source URL and a confidence level, plus gaps for anything it couldn't verify. The root weaves in only cited findings and surfaces the gaps to you instead of papering over them.
The reviewer handles the final pass. On a finished draft, the root sends the full text plus any voice or audience context, and the reviewer judges it against the writing-quality rubric with fresh eyes, returning a ready or revise verdict with one entry per concrete issue: the severity, the rule broken, the offending quote, and a suggested fix. A reviewer that never saw the sources or the drafting reasoning catches the AI-tells and bloated wording that self-review reads right past.
Two implementation details make the reviewer self-sufficient: it carries its own copy of the writing-quality skill under agent/subagents/reviewer/skills/, and it declares its own sandbox.ts, because a subagent's sandbox doesn't inherit from the root and it needs one to read the skill's reference files.
Copy link to headingRemembering each user, agent/tools/*_user_preferences.ts
Three tools give the copilot durable, per-user memory backed by Vercel Blob: get_user_preferences, save_user_preferences, and clear_user_preferences.
When a user states a standing preference ("our production dataset is prod", "always write titles in sentence case"), the agent loads the current document, merges the note in, and saves the full result, so the file stays curated rather than append-only.
The isolation model is the part worth reading:
The Blob key is derived entirely from the framework-resolved principal (ctx.session.auth.current), never from model input, so a session can only ever read or write its own user's file, and the principal id is hashed so the stored path carries no raw identifier. The general asset tools refuse the reserved user-preferences/ prefix, so they can't be used as a side channel to another user's preferences. clear_user_preferences is irreversible and gated on approval.
Copy link to headingStoring assets in Vercel Blob
Five tools store and manage files in Vercel Blob: upload_asset, list_assets, get_asset_info, download_asset, and delete_asset.
Two of the tools are hardened for an agent that runs on model output. download_asset fetches only URLs whose host ends in .blob.vercel-storage.com, which stops the model from using it to reach arbitrary internal addresses. delete_asset is irreversible, so it's gated on human approval:
always() from eve/tools/approval displays a card with approve and deny buttons in Slack before the deletion runs, so the model can never delete a file on its own.
Copy link to headingBehavior and model
instructions.md is the agent's always-on system prompt.
eve prepends it to every model call, and it encodes the loop from the section above: load preferences and the right skill, ground work in the real project with GROQ and schema reads, work in drafts and publish only on approval, draft long pieces into Notion, and get a fresh-eyes review before proposing a draft.
The rule worth calling out is the drafts contract: the agent never publishes, patches, or deploys speculatively, and the approval gates in the connections back that promise up at the tool layer.
The runtime configuration adds session management on top of the model:
Conversation history is compacted once it reaches 75% of the context window, and the per-session token limits cap runaway sessions; raise them if long content work hits the caps. The model id routes through the Vercel AI Gateway, which authenticates through the linked project, so there's no provider API key to set.
To use a different model, change this string (any Gateway model ID works) or run /model in the dev TUI. Each subagent sets its own model in its agent.ts.
Copy link to headingCustomize the copilot
The agent picks up changes to these files as you save:
- Change the approval gates. Edit the
APPROVAL_REQUIRED_TOOLSlists inagent/connections/sanity.tsandagent/connections/notion.tsto change which MCP tools pause for a human decision. For example, addnotion-create-pagesto gate Notion page creation too. - Change the behavior. Edit
agent/instructions.md; it describes the whole workflow the agent follows. - Edit or add skills. Each folder under
agent/skills/holds aSKILL.md(give it adescriptionthat names when to use it) plus reference files, and eve discovers new skills automatically. The reviewer keeps its own copy ofwriting-qualityunderagent/subagents/reviewer/skills/, so keep the two in step when you edit either. - Add or change tools. Files under
agent/tools/are tools, and the filename is the tool name.
To add another chat platform, add a channel file under agent/channels/ and a matching Vercel Connect connector. eve discovers the channel from the file the same way it discovers slack.ts, and Vercel Connect brokers the new platform's credentials, so the workflow, skills, tools, and connections stay unchanged.
Copy link to headingPair it with the content agent template
The eve content agent template is a full content assistant: per-surface style skills, a house voice, and a style lint. Instead of merging all of that into this copilot, you can deploy it as its own agent and let the copilot delegate to it through eve's remote agents feature.
Deploy the content agent template as its own Vercel project, then add a remote subagent file to this repo. The filename is the tool name, and vercelOidc() handles deployment-to-deployment auth with no shared secret:
Set CONTENT_AGENT_URL in this project's environment variables and mention the new subagent in agent/instructions.md so the copilot knows when to hand off.
The remote agent runs in its own deployment with its own skills and connections, and it never sees this copilot's conversation history, so the copilot packs everything the writer needs into the call message. The result comes back as a normal tool result, the same shape as the local researcher and reviewer subagents.
Copy link to headingTroubleshooting
Each item below lists a symptom, its cause, and the fix.
Copy link to heading@mentions don't get a response
Symptom: You @mention the bot in Slack, but it doesn't reply.
Cause: The bot isn't in the channel yet, or the deployment that registered the Slack trigger hasn't finished.
Fix: Invite the bot to the channel and confirm the deployment succeeded, then @mention it again. The Deploy button points Slack's events at the route the agent serves (/eve/v1/slack), so the path is already correct.
Copy link to headingConnectors work locally but fail in production
Symptom: The agent works in the dev TUI, but the deployed bot can't reach Slack, Sanity, or Notion.
Cause: .env.local is local-only; the deployed app reads the Vercel project's environment.
Fix: Set SLACK_CONNECTOR, SANITY_CONNECTOR, and NOTION_CONNECTOR in the project's production environment. The Deploy button sets them for you.
Copy link to headingA Sanity edit pauses and nothing happens
Symptom: A patch, publish, or schema deploy appears to stall.
Cause: The call is gated on approval and is waiting for a decision. In Slack it renders as approve/deny buttons; in the dev TUI it renders as an approval prompt.
Fix: Approve or deny the pending call. To change which tools are gated, edit the APPROVAL_REQUIRED_TOOLS lists in agent/connections/sanity.ts and agent/connections/notion.ts.
Copy link to headingThe bot sits on "Working…"
Symptom: A multi-step turn shows "Working…" with no visible progress.
Cause: The dev TUI hides logs by default, so a long but legitimate turn looks stalled.
Fix: Show logs with npx eve dev --logs all, or run /loglevel all in the session.
Copy link to headingRelated resources
- Run a marketing team from Slack with eve
- Sanity copilot eve template
- eve documentation
- Vercel Connect and Vercel Connect CLI
- Sanity documentation and the Sanity Agent Toolkit
- Vercel Blob
- Vercel Sandbox
- Vercel AI Gateway
- Vercel OIDC
- eve content agent template
eve Software Factory Template
Four coding stations behind one orchestrator: triage, planning against a live checkout of your repo, implementation verified with your own checks, and independent review.