${ENV_VAR} in skill files: wiring channels without leaking secrets into git
Your agent's definition belongs in git. Its Slack token does not. How rysh skill files keep those two sentences compatible.
Two things I believe about AI agents, which appear to be on a collision course:
- An agent's definition should live in git. Persona, knowledge, channels — reviewable in a PR, diffable, revertible, blame-able. If your bot's behavior isn't versioned, you don't operate a bot; you operate a rumor.
- A Slack bot token must never, ever live in git. Not in the repo, not in the history, not "temporarily." Every security postmortem template has a line for this.
Most bot platforms resolve the collision by giving up on #1 — configuration moves into a dashboard, secrets and logic together, and now your agent is unversioned vendor state. rysh resolves it the other way, with a mechanism old enough to collect a pension: environment variable interpolation.
The file you commit
A rysh humanoid — an agent with external channels — is one markdown skill file. Channels are declared in the frontmatter's contacts section, and every secret is a ${TOKEN} reference, not a value:
---
name: support-bot
description: Support humanoid for Northwind Devtools
contacts:
slack:
enabled: true
bot_token: "${SLACK_BOT_TOKEN}"
app_token: "${SLACK_APP_TOKEN}"
reply_mode: mentions
channels:
- "#support"
email:
enabled: true
config:
address: "support@northwind.dev"
imap_host: "imap.northwind.dev"
smtp_host: "smtp.northwind.dev"
username: "support@northwind.dev"
password: "${SUPPORT_EMAIL_PASSWORD}"
---
You are support-bot, the support engineer for Northwind Devtools.
Answer only from the product facts below…
At spawn time — ##humanoid spawn support-bot.md — rysh resolves each ${ENV_VAR} on the machine doing the spawning: from its secret store (##secret set), falling back to the environment rysh was started in. The values exist in the running process; the file never contains them, before or after.
##secret set SLACK_BOT_TOKEN xoxb-… # kept in .rysh/secrets, which git ignores
##secret set SLACK_APP_TOKEN xapp-…
##humanoid spawn support-bot.md
##humanoid channel start support-bot slack
# slack [connected]
That's the entire mechanism. No new vault product, no proprietary secret syntax to learn — the interpolation shape you already know from docker-compose and CI configs, applied to agent definitions.
What this buys you, concretely
git log becomes your bot's changelog — safely. Every persona tweak, every added product fact, every new channel is a commit. You can git revert a bad prompt change at 2 a.m. And because tokens were never in the file, none of that history is radioactive. No git filter-branch archaeology, no rotating every credential because a repo went public.
The file is shareable; the access isn't. Email support-bot.md to a colleague, post it in a gist, use it as a template across three client projects (agencies, this is your workflow) — whoever receives it gets the logic. They can't impersonate your bot, because the file grants nothing. Access comes only from the secrets on the machine that spawns it.
Rotation is boring — the way it should be. Slack token compromised? Rotate it at the source, update the secret, respawn the humanoid (a channel restart reuses the token resolved at spawn):
##secret set SLACK_BOT_TOKEN xoxb-NEW…
##humanoid stop support-bot
##humanoid spawn support-bot.md
Zero edits to the skill file. Zero commits. The definition of the agent and the credentials of the agent have separate lifecycles, because they're separate concerns.
Environments come for free. Same file on your laptop with a test-workspace token, on the Mac mini with production credentials. Dev/prod parity for a bot, using nothing fancier than a different set of secrets.
Part of a bigger posture
Keeping secrets out of skill files is one layer of how rysh treats credentials generally — the platform assumes agents will be near secrets and is built to be paranoid about them: secrets the detectors recognise are swapped for tokens before a request leaves your machine, and the agent's env_read tool returns <redacted> for variables whose names look sensitive. Different layers, same principle: the value stays on your machine; everything else gets a reference or a redaction.
And it composes with the governance story. The skill file in git tells your reviewers what the agent is allowed to say and where. The environment tells the machine what it's allowed to connect to. A PR reviewer can approve a new email channel block without ever being able to see — or needing to see — the mailbox password.
The habit worth forming
If you adopt one convention from this post, make it this: treat agent definitions like application code, and agent credentials like application credentials. Same repos and reviews for the first; same env vars and secret managers for the second. The ${ENV_VAR} syntax is just the seam that lets one file participate in both worlds without contaminating either.
Honest scope, per house rules: live proof today is website chat (nightly CI round-trip); Slack has been demonstrated once; email (IMAP IDLE/SMTP), WhatsApp and nine more channels ship unproven. rysh runs on Claude with your own Anthropic key (that key can be an env var too). Open-core — CLI Apache-2.0, server proprietary. Private beta.
Your agent in git, your secrets in the environment, and never the twain shall meet — in a diff, at least.
If your team already reviews infrastructure as code and wants agents to work the same way, you're our kind of people — rysh is onboarding design partners.
More in this series: Your agents live in git · One SKILL.md, three channels