CheddahBot/CLAUDE.md

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 under cheddahbot/tools/ — auto-discovered on startup
  • Tool context: Tools can accept ctx: dict | None = None to get config, db, agent, memory, agent_registry injected
  • Skills: .md files in skills/ with YAML frontmatter (name, description, tools, agents). Files without frontmatter are data files (skipped by registry)
  • Multi-agent: Configure agents in config.yaml under agents: key. Each agent has name, display_name, model (override), tools (whitelist), memory_scope. First agent is the default. Use delegate_to_agent tool for cross-agent delegation (depth limit: 3).
  • Memory scoping: Agents with memory_scope set use memory/{scope}/ subdirectory. Empty scope = shared memory/ 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}:state keys
  • ClickUp field mapping: Work Category field (not Task Type) identifies task types like "Press Release", "Link Building". The Customer field holds the client name.
  • Notifications: All scheduler events go through NotificationBus.push(), never directly to a UI
  • Tests: Use respx to mock httpx calls, tmp_db fixture for isolated SQLite instances
  • ClickUp attachments: ClickUpClient.upload_attachment() uses module-level httpx.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 .venv Python, never uv run from 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

  1. 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
  1. (Optional) Create a personality file at the path you specified — same format as identity/SOUL.md.

  2. Restart the app. Agents are wired at startup in __main__.py and 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.pykv_scan and notifications table methods
  • test_notifications.py — NotificationBus pub/sub behavior
  • test_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 creation
  • test_press_advantage.py — Press Advantage API client, company parsing, link building, submit tool
  • test_scheduler_helpers.py_extract_docx_paths regex 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 — use uv add (pyproject.toml)
  • Don't call tools directly from UI code — go through NotificationBus for 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 (or bash start.sh). Do NOT launch a second instance without killing the first or you'll get port conflicts.