AI Coding Assistant for Slack — Powered by Claude Code SDK
A multi-tenant Slack bot that turns every workspace conversation into an AI-powered coding session. Send a DM, mention in a channel, or reply in a thread — the bot reads code, reviews PRs, plans Jira issues, and writes solutions with full context awareness.
You: Review this PR https://github.com/org/repo/pull/42
Bot: [Analyzes diff, reads source, posts line-by-line review comments]
You: Summarize issue PROJ-1234
Bot: [Fetches Jira issue, cross-references PRs/code, generates executive summary]
You: Optimize this function [attaches file]
Bot: [Analyzes uploaded code, identifies bottlenecks, proposes optimized version]
- Start with the docs map for current architecture, specs, traces, archives, and routing rules.
- Durable decisions live in the ADR index.
- Completed or archived work is tracked in the completed work ledger.
- The 2026-05-18 research note on AI-agent-friendly project documentation is in docs/research.
- Slack Block Kit/API constraints are maintained in docs/misc/reference/slack-block-kit.md.
The bot classifies user input and routes it to the optimal workflow — no manual selection needed.
| Workflow | Trigger | What Happens |
|---|---|---|
| PR Review | GitHub PR URL | Full code review with inline comments |
| PR Fix & Update | fix + PR URL |
Implements fix, commits, pushes |
| PR Docs | document + PR URL |
Generates Confluence documentation |
| Jira Planning | Jira issue + plan |
Task decomposition & work breakdown |
| Jira Summary | Jira issue + summary |
Executive report generation |
| Jira Brainstorming | Jira issue + brainstorm |
Idea divergence & synthesis |
| Jira → PR | Jira issue + create PR |
Auto-creates pull request from issue |
| Deploy | Deploy-related request | Deployment workflow orchestration |
| Onboarding | New user / onboarding |
Interactive guided setup |
| Default | Everything else | General-purpose coding assistant |
Additional session-handoff entrypoints (z-plan-to-work, z-epic-update) exist for z controller handoffs (#695) — they enter via CONTINUE_SESSION handoff, not free-text classification. Source of truth: WorkflowType in somalib/model-commands/session-types.ts and VALID_WORKFLOWS in src/dispatch-service.ts; prompt files live in src/prompt/workflows/.
Switch the bot's personality and reasoning style. Each persona brings a distinct approach to problem-solving.
/z persona set einstein → First-principles physics thinking
/z persona set linus → Ruthless code review, no BS
/z persona set feynman → "If I can't explain it simply..."
/z persona set vonneumann → Mathematical precision
Available: default · linus · buddha · davinci · einstein · elon · feynman · jesus · newton · turing · vonneumann — source of truth: src/persona/
Connect any MCP-compatible server (stdio/SSE/HTTP) to extend Claude's capabilities infinitely. Built-in statistics tracking and estimated completion times.
Slack-native button/form UX for permission approvals, option selection, and session management. Bypass mode available for trusted users.
Upload images (JPG/PNG/GIF/WebP), text, or code files directly in Slack. 50MB limit per file.
GitHub App (recommended) or Personal Access Token authentication with automatic token renewal.
Run multiple independent AI agents within a single process. Each sub-agent is a separate Slack App with its own persona, system prompt, and session isolation.
@soma → Main bot (제갈공명 persona)
@soma-jangbi → Code review specialist (장비 persona)
@soma-gwanu → DevOps & infrastructure (관우 persona)
- Direct mention (pending): sub-agent apps connect via Socket Mode and receive @mentions/DMs, but handling is not yet wired (see TODO in
src/agent-instance.ts) - Delegation (partial): main bot reaches agents via the
agent_chatMCP tool; Claude SDK query integration is still a placeholder - Zero-config scaling: Add agents by editing
config.json— no code changes needed - Error isolation: One agent crashing doesn't affect others
Note: multi-agent integration is partial. The agent MCP server (
packages/mcp-servers/agent/) exposesagent_chat/agent_replyand routes to configured agents, but the actual Claude SDK query integration is still a placeholder (see TODO inagent-mcp-server.ts). Direct @mention/DM handling on sub-agent apps is likewise pending fullSlackHandlerintegration (see TODO insrc/agent-instance.ts).
See How to Add a New Agent for setup instructions.
┌─────────────────────────────────────────────────────────────┐
│ Slack Events │
│ (DM / Mention / Thread) │
└──────────┬──────────────────────────────────┬─────────────────┘
│ │
┌──────▼──────┐ ┌────────▼─────────┐
│ Main Bot │ │ AgentManager │
│ SlackHandler│ │ ┌─────────────┐ │
└──────┬──────┘ │ │ jangbi │ │
│ │ │ App+Handler│ │
┌────────┼──────────┐ │ └─────────────┘ │
│ │ │ │ ┌─────────────┐ │
┌─▼──┐ ┌──▼───┐ ┌────▼───┐ │ │ gwanu │ │
│Evt │ │ Cmd │ │ Stream │ │ │ App+Handler│ │
│Rtr │ │ Rtr │ │ Proc │ │ └─────────────┘ │
└─┬──┘ └──┬───┘ └────┬───┘ └───────────────────┘
│ │ │
│ ┌─────▼────┐ ┌───▼──────┐
│ │ Command │ │ Pipeline │
│ │ Handlers │ │ in→sess→ │
│ └──────────┘ │ stream │
│ └────┬─────┘
│ │
┌────────────────────▼────────────────────┐
│ ClaudeHandler │
│ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │ Session │ │ Prompt │ │Dispatch│ │
│ │ Registry │ │ Builder │ │Service │ │
│ └──────────┘ └──────────┘ └────────┘ │
└──────────────────┬──────────────────────┘
│
┌─────────────┼──────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌─────▼─────┐
│ MCP │ │ GitHub │ │ Permission│
│ Manager │ │ Auth │ │ Service │
└────┬────┘ └─────────┘ └───────────┘
│
┌────▼─────────────────────────────────┐
│ Internal MCP Servers │
│ llm · model-command · slack-mcp │
│ cron · agent · server-tools │
│ permission · mcp-tool-permission │
└─────────────────────────────────────────┘
Key Facades — SlackHandler, ClaudeHandler, McpManager, AgentManager — present simple interfaces over complex subsystems. Each module follows Single Responsibility Principle.
Four prefix families — each with a clear, non-overlapping purpose:
| Prefix | Scope | Persistence | Example |
|---|---|---|---|
/z <topic> … |
Primary command surface (Block Kit UI where it makes sense) | User-global | /z persona set linus |
%<sub> … |
Current session only — override without saving | Ephemeral (reset on new/renew) |
%model opus |
$<skill> / $<plugin>:<skill> |
Force skill invocation (reads SKILL.md, emits RPG banner) |
Per-message | $z, $stv:new-task |
| naked text | Whitelisted bare forms (see below) or chat / workflow dispatch | n/a | sessions, new, fix PR 123 |
Split history (#508):
$used to double as both "force skill" and "session setting". It was split —%took over session settings,$is now skill-only. Legacy$model/$verbosity/$effort/$thinking/$thinking_summarystill work during the deprecation grace period but emit a one-line notice nudging users to%.
Grammar: /z <topic> [verb] [args...]. See docs/current/spec/01-slack-integration.md for the full surface, docs/runbook/rollback-z-refactor.md for emergency rollback.
| Command | Description |
|---|---|
/z help |
Show all topics |
/z cwd [set <path>] |
Show / set working directory |
/z mcp [list|reload] |
List MCP servers / reload config |
/z bypass [set on|off] |
Toggle permission bypass |
/z persona [set <name>|list] |
Switch persona |
/z model [set <name>|list] |
Switch model (sonnet, opus, haiku) |
/z verbosity [set <level>] |
Set output verbosity |
/z session · sessions |
List active sessions (sessions naked form still accepted) |
/z new · /z renew |
Reset / renew session (naked new / renew also accepted) |
/z close |
Close current thread session |
/z restore |
Restore a session |
/z context · /z compact |
Show / compact the context window |
/z link <type> <url> |
Attach issue/PR/doc links |
/z onboarding |
Run onboarding workflow |
/z admin [accept|deny|users|config|llmchat|session list] |
Admin commands |
/z cct [set <name>|next] |
CCT token status / manual switch |
/z auth [llmux|cct|switch <name>] |
Auth backend card: runtime mode switch (llmux default / cct legacy), llmux pool usage, account switch/add/remove |
/z marketplace [add <x>] |
Plugin marketplace |
/z plugin [add|update|remove|rollback|backups] |
Manage installed plugins |
/z skill [list|download] |
Skills directory |
/z report [today|daily|weekly] |
Usage reports |
Overrides that apply only to the current session and do not persist. Cleared on /z new or /z renew.
| Command | Description |
|---|---|
% |
Show session info (model, verbosity, effort, thinking flags) |
%model [<name>] |
Get / set session model (e.g. %model opus) |
%verbosity [<level>] |
Get / set session verbosity |
%effort [<level>] |
Get / set session effort |
%thinking [on|off] |
Toggle extended thinking for this session |
%thinking_summary [on|off] |
Toggle thinking-output display |
The
$prefix is still accepted for these subcommands ($model opus, …) during the deprecation grace period — the bot will reply with a one-line hint directing you to%.
Forces the bot to read a skill's SKILL.md and follow it. Emits a red RPG banner ("⚡ skill 강제 발동") on resolution.
| Syntax | Resolves to |
|---|---|
$<skill> |
src/local/skills/<skill>/SKILL.md (bare shorthand for local skills) |
$local:<skill> |
Same as above, explicit form |
$user:<skill> |
DATA_DIR/{userId}/skills/<skill>/SKILL.md (per-user skills) |
$<plugin>:<skill> |
plugins/<plugin>/skills/<skill>/SKILL.md |
Representative examples: $z, $zcheck, $stv:new-task. For the current inventory see src/local/skills/ (hardcoded lists drift — always check the directory).
Nested $plugin:skill references inside skill content are resolved recursively (max depth 10).
A whitelist of bare (no-prefix) forms is still accepted for legacy reasons. Source of truth is split across two layers: src/slack/z/whitelist.ts (z-topic forms) and the CommandParser.is*Command matchers routed by src/slack/commands/command-router.ts (operational cards such as auth, cct).
| Form | Notes |
|---|---|
session · sessions |
List active sessions |
sessions public |
Public session listing |
sessions terminate <key> |
Terminate a session by key |
theme · theme <name> · theme set <name> · theme=<name> |
Theme get/set (both set-prefixed and bare-value forms accepted) |
sessions theme [<name>] · sessions theme=<name> |
Session-scoped theme |
new [<prompt>] · renew [<prompt>] |
Reset / renew session, optional prompt carries over |
auth · auth llmux|cct · set auth <mode> · auth switch <name> |
Auth backend card / runtime mode switch (#1189; mutations admin-only) |
cct · cct set <n> · cct next · cct usage [<n>] · cct auto [dry] |
CCT token status / rotation; auto = admin-only manual auto-rotate (token mutation is card-only since #569) |
cron · schedule (also 크론 · 스케줄) |
Interactive cron card — per-job model/output-target dropdowns + delete button; routed as a command so autogoal can never swallow it; admins see all users' jobs with the owner shown |
cron model <name> <default|fast|model> · cron target <name> <channel|dm|thread> · cron delete <name> |
Change a job's model (default = creator's current model at fire time) / delivery target / delete; admins address another user's job by appending <@owner> |
$ · $model <v> · $verbosity <v> · $effort <v> · $thinking <v> · $thinking_summary <v> |
Legacy session prefix during deprecation grace period (emits one-line notice, use % going forward) |
Any free-form text not matching the whitelist is treated as a chat / workflow dispatch prompt.
Migration (#506): Legacy naked command forms (persona linus, model sonnet, show_prompt, …) outside this whitelist are deprecated. First use per user shows a tombstone hint; subsequent uses are dropped. Set SOMA_ENABLE_LEGACY_SLASH=true to restore the pre-refactor behavior during rollback.
git clone https://github.com/2lab-ai/soma-work.git
cd soma-work
npm install- Go to api.slack.com/apps → Create New App → From an app manifest
- Paste the contents of
infra/slack/slack-app-manifest.json - After creation:
- OAuth & Permissions → copy Bot User OAuth Token (
xoxb-...) - Basic Information → generate App-Level Token with
connections:writescope (xapp-...) - Basic Information → copy Signing Secret
- OAuth & Permissions → copy Bot User OAuth Token (
cp .env.example .env# Required
SLACK_BOT_TOKEN=xoxb-...
SLACK_APP_TOKEN=xapp-...
SLACK_SIGNING_SECRET=...
BASE_DIRECTORY=/path/to/code/
# Optional
ANTHROPIC_API_KEY=... # Only needed without Claude Code subscription
GITHUB_APP_ID=123456
GITHUB_PRIVATE_KEY="-----BEGIN RSA..."
GITHUB_INSTALLATION_ID=12345678
GITHUB_TOKEN=ghp_... # Fallback when GitHub App not configured
CLAUDE_CODE_USE_BEDROCK=1 # Use AWS Bedrock
CLAUDE_CODE_USE_VERTEX=1 # Use Google Vertex AI
DEBUG=trueEdit the mcpServers section of config.json:
cp config.example.json config.json{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path"]
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": { "GITHUB_TOKEN": "..." }
}
}
}Sometimes you need to set env vars on the SDK subprocess only (not on the
host process), for example to disable a built-in MCP server bundled with the
SDK. Add a claude.env block to config.json:
{
"claude.env": {
"ENABLE_CLAUDEAI_MCP_SERVERS": false,
"FOO": "bar",
"MAX_TOKENS": 4096
}
}This is equivalent to running ENABLE_CLAUDEAI_MCP_SERVERS=false claude ...
as a shell prefix — the values are forwarded to every Claude Agent SDK
query() call across the app. Internals: setQueryEnvAdditional in
src/auth/query-env-builder.ts installs the map at boot; buildQueryEnv
overlays it between the inherited process.env and the per-lease
CLAUDE_CODE_OAUTH_TOKEN override.
Value coercion. boolean and finite number JSON values are
String()-coerced before injection (env-var values must be strings). Empty
string is allowed — it's the explicit "clear an inherited var" form.
Reserved keys (denylist). The following are dropped on load with a warning. They are owned by the lease/auth path and operator overrides would either re-route auth, redirect credential storage, or widen the TLS trust surface:
CLAUDE_CODE_OAUTH_TOKEN(lease token wins)ANTHROPIC_API_KEY,ANTHROPIC_AUTH_TOKENCLAUDE_CONFIG_DIRCLAUDE_CODE_USE_BEDROCK,CLAUDE_CODE_USE_VERTEXHTTP_PROXY,HTTPS_PROXY,NODE_EXTRA_CA_CERTS
Restart required. There is no hot reload — the operator must restart the
process after editing claude.env for changes to take effect. The boot log
prints the active key list (keys only — values are never logged) so
misconfigurations are visible.
No $VAR expansion. Values are passed verbatim. If you need shell-style
interpolation, set the variable in the host environment before launch
instead of in claude.env.
The ui section of config.json controls what the thread header, turn-end
card and dashboard card header show, and how each field renders. Inspect the
built-in defaults in config.default.json (generated, do not edit), copy the
sections you want to change into config.json, and restart. Schema and
examples: docs/ui-surfaces.md.
npm run dev # Development (watch mode)
npm start # Development (tsx)
npm run build && npm run prod # Productiondocker compose -f infra/docker/docker-compose.yml up -d
docker compose -f infra/docker/docker-compose.yml logs -f./scripts/service.sh install # Install as LaunchAgent
./scripts/service.sh start # Start service
./scripts/service.sh logs follow # Stream logsService identifier: ai.2lab.soma-work — auto-restarts on crash.
⚠️ Do not runscripts/service.shduring development. Multiple instances with the same Slack token cause message conflicts.
- Create an app at GitHub Developer Settings
- Required permissions: Contents (RW), Issues (RW), Pull Requests (RW), Metadata (R)
- Generate and download a Private Key
- Install the app on target repositories; note the Installation ID
- Set
GITHUB_APP_ID,GITHUB_PRIVATE_KEY,GITHUB_INSTALLATION_IDin.env
- GitHub Settings → Developer Settings → Personal Access Tokens
- Required scopes:
repo,read:org - Set
GITHUB_TOKENin.env
GitHub App takes priority when configured. Falls back to PAT automatically.
Counts drift — always check the directories themselves. Full component wiring: docs/misc/reference/architecture.md.
src/ # TypeScript source
├── agent-manager.ts # Sub-agent lifecycle management
├── agent-instance.ts # Individual agent (Slack App + Handler)
├── agent-runtime/ # Claude Agent SDK execution runtime
├── slack/ # Slack integration layer
│ ├── actions/ # Interactive action handlers
│ ├── commands/ # Command handlers
│ ├── pipeline/ # Stream processing pipeline
│ ├── directives/ # Channel/session link directives
│ ├── formatters/ # Output formatters
│ └── z/ # /z command surface + naked whitelist
├── auth/ # CCT lease + query env injection
├── conversation/ # Conversation recording & replay
├── model-commands/ # Model command catalog & validation
├── mcp/ # MCP server management
├── github/ # GitHub App auth + Git CLI
├── permission/ # Permission service + Slack UI
├── plugin/ # Plugin system (marketplace, cache)
├── prompt/ # System prompts
│ ├── workflows/ # Dispatch workflow prompts
│ ├── jangbi/ # Sub-agent: code review specialist
│ └── gwanu/ # Sub-agent: DevOps specialist
├── persona/ # Bot personas
├── sandbox/ # Execution sandbox gate
├── metrics/ # Token/cost telemetry
├── notification-channels/ # Slack · DM · Telegram · Webhook routing
└── local/ # Claude Code SDK extensions
├── agents/ # Agent definitions
├── skills/ # Skill implementations
├── hooks/ # Git/build hooks
├── commands/ # Local slash commands
└── prompts/ # Local prompts
packages/ # Workspace packages
├── mcp-servers/ # Internal MCP servers
│ ├── agent/ # agent_chat / agent_reply tools
│ ├── llm/ # LLM aggregate (codex)
│ ├── model-command/ # Session/UI model commands
│ ├── slack-mcp/ # Thread context + file upload
│ ├── cron/ # Cron job management
│ ├── server-tools/ # Server administration
│ ├── permission/ # Permission prompt service
│ └── mcp-tool-permission/ # Per-tool permission gating
├── common/ · slack/ · process-shared/ · test-utils/
somalib/ # Shared soma-family library
services/a2t/ # Audio-to-text Python worker
infra/ # docker / slack manifest / claude config
scripts/ # Utility scripts (provision-agent.ts, ...)
docs/ # Architecture & feature specs
└── README.md # Docs routing map — start here
- Facade Pattern — Four facades (
SlackHandler,ClaudeHandler,McpManager,AgentManager) simplify complex subsystems - Single Responsibility — One responsibility per module
- Pipeline Architecture — Input preprocessing → session init → stream execution
- Workflow Dispatch — Input classification → specialized workflow prompts
- Append-Only Messages — New Slack messages instead of edits (reliability)
- Session-Based Context — Per-thread session persistence with auto-resume
- Error Isolation — Sub-agent failures don't propagate to the main bot
- Dependency Injection — Testability through injected dependencies
- Hierarchical CWD — Thread > Channel > User working directory priority
Add sub-agents to config.json (or config.dev.json on non-main branches):
{
"agents": {
"jangbi": {
"slackBotToken": "xoxb-...",
"slackAppToken": "xapp-...",
"signingSecret": "...",
"description": "Code review specialist"
}
}
}Automated provisioning:
npx tsx scripts/provision-agent.ts jangbi "코드 리뷰 전문 에이전트"Full guide: docs/misc/guides/how-to-new-agent.md
npx vitest run # Single run
npx vitest # Watch modeTest coverage includes: event routing, stream processing, command parsing, permission validation, tool formatting, session management, action handlers, pipeline processing, MCP integration, multi-agent lifecycle, and agent MCP server.
| Symptom | What to Check |
|---|---|
| Bot not responding | Logs (DEBUG=true), Slack token validity, channel invitation |
| Auth errors | API keys, Socket Mode enabled, token expiration |
| Broken formatting | Markdown → Slack mrkdwn conversion edge cases |
| Session conflicts | Multiple instances running with same Slack token |
| Sub-agent not starting | Verify slackBotToken/slackAppToken format in config.json |
agent_chat "Unknown agent" |
Agent name must match config.json key (case-sensitive) |