claude-code-discord-bridge
AGENTS.md66 starsChanged 9 months agoMITView on GitHub
Discord frontend for Claude Code CLI. This is a framework (OSS library), not a personal bot. 略称: ccdb (claude-code-discord-bridge) Consumers must get new features by updating the package alone — no code changes required.
- Reads credentials
- Installs packages
We split this AGENTS.md into the rules it gives an agent
20 rules. About 0 are common, 7 are distinctive, and 13 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈7
Distinctive · estimate
This author's own choices. Worth a look.
13
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- could be automatic: a setting could enforce it
- Distinctive: line 11
When adding features: if it's useful to anyone → add here.
- Distinctive: line 19
If a feature requires consumers to wire something up, the design is wrong — fix it in ccdb
- Very specific: line 34
Fence-aware chunking: Never split messages inside a code block.
- Distinctive: line 83
All tests must pass before submitting a PR.
- Very specific: line 158
When a change adds or reshapes something a user sees in the Console, update a scenario under examples/demo/ so the feature shows up in the …
- Very specific: line 174
Use contextlib.suppress(discord.HTTPException) for Discord API calls that may fail (reactions, message edits)
- Distinctive: line 175
Never silently swallow errors in business logic — log them
- Very specific: line 184
Always
create_subprocess_exec: Never use shell=True. The prompt is a direct argument, not shell-interpolated. - Very specific: line 185
--separator: Always use -- before the prompt argument to prevent flag injection - Very specific: line 191
If you modify runner.py, _run_helper.py, or any Cog, the security audit is mandatory before committing.
- Very specific: line 205
RED: Write a failing test → uv run pytest tests/test_xxx.py -v → confirm it FAILS
- Distinctive: line 206
GREEN: Write minimal code to pass → confirm it PASSES
- Very specific: line 212
Use pytest with pytest-asyncio (auto mode)
- Very specific: line 213
Test files go in tests/ mirroring the source structure
- Very specific: line 340
If it runs Claude CLI, use _run_helper.run_claude_in_thread() — don't duplicate the streaming logic
- Very specific: line 342
Add to claude_discord/__init__.py public API
- Very specific: line 343
Write tests in tests/test_your_cog.py
- Very specific: line 369
Add to the appropriate file in claude_discord/discord_ui/
- Distinctive: line 371
Test edge cases (empty strings, very long strings, Unicode, code blocks)
- Distinctive: line 377
No direct push to main: Always create a PR
≈ “Common” is an estimate: at least 1 in 5 of the 36,368 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
claude-code-discord-bridge (ccdb)
Discord frontend for Claude Code CLI. This is a framework (OSS library), not a personal bot.
略称: ccdb (claude-code-discord-bridge)
Framework vs Instance
- claude-code-discord-bridge (this repo) = reusable OSS framework. No personal config, no secrets, no server-specific logic.
- Personal instances (e.g. EbiBot) use the custom Cog loader (
CUSTOM_COGS_DIR/--cogs-dir) to add their own Cogs. Seeexamples/ebibot/for the reference implementation. - When adding features: if it's useful to anyone → add here. If it's personal workflow → add as a custom Cog.
Zero-Config Principle (Critical)
Consumers must get new features by updating the package alone — no code changes required.
- New features should be enabled by default (auto-discovery, sensible defaults)
- New constructor parameters must have backward-compatible defaults (
= None) - If a feature requires consumers to wire something up, the design is wrong — fix it in ccdb
- Consumers should NEVER need to copy, wrap, or subclass ccdb Cogs. If they do, ccdb is missing an extension point
Architecture
- Python 3.12+ with discord.py v2
- Cog pattern for modular features
- Repository pattern for data access (SQLite via aiosqlite)
- asyncio.subprocess for Claude Code CLI invocation (never shell=True)
Key Design Decisions
- CLI spawn, not API: We invoke
claude -p --output-format stream-jsonas a subprocess, not the Anthropic API directly. This gives us all Claude Code features (CLAUDE.md, skills, tools, memory) for free. - Thread = Session: Each Discord thread maps 1:1 to a Claude Code session ID. Replies in a thread continue the same session via
--resume. - Emoji reactions for status: Non-intrusive progress indication on the user's message. Debounced to avoid Discord rate limits.
- Fence-aware chunking: Never split messages inside a code block. The logic lives in
claude_code_core/rendering/and is driven bySurfaceCapabilities, not by Discord's numbers — a surface with a 100 KB limit sends the same answer as one message instead of inheriting Discord's 2,000-character fragmentation. - Installable package:
claude_discordis a proper Python package. Consumers install viauv add git+...orpip install git+..., not by copying files. - Shared run helper:
cogs/_run_helper.pycentralizes Claude CLI execution logic used by both ClaudeChatCog and SkillCommandCog. - REST API as the control plane: Claude Code subprocesses communicate back to ccdb via REST API (
CCDB_API_URLenv var), not via stdout markers or special output formats. This makes the interface explicit, testable, and usable by external systems (GitHub Actions, etc.). Seeext/api_server.py. - SQLite-backed dynamic scheduler: Scheduled tasks are stored in
scheduled_tasksDB table and executed by a singlediscord.ext.tasksmaster loop (every 30s). Tasks are registered at runtime via REST API — no code changes needed to add new tasks.discord.ext.tasksdecorators are only used for the master loop, not per-task (they're static/compile-time constructs). - Claude handles "what", ccdb handles "when": For scheduled tasks, ccdb only manages the schedule. All domain logic (what to check, how to deduplicate, what to post) lives in the Claude prompt. No GitHub/AzureDevOps-specific code in ccdb itself.
- CLI env overlay (
CCDB_CLI_ENV_FILE): OptionalKEY=VALUEfile read on every CLI spawn to inject env vars into the subprocess. Enables live configuration changes (e.g., switching to Azure Foundry) without restarting the bot. The file is read by_build_env()inrunner.py. - Model suggestions are discovered, not hardcoded (
model_catalog.py): the/modelautocomplete asksGET /v1/models(reusing the CLI's own credentials) instead of shipping a constant that goes stale on every model launch. This is the only sanctioned direct Anthropic API call, and it is strictly non-essential: no credentials, no network, a 3P provider, orCCDB_MODEL_DISCOVERY=0all degrade to the staticSUGGESTED_MODELSfallback. Never let it raise into a command path, and never log the token. Codex is discovered too, but without a second vendor call: the Codex CLI already fetches its own catalog and writes it to$CODEX_HOME/models_cache.json, socodex_model_choices()reads that file. A new generation (gpt-6-astra) appears as soon as the CLI has seen it, and decision 13's "ccdb does not phone home" still holds. - Anonymization replaces by rule, inspects by model (
claude_code_core/privacy/): an optional gateway wraps anySessionBackendand swaps organisation-identifying terms for stable aliases before the prompt reaches the CLI, restoring them in the answer. The substitution is a rule table — never a model — because a model produces a different alias every run and an alias that changes cannot be restored. The local LLM's only job is to report proper nouns the rules missed. Reversing those two roles breaks the feature.CCDB_ANONYMIZE_POLICY=adoptkeeps that split while closing the loop: a reported term is minted into the mapping table and replaced by the table, so it is as restorable as a rule term and never needs the model again — without it, every unseen customer name stops/askuntil someone hand-edits the rules file. Off until a rules file exists; a malformed rules file raises rather than silently degrading to "send the real names". Scope defaults toescalation(the/askhop only) — wrapping every backend is wrong when the agent already runs locally, and with the fail-closed default it would stop every thread whenever the inspector is unreachable;CCDB_ANONYMIZE_SCOPE=allrestores it. Seedocs/anonymization.md. - A local backend is a measured claim, not a configured one (
claude_code_core/local_backend.py):/backend localruns the Codex CLI against a model on the user's own hardware. Pointing the CLI at a local endpoint is not sufficient — measured on codex-cli 0.145.0, a fully local, logged-out run still contactschatgpt.comfor the startup update check and analytics. ccdb therefore generates and owns a separateCODEX_HOMEwith both disabled, re-verifies those settings on every spawn, and refuses to start rather than run a "local" thread that phones home. The check is structural (works on Windows too), not an OS egress rule; re-measure after a CLI upgrade. The separateCODEX_HOMEdoes not cost the operator their skills, despite the obvious worry: codex-cli discovers~/.codex/skillsand~/.claude/skillsindependently ofCODEX_HOME(measured on 0.147.0 by reading the session rollout — 135 skills reached the model from aCODEX_HOMEwith noskills/directory at all). When a local run ignores a skill the index was present and the model did not act on it; do not "fix" that by mirroring directories. Seedocs/local-backend.md. - Escalation is isolated by verified argv, not by procedure (
claude_code_core/escalation.py):/asksends one anonymized, self-contained question to an external model. The CLI runs with--setting-sources "", an empty temp cwd,--tools ""plus--strict-mcp-config, and--before the prompt;verify_isolation()checks the argv immediately before spawning and refuses otherwise. Tools are selected by that one empty allow list and by nothing else — the enumerated deny list that used to sit beside it namedSlashCommandlong after the CLI dropped it, so verification now rejects any argv that names a tool at all, whether to allow or to deny. The first flag is load-bearing — measured with cwd=/home/ebi and all tools already off, the CLI reports a CLAUDE.md-only term as present in its context without it and absent with it, so a naive escalation ships the whole file regardless of how well the question was anonymized. Seedocs/escalation.md. - Runtime management belongs to the runtime's own API (
claude_code_core/ollama_client.py,cogs/ollama_command.py):/backendand/modelchoose a model; they cannot say what is installed, what fits, or what is resident in memory — and when the cloud backends are unavailable those are the only questions that matter./ollamamirrors Ollama's native API (/api/tags,/api/ps,/api/show,/api/pull,/api/delete), deriving its origin fromCCDB_LOCAL_BASE_URLby replacing the terminal/v1so there is still one address to configure. Two judgements are enforced rather than documented: a model that does not advertise thetoolscapability is flagged, because Codex acts only through tool calls and such a model describes work instead of doing it; and the selected model cannot be deleted, because that failure would otherwise surface mid-thread. The install catalog is a static shortlist, never fetched at runtime — a catalog refresh would be exactly the vendor call decision 13 exists to prevent. Relatedly,APPEND_SYSTEM_PROMPTreaches Codex andlocalasdeveloper_instructions, not just Claude: a small model follows one blunt directive far more reliably than the 15 KB skill index it is already given. Seedocs/local-backend.md. - A guard's failure direction follows what it guards (
claude_code_core/privacy/answerability.py):/askruns two local checks on the anonymized text, and they fail in opposite directions on purpose. The leak inspector guards a safety property, so an unreachable inspector blocks — the harm is a real name leaving the machine. The answerability judge guards a quality property (replacement can hide the very subject the question is about, making "the pros and cons oforg-002" perfectly anonymized and unanswerable), so an unreachable judge sends — the harm is refusing a good question, and being wrong costs one call.AnswerabilityVerdict.blocksis the single place that asymmetry lives; copying the inspector's fail-closed stance here would take/askdown whenever the local model is busy, in the name of saving an API call. The judge is skipped entirely when nothing was replaced (replacement cannot break what it did not touch) and overridable withforce: true. Seedocs/escalation.md. - A backend that cannot offer a control says so, rather than pretending (
claude_code_core/pi_runner.py): the pi CLI has no sandbox and shows no trust prompt in the non-interactive modes ccdb uses, so there is no equivalent ofpermission_modeto configure — unsandboxed is the only mode pi has, and--toolsis the only restriction left./backend pitherefore refuses to spawn untilCCDB_PI_ALLOW_UNSANDBOXED=1is set in the deployment environment (never per thread, or any Discord user could grant it to themselves), and declines project trust with--no-approveunlessCCDB_PI_APPROVE_PROJECT=1— a repository ccdb checks out must not be able to reconfigure the agent about to run inside it. The parser is built from recorded runs for the same reason: pi's published event list says a failed turn is anerrorevent, and the measured truth is an assistantmessage_endwithstopReason: "error"and exit code 0, which a trusting parser renders as a successful empty answer. Same for the terminal event (agent_settled, undocumented) and usage (per message, not per turn). Seedocs/pi-backend.mdand ADR-0007. - Where an agent runs is the operator's choice, bounded per thread by an allowlist (
claude_code_core/execution/): every CLI runner passes its launch toprepare_launch()right beforecreate_subprocess_exec, and the selected environment (host,native,bwrap,container,ssh) preflights and rewrites it.hostis the default and is the old code path untouched, not a wrapper. The default and allowlist are env-only (CCDB_EXECUTION_MODE,CCDB_EXECUTION_ALLOWED_MODES);/sandboxchooses only within the list and the runner re-checks it at spawn time. A misconfigured mode refuses every turn instead of degrading tohost, and a mode never labels a boundary it does not provide (nativerefuses pi and adanger-full-accessCodex override). Secrets never go on a command line: containers get variable names, ssh sends listed names withSendEnv.bwraptreats$HOMEas an allowlist (empty tmpfs + binds), never as a deny list, and re-binds the agent's own hook/config files read-only (a sandboxed run must not be able to arm the next unsandboxed one) and hides the user runtime dir (systemd-run --userwould escape)./sandbox,/backend,/model,/effort,/engine-statusand/ollama pull|rm|useuse the sameallowed_user_idsas/skill. Seedocs/execution-environments.mdand ADR-0008. - agy is mapped, not gated (
claude_code_core/antigravity_runner.py): unlike pi, the Antigravity CLI has an approval loop that fails closed in print mode (approval-gated tools are auto-denied, the turn still reportsSUCCESS). So ccdb's own skip-permissions / permission mode map onto--dangerously-skip-permissions/--mode, a denial-only turn is surfaced as an error instead of an empty answer, andCCDB_ALLOWED_TOOLSis refused in every mode where agy approves tools on its own (skipped permissions,acceptEdits,auto) because agy cannot enforce it — onlydefault(auto-deny) andplanrun with an allowlist. Per-turn usage is rebuilt from steps:result.usageis the conversation's running total. Seedocs/backends.mdand ADR-0016.
Why REST API over stdout markers for Claude→ccdb communication
Alternative considered: Claude embeds <!-- ccdb:schedule {...} --> in response text; ccdb parses stdout.
Rejected because: fragile text parsing, untestable, can't be triggered externally, implicit side effect from output.
REST API chosen because: clean interface, independently testable, usable by external systems, already an established ccdb pattern (ext/api_server.py). Claude uses its Bash tool to curl $CCDB_API_URL/api/tasks.
Development
Setup
git clone https://github.com/ebibibi/ebi-agent-chat-relay.git
cd ebi-agent-chat-relay
uv sync --devRunning Tests
uv run pytest tests/ -v --cov=claude_discordAll tests must pass before submitting a PR. CI runs on Python 3.12 and 3.13.
Linting & Formatting
uv run ruff check claude_discord/ # lint
uv run ruff format claude_discord/ # formatCI enforces both ruff check and ruff format --check. Fix all issues before pushing.
Type Checking
uv run pyright claude_discord/ # CI runs this too — a clean ruff run is NOT enoughgetattr(obj, "attr", None) narrows to None for pyright, and discord.py's channel
union has no common history(); annotate such locals as Any rather than ignoring.
Running (standalone)
cp .env.example .env
# Edit .env with your Discord bot token and channel ID
uv run python -m claude_discord.mainEbiBot のデプロイ(⚠️ 手動操作が必要と断言する前に読め)
「手動で git pull が必要」「systemctl restart が必要」等と言う前に、
scripts/pre-start.sh・.github/workflows/・examples/ebibot/cogs/ を読むこと。
自動化されている可能性が高い。
開発フロー(worktree + ローカルテスト)
EbiBot は /home/ebi/claude-code-discord-bridge/ から直接起動する(systemd WorkingDirectory)。
コード変更をPRマージ前に EbiBot で動作確認するための dev worktree モードが用意されている。
# 1. worktreeを作成してブランチで作業(Claude Codeが自動で行う)
git worktree add ../wt-my-feature -b feat/my-feature
# 2. worktreeで実装・ユニットテスト
cd /home/ebi/wt-my-feature
uv run pytest tests/ -v
# 3. dev modeを有効化してDiscord上で動作確認
make dev-on # ~/.ccdb-dev-worktree にパスを書いてbotを再起動
# → Discord上でEbiBotを実際に操作してテスト
# → ユーザーが「OK」と確認するまでこのフェーズを続ける
# 4. 動作確認OKの後、dev modeを解除してPR作成・マージ
make dev-off # ~/.ccdb-dev-worktree を削除してbotを再起動
make pr # ブランチをpushしてGitHub PRを作成
# → PRマージは動作確認が完了した後に行う。先にマージしない!仕組み(pre-start.sh の実装):
uv sync実行後、_ccdb_dev_hook.pyと_ccdb_dev_hook.pthを venv の site-packages に配置する_ccdb_dev_hook.pthが Python 起動時にimport _ccdb_dev_hookを実行する_ccdb_dev_hook.pyがsys.meta_path[0]に_Finderを挿入する_Finderが~/.ccdb-dev-worktreeを読み、claude_discordのimportをworktreeに横取りする
sys.meta_path フックは python -m の CWD-first(sys.path[0] = '')より優先される。
.pth / PYTHONPATH / sitecustomize.py では CWD に勝てないため、この方式を採用した。
通常起動(本番モード):
~/.ccdb-dev-worktree が存在しない場合、フックは何もしない。
mainブランチで変更なしの場合、pre-start.sh が git pull して最新コードを取得する。
Demo scenarios
When a change adds or reshapes something a user sees in the Console, update a scenario under
examples/demo/ so the feature shows up in the first minute of a demo (see docs/demo.md).
tests/test_demo.py loads every bundled scenario.
Code Conventions
Style
- Formatter/Linter: ruff (config in
pyproject.toml) - Type hints: Required on all function signatures
- Python: 3.12+ — use
from __future__ import annotationsin every file - Line length: 100 characters max
- Imports: Sorted by ruff (
Irule). UseTYPE_CHECKINGfor type-only imports
Error Handling
- Use
contextlib.suppress(discord.HTTPException)for Discord API calls that may fail (reactions, message edits) - Never silently swallow errors in business logic — log them
- CLI subprocess errors should yield a
StreamEventwitherrorfield, not raise exceptions
Security (Critical — Auto-Enforced)
This project runs arbitrary Claude Code sessions. Security is non-negotiable.
Before every commit, run the security audit (see .claude/skills/security-audit/SKILL.md):
- Always
create_subprocess_exec: Never useshell=True. The prompt is a direct argument, not shell-interpolated. --separator: Always use--before the prompt argument to prevent flag injection- Session ID validation: Strict regex
^[a-f0-9\-]+$before passing to--resume - Skill name validation: Strict regex
^[\w-]+$before passing to Claude - Environment stripping:
DISCORD_BOT_TOKENand other secrets are removed from the subprocess env so Claude's Bash tool can't read them - No
dangerously_skip_permissionsby default: This flag exists for advanced users who understand the risk
If you modify runner.py, _run_helper.py, or any Cog, the security audit is mandatory before committing.
Naming
- Files:
snake_case.py - Classes:
PascalCase(e.g.,ClaudeRunner,StatusManager) - Functions/methods:
snake_case - Private: prefix with
_(e.g.,_build_args,_run_helper.py) - Constants:
UPPER_SNAKE_CASE
Testing (TDD Enforced)
All new features and bug fixes MUST follow TDD: write tests FIRST, then implement.
- RED: Write a failing test →
uv run pytest tests/test_xxx.py -v→ confirm it FAILS - GREEN: Write minimal code to pass → confirm it PASSES
- REFACTOR: Clean up, keeping tests green
- VERIFY:
uv run ruff check claude_discord/ && uv run pyright claude_discord/ && uv run pytest tests/ -v --cov=claude_discord
See .claude/skills/tdd/SKILL.md for detailed patterns per module type.
- Use
pytestwithpytest-asyncio(auto mode) - Test files go in
tests/mirroring the source structure - Pure logic (parser, chunker, types): 90%+ coverage
- Discord-dependent code (Cogs, StatusManager): use mocks, 30%+ coverage
- Never write implementation code without a corresponding test
Project Structure
claude_discord/ # Installable Python package
__init__.py # Public API exports
cli.py # CLI entry point (ccdb setup/start commands)
main.py # Standalone entry point (setup_bridge + custom cog loader)
setup.py # setup_bridge() — one-call Cog wiring
cog_loader.py # Dynamic custom Cog loader (CUSTOM_COGS_DIR / --cogs-dir)
bot.py # Discord Bot class
protocols.py # Shared protocols (DrainAware)
frontend.py # DiscordFrontend — resolve/create a conversation by key
stores.py # build_session_stores() — every repo, no frontend
concurrency.py # Worktree instructions + active session registry
lounge.py # AI Lounge prompt builder
session_sync.py # CLI session discovery and import
worktree.py # WorktreeManager — safe git worktree lifecycle
cogs/
claude_chat.py # Main chat Cog (thread creation, message handling)
skill_command.py # /skill slash command with autocomplete
session_manage.py # /sessions, /sync-sessions, /resume, /resume-info, /sync-settings
session_sync.py # Thread-creation and message-posting for sync-sessions
prompt_builder.py # build_prompt_and_images() — pure function
webhook_trigger.py # Webhook → Claude Code task execution (CI/CD)
auto_upgrade.py # Webhook → package upgrade + drain-aware restart
ollama_command.py # /ollama status|list|ps|show|pull|rm|use — the local runtime
scheduler.py # Scheduled task executor (SQLite-backed, master loop)
event_processor.py # EventProcessor — state machine for stream-json events
run_config.py # RunConfig dataclass — bundles all CLI execution params
_run_helper.py # Thin orchestration layer (run_claude_with_config)
claude/
runner.py # Claude CLI subprocess manager
parser.py # stream-json event parser
types.py # Type definitions for SDK messages
coordination/
service.py # Posts session lifecycle events to shared channel
database/
models.py # SQLite schema
repository.py # Session CRUD operations
task_repo.py # Scheduled task CRUD (SchedulerCog)
ask_repo.py # Pending AskUserQuestion CRUD
notification_repo.py # Scheduled notification CRUD (REST API)
lounge_repo.py # AI Lounge message CRUD
resume_repo.py # Startup resume CRUD (pending resumes)
settings_repo.py # Per-guild settings
frontend_thread_repo.py # ThreadKey → where the conversation lives
discord_ui/
status.py # Emoji reaction status manager (debounced)
chunker.py # Discord's limits + shim over claude_code_core.rendering
embeds.py # Discord embed builders
views.py # Stop button, ToolSelectView, and shared UI components
prompt_views.py # ChoiceView / FormModal — renders the protocol's prompts
ask_bus.py # Event bus for AskUserQuestion communication
ask_view.py # Buttons/Select Menus for AskUserQuestion
ask_handler.py # collect_ask_answers() — AskUserQuestion UI + DB lifecycle
streaming_manager.py # StreamingMessageManager — debounced message edits
tool_timer.py # LiveToolTimer — elapsed time counter
thread_dashboard.py # Live pinned embed showing session states
file_sender.py # File delivery via .ccdb-attachments-{thread_id}
thread_renamer.py # suggest_title() — background claude -p call for auto thread renaming
thread_retitle.py # RetitleTracker — when a thread has drifted enough to be re-titled
ext/
api_server.py # REST API server (optional, requires aiohttp)
ingest_manifest.py # Reconciles attachments_manifest against delivered files
utils/
logger.py # Logging setup
claude_teams/ # Microsoft Teams frontend (optional extra: [teams])
capabilities.py # The Teams column of SurfaceCapabilities — the shipped one, imported by the conformance tests
config.py # TeamsConfig — validates the identity/addressing whose mistakes Teams reports as silence
manifest.py # App package generator (RSC + SSO declared); icons.py writes placeholder PNGs with no dependency
auth.py # Inbound token verification — algorithms pinned; the claim is `serviceurl`, lower case (measured)
serve.py # `python -m claude_teams serve` — echo-only endpoint for first-contact verification
jwks.py # Signing keys: refresh on unknown kid, rate-limited because that trigger is public
token.py # Outbound client-credentials token, cached and refreshed before it expires
activity.py # InboundActivity — the fields a reply needs, parsed once
conversation.py # ConversationRef — the Bot Connector host + conversation a message belongs to
connector.py # Posting and editing activities on the serviceUrl the conversation named
surface.py # TeamsSurface — the ConversationSurface. One card, not a column of embeds
frontend.py # TeamsFrontend — resolve/create a conversation by key. Passes check_frontend
cards.py # The session card and the prompt cards, bounded to the 28 KB payload Teams accepts
interactions.py # Who may answer a prompt and with what — every field of an inbound action is untrusted
files.py # The consent handshake, and the host allowlist the file's bytes are checked against
commands.py # The text command router — Teams has no slash commands, so this is the whole surface
mentions.py # Who was addressed (by id, not name), and taking the <at> markup out of the prompt
pacer.py # Coalescing per target, one update per interval — the 1,800/hour budget
endpoint.py # The aiohttp route. Nothing is done before the token check; 5xx is never the answer after acceptance
http.py # The only file that knows about aiohttp — everything else takes its transport as a callable
relay/ # Inbound for Teams without inbound on the session host — see docs/teams-relay.md
receiver.py # Public side: verify, enqueue, answer. No client secret, no route to the host
puller.py # Private side: poll outbound, ack only when done, filter duplicates, drop poison
envelope.py # What crosses the queue — carries the token's serviceurl, not the body's
queue.py # push/pull/ack. A separate ack is what makes it at-least-once
storage_queue.py # Azure Queue Storage over REST, defusedxml, pop receipts fully URL-encoded
tests/ # pytest test suite
examples/
ebibot/ # Real-world example: personal bot with custom Cogs
cogs/ # ReminderCog, AutoUpgradeCog, DocsSyncCog,
# AlertResponderCog, JobFailureTriageCog, ThreadCompletionCog
pyproject.toml # Package metadata + dependencies
uv.lock # Dependency lock file
CONTRIBUTING.md # Contribution guidelinesclaude_code_core/privacy/ — the anonymization gateway (surface-agnostic, so a
Teams or CLI frontend gets it for free):
rules.py # Rule table loader (literals, regexes, builtin detectors)
mapping.py # 対応表 — persistent, local, bidirectional alias store
engine.py # Deterministic replace + restore. Calls no model, ever
local_llm.py # The one Ollama transport both local guards share (think:false lives here)
inspector.py # Local leftover check. Reports only. Fails closed
answerability.py # Did replacement leave a question worth asking? Fails open
config.py # Env-driven config; absent rules file = feature off
audit.py # JSONL trail of what actually left the machine
gateway.py # Policy (block/warn/off) + process-wide accessor
backend.py # AnonymizingBackend — SessionBackend decoratorAdding a New Cog
- Create
claude_discord/cogs/your_cog.py - If it runs Claude CLI, use
_run_helper.run_claude_in_thread()— don't duplicate the streaming logic - Export from
claude_discord/cogs/__init__.py - Add to
claude_discord/__init__.pypublic API - Write tests in
tests/test_your_cog.py
Custom Cog Protocol (for external Cogs loaded via --cogs-dir)
Custom Cog files are loaded by cog_loader.py from the directory specified by CUSTOM_COGS_DIR env or --cogs-dir CLI flag. Each .py file must expose:
async def setup(bot, runner, components):
"""Called by load_custom_cogs().
Args:
bot: discord.ext.commands.Bot instance
runner: ClaudeRunner (may be None if Claude chat is disabled)
components: BridgeComponents (session_repo, task_repo, etc.)
"""
await bot.add_cog(MyCog(bot))Rules:
- Files prefixed with
_are skipped - Load order is deterministic (
sorted()by filename) - One Cog's failure is logged and skipped — never blocks others
examples/ebibot/cogs/is the canonical reference implementation
Adding a New Discord UI Component
- Add to the appropriate file in
claude_discord/discord_ui/ - Export from
__init__.pyif it's part of the public API - Test edge cases (empty strings, very long strings, Unicode, code blocks)
Git & PR Workflow
- Branch from
main:feature/description,fix/description,docs/description - CI must pass: Both Python versions x (ruff check + ruff format + pytest)
- No direct push to main: Always create a PR
- Squash merge preferred: Keeps main history clean
- Commit style:
<type>: <description>— types: feat, fix, refactor, docs, test, chore, security
AI Agent Configuration
This project ships AI agent configs for all major tools:
| File | Tool | Purpose |
AGENTS.md | Claude Code, OpenAI Codex | Project context (this file) — the single instruction file |
.github/copilot-instructions.md | GitHub Copilot | Condensed instructions |
.cursorrules | Cursor | IDE-specific rules |
There is no CLAUDE.md. Claude Code reads AGENTS.md (2.1.277+), so one file now
serves both agents instead of a file plus a symlink pointing at it.
Skills (.claude/skills/)
Project-specific skills that help AI agents work effectively on this codebase:
| Skill | Purpose |
tdd | Enforced test-driven development — write tests FIRST, then implement |
verify | Pre-commit quality gate (lint + format + test + security) |
add-cog | Step-by-step guide to scaffold a new Cog |
security-audit | Security checklist specific to subprocess/injection threats |
python-quality | Python coding patterns and project conventions |
test-guide | Testing patterns, mocking Discord objects, coverage goals |
Commands (.claude/commands/)
| Command | Usage |
/verify | Run full verification pipeline |
/new-cog <name> | Scaffold a new Cog with tests |
Hooks (.claude/settings.json)
- PostToolUse (Edit/Write): Auto-format
.pyfiles with ruff after editing
What Does NOT Belong Here
- Personal bot configuration (tokens, channel IDs, user IDs)
- Server-specific Cogs or workflows
- Direct Anthropic API calls (we use Claude Code CLI, not the API). Sole exception: the read-only model lookup in
model_catalog.py, because the CLI can't enumerate models — see Key Design Decision 11 for the rules it has to obey - Heavy dependencies that most users won't need
- Anything that requires secrets to import the package
Other versions1 project uses a version of this.
Word for word · 1
- ebibibi/ebi-agent-chat-relay★ 66100% same
Edited · 0
None found.
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 28,693 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository9 other files it gives its agents.
- .github/copilot-instructions.mdCopilot instructions
- add-cogSkill
- python-qualitySkill
- releaseSkill
- security-auditSkill
- tddSkill
- test-guideSkill
- verifySkill
- relay-thread-managerSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

