Your team just hired a data scientist.
daimon is a collaborative data science agent in your team's Discord or Slack. It writes and runs code, fits Bayesian models with PyMC, and delivers charts and runnable notebooks in the thread.
Add it to your server in one click → or self-host it from this repo.
Most chat bots are one agent shared across a workspace. daimon is
many-to-many: you deploy it once, on your own Anthropic API key, and any
number of Discord servers and Slack workspaces install it from that single
deployment. Every install is isolated: one tenant, its own data, scoped to
that server or workspace. Adding one takes about two minutes: invite the bot,
@mention it, and it sets itself up. From there, everyone there can just ask
it for things.
Built on Anthropic Managed Agents by PyMC Labs, the team behind the PyMC project.
Status: early. Self-hosting works and is documented below — expect rough edges and breaking changes while things settle. The hosted version at daimon.decision.ai is the zero-setup path.
ChatGPT analyzes your data for you, alone, in a tab. daimon does it with your whole team, in the thread — and hands back a notebook anyone can run.
@daimonin a channel starts (or continues) a threaded conversation with session continuity- Everything happens in conversation — setup, scheduling, billing:
@mentionthe bot and ask. Slash commands (/agent-setup,/routines,/billing,/privacy,/help) still exist if you prefer them; setup and routines require Discord'sManage Serverpermission - Scheduled routines: recurring agent runs dispatched headlessly
- Slack adapter (early, not yet as battle-tested as Discord) with
per-workspace OAuth install and opt-in per-user access
(
docs/slack.md) - CLI and MCP adapters sharing the same core turn pipeline
- Tenant isolation enforced at the database
tenant_idlayer, so one shared Anthropic key can safely power every guild
"Here's last quarter's sales export — we changed pricing in week 6. Did it actually help?"
"Is variant B actually better than A, or is that just noise?"
"Forecast next month's signups, with uncertainty bands."
"Every Monday at 9am, pull the weekend's numbers and post a summary here."
Answers come back in the thread: a fitted model, a chart, a plain-English read on the uncertainty, and a runnable marimo notebook that reproduces the analysis.
flowchart LR
a["your Discord server"] --> d
b["another Discord server"] --> d
c["a Slack workspace"] --> d
d["daimon<br>one deployment, your Anthropic key"] --> e["Claude<br>(Anthropic Managed Agents)"]
You run one copy of daimon. Every community that installs it gets its own
agent with its own memory, and none of them can see each other's data. When
someone @mentions the bot, daimon hands the conversation to Claude and
posts the replies back into the thread.
Technical architecture
flowchart LR
subgraph adapters
direction TB
Discord
Slack
CLI
MCP
Scheduler
end
adapters --> core["daimon core<br>turn pipeline"]
core <--> ma["Anthropic Managed Agents<br>agents · sessions · skills"]
core --> pg[("Postgres<br>tenants · thread↔session map")]
A turn: the adapter derives the tenant from platform identity, core opens or resumes a Managed Agents session, streams its events, and the adapter renders deltas into the thread until the session goes idle.
daimon.coreowns schema, stores, and the turn pipeline, and imports no adapters. Each adapter owns one platform's I/O and auth, and adapters never import each other.import-linterenforces both rules in CI.- Managed Agents holds the agents, environments, sessions, and skills themselves. Postgres holds only metadata about them: tenant identity, thread-to-session mappings, config, credentials, and billing.
- One Discord guild (or Slack workspace) is one tenant. Isolation lives at
the database
tenant_idlayer, not the API-key boundary.
You need an Anthropic API key in a workspace dedicated to this deployment (daimon manages the workspace's Managed Agents resources as its own, so sharing the workspace with anything else causes collisions) and Docker.
cp .env.example .envOpen .env, then uncomment and fill in:
DAIMON_ANTHROPIC__API_KEY: your Anthropic API keyDAIMON_MCP__JWT_SECRET: any random string (e.g.openssl rand -hex 32)DAIMON_MCP__PUBLIC_URL:http://localhost:8765/mcpis fine for local usePOSTGRES_PASSWORD: a strong, URL-safe value (avoid@ : / % #)
All four must be set before your first docker compose command:
docker-compose.yml interpolates them for every service with fail-fast
${VAR:?...} guards. You'll add the Discord bot token in step 2. .env is
gitignored, so secrets never get committed.
Hosted MCP clients receive bounded chart images directly from the Anthropic
Files API; this embed-only path is enabled by default and needs no bucket.
Clients that orchestrate start_turn, get_my_session, and list_events
themselves can call deliver_turn_charts(handle) after get_my_session
reports idle or terminated to receive the same chart payload. Calls made
while a turn is running or rescheduling are refused. When URL delivery is
configured, this call writes the chart to the private artifact store.
Optionally configure DAIMON_ARTIFACTS__ENDPOINT_URL,
DAIMON_ARTIFACTS__BUCKET, DAIMON_ARTIFACTS__ACCESS_KEY_ID, and
DAIMON_ARTIFACTS__SECRET_ACCESS_KEY to add short-lived presigned chart links.
The storage boundary uses the vendor-neutral S3 API with SigV4 and
virtual-hosted-style addressing; confirm that contract with your provider.
Path-style-only endpoints, including default MinIO setups, are not supported.
Objects remain private;
daimon never applies a public-read ACL. Presigned URL expiry does not delete
stored objects, so configure a bucket lifecycle rule for the retention period
your deployment requires. See .env.example for the optional region, URL
lifetime, and image-embedding controls.
Coding-agent clients such as Claude Code connect through the plugin in
plugin/ instead of a per-agent token: it logs in via Slack or
Discord OAuth and reaches every daimon install the logged-in person belongs
to.
Each platform's mount needs its own OAuth app, plus DAIMON_HUB__*,
DAIMON_CRYPTO__KEYS (the login state is encrypted at rest) and
DAIMON_MCP__PUBLIC_URL (the mounts derive their public base URL from it) on
the server. Register these redirect URIs on the OAuth apps, where the origin
is DAIMON_MCP__PUBLIC_URL without the trailing /mcp:
- Slack:
{origin}/slack/auth/callback - Discord:
{origin}/discord/auth/callback
The Discord app requests the identify and guilds scopes, enough to learn
who logged in and which servers they are in. The Slack app requests user
scopes (users:read, channels:history, groups:history, channels:read,
groups:read, im:history, mpim:history, im:read, mpim:read,
search:read) so a daimon reads Slack as the person asking and never sees a
channel they cannot.
A login reaches only workspaces where daimon is installed and ready, checked
on every call. Membership itself is re-read when the login token is issued or
refreshed: a Slack token stops working the moment its user leaves the
workspace, while someone removed from a Discord server keeps that server's
daimons until their Discord token expires. DAIMON_HUB__ALLOWED_CLIENT_REDIRECT_URIS
limits which clients may complete a login; the default covers coding agents
on loopback and claude.ai.
- Create an application in the Discord Developer Portal.
- Under Bot, create a bot user and copy its token into
.envasDAIMON_DISCORD__BOT_TOKEN. - Still under Bot, enable the Message Content Intent. It's a privileged intent, and without the portal toggle the bot can't read mentions.
- Under OAuth2 → URL Generator, select the
botandapplications.commandsscopes, then under Bot Permissions select at leastSend Messages,Send Messages in Threads,Create Public Threads,Manage Threads, andRead Message History. - Open the generated URL in a browser and invite the bot to a test server you control.
docker compose up --build -dOne command brings up Postgres, runs migrations and seeds the default
agents, environments, and skills (the init service does both
automatically), then starts the mcp, discord, and scheduler services.
Once it settles, send a message that @mentions the bot. It replies in a
new thread, and that's a working deployment. If the bot stays silent, check
docker compose logs discord — an unset DAIMON_DISCORD__BOT_TOKEN is the
usual cause.
Prefer to run the processes by hand?
Requires uv:
uv sync --all-extras --all-packages
docker compose up -d postgres
export DAIMON_DATABASE_URL=postgresql+asyncpg://daimon:<your-POSTGRES_PASSWORD>@localhost:5432/daimon
uv run alembic upgrade head
uv run daimon defaults apply
uv run python -m daimon.adapters.discordThe export is required because the alembic CLI reads the shell
environment and does not auto-load .env.
Slack needs a publicly reachable DAIMON_MCP__PUBLIC_URL — the bot token is
issued by an OAuth install callback served by the mcp process, not read from
an env var, and Slack won't redirect to localhost.
- Create the Slack app from
docs/slack-app-manifest.yamland follow the steps in its header comment. It fills in the scopes, slash commands, events, and Socket Mode toggles for you. - Put the resulting
DAIMON_SLACK__SIGNING_SECRET,DAIMON_SLACK__APP_TOKEN,DAIMON_SLACK__CLIENT_ID, andDAIMON_SLACK__CLIENT_SECRETin.env, plusDAIMON_CRYPTO__KEYS(a Fernet key — the adapter refuses to start without one, since it stores workspace tokens encrypted). docker compose --profile slack up --build -d- Open
https://<your-host>/oauth/slack/installand install to a workspace.
docs/slack.md covers the trust model for per-user Slack
access, which operators should read before enabling it.
Ask the agent to connect a server and it posts a card only you can open.
A server that takes a bearer token (Linear, GitHub) gets a private token
form; the token is checked against the server before it is stored and is
shared by everyone who talks to that agent. A server that signs people in
through a browser (Notion, Slack, Atlassian) gets a sign-in link instead:
daimon registers itself as an OAuth client, you approve in the browser, and
the grant lands in your own vault, refreshed by Anthropic. Each person
connects their own account. The sign-in routes live at
<DAIMON_MCP__PUBLIC_URL without /mcp>/oauth/mcp/start and
.../oauth/mcp/callback, so the public URL must be reachable from a browser
and DAIMON_CRYPTO__KEYS must be set. If one connection fails, the agent
still answers and names the server it could not use under the reply; ask it
to disconnect the server or connect it again.
packages/core/—daimon-corelibrary (MA client, stores, turn pipeline)packages/adapters/cli/— thedaimonadmin CLIpackages/adapters/discord/— the Discord bot adapterpackages/adapters/mcp/— the MCP server adapterpackages/adapters/slack/— the Slack adapter (optional)packages/adapters/scheduler/— the routines scheduler adapterpackages/testing/— shared test fixtures/harnessapps/notebook-host/— standalone marimo notebook host servicedefaults/— YAML defaults seeded into Managed Agents + local DBtests/— cross-package integration tests
See CONTRIBUTING.md for dev environment setup and the
quality gates every PR must keep green.
See SECURITY.md for how to report a vulnerability.