⚠️ Notice
This repo is a small, experimental project built for fun and learning. It’s shared publicly as-is with no promised support, roadmap, or guarantees.
Query and monitor ElastiFlow network flows from your team's chat platform.
Quick start - Deploy - Configure - E2E
Kaytoo is a chat-first interface for ElastiFlow data stored in OpenSearch or Elasticsearch. It functions as an OpenAI-compatible tool agent, allowing you to query, analyze, and monitor network flows directly from your team's chat platform.
- Analysis: Built-in tools for flow search, rankings, fan-in analysis, and namespace/protocol rollups.
- Aggregations: Bounded
flowAggregateand heuristic detectors for anomaly identification. - Insights: Optional scheduled posts to keep your team updated on network trends.
- Multi-platform Support: Slack (Socket Mode), Mattermost (WebSocket), and Matrix.
- Ops-ready: Structured JSON logging and an official Helm chart for easy Kubernetes deployment.
- Node.js 24+
- Access to OpenSearch/Elasticsearch with ElastiFlow flow documents
- LLM endpoint (OpenAI-compatible)
- For chat adapters: credentials for the platform you're integrating with
npm ci
export SLACK_BOT_TOKEN="xoxb-..."
export SLACK_APP_TOKEN="xapp-..."
export SLACK_CHANNEL_ID="C01234567"
export OPENSEARCH_URL="https://example.opensearch.org:9200"
export OPENSEARCH_USERNAME="admin"
export OPENSEARCH_PASSWORD="..."
export OPENSEARCH_TLS_INSECURE="true"
export LLM_BASE_URL="http://example.ai.org:3000"
export LLM_API_KEY="..."
export LLM_MODEL="gpt-5.4-codex"
npm run devThis mode is intended for local development and e2e verification.
npm ci
export OPENSEARCH_URL="https://example.opensearch.org:9200"
export OPENSEARCH_USERNAME="admin"
export OPENSEARCH_PASSWORD="..."
export OPENSEARCH_TLS_INSECURE="true"
export LLM_BASE_URL="http://example.ai.org:3000"
export LLM_API_KEY="..."
npm run dev -- --output consoleKaytoo is configured via environment variables. For a minimal example, see .env.example.
Slack (required for chat mode. See Slack setup (Socket Mode) below)
| Variable | Required | Default | Notes |
|---|---|---|---|
SLACK_BOT_TOKEN |
yes | - | Bot token (xoxb-...). |
SLACK_APP_TOKEN |
yes | - | App-level token for Socket Mode (xapp-...). |
SLACK_CHANNEL_ID |
yes | - | Channel for scheduled insight posts. |
Search backend (Choose either OpenSearch or Elasticsearch. Do not set both)
OpenSearch
| Variable | Required | Default | Notes |
|---|---|---|---|
OPENSEARCH_URL |
yes | - | Example: https://example.opensearch.org:9200. |
OPENSEARCH_USERNAME |
yes | - | - |
OPENSEARCH_PASSWORD |
yes | - | - |
OPENSEARCH_TLS_INSECURE |
no | false |
Set true to skip TLS verification. |
OPENSEARCH_INDEX_PATTERN |
no | elastiflow-flow-codex-* |
Must match your ElastiFlow index naming/mapping. |
OPENSEARCH_MCP_URL |
no | - | If set, startup probes URL reachability only. |
Elasticsearch
| Variable | Required | Default | Notes |
|---|---|---|---|
ELASTICSEARCH_URL |
yes | - | Example: https://elasticsearch:9200. |
ELASTICSEARCH_USERNAME |
yes | - | - |
ELASTICSEARCH_PASSWORD |
yes | - | - |
ELASTICSEARCH_TLS_INSECURE |
no | false |
Set true to skip TLS verification. |
ELASTICSEARCH_INDEX_PATTERN |
no | elastiflow-flow-codex-* |
Must match your ElastiFlow index naming/mapping. |
ELASTICSEARCH_MCP_URL |
no | - | If set, startup probes URL reachability only. |
LLM (OpenAI-compatible)
| Variable | Required | Default | Notes |
|---|---|---|---|
LLM_BASE_URL |
yes | - | Example: http://openwebui:3000 or http://ollama-proxy:11434. |
LLM_API_KEY |
yes | - | Some self-hosted backends accept an empty string. |
LLM_MODEL |
no | gpt-5.4-codex |
- |
Conversation agent (chat adapters: Slack / Matrix / Mattermost)
| Variable | Required | Default | Notes |
|---|---|---|---|
KAYTOO_KB_DOCS_DIR |
no | - | Directory of .md/.txt docs; enables kbSearch. |
KAYTOO_MCP_JSONRPC_URL |
no | - | JSON-RPC endpoint; enables mcpToolCall (tools/call). |
KAYTOO_MCP_JSONRPC_BEARER |
no | - | Bearer token for the JSON-RPC endpoint. |
Agent tools include flow search, rankings, fan-in, rare-destination / port-scan / egress-vs-baseline queries, namespace traffic split, protocol x namespace rollup, and bounded flowAggregate. Use KAYTOO_MCP_JSONRPC_URL for custom JSON-RPC tools beyond that set.
| Variable | Required | Default | Notes |
|---|---|---|---|
LOG_LEVEL |
no | info |
One of fatal, error, warn, info, debug, trace. |
Logging verbosity, redaction, and the env field use fixed defaults and are not configurable via environment variables.
In Kubernetes with Loki or VictoriaLogs, query on stable fields such as service="kaytoo", component, level, pollId, eventId, and msg.
Kaytoo supports multiple adapters; configure the one(s) you need.
Uses Slack Socket Mode for inbound messages and the Web API for replies and scheduled insight posts.
| Variable | Required | Default | Notes |
|---|---|---|---|
SLACK_BOT_TOKEN |
yes | - | Bot token (xoxb-...). |
SLACK_APP_TOKEN |
yes | - | App-level token for Socket Mode (xapp-...). |
SLACK_CHANNEL_ID |
yes | - | Channel for scheduled insight posts. |
These steps assume Slack Socket Mode.
-
Create an app at api.slack.com/apps (create from scratch, pick your workspace).
-
Turn on Socket Mode ( Settings -> Socket Mode ). Create an App-Level Token with the
connections:writescope. Copy the token; it starts withxapp-1-- this is theSLACK_APP_TOKEN. -
OAuth & Permissions -> Scopes -> Bot Token Scopes - add at least:
chat:write- post replies and insight summaries.channels:history- receivemessageevents in public channels the bot is in (addgroups:historyfor private channels, andim:history/mpim:historyif you want DMs / group DMs).
-
Event Subscriptions - enable events, then under Subscribe to bot events add:
message.channels(andmessage.groups,message.im,message.mpimif you enabled the matching history scopes in step 3).
-
Install the app to your workspace (Install App). Copy Bot User OAuth Token (
xoxb-...) - this is theSLACK_BOT_TOKEN. -
Invite the bot into channels where people should talk to it:
/invite @YourBotName. Replies are posted in the same channel/thread as the user's message. In Slack app settings -> Display Name (and the app's short name), usekaytooso teammates can mention@kaytooconsistently. -
SLACK_CHANNEL_ID- the channel ID (starts withC...) where scheduled insight posts are sent (independent of which channel users chat in). Open the channel in Slack -> channel name -> View channel details or copy the channel link; the ID is theC...segment in the URL. The bot must be a member of that channel and allowed to post there. -
Export the three Slack variables plus OpenSearch and LLM settings, and run Kaytoo (see Run locally -> With Slack below).
Official references: Socket Mode, token types, event types.
Uses matrix-js-sdk with an in-memory store (no local sync database). The bot auto-joins rooms it is invited to. End-to-end encrypted rooms are not supported for this bot path.
| Variable | Required | Default | Notes |
|---|---|---|---|
MATRIX_HOMESERVER |
yes | - | Homeserver base URL. |
MATRIX_ACCESS_TOKEN |
yes | - | Access token for the bot user. |
MATRIX_DEFAULT_ROOM_ID |
no | - | If set, Kaytoo attempts to join on startup. |
-
Create a dedicated Matrix user for Kaytoo (recommended) on your homeserver.
- If registration is closed, have a homeserver admin create the account.
- Log in once with a Matrix client (for example Element) to confirm the account works.
-
Create an access token for that user.
- In Element Web: open Help & About -> Advanced -> Access Token (wording varies by client).
-
Set Kaytoo env vars:
MATRIX_HOMESERVERshould be the client API base URL for your homeserver (oftenhttps://matrix.example.com, not always the same host as your MXID server part).MATRIX_ACCESS_TOKENis the token from step 2.
-
Room membership:
- Invite the bot user into each room where Kaytoo should respond.
- Kaytoo auto-joins rooms it is invited to.
-
(Optional) default room:
- Set
MATRIX_DEFAULT_ROOM_IDif you want Kaytoo to attempt to join a specific room on startup.
- Set
-
Run Kaytoo with OpenSearch/Elasticsearch + LLM configured (same as Slack).
Notes:
- End-to-end encrypted rooms are not supported for this bot path.
- Prefer non-encrypted rooms for operational chat bots.
Inbound messages use the Mattermost WebSocket API (/api/v4/websocket); outbound replies use REST.
| Variable | Required | Default | Notes |
|---|---|---|---|
MATTERMOST_URL |
yes | - | Base URL for your Mattermost instance. |
MATTERMOST_TOKEN |
yes | - | Personal access token for the bot user. |
MATTERMOST_CHANNEL_ID |
yes | - | Channel ID where Kaytoo listens/posts. |
MATTERMOST_BOT_USER_ID |
no | - | Optional explicit bot user ID. |
-
Create a dedicated Mattermost user for Kaytoo (recommended).
- Use a normal user account; Kaytoo authenticates with a personal access token.
-
Create a Personal Access Token (PAT) for that user.
- In Mattermost: Profile -> Security -> Personal Access Tokens (wording varies by version).
- Copy the token once; you cannot retrieve it later.
- Treat the PAT like a password: rotate it if it leaks.
-
Pick the channel Kaytoo should monitor.
- Copy the channel ID (Mattermost UI: channel menu -> View Info / channel header details; the ID is also visible in some deep links depending on version).
- Add the Kaytoo user as a member of that channel.
-
Set Kaytoo env vars:
MATTERMOST_URLis your server base URL (example:https://chat.example.com).MATTERMOST_TOKENis the PAT from step 2.MATTERMOST_CHANNEL_IDis the channel ID from step 3.
-
(Optional) bot user id:
- Set
MATTERMOST_BOT_USER_IDonly if you need to pin the bot identity explicitly (most deployments can omit this).
- Set
-
Run Kaytoo with OpenSearch/Elasticsearch + LLM configured (same as Slack).
Notes:
- The bot user must be allowed to read channel history and post messages in the configured channel.
- If Kaytoo can connect but never responds, verify the user is in the channel and the channel ID matches the environment you configured.
docker build -t kaytoo:dev .
docker run --rm \
-e SLACK_BOT_TOKEN \
-e SLACK_APP_TOKEN \
-e SLACK_CHANNEL_ID \
-e OPENSEARCH_URL \
-e OPENSEARCH_USERNAME \
-e OPENSEARCH_PASSWORD \
-e OPENSEARCH_TLS_INSECURE \
-e OPENSEARCH_INDEX_PATTERN \
-e OPENSEARCH_MCP_URL \
-e LLM_BASE_URL \
-e LLM_API_KEY \
-e LLM_MODEL \
kaytoo:devEnd-to-end on kind uses the repo-root .env (OpenSearch password, LLM, etc.), generated files under e2e/.generated/ (gitignored), and the scripts in e2e/. See e2e/README.md for bring-up, tear-down, verify, host dev, and file layout.
The chart is in helm/kaytoo.
helm upgrade --install kaytoo ./helm/kaytoo \
--namespace elastiflow --create-namespace \
--set image.repository="<your-dockerhub-user>/kaytoo" \
--set image.tag="latest" \
--set config.slack.channelId="C01234567" \
--set config.opensearch.url="https://example.opensearch.org:9200" \
--set config.opensearch.tlsInsecure="true" \
--set config.llm.baseUrl="http://openwebui.elastiflow.svc:3000" \
--set secrets.slackBotToken="xoxb-..." \
--set secrets.slackAppToken="xapp-..." \
--set secrets.opensearchUsername="admin" \
--set secrets.opensearchPassword="..." \
--set secrets.llmApiKey="..."Pushes to main run elastiflow/gha-reusable prepare-release, which may open a pull request that bumps helm/kaytoo/Chart.yaml, package.json, and prepends CHANGELOG.md. Squash-merge that PR so the resulting commit subject is exactly [release vX.Y.Z] (#NNN) (GitHub’s default squash title matches the PR title). Merging with that message triggers the GitHub release, container push to ghcr.io/<owner>/<repo>, and a Helm chart GitHub release plus cr index publish to the gh-pages branch when GitHub Pages is enabled for that branch.
npm run lintnpm testnpm run buildnpm run dev(runssrc/main.tsviatsx)- E2e (kind): see
e2e/README.md
Kaytoo is licensed under the Apache 2.0 license (see LICENSE).