@slack/web-api. You write a scene with a #support channel, two questions and checks for the right reply, run the bot three times, find a bug from the evidence, and prove the fix. It takes about ten minutes and needs no Slack workspace and no API keys.
Before you start
- Node.js 24 or later.
- The Ancha CLI:
npm install -g @ancha-labs/cli. See Quickstart.
1. Create the bot
bot.mjs. It reads #support, picks a question, and replies in its thread with an answer from a small playbook. It has a bug that Ancha will find.
bot.mjs
SLACK_API_URL and SLACK_BOT_TOKEN, so the same code runs against real Slack when you give it real values.
2. Set up Ancha
init writes ancha.yaml with your command, a starter scene and .ancha/ in .gitignore. Because package.json depends on @slack/web-api, the starter only checks that the bot reaches Slack. Remove it; you will write a scene for this bot.
3. Write the scene
Save this asscenes/support-answer/scene.yaml:
scenes/support-answer/scene.yaml
answered_newestlooks at the final Slack state: the bot replied in the thread of the newest question, with the right instructions.from: $botkeeps Ben’s earlier answer from counting.posted_a_replylooks at the bot’s calls. It usesPOSTbecause the SDK calls every Slack method with POST.
Output
4. Run it three times
Output
1. Each trial ran on a fresh copy of the channel, and all three failed the same way. The bot got only Ancha’s variables and a small base set from your shell, which is all it needs.
5. Find out why
Output
--trial 1, explain prints all three trials.
The bot did post one message, just not in the newest question’s thread. The trial’s files show where it went:
agent.log says Answered: Ridgeway Robotics cannot find last month's CSV export. Where should I point them?, and the request in trace.jsonl carries that question’s thread_ts. The bot answered the oldest question. conversations.history returns messages newest first, and findLast takes the last match, which is the oldest.
6. Fix it and prove the fix
Save the failing run as the reference, then fix the bot:bot.mjs, change messages.findLast( to messages.find( so the bot takes the newest question. Then run again and compare:
Output
0: every check passed and no scene and no check got worse than before.
Next steps
-
Grade the wording. Add a judge check, and keep it out of
passwhile you tune it. WithoutOPENROUTER_API_KEYit is skipped with a warning and does not change the verdict: -
Cover more cases. Add scenes for a question the playbook does not know, a question in a thread, or no question at all. Each scene is a file under
scenes/. -
Test a model-based bot. Its answers vary, so run more trials and set
min_pass_rateinancha.yaml. List its provider key underagent.pass_env; Ancha withholds the rest of your shell from the bot. See ancha.yaml. - React to events. A bot that listens for messages reads Slack events from the trial’s event feed, or by HTTP or Socket Mode. See Slack.
- Run it in CI. See CI.
- Hand it to a coding agent. See For coding agents.