Skip to content
Docs

Run a marketing team from Slack with eve

Team of five marketing agents built on eve. The lead routes work to specialists that write long-form into Notion, queue social posts in Typefully, run email in Resend, and audit for SEO, with approvals for anything irreversible.

Ben SabicContent Engineer

Run a marketing team without leaving Slack. You bring work to a team lead: a launch to plan, posts to write, a page that isn't converting. The lead holds the shared picture of your product, routes the request to the specialist who does that kind of work, and hands back what they produced. The content marketer writes long-form into Notion, the social media coordinator queues drafts in Typefully, the email specialist builds and sends campaigns in Resend, and irreversible actions like sending a broadcast pause for your approval, rendered as buttons in the thread.

The team is built on eve, a filesystem-first framework for durable backend agents from Vercel. Notion, Resend, and Slack authenticate through Vercel Connect, and Vercel Blob and AI Gateway authenticate with your project's OpenID Connect (OIDC) token. The single static credential in the project is the Typefully API key.

Deploy the template now, or read on for a deeper look at how it all works.

Marketing Team eve Template

Five marketing specialists behind one Slack lead: positioning and brand context, long-form content in Notion, social drafts in Typefully, SEO audits, and email campaigns in Resend.

Deploy Template

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:

Agent Prompt

I want to build a team of marketing agents with the eve framework, using the marketing team template. Read the setup instructions at https://agent-resources.dev/marketing-team-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.

Terminal
npx plugins add vercel/vercel-plugin

Copy link to headingMeet the team

The lead routes each request to exactly one of five specialists.

SpecialistOwnsHands back
product-marketerPositioning, messaging, competitive alternatives, and the shared brand context documentThe brand context document itself
content-marketerLong-form: blog posts, landing pages, case studies, newsletters, docsA Notion page link
social-media-coordinatorShort-form for X, LinkedIn, Threads, Bluesky, and Mastodon, plus the Typefully queueDrafts in Typefully
seoPage and site audits, hierarchy and internal linking, JSON-LD schema, templated page setsRecommendations, long audits as artifacts
emailReworking existing copy to survive an inbox, then building, targeting, and sending in ResendA Resend campaign link

The dependency runs one way. The product marketer writes the brand context; every other specialist reads it at the start of each task. That makes it the one piece of shared state whose quality bounds everything else, which is why a single specialist owns authoring it.

A newsletter is the case where two specialists chain. The content marketer authors the prose, and the email specialist reworks it for the inbox and runs Resend. The lead runs the two calls in order and passes the first specialist's output into the second's brief.

Copy link to headingSetup and deployment

Copy link to headingWhat you need before deploying

You need five things to deploy and run the team:

  • A Vercel account.
  • A Slack workspace where you can install an app.
  • A Notion workspace for briefs and finished long-form pieces.
  • A Resend account with a domain you can verify, if you want the email specialist.
  • A Typefully workspace and its API key, if you want the social specialist.

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 most of what the team needs:

ProvisionedSets
Notion connectorNOTION_CONNECTOR
Resend connectorRESEND_CONNECTOR
Slack connector, with its trigger pointed at /eve/v1/slackSLACK_CONNECTOR
Public Vercel Blob store for all assetsBlob credentials
Prompt for your Typefully API keyTYPEFULLY_API_KEY

One step stays manual after the deploy finishes: verifying a Resend sending domain. It's covered below, and it's skippable if you don't use email.

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 and @mention it or DM it to start a thread.

The bot also auto-replies to un-mentioned messages in a thread it's already part of, but only while the original requester is the sole human participant. Once anyone else joins the thread, an explicit @mention is the way to re-engage it.

Slack is optional. The eve.ts channel serves the dev terminal UI (TUI) and your own front end without it.

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 TUI.

Copy link to headingAuthorize Notion and Resend

The first time a team member asks the agent to touch Notion or Resend, 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 Notion page is created by the real author, and a Resend send is attributed to the person who approved it, with no shared integration token to manage.

