Everything runs on your machine with docker compose: one container per agent,
plus a self-hosted Langfuse stack for tracing. This is
the default mode (target: local) and the right place to start even if you
plan to deploy to Kubernetes later — agents behave identically in both.
-
Docker — Docker Desktop, or any Docker Engine with Compose v2.20+ (
docker compose version). -
Node.js ≥ 22 (
node --version). -
Slack workspace + Slack CLI — wrapper creates one Slack app per agent, fully automatically, through the Slack CLI:
brew install --cask slack-cli # or the installer for your OS slack auth login # one-time, per workspace
-
Your Slack team ID (
T0…) — shown byslack auth list, or in Slack under workspace name → "About this workspace". -
An LLM provider API key — e.g.
ANTHROPIC_API_KEY. Any provider supported by pi-ai works; the model is chosen per agent asprovider/model-id.
git clone <this-repo> && cd wrapper
npm install --ignore-scripts
npm run build
cp wrapper.example.yaml wrapper.yaml # edit: target: local, your slackTeamId
node packages/cli/dist/index.js init --team-id T0XXXXXXXXXTip: alias the CLI — alias wrapper="node $PWD/packages/cli/dist/index.js".
The rest of the docs assume this alias.
init does three things (idempotent — safe to re-run):
- Generates all secrets for the Langfuse stack into
infra/langfuse/.envandwrapper.secrets.json(both gitignored, mode 600). Existing values are kept. - Writes an
agents/.envstub for shared provider API keys (excluded from git in both repos; a legacy root.envstill works, with per-agent >agents/.env> root precedence). - Starts the Langfuse v3 stack (web, worker, postgres, clickhouse, redis,
minio) and provisions the
wrapperorg + sharedwrapper-agentsproject headlessly. First run downloads several images — expect a few minutes.
When it finishes you get the Langfuse UI URL (http://localhost:3010) and a
one-time printout of the admin login (also kept in wrapper.secrets.json).
Per-agent Langfuse projects are an enterprise-only API on OSS self-hosted Langfuse, so all agents share the
wrapper-agentsproject and are separated by tag and session. To give an agent its own project, create it in the UI andwrapper env set <agent> LANGFUSE_PUBLIC_KEY=… LANGFUSE_SECRET_KEY=….
Already running Langfuse somewhere (self-hosted or Langfuse Cloud)? Set
langfuse.external: true in wrapper.yaml and skip the bundled stack
entirely — see langfuse.md.
Shared across all agents (each agent's own .env overrides it):
echo 'ANTHROPIC_API_KEY=sk-ant-...' >> agents/.envwrapper agent add support-bot --channels "#support" --model anthropic/claude-sonnet-4-5
wrapper up support-botagent add scaffolds agents/support-bot/ (config, prompt, an example tool),
creates + installs a dedicated Slack app non-interactively, mints its
SLACK_BOT_TOKEN/SLACK_APP_TOKEN straight into agents/support-bot/.env
(never printed), and writes the Langfuse keys. up regenerates
docker-compose.yml and starts the container.
Then in Slack: /invite @support-bot to a channel and @mention it, or DM it
directly. Every thread is a separate conversation with persistent history;
every reply shows up as a trace in Langfuse with token usage, cost, and tool
spans.
See agents.md for everything you can put in an agent — custom tools, cron schedules, memory, workspace files, custom Docker images.
wrapper status # containers + langfuse health
wrapper ui # web console at localhost:3020 (status, logs, env)
wrapper logs support-bot -f # follow logs
wrapper env set support-bot K=V # set container env (then restart)
wrapper env list support-bot # list env (secrets masked; --show to reveal)
wrapper restart support-bot # picks up .env changes
wrapper down [agent...] # stop agents (no args = whole stack)
wrapper agent remove support-bot [--purge] # delete Slack app (+files with --purge)| Path | What | In wrapper's git? |
|---|---|---|
wrapper.yaml |
your deployment config (copy of wrapper.example.yaml); can live at agents/wrapper.yaml to be versioned in your agents repo |
no |
wrapper.secrets.json |
Langfuse admin/project keys, generated by init |
no |
agents/.env |
shared provider API keys (all agents) | never, in any repo |
agents/<name>/ |
agent config, prompt, tools | no — your own git (see below) |
agents/<name>/.env |
that agent's Slack tokens + keys | never, in any repo |
docker-compose.yml |
generated from agents/*/agent.yaml — do not edit |
no |
k8s/ |
generated manifests (k8s target only) — do not edit | no |
infra/langfuse/ |
the Langfuse stack compose file (+ generated .env) |
yes (not .env) |
Everything gitignored is either a secret, regenerated by the CLI, or yours; a
fresh clone needs only npm install, npm run build, a wrapper.yaml, and
wrapper init.
Wrapper's repo ignores the entire agents/ folder — your agents are your
code, versioned separately, so git pull in the wrapper root updates the
framework without ever touching your agents (and your agents' history never
lands in the framework repo). Typically:
cd agents && git init && git remote add origin <your-agents-repo>wrapper agent add scaffolds the folder with a README and .gitignore files
(at agents/ and per agent) that keep secrets (.env, data/,
slack/.slack/) out of your repo. Consider committing your wrapper.yaml as
agents/wrapper.yaml too — the CLI reads it from there when the root has
none, making your whole deployment restorable from that one private repo.
Updating wrapper later:
git pull && npm install --ignore-scripts && npm run build
wrapper up # rebuild + restart agents on the new runtimeinithangs waiting for Langfuse — checkdocker compose logs langfuse-web. The healthcheck hitshttp://$HOSTNAME:3000/api/public/healthinside the container (Next.js binds the container IP, not loopback).- Traces get 401s right after setup — Langfuse caches API-key verdicts in Redis; a key used before it existed can have a cached 401. Wait for the TTL or flush Redis.
- Bot doesn't answer in a channel — it must be invited, and if
channels: [...]is set inagent.yaml, the channel must be listed (the allowlist applies to @mentions; DMs are always answered). - Two replies or missed events — never run two copies of the same agent (e.g. local container + k8s pod): both hold Socket Mode connections and Slack splits events between them.