Create Slack AI Agents on Valet
Purpose
Create, deploy, and manage AI agents on the Valet platform that live inside Slack and handle research, follow-up, sync, and reporting workloads for teams in Sales, Venture, Finance, Product, Compliance, Procurement, Engineering, and Nonprofit. Valet runs each agent in an isolated cloud VM that is wired to Slack via a per-agent Slack app (so each agent gets its own @bot-name), to external tools via MCP/command connectors, and to triggers via webhook / cron / heartbeat / Slack-message channels. Agents are defined by plain-English markdown (SOUL.md) rather than code, and the entire lifecycle — scaffold → deploy → attach connectors → install in Slack → tail logs — is driven by the valet CLI. Read-only by default; the agent never sees secret values, only the tools that depend on them.
When to Use
- A user wants an in-Slack AI teammate that summarizes Granola calls into a deal channel after each meeting.
- A user wants a morning briefing posted to
#founders-meetings30 minutes before each calendar invite (Venture / Investing). - A user wants daily cash, AP, AR, and runway from Mercury posted to
#finance-daily, with@mentionfollow-ups (Finance Ops — read-only by design). - A user wants an end-of-day digest of GitHub merges, deploys, and customer-visible changes in
#ship-log(Product / Engineering). - A user wants a Slack bot that handles a recurring SOP that currently lives in a wiki page or onboarding doc — anything described in plain English ("after a PR is opened, do X, then post Y to Slack") is in scope.
- A user has a
SOUL.mdalready drafted and just needs it deployed to a Slack workspace. - A user wants to clone a 1-click template (
github.com/valet-agents/*) and customize it. - Not when the user wants Slack-only automation with no AI reasoning (use a Zapier / Slack workflow). Not when the user needs an agent that moves money or executes irreversible actions without human confirmation — Valet's Finance template is explicitly read-only.
Workflow
The optimal path is the valet CLI. Valet publishes its own canonical operating instructions at https://valet.dev/SKILL.md — fetch and follow that document as the authoritative reference; the steps below are a focused subset for the Slack team-agent use case. If the user does not have a terminal available, fall back to the Deploy-URL path in the subsection at the end.
1. Install and authenticate
# Confirm the CLI is installed (do NOT troubleshoot brew failures — hand back to the user)
valet version || brew install valetdotdev/tap/valet
# Log in (opens a browser for OAuth)
valet auth login
valet auth whoami # confirms session + default org + any linked project
If brew install valetdotdev/tap/valet fails for any reason, stop the workflow and ask the user to resolve brew manually — the upstream skill explicitly forbids retrying or working around brew failures.
2. Pick a starting point
Choose one:
- From a template (fastest — every domain on
valet.devhas a published template):valet agents create my-sales-recap --from github.com/valet-agents/granola-summaries valet agents create my-vc-brief --from github.com/valet-agents/calendar-research valet agents create my-finance-daily --from github.com/valet-agents/mercury-reports valet agents create my-ship-log --from github.com/valet-agents/github-digests - From scratch:
valet new my-agent # scaffolds ./my-agent with SOUL.md, AGENTS.md, channels/, skills/ cd my-agent # Edit SOUL.md — define Purpose, Workflow phases, and Guardrails (Always / Never) - From the catalog (Valet-curated agent templates):
valet agents create my-agent --from catalog:<name>
3. Set org-scoped secrets
Always create secrets at the org level so they can be reused across agents. Never ask the user for secret values in chat — direct them to run the command themselves:
Please run this in your terminal and confirm when done:
valet secrets set SLACK_BOT_TOKEN=<your xoxb-… token> --org <your-org> valet secrets set GRANOLA_API_KEY=<token from Granola → Settings → Developer> --org <your-org>
4. Add the Slack channel (two-step — this is the critical gotcha)
Slack is a two-step channel on Valet, not a one-step. Step 1 is once per org; Step 2 is once per agent.
# Step 1 — once per org (a prerequisite, NOT a reusable channel)
# Generates: org-level Slack app authorization. The CLI prompts for a config token + refresh token
# from https://api.slack.com/apps → Your App Configuration Tokens.
valet channels create slack --org <your-org>
# Step 2 — once per agent. Provisions a dedicated Slack app for THIS agent with its own @bot-name.
valet channels create slack --agent my-agent --bot-name "Sales Valet"
After Step 2 the CLI opens a browser tab for the Slack OAuth install flow, polls until install completes, and prints the bot name + workspace. The user then invites @Sales Valet to one or more channels (e.g. #deals-acme).
5. Add connectors (catalog-first)
valet connectors catalog # browse curated connectors (GitHub, Slack, Sentry, Linear, …)
valet connectors catalog get granola-mcp # inspect required secret slots
# Create org-scoped from the catalog (preferred)
valet connectors create granola-mcp --org <your-org>
valet connectors create github --org <your-org>
# Attach to the agent
valet connectors attach granola-mcp --agent my-agent
valet connectors attach github --agent my-agent
6. Verify every secret-backed command locally — non-negotiable
valet exec is the only way to run commands locally with Valet-managed secrets injected. Test each connector before deploying:
valet exec -a my-agent GRANOLA_API_KEY -- curl -H "Authorization: Bearer {{GRANOLA_API_KEY}}" https://api.granola.ai/v1/calls
valet exec -a my-agent GITHUB_TOKEN -- npx -y @modelcontextprotocol/server-github
Any failure here will become a runtime crash after deploy. Do not skip.
7. Deploy and run the interactive test loop
valet agents deploy
valet logs -a my-agent > /tmp/valet-test.log 2>&1 & # background tail
# Ask the user to trigger the channel (post a message, fire the cron, send the webhook).
# Stop the tail and inspect: look for mcp_call_tool_start / mcp_call_tool_done pairs and dispatch_complete.
Browser fallback — 1-click Deploy URL (no terminal)
If the user has no terminal (e.g. non-engineer admin signing up from a phone), every template on the homepage has a Deploy URL that walks them through Slack OAuth and connector setup in the dashboard:
- From
valet.dev, click Add to Slack under the desired template card (Sales / Venture / Finance / Product). This openshttps://dashboard.valet.dev/deploy?from=github.com/valet-agents/<template>. - Sign up or log in at
dashboard.valet.dev. - The wizard walks through Slack OAuth (Step 1 + Step 2 collapsed into one flow) and each connector's secret slots.
- Click Deploy. The agent comes up in the same isolated VM as a CLI-deployed agent.
- To customize the agent's
SOUL.mdafterward, eithervalet agents link <name>from a local clone of the template repo, or push changes viavalet agents drafts push <draft-id>andvalet agents drafts publish <draft-id>.
The Deploy URL path only works for templates already in github.com/valet-agents/* or the Valet catalog — custom SOUL.md content requires the CLI.
Site-Specific Gotchas
- There is an official upstream
SKILL.mdathttps://valet.dev/SKILL.md(also discoverable via theLink: </SKILL.md>; rel="service-doc"; type="text/markdown"response header onvalet.dev). It is the authoritative source for every command, flag, and edge case. Fetch it and treat it as the contract. The skill is also discoverable via the/.well-known/agent-skills/index.jsonlink in the same header. - Slack is two channels, not one.
valet channels create slack --org <org>is a prerequisite authorization, not a reusable channel — it lets Valet create Slack apps in your workspace. Each agent then needs its ownvalet channels create slack --agent <name>(orvalet channels attach slack --agent <name>) which provisions a dedicated Slack app with a unique@bot-name. Forgetting Step 1 makes Step 2 error out with a directive to run Step 1 first. Forgetting Step 2 means the agent has no Slack identity. --bot-nameis mandatory in practice. The upstream skill says to always pass--bot-name "<Display Name>"on every per-agent Slack create/attach. Server-side defaults from agent name are unreliable across orgs.- Connectors must be named after the CLI command, not the npm package. A connector named
agentmailworks (the wrapper injects secrets when the agent typesagentmail …); a connector namedagentmail-clidoes not (the agent's PATH has no executable by that name). TheSOUL.mdworkflow must reference the connector name, notnpx <package>. - Secrets are never in the shell. Regular
curl/npx/nodecannot read Valet secrets — they live in the control plane and are only injected byvalet execor by deployed connectors.curl https://api.example.com?key=$API_KEYalways fails locally; usevalet exec -a <agent> API_KEY -- curl … {{API_KEY}}. - Org-scoped vs. agent-scoped defaults. Default everything (secrets, connectors, channels) to
--orgso it can be reused across agents. Drop to--agentonly when the resource is genuinely single-use (distinct credentials for the same service, per-agent rate limits, or one-off test agents). Slack is the one documented exception: per-agent Slack channels are mandatory because each one provisions a separate Slack app identity. - Setting an agent-scoped secret triggers a redeploy. Setting an org-scoped secret does not. Plan rollouts accordingly.
- All deployed files are read-only at runtime. The agent can write files (e.g.
MEMORY.md), but those writes do not survive the next deploy. Anything that must persist between deploys belongs inSOUL.mdor a channel file, not in agent-written memory. - Manifest inline channels. For
cronandheartbeat, declare them invalet.yamlunderchannels:withtype: cron(ortype: heartbeat) and they auto-create onvalet agents create --from. No separatevalet channels createstep. Mutually exclusive withcatalog:. valet.yamlis for catalog-published / 1-click deployable agents only. Don't generate it automatically — only when the user explicitly says "yaml", "deploy button", "dashboard setup", "1-click deploy", or "setup on web".catalog:references in the manifest must match existing catalog entries verbatim, orvalet manifest validaterejects it.- Story block in
valet.yamlhas length caps. Exactly 3stepsin order:trigger→action→outcome.hero≤ 80 chars,subheadline≤ 200 chars, steptitle≤ 60, stepbody≤ 140. Sweet spots are tighter (45–75 / 110–170 / 25–50 / 80–130). - Homebrew failures are a hard stop. The upstream skill explicitly forbids retrying or working around
brew install valetdotdev/tap/valeterrors. Hand the user the exact command and wait. - No secret values in chat, ever. Direct the user to run
valet secrets set NAME=VALUE --org <org>in their own terminal and wait for explicit confirmation before moving on.
Expected Output
A successful run produces these artifacts and side-effects. Each numbered shape corresponds to a real terminal/UI state.
1. CLI agent-create success (the common path)
{
"outcome": "deployed",
"agent": {
"name": "my-sales-recap",
"org": "acme",
"release": "v1",
"process_state": "ready",
"linked_directory": "/Users/me/Developer/my-sales-recap/.valet/config.json"
},
"channels": [
{
"name": "slack",
"type": "slack",
"agent_scope": true,
"bot_name": "Sales Valet",
"workspace": "acme-workspace",
"oauth_status": "installed"
},
{
"name": "heartbeat-daily",
"type": "heartbeat",
"every": "24h"
}
],
"connectors": [
{ "name": "granola-mcp", "type": "mcp-server", "scope": "org", "attached": true },
{ "name": "slack-mcp", "type": "mcp-server", "scope": "org", "attached": true }
],
"secrets_referenced": ["GRANOLA_API_KEY", "SLACK_BOT_TOKEN"],
"dashboard_url": "https://dashboard.valet.dev/orgs/acme/agents/my-sales-recap"
}
2. Deploy-URL (browser fallback) success
{
"outcome": "deployed_via_dashboard",
"agent": {
"name": "granola-summaries",
"template": "github.com/valet-agents/granola-summaries",
"org": "acme",
"process_state": "ready"
},
"slack_install": "completed",
"bot_name": "Sales Valet",
"user_next_step": "Invite @Sales Valet to one or more deal channels (e.g. #deals-acme)."
}
3. Slack prerequisite missing (Step 1 not run)
{
"outcome": "error",
"command": "valet channels create slack --agent my-agent --bot-name 'Sales Valet'",
"error_code": "SLACK_ORG_AUTH_MISSING",
"message": "Org-level Slack authorization not found. Run `valet channels create slack --org <your-org>` first, then re-run this command.",
"next_step": "valet channels create slack --org acme"
}
4. Connector verification failed (caught by valet exec before deploy)
{
"outcome": "verification_failed",
"step": "valet exec",
"connector": "granola-mcp",
"command": "valet exec -a my-agent GRANOLA_API_KEY -- curl ...",
"exit_code": 401,
"diagnosis": "Granola API rejected the token. Confirm GRANOLA_API_KEY is a personal access token minted in Granola → Settings → Developer / API, not an OAuth token.",
"action": "Re-run `valet secrets set GRANOLA_API_KEY=<correct-token> --org <org>` and try again. Do NOT proceed to `valet agents deploy` until verification succeeds."
}
5. Homebrew install failure (hard stop)
{
"outcome": "blocked",
"step": "install",
"command": "brew install valetdotdev/tap/valet",
"message": "Homebrew install did not succeed. Per upstream skill guidance, this is not automatable — the user must resolve brew issues manually.",
"user_instruction": "Please run `brew install valetdotdev/tap/valet` in your terminal and resolve any errors. Come back once the CLI is installed."
}
6. Catalog list (useful before adding a connector / channel)
{
"outcome": "catalog_listed",
"connectors": [
{ "name": "github", "type": "mcp-server", "slots": ["GITHUB_TOKEN"] },
{ "name": "slack-mcp", "type": "mcp-server", "slots": ["SLACK_BOT_TOKEN", "SLACK_TEAM_ID"] },
{ "name": "granola-mcp", "type": "mcp-server", "slots": ["GRANOLA_API_KEY"] },
{ "name": "linear", "type": "mcp-server", "slots": ["LINEAR_API_KEY"] },
{ "name": "sentry", "type": "mcp-server", "slots": ["SENTRY_AUTH_TOKEN"] }
],
"channels": [
{ "name": "slack", "type": "slack", "slots": ["SLACK_CONFIG_TOKEN", "SLACK_REFRESH_TOKEN"] },
{ "name": "github-webhook", "type": "webhook", "verify": "hmac-sha256", "slots": ["GITHUB_WEBHOOK_SECRET"] },
{ "name": "stripe-webhook", "type": "webhook", "verify": "stripe", "slots": ["STRIPE_SIGNING_SECRET"] }
]
}