11 KiB
CLAUDE.md
Project Overview
CheddahBot is a personal AI assistant for Bryan's SEO/AI agency and general life. It has a two-brain architecture: a chat brain (OpenRouter/Ollama/LM Studio) for conversational UI, and an execution brain (Claude Code CLI) for autonomous tasks like press releases, file operations, and shell commands among others.
The bot polls ClickUp for tasks, maps them to skills, and auto-executes or asks permission — then reports results back to ClickUp and the chat UI.
Coding Workflow Rules - while we are working on building/changing the code
- Before making any changes, explain WHAT you plan to change and WHY
- Wait for my approval before editing files UNLESS I TELL YOU I AM GOING AWAY FOR A BIT - then you can use your judgement but commit the code at every big change.
- After making changes, provide a brief summary of every file modified and what changed
- If you encounter a problem during implementation, STOP and explain it instead of trying to fix it silently
- Never refactor, rename, or reorganize code beyond what was explicitly asked for
Architecture
Gradio UI (ui.py)
↓
AgentRegistry (agent_registry.py)
├── default agent ← AgentConfig (config.py)
├── writer agent
├── researcher agent
└── ops agent
↓
Agent (agent.py) ← Memory (memory.py, 4-layer, per-agent scoping)
↓ ← Skills (skills.py, markdown skills with frontmatter)
LLM Adapter (llm.py)
├── Chat brain: OpenRouter / Ollama / LM Studio (per-agent model override)
└── Execution brain: Claude Code CLI (subprocess)
↓
Tool Registry (tools/__init__.py) ← auto-discovers tools in tools/
├── delegate_task → execution brain
└── delegate_to_agent → cross-agent delegation (depth-limited)
↓
Scheduler (scheduler.py)
├── Poll loop: cron-based scheduled tasks
├── Heartbeat: periodic checklist from HEARTBEAT.md
└── ClickUp loop: polls ClickUp → maps to skills → executes
↓
NotificationBus (notifications.py) → Gradio / future Discord / Slack
Commands
# Run the app
uv run python -m cheddahbot
# Run tests (124 tests)
uv run pytest
# Run tests verbose
uv run pytest -v --no-cov
# Run only integration tests (requires live ClickUp API token)
uv run pytest -m integration
# Lint
uv run ruff check .
# Format
uv run ruff format .
# Add a dependency
uv add <package>
# Add a dev/test dependency
uv add --group test <package>
Key Files
| File | Purpose |
|---|---|
cheddahbot/__main__.py |
Entry point, multi-agent wiring |
cheddahbot/agent.py |
Core agentic loop (chat + tool execution) |
cheddahbot/agent_registry.py |
Multi-agent registry (named agents, default) |
cheddahbot/llm.py |
Two-brain LLM adapter |
cheddahbot/config.py |
Config + AgentConfig dataclasses |
cheddahbot/db.py |
SQLite persistence (WAL, thread-safe) |
cheddahbot/scheduler.py |
Three daemon threads: poll, heartbeat, ClickUp |
cheddahbot/clickup.py |
ClickUp REST API v2 client (httpx) |
cheddahbot/notifications.py |
UI-agnostic pub/sub notification bus |
cheddahbot/memory.py |
4-layer memory with semantic search + scoping |
cheddahbot/router.py |
System prompt builder |
cheddahbot/skills.py |
Markdown skill registry (discovers skills/*.md) |
cheddahbot/ui.py |
Gradio web interface |
cheddahbot/tools/ |
Tool modules (auto-discovered) |
cheddahbot/tools/delegate.py |
delegate_task + delegate_to_agent tools |
config.yaml |
Runtime configuration (incl. agents section) |
identity/SOUL.md |
Agent personality |
identity/USER.md |
User profile |
skills/ |
Markdown skill files with YAML frontmatter |
scripts/create_clickup_task.py |
CLI script to create ClickUp tasks |
docs/clickup-task-creation.md |
Task creation conventions, per-type fields, and defaults |
Conventions
- Config precedence: env vars > config.yaml > dataclass defaults
- ClickUp env vars:
CLICKUP_API_TOKEN,CLICKUP_WORKSPACE_ID,CLICKUP_SPACE_ID - Tool registration: Use the
@tool("name", "description", category="cat")decorator in any file undercheddahbot/tools/— auto-discovered on startup - Tool context: Tools can accept
ctx: dict | None = Noneto getconfig,db,agent,memory,agent_registryinjected - Skills:
.mdfiles inskills/with YAML frontmatter (name,description,tools,agents). Files without frontmatter are data files (skipped by registry) - Multi-agent: Configure agents in
config.yamlunderagents:key. Each agent hasname,display_name,model(override),tools(whitelist),memory_scope. First agent is the default. Usedelegate_to_agenttool for cross-agent delegation (depth limit: 3). - Memory scoping: Agents with
memory_scopeset usememory/{scope}/subdirectory. Empty scope = sharedmemory/root. Fallback search checks both scoped and shared directories. - Database: SQLite with WAL mode, thread-local connections via
threading.local() - KV store: Task state stored as JSON at
clickup:task:{id}:statekeys - ClickUp field mapping:
Work Categoryfield (notTask Type) identifies task types like "Press Release", "Link Building". TheCustomerfield holds the client name. - Notifications: All scheduler events go through
NotificationBus.push(), never directly to a UI - Tests: Use
respxto mock httpx calls,tmp_dbfixture for isolated SQLite instances - ClickUp attachments:
ClickUpClient.upload_attachment()uses module-levelhttpx.post()(not the shared client) for multipart uploads - External tools: Subprocess calls to external projects (e.g. Big-Link-Man) must use the tool's own
.venvPython, neveruv runfrom CheddahBot's environment. Resolve the venv python at{tool_dir}/.venv/Scripts/python.exe(Windows) or{tool_dir}/.venv/bin/python(Linux/Mac). Fail if no venv exists.
Multi-Agent Configuration
Agents are defined in config.yaml under the agents: key. Each entry creates an AgentConfig (see cheddahbot/config.py). The first agent is always the default — used by the scheduler, heartbeat, and UI.
If you omit the agents: section entirely, CheddahBot runs in single-agent mode (backward compatible).
AgentConfig fields
| Field | Type | Default | What it does |
|---|---|---|---|
name |
str | "default" |
Internal ID, used for delegation and registry lookup |
display_name |
str | "CheddahBot" |
Human-readable name shown in logs/UI |
personality_file |
str | "" |
Path to a SOUL-like .md file. Empty = use identity/SOUL.md |
model |
str | "" |
Chat brain model override. Empty = use global chat_model |
tools |
list/null | null |
Tool whitelist. null = all tools, [] = no tools, ["tool1"] = only those |
skills |
list/null | null |
Skill filter. null = auto (skills matching agent name) |
memory_scope |
str | "" |
Memory namespace. Empty = shared memory/ root. Set to e.g. "research" to use memory/research/ |
Adding a new agent
- Add an entry to
config.yaml:
agents:
# ... existing agents ...
- name: myagent
display_name: My Agent
personality_file: "identity/MYAGENT.md" # optional
model: "" # or override e.g. "anthropic/claude-sonnet-4.5"
tools: [web_search, delegate_task, remember, search_memory]
memory_scope: "" # "" = shared, "myagent" = isolated
-
(Optional) Create a personality file at the path you specified — same format as
identity/SOUL.md. -
Restart the app. Agents are wired at startup in
__main__.pyand cannot be hot-reloaded.
How agents interact
- All agents share the same ToolRegistry and SkillRegistry (singletons). Tool whitelists just filter what each agent sees.
- Cross-agent delegation: Any agent can call
delegate_to_agent("researcher", "find X")to route work to another agent. Max depth: 3 (prevents infinite loops). - Execution brain: Any agent can call
delegate_task("do X")to drop work to the Claude Code CLI subprocess. This is the "doer" — it has Bash, Read, Edit, Write, Glob, Grep. - Memory: Agents with the same
memory_scope(or empty) share memory. Set a unique scope to isolate an agent's long-term memory and daily logs.
Key files
| File | Role |
|---|---|
config.yaml (agents section) |
Defines which agents exist and their config |
cheddahbot/config.py (AgentConfig) |
Dataclass defining agent fields |
cheddahbot/agent.py (Agent) |
Agent instance — conversation loop, tool calls |
cheddahbot/agent_registry.py |
Registry holding all agents by name |
cheddahbot/__main__.py |
Startup — loops over config, creates and wires agents |
cheddahbot/tools/delegate.py |
delegate_task + delegate_to_agent tools |
ClickUp Skill Mapping
The scheduler maps ClickUp Work Category → tool name via config.yaml:
skill_map:
"Press Release":
tool: "write_press_releases"
auto_execute: true
required_fields: [topic, company_name, target_url]
field_mapping:
topic: "PR Topic" # ClickUp custom field for PR topic/keyword
company_name: "Customer" # looks up "Customer" custom field
target_url: "IMSURL" # target money-site URL (required)
Task lifecycle: to do → discovered → approved/awaiting_approval → executing → completed/failed (+ attachments uploaded)
Testing
Tests live in tests/ and use pytest. All tests run offline with mocked APIs.
test_clickup.py— API response parsing + HTTP client (respx mocks)test_db.py—kv_scanand notifications table methodstest_notifications.py— NotificationBus pub/sub behaviortest_clickup_tools.py— Chat tool state machine (approve/decline)test_email.py— EmailClient SMTP send + attachments (mocked)test_docx_export.py— Plain text → .docx formatting and file creationtest_press_advantage.py— Press Advantage API client, company parsing, link building, submit tooltest_scheduler_helpers.py—_extract_docx_pathsregex extraction from tool output
Fixtures in conftest.py: tmp_db (fresh SQLite), sample_clickup_task_data (realistic API response).
Don't
- Don't edit
.env— it contains secrets - Don't manually activate venvs — use
uv run - Don't add to
requirements.txt— useuv add(pyproject.toml) - Don't call tools directly from UI code — go through
NotificationBusfor scheduler events - Don't store ClickUp state outside of
kv_store— it's the single source of truth - Restarting CheddahBot — there is no watchdog/autostart. Kill the old process, then relaunch with
uv run python -m cheddahbot(orbash start.sh). Do NOT launch a second instance without killing the first or you'll get port conflicts.