ServiceAuthenticates asYou manage
NotionEach user, OAuth via Vercel ConnectConnector UID only
ResendEach user, OAuth via Vercel ConnectConnector UID only
SlackBot token brokered by Vercel ConnectConnector UID only
TypefullyOne shared workspace keyTYPEFULLY_API_KEY
Vercel BlobProject OIDC tokenNothing
AI GatewayProject OIDC tokenNothing

Per-user scoping earns its keep most on Resend. Sending is the only thing this team does that cannot be undone, so a per-user token means the approval names who agreed to it and Resend records who sent it, instead of every campaign arriving from one shared workspace key.

Typefully is the exception: its MCP server only accepts a static key, so TYPEFULLY_API_KEY is the single static credential in the project and the connection is shared across all users.

On top of user scoping, every irreversible action is gated on approval: the call pauses and renders approve/deny buttons in Slack (or an approval prompt in the TUI) before it runs.

ActionPauses
Send a Resend broadcast, email, or batchAlways, scheduled or not
Delete a Resend broadcast, template, contact, segment, or topicAlways
Change a contact's topic subscriptionsAlways
Delete a Typefully draft, thread, or commentAlways
Create or edit a Typefully draftOnly when the call schedules or publishes it
Move pages or change views in NotionAlways
Create or update a Notion pageNo, drafting is the normal flow
delete_asset, clear_user_preferencesAlways
save_brand_contextNo, by design. The tool description tells the model to agree the document with you first

The Resend connection goes one step further than gating. It narrows what the model can discover at all with a tools.allow list, cutting roughly 85 published tools down to the 47 that make up the campaign, list, and diagnostic surface. Account administration such as API key creation and domain writes is never on the table.

Copy link to headingVerify a Resend sending domain

Verify a sending domain and create at least one segment in the Resend dashboard. The agent reads that state and picks from it, but cannot create a domain or verify DNS for you. Nothing can be sent without a verified domain. Skip this step if you're not using email.

Copy link to headingLocal development

You can run the team locally in the dev TUI.

First, clone the repository that the one-click flow created under your GitHub account:

Terminal
git clone https://github.com/your-username/marketing-team-eve-template.git
cd marketing-team-eve-template

Then link it to the Vercel project you deployed and pull the environment variables:

Terminal
vercel link
vercel env pull

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:

Terminal
pnpm dev

In the TUI, talk to the lead and watch which specialist it picks. Approval gates render there too, so it's the place to confirm a send actually stops before it goes out.

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 Notion and Resend connections work from the TUI without affecting production.

These are the commands you'll use day to day:

CommandDoes
pnpm devStart the eve dev TUI
pnpm validateLint, typecheck, and discovery diagnostics in one
pnpm check / pnpm fixCheck formatting and lint rules, and auto-fix what it can
pnpm typechecktsc --noEmit
npx eve infoPrint every discovered tool, skill, connection, and subagent
eve deployShip to production, the same as vercel deploy --prod

Linting and formatting come from Ultracite, a Biome preset. npx eve info is the fastest way to confirm a change landed: if a file you added doesn't appear, discovery didn't classify it as an authored slot, and .eve/discovery/diagnostics.json says why.

Copy link to headingHow the marketing team works

The lead runs one loop: ground itself in the shared brand context and the user's preferences, route the request to exactly one specialist with a complete brief, and hand back what the specialist produced.

  1. @mention in Slack. A team member @mentions the bot or DMs it with a marketing task: a launch to plan, a thread to draft, a page to audit, a newsletter to send. The Slack channel turns the message into a session and threads every reply.
  2. Ground in brand context and preferences. The lead calls get_brand_context for the team's shared picture of the product and get_user_preferences for that person's standing notes (default platforms, a length they always want, a review step they expect). When the brand context comes back empty, the lead routes to the product marketer first to interview you and write it, then returns to the original request.
  3. Route once, with a full brief. Each specialist starts in a fresh session and works from its brief alone: no conversation history, no parent skills, tools, or connections. So the lead's real job is packing the brief with the deliverable, the relevant brand context quoted rather than referenced, the constraints, and the destination. When a request has an order (a newsletter needs prose before it needs an inbox pass), the lead runs the calls in sequence and chains the output.
  4. Specialists load skills on demand. Each specialist pulls in the skill that matches the task, from per-platform style guides with banned-words lists to SEO audit checklists and email deliverability rules. Loading a skill only adds instructions to the turn; it never adds a new action the agent can take.
  5. Specialists find their own evidence and edit their own drafts. Each one uses web_search and web_fetch against a source budget for outside facts, runs lint_against_style on each draft to catch banned words, and edits in deliberate passes against the shared writing-quality rubric before handing work back.
  6. Deliverables land where they belong. Long-form goes into Notion as a page, social drafts land in the Typefully queue, campaigns are built in Resend, and long audits are saved as handoff artifacts in Vercel Blob and passed by ID. The lead relays the link or ID rather than pasting the document into the thread.

