π―π΅ ζ₯ζ¬θͺη: README.ja.md
A context-aware Slack bot that remembers your team's conversations, answers questions grounded in history, and executes commands via natural language β all powered by a local LLM.
SAI silently reads every message posted in its joined channels and stores them in memory. When you @mention SAI, it retrieves relevant past conversations and uses them as context for its answer.
Alice β @SAI What did Bob say about the deployment schedule last week?
ββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
SAI β Based on the conversation from Tuesday, Bob mentioned that the
β deployment is planned for Friday at 2:00 PM JST, but noted
β there may be a delay if the staging tests don't pass by Thursday.
Alice β @SAI Has anyone discussed the Redis migration?
ββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
SAI β Yes β Carol brought it up on Monday. She suggested migrating
β from Redis 6 to Redis 7 during the maintenance window on the
β 25th. Dave replied that he wanted to review the breaking changes
β first and would share a summary by end of week.
SAI can run pre-registered shell scripts in response to natural language requests. You don't need to remember command names β just describe what you want.
Bob β @SAI Can you check the server status?
ββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
SAI β β
`server_status`:
β ```
β Hostname : prod-server-01
β Uptime : up 14 days, 3 hours
β Load avg : 0.42, 0.38, 0.35
β
β Memory:
β total used free
β 32Gi 12Gi 18Gi
β ```
Carol β @SAI How much disk space is left on /var/log?
ββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
SAI β β
`disk_usage`:
β ```
β Filesystem Size Used Avail Use%
β /dev/sda1 50G 31G 19G 62%
β
β Top directories:
β 8.2G /var/log/nginx
β 4.1G /var/log/app
β 1.3G /var/log/syslog
β ```
Dave β @SAI ping google.com for me
ββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
SAI β β
`ping_host`:
β ```
β PING google.com: 4 packets, 0% loss
β min/avg/max = 4.2/4.8/5.3 ms
β ```
Add a π reaction (or β π π) to any message and SAI will store it in pinned memory β it will never be summarized, never be archived, and will always be available as context for future answers.
Eve β The production DB password rotation is scheduled for Dec 1st.
β All services must update their credentials by Nov 30th 5PM.
β
β [someone adds π reaction]
β
Alice β @SAI When is the DB password rotation?
ββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
SAI β The production DB password rotation is scheduled for December
β 1st. All services need to update credentials by November 30th
β at 5:00 PM.
Even months later, pinned messages remain available β unlike regular messages which are gradually summarized and eventually archived.
SAI manages its own memory so it doesn't grow forever. Regular messages follow this lifecycle:
HOT (< 24h) Full original text β every detail preserved
β
WARM (1β7 days) LLM-generated summary β key points kept
β
COLD (> 7 days) Marked for removal from active memory
β
ARCHIVE Moved to cold storage, no longer retrieved
PINNED Reaction-triggered β never ages, never archived
This means SAI has good recall for recent events and useful summaries for older ones, while staying within the LLM's context window.
SAI tracks changes to stored messages:
- Edit β When a message is edited, the original record is kept unchanged. A new HOT record is added with the updated text (prefixed
[edited by <user>]), so RAG retrieval can surface the corrected content going forward. - Delete β When a message is deleted:
- HOT or PINNED record: deleted from memory immediately, including its embedding. The author may have removed sensitive content.
- WARM or COLD record: already summarised β the summary cannot be undone, so the record is left as-is.
Reply to any thread that SAI has already participated in β without needing to @mention SAI again β and the conversation continues naturally. SAI detects that the parent message is in memory and treats the reply as a mention automatically.
Ask SAI to summarise what happened in a channel or thread:
@SAI summarise this channel
@SAI summarise this thread
SAI retrieves all stored memory records for the requested scope, generates a summary, and clearly states the time range covered by the records:
## Summary of #general β 2026-03-20 10:00 UTC to 2026-03-24 15:30 UTC
β’ The team discussed the Q1 release plan ...
β’ Bob and Alice agreed on the API design ...
Note: Records older than 24 hours may already be LLM-generated summaries (WARM/COLD state). Long-range summaries are therefore "summaries of summaries" and may be less precise for older periods. SAI notes this in the response.
- SAI does not proactively send messages β it only replies when @mentioned
- SAI does not run arbitrary shell commands β only pre-registered scripts in
scripts/commands.json - SAI does not have internet access β it uses a locally running LLM
- Python 3.12+
- uv
- LM Studio (or any OpenAI-compatible local API)
- Slack app with Socket Mode enabled β see docs/slack-setup.md
# 1. Install dependencies
uv sync
# 2. Copy and fill in your credentials
cp .env.example .env
# Edit .env with SAI_SLACK_BOT_TOKEN and SAI_SLACK_APP_TOKEN
# 3. (Optional) Copy and customize the full config
cp sai.toml.example sai.toml
# Edit sai.toml β set workspace_name, response_language, model names, etc.
# sai.toml is read from the current directory where you run `uv run sai start`
# 4. Initialize the database
uv run sai init-db
# To wipe all data and start fresh (prompts for confirmation):
# uv run sai init-db --reset
# 5. Verify LLM connectivity
uv run sai check
# 6. Start the bot
uv run sai startSettings are loaded from sai.toml (read from the current directory by default, override with --config) and SAI_* environment variables. Environment variables override the config file.
| Variable | Default | Description |
|---|---|---|
SAI_SLACK_BOT_TOKEN |
(required) | Slack bot token (xoxb-...) |
SAI_SLACK_APP_TOKEN |
(required) | Socket Mode app token (xapp-...) |
SAI_SLACK_RESPONSE_LANGUAGE |
(auto-detect) | Language for bot replies (e.g. Japanese, English) |
SAI_LLM_BASE_URL |
http://localhost:1234/v1 |
LM Studio endpoint |
SAI_LLM_API_KEY |
lm-studio |
API key |
SAI_LLM_MODEL |
openai/gpt-oss-20b |
Chat model |
SAI_LLM_EMBED_MODEL |
text-embedding-nomic-embed-text-v1.5 |
Embedding model |
SAI_LLM_MAX_CONCURRENT_REQUESTS |
4 |
Max parallel LLM requests |
SAI_MEMORY_PIN_REACTIONS |
pushpin,star,bookmark,memo |
Reactions that pin memory |
SAI_LOG_LEVEL |
INFO |
Log level |
For all available settings with descriptions and defaults, see sai.toml.example.
Secrets (tokens, API keys) can be placed in .env β see .env.example.
1. Write a shell script in scripts/. Parameters arrive as JSON on stdin:
#!/usr/bin/env bash
# scripts/my_command.sh
params=$(cat)
target=$(echo "$params" | python3 -c "
import json, sys
print(json.load(sys.stdin)['args']['target'])
")
echo "Running check on: $target"
# ... your logic here2. Register it in scripts/commands.json:
{
"name": "my_command",
"description": "Check the health of a given service",
"script_path": "my_command.sh",
"required_args": ["target"],
"max_runtime_seconds": 30
}SAI's LLM will automatically map user requests like "check the health of nginx" to this command.
SAI is designed with multiple layers of defense:
- ACL β whitelist/blacklist by Slack user ID
- Rate limiting β per-user sliding window (configurable per minute and per hour)
- Prompt injection defense β per-request nonce XML encapsulation, input sanitizer, role-separated prompts
- Command sandboxing β only pre-registered scripts run; parameters via stdin JSON (never CLI args); resource limits applied
- Response sanitization β model internal tags (
<think>,[THINK],<reasoning>, etc.) are stripped before posting to Slack
See docs/development-rules.md for the full security policy.
While SAI is stopped, you can inspect the memory database from the CLI:
# Record counts by state
uv run sai memory stats
# List recent records (supports --state / --user / --channel / --limit filters)
uv run sai memory list
uv run sai memory list --state pinned
uv run sai memory list --channel C09BLA40DFY --limit 50
# Full detail of one record (use the ID prefix shown in list)
uv run sai memory show <id-prefix>Memory states: hot (< 24h, full text) β warm (1β7 days, summarized) β cold β archived; pinned never ages.
uv run pytest
uv run pytest --cov=sai --cov-report=term-missingsai/
βββ config/ # Configuration schema and loader
βββ sai/
β βββ app.py # Application orchestrator β event pipeline
β βββ db/ # DuckDB + VSS repository layer (async interface)
β βββ llm/ # LLM client, prompts, nonce, sanitizer
β βββ memory/ # Memory models, lifecycle state machine, scheduler
β βββ rag/ # Embedding generation and retrieval
β βββ security/ # ACL, rate limiter, injection detection
β βββ slack/ # Socket Mode handler, event parser, cache
β βββ commands/ # Registry, NL interpreter, executor
β βββ utils/ # Logging, time helpers, ID generation
βββ scripts/ # Command scripts + commands.json manifest
βββ tests/ # Unit and integration tests
βββ docs/ # Setup guides, architecture, development rules
| Document | Description |
|---|---|
| docs/slack-setup.md / ja | Step-by-step Slack app setup guide |
| docs/development-rules.md / ja | Coding standards, security rules, git conventions |