Demo: support triage with a real Slack post¶
This walkthrough runs examples/support-triage-demo.yaml. A support message arrives on a
webhook. The agent classifies it into a typed object, looks the customer up over HTTP, drafts a
reply, waits for you to approve it, and posts the reply to a Slack channel.
It shows the version 1.1 features: vars, trigger.*, expressions in {{ }}, a validated
output_schema, and per-step instructions and temperature. The model is a cheap DeepSeek
model on OpenRouter.
What you need¶
- Node.js 22 or newer, and the repo built (
npm install && npm run build). - An OpenRouter API key.
- A Slack workspace where you can install an app (steps below).
1. Set up Slack¶
You need a bot token and a channel. It takes about five minutes.
- Go to https://api.slack.com/apps, choose Create New App, then From scratch. Name it
(for example
kampong-demo) and pick a workspace. A personal test workspace avoids needing admin approval. - Open OAuth & Permissions. Under Bot Token Scopes, add
chat:write. Addchat:write.publictoo if you want it to post in public channels without being invited. - Click Install to Workspace at the top of that page, then Allow.
- Copy the Bot User OAuth Token (it starts with
xoxb-). This isSLACK_BOT_TOKEN. If you change scopes later, click Reinstall to Workspace. The token stays the same. - Create a channel such as
#support-triage, then type/invite @kampong-demoin it. Without the invite, Slack answersnot_in_channel(unless the channel is public and you addedchat:write.public). - Set
SLACK_CHANNEL. The channel ID is the most reliable value: right-click the channel, choose View channel details, and copy the ID at the bottom (it looks likeC0123456789). A name like#support-triageworks for a public channel. A private channel needs the ID. - Optional, only for Approve and Reject buttons on a headless
kampong serverun: copy the Signing Secret from Basic Information intoSLACK_SIGNING_SECRET, turn on Interactivity & Shortcuts, and set its Request URL tohttps://<your-public-host>/slack/interactions. Slack cannot reachlocalhost, so this needs a tunnel such as ngrok or Cloudflare (kampong does not start one). In the canvas, approval is the in-app dialog and none of this is needed. The traps to avoid, including Socket Mode, are in Slack approvals locally.
2. Put the secrets in .env¶
Fill in OPENROUTER_API_KEY, SLACK_BOT_TOKEN and SLACK_CHANNEL. .env is gitignored, and a
spec never holds a value, only the ${ENV_VAR} name. Don't paste these into chat, issues or
commits.
You can also set them from the canvas: Secrets in the status bar (with a count of the names that have no value yet) lists every name the spec needs (including the
Slack token, which the Slack component reads on its own, and the secret a webhook trigger checks, such as
GITHUB_WEBHOOK_SECRET), and lets you set or replace a value. When a test run or a check says a variable is not set,
its error has a Set it here button that opens Secrets on that name. Values
are saved to .kampong/secrets.env on this machine and are never shown again. This applies to the canvas
(kampong dev, make up); kampong run and kampong serve still read the process environment.
The CLI reads its process environment and does not load .env itself, so load it into your shell
first:
This runs .env as shell code, so a value with an unquoted space breaks it. Quote such a value
(NAME="two words"). The Docker stack (make up) reads the same file without this step.
3. Check the setup¶
doctor lists each variable by name only (never a value), and --probe asks Slack whether the
token is accepted. You want Nothing would stop a run.
4. Run it in the canvas¶
cp examples/support-triage-demo.yaml workspace/agent.yaml
node packages/cli/dist/cli.js dev workspace
Open http://localhost:4310. You see the trigger, the two tools and the five workflow steps
(classify, lookup, draft, review, send). Open the </> drawer (Alt+Y) to see the same file as YAML.
Click Run in the top bar and paste:
{"customer_id":3,"subject":"Charged twice this month","body":"Hi, I was charged twice for my subscription on the 3rd. Can you refund the duplicate? Thanks, Clementine"}
The run pauses at review with the drafted reply. Approve it and the message appears in your
Slack channel. Try a second message about an outage to see urgency: high add the siren emoji in
the Slack text.
5. Run it from the terminal¶
node packages/cli/dist/cli.js run examples/support-triage-demo.yaml \
--input '{"customer_id":3,"subject":"Charged twice","body":"I was charged twice on the 3rd."}'
It asks for approval on stdin. --approve-all approves without asking, and --var channel=C0123456789
overrides a var.
To run without touching the network again, record once and replay:
... run <spec> --tools record --fixtures examples/fixtures/support-triage-demo --input '...' --approve-all
... run <spec> --tools replay --fixtures examples/fixtures/support-triage-demo --input '...' --approve-all
Recording makes live calls, including the Slack post. Replay needs no network and no credentials for tools. The model call is still live.
6. Run it as a webhook service¶
curl -s -X POST localhost:8080/webhook -H 'content-type: application/json' \
-d '{"customer_id":3,"subject":"Charged twice","body":"I was charged twice on the 3rd."}'
# { "success": true, "id": "<run id>", "state": ... }
curl -s localhost:8080/runs/<run id> # state; paused at "review"
curl -s -X POST localhost:8080/runs/<run id>/approve \
-H 'content-type: application/json' -d '{"approved":true}'
With SLACK_SIGNING_SECRET and a public Request URL (step 1.7) the pause also posts an Approve or
Reject prompt to Slack.
Known limits¶
- Export works, with a limit.
kampong exportproduces a standalone project for this spec. Run it withnpm start -- --input '{"customer_id":3,...}' --var channel=C0123456789, ornpm run servefor the webhook. The hosted server runs version 1.1 specs, except thepollandscheduletriggers (KAM-203). - The canvas shows and runs 1.1 specs but has no forms for the new fields yet. Edit
vars, expressions andoutput_schemain the YAML panel for now (KAN-1850). - The customer lookup uses the public
jsonplaceholder.typicode.comAPI (users 1 to 10), so use acustomer_idin that range. Pointvars.apiat your own service with--var api=....