Every credential in this loop is brokered at runtime:

  • Notion, Resend, and Slack through Vercel Connect
  • Vercel Blob and AI Gateway through the OIDC token
  • Typefully through the one static workspace key

Copy link to headingCode walkthrough

The whole team is defined under agent/, and eve discovers each capability from the filesystem. A tool's name is its filename, a subagent's name is its directory name, and a connection's name is its filename, so there's no central registry to wire up. These are the pieces that matter.

Copy link to headingThe Slack surface

The Slack channel lives in agent/channels/slack.ts:

agent/channels/slack.ts
export default slackChannel({
credentials: slackCredentials,
onAppMention: handleInbound,
onDirectMessage: handleInbound,
async onMessage(ctx, message) {
if (message.author?.isBot) {
return null;
}
if (!(await ctx.isSubscribed())) {
return null;
}
if (!(await isSoleThreadParticipant(ctx, message))) {
return null;
}
return handleInbound(ctx, message);
},
threadContext: { since: "thread-root" },
});

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.

The onMessage handler is the detail worth reading: un-mentioned messages only get an auto-reply in a thread the bot is already subscribed to, and only while the original requester is the sole human participant. The channel also attributes Slack Workflow posts (which have no human author) to the first user the message mentions besides the bot, so workflow-triggered sessions still resolve to a real principal for the user-scoped connections.

Copy link to headingThe lead that routes, not writes

The lead's runtime configuration is minimal:

agent.ts
import { defineAgent } from "eve";
export default defineAgent({
compaction: { thresholdPercent: 0.9 },
model: "anthropic/claude-opus-5",
});

Everything else about the lead is agent/instructions.md, and its core rule is that the lead is a routing layer, not a second editor. It grounds itself in the brand context and user preferences, picks one specialist by its description, writes a complete brief, and passes the result back largely as the specialist wrote it. If what comes back is wrong or thin, it re-briefs rather than patching the work itself.

The model ID routes through the Vercel AI Gateway, which authenticates through the linked project, so there's no provider API key to set. Every agent runs the same model by default, and because the lead routes rather than produces, it's the first place to try a cheaper tier if cost matters more than routing quality.

Copy link to headingSpecialists as subagents

Each specialist is a directory under agent/subagents/ with the same shape: an agent.ts whose description is all the lead sees when routing, an instructions.md, its own sandbox.ts, its own connections/, a skills/ directory, and a tools/ directory. Subagents inherit nothing from the lead: no conversation history, no skills, tools, or connections. That's why each specialist loads the brand context again through its own get_brand_context, and why the lead's brief has to be complete.

The routing description is a contract, not a label. Here's the email specialist's:

export default defineAgent({
compaction: { thresholdPercent: 0.9 },
description:
"Own email as a channel: take copy that already exists and make it work as email, then build " +
"and run it in Resend. Reviews a draft for email fit (subject and preview text, one call to " +
"action, scannable structure, a plain text version, link and image hygiene), then builds the " +
"template or broadcast, picks the verified sending address, targets the segment, and reports " +
"on what was delivered and opened. Sending a broadcast or an email pauses for approval. " +
"Route long-form prose to the content marketer first and pass what came back to this agent.",
model: "anthropic/claude-opus-5",
});

The tree is deliberately one level deep. Each specialist gathers its own evidence with web_search and web_fetch and runs its own review pass against a written rubric, so it doesn't delegate further. Each hop costs a full context handoff, so the lead delegates and specialists do not.

Two specialists carry an evidence boundary worth knowing about. seo has no crawler, rank tracker, or Search Console, and its audit checklist marks every check as verifiable by fetch or not verifiable at all. email cannot see inbox placement or domain reputation, and its deliverability checklist does the same. Both are built to say what they could not check rather than infer it.

Copy link to headingGating the Resend connection

Resend is the connection with the most safeguards, because it's the one whose writes reach people directly. Three lists in resend.ts carry the weight:

agent/subagents/email/connections/resend.ts
const ALLOWED_TOOLS = [
"create-broadcast",
"compose-broadcast",
// ...other allowed tools
];
const SEND_TOOLS = ["send-broadcast", "send-email", "send-batch-emails"];
const DESTRUCTIVE_TOOLS = [
"remove-broadcast",
"remove-template",
"remove-contact",
"remove-segment",
"remove-topic",
"update-contact-topics",
];
export default defineMcpClientConnection({
approval: ({ toolName }) =>
[...SEND_TOOLS, ...DESTRUCTIVE_TOOLS].some((tool) =>
toolName.includes(tool)
)
? "user-approval"
: "not-applicable",
auth: connect(resendConnector),
description:
"Resend: build and send email campaigns. Create, compose, and send broadcasts to a segment; " +
"manage contacts, segments, topics, and topic subscriptions; read delivery status and logs; " +
"and list verified sending domains with their DKIM and SPF records.",
tools: { allow: ALLOWED_TOOLS },
url: "https://mcp.resend.com/mcp",
});

Three details matter here:

  • tools.allow is an allow list, not a block list. Resend's MCP server publishes around 85 tools, including API key creation, domain removal, and webhook CRUD. The template allows the 47 that make up the campaign, list, and diagnostic surface, so a tool the server adds later is invisible until someone adds it on purpose.
  • Sends are always gated, scheduled or not. Resend splits composing from committing into separate tools, and every tool in SEND_TOOLS is the commit step. Mail cannot be recalled, so there's no unscheduled case worth letting through.
  • update-contact-topics is gated alongside the deletes because it changes what someone consented to receive, which is the person's decision to reflect rather than the model's to infer.

Copy link to headingConditional gating on Typefully

Typefully's connection in typefully.ts shows a subtler approval policy: deletes always pause, but creating or editing a draft pauses only when the call actually publishes or schedules, so saving a plain draft stays friction-free.

agent/subagents/social-media-coordinator/connections/typefully.ts
const DELETE_TOOLS = [
"typefully_delete_draft",
"typefully_delete_comment",
"typefully_delete_thread",
];
const PUBLISH_TOOLS = ["typefully_create_draft", "typefully_edit_draft"];
const readPublishAt = (input: unknown): unknown => {
if (typeof input !== "object" || input === null) {
return;
}
const body = (input as { requestBody?: unknown }).requestBody;
if (typeof body !== "object" || body === null) {
return;
}
return (body as { publish_at?: unknown }).publish_at;
};
export default defineMcpClientConnection({
approval: ({ toolName, toolInput }) => {
if (DELETE_TOOLS.some((tool) => toolName.includes(tool))) {
return "user-approval";
}
if (PUBLISH_TOOLS.some((tool) => toolName.includes(tool))) {
const publishAt = readPublishAt(toolInput);
return typeof publishAt === "string" && publishAt.length > 0
? "user-approval"
: "not-applicable";
}
return "not-applicable";
},
auth: {
getToken: () => {
const token = process.env.TYPEFULLY_API_KEY;
if (!token) {
throw new Error("TYPEFULLY_API_KEY is not set.");
}
return Promise.resolve({ token });
},
},
description:
"Typefully: draft, schedule, and manage social posts across X, LinkedIn, Threads, " +
"Bluesky, and Mastodon.",
url: "https://mcp.typefully.com/mcp",
});

The approval policy receives the tool's input as well as its name, so it can gate on what the call does rather than only on what it's called: a typefully_create_draft with publish_at set commits to publishing, and the same call without it saves work with no gate.

Copy link to headingMarketing expertise as skills

Twenty-three skills sit under the specialists, each a folder with a SKILL.md and reference files:

SpecialistSkills
product-marketerpositioning, messaging, customer-research, brand-context
content-marketerblog-style, content-planning, content-editing, writing-quality
social-media-coordinatorx-style, linkedin-style, threads-style, bluesky-style, mastodon-style, writing-quality
seoseo-audit, site-architecture, schema, programmatic-seo
emailemail-style, email-adaptation, deliverability, resend-build, writing-quality

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. Each <surface>-style skill carries a references/banned-words.json, which is also what the lint_against_style tool reads at runtime, so the style skill stays the single source of truth for its surface.

The writing-quality skill is shared differently. eve scopes skills to one agent, so instead of four byte-identical copied directories, the skill is a defineSkill factory in agent/lib/writing-quality/ called from a one-line skills/writing-quality.ts file in each prose agent. The factory materializes the reference files as real siblings in the sandbox, so the compiled package matches an authored directory.

Copy link to headingShared brand context and per-user memory

Two kinds of durable state live in Vercel Blob, and their key derivation is the part worth reading.

The brand context is one shared document at a constant key. Every team member and every specialist reads and writes the same file, because it describes the product rather than any one person. save_brand_context is deliberately ungated; its tool description tells the model to agree with the document first.

User preferences are the opposite: one file per person, keyed by the framework-resolved principal.

export const userPreferencesKey = (principal: UserPrincipal): string | null => {
if (principal?.principalType !== "user" || !principal.principalId) {
return null;
}
const id = createHash("sha256").update(principal.principalId).digest("hex");
return `${USER_PREFERENCES_PREFIX}${id}.md`;
};

The Blob key is derived entirely from 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 every reserved prefix, so they can't be used as a side channel to the brand context or another user's preferences. clear_user_preferences is irreversible and gated on approval.

Copy link to headingHandoff artifacts

Long documents move between specialists by id rather than by pasting text through the lead's context. save_artifact writes a Markdown document (up to 200,000 characters) to a private Blob under a reserved artifacts/ prefix and returns an id; read_artifact reads it back through the authenticated path. All five specialists hold both tools, and the lead holds only the reader.

This is the main channel from the content marketer to the email agent: the lead relays the artifact id from one brief into the next, so an SEO audit or a finished draft never passes through the thread. Artifact ids are model-supplied on read, which is why the id format is validated strictly; without that, a caller could pass a path-traversal string and read a managed document through a tool that was never meant to reach one.

Copy link to headingTracked links

build_tracked_link adds utm_* parameters to a batch of links, deriving the source and medium from a fixed surface vocabulary and normalizing the campaign name, so one campaign doesn't arrive in analytics as several spellings. The social media coordinator and the email agent hold it, and it's deliberately not used on links between pages of your own site.

Copy link to headingNo shell for the specialists

Each specialist's tools/bash.ts disables the default-harness shell:

import { disableTool } from "eve/tools";
export default disableTool();

Left with a shell, a research-capable agent reaches for curl plus inline scripts to pull raw HTML into the sandbox and take it apart, which costs several times what web_fetch does. Removing the shell leaves web_fetch, which returns converted markdown in a single call that the source budget in each specialist's instructions can actually count. The file tools stay, because they're how an agent reads its own skills' references/ files.

Copy link to headingCustomize the team

There is no registry file: a tool's name is its filename, and a subagent's or skill's name is its directory name. Add a file to add a capability, delete it to remove one. The agent picks up changes to these files as you save.

To changeEditNotes
Who is on the teamAdd a directory under agent/subagents/The directory name is the tool name; the description in its agent.ts is all the lead sees when choosing
The lead's behavioragent/instructions.mdGrounding, routing, brief writing, handing back
A specialist's craftIts instructions.md and skills/A skill's frontmatter description decides when it loads
Voice, all surfacesagent/lib/writing-quality/config.tsShared by every prose agent
Voice and banned words, one surfacereferences/banned-words.json in that <surface>-style skillAdd a surface by adding the skill folder and passing its name in that agent's tools/lint_against_style.ts
Approval gatesAPPROVAL_REQUIRED_TOOLS in connections/notion.ts, DELETE_TOOLS / PUBLISH_TOOLS in typefully.ts, SEND_TOOLS / DESTRUCTIVE_TOOLS in resend.tsThe Notion copies are identical, so change them together
What the email agent can reachALLOWED_TOOLS in agent/subagents/email/connections/resend.tsPrefer adding a name here over loosening a gate
Modelsagent/agent.ts and each agent/subagents/<id>/agent.tsAny Gateway model ID works, or run /model in the dev TUI

Not using a surface? Delete its specialist; nothing references one by name, so removing the directory is the whole job.

Not usingDelete
Emailagent/subagents/email/
Socialagent/subagents/social-media-coordinator/
SEOagent/subagents/seo/
Slackagent/channels/slack.ts

Keep product-marketer, since it owns the brand context everything else reads.

For the full walkthrough, with recipes for adding a specialist, skill, tool, or connection, and the silent failures worth knowing about, see CUSTOMIZING.md in the repository.

Copy link to headingAdd a remote specialist

Specialists don't have to live in this repo. eve's remote agents let the lead delegate to an agent in its own deployment, with its own skills, connections, and release cycle. The filename is the tool name, and vercelOidc() handles deployment-to-deployment auth with no shared secret:

agent/subagents/paid_ads.ts
import { defineRemoteAgent } from "eve";
import { vercelOidc } from "eve/agents/auth";
export default defineRemoteAgent({
url: () => process.env.PAID_ADS_AGENT_URL ?? "https://your-paid-ads-agent.vercel.app",
description:
"Plan and write paid search and social ads: audience, offer, and the copy variants to test. " +
"Pass the campaign goal, the audience, the budget, and any brand constraints in the message.",
auth: vercelOidc(),
});

Set PAID_ADS_AGENT_URL in the project's environment variables, and the lead picks the specialist up from its description exactly as it does the local ones. The remote agent runs in its own deployment and never sees this team's conversation history, so the lead packs everything it needs into the call message, and the result comes back as a normal tool result. This suits work needing credentials or a release cycle you would rather keep out of this repo, such as ad platform access.

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 headingThe email specialist can't send anything

Symptom: Email work fails or the agent reports no usable sending address or audience.

Cause: The Resend workspace has no verified sending domain, or no segment to target. The agent reads that state and picks from it, but cannot create a domain or verify DNS for you.

Fix: Verify a domain and create at least one segment in the Resend dashboard, then ask again.

Copy link to headingA send or delete pauses and nothing happens

Symptom: A Resend send, a Typefully delete, or a scheduled post 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 lists in resend.ts, typefully.ts, and the notion.ts copies.

Copy link to headingConnectors work locally but fail in production

Symptom: The team works in the dev TUI, but the deployed bot can't reach Notion, Resend, Slack, or Typefully.

Cause: .env.local is local-only; the deployed app reads the Vercel project's environment.

Fix: Set NOTION_CONNECTOR, RESEND_CONNECTOR, SLACK_CONNECTOR, and TYPEFULLY_API_KEY in the project's production environment. The Deploy button sets them for you.

Copy link to headingA file you added doesn't show up

Symptom: A new tool, skill, or specialist isn't discovered.

Cause: eve walks agent/ at build time and only registers files that classify as an authored slot; a naming or placement problem drops the file from discovery.

Fix: Run npx eve info to see what eve discovered, and check .eve/discovery/diagnostics.json for why a file was skipped.

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 (a delegation is a full specialist session) looks stalled.

Fix: Show logs with npx eve dev --logs all, or run /loglevel all in the session.

Copy link to headingRelated resources

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.

Learn more

Related documentation

More eve guides