Skip to main content
This guide tests a small Slack support bot built with @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

Save this as 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
The bot reads 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 as scenes/support-answer/scene.yaml:
scenes/support-answer/scene.yaml
The world has an old question that Ben already answered and a new one from two minutes ago. The checks say what a good outcome looks like:
  • answered_newest looks at the final Slack state: the bot replied in the thread of the newest question, with the right instructions. from: $bot keeps Ben’s earlier answer from counting.
  • posted_a_reply looks at the bot’s calls. It uses POST because the SDK calls every Slack method with POST.
Check the file:
Output

4. Run it three times

Output
The command exits with code 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
Without --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:
In bot.mjs, change messages.findLast( to messages.find( so the bot takes the newest question. Then run again and compare:
Output
Exit code 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 pass while you tune it. Without OPENROUTER_API_KEY it 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_rate in ancha.yaml. List its provider key under agent.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.