CheddahBot/CLAUDE.md

206 lines
10 KiB
Markdown

# 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
```bash
# 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 |
## 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
## 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`:
```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
```
2. (Optional) Create a personality file at the path you specified — same format as `identity/SOUL.md`.
3. 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`:
```yaml
skill_map:
"Press Release":
tool: "write_press_releases"
auto_execute: true
field_mapping:
topic: "task_name" # uses ClickUp task name
company_name: "Customer" # looks up "Customer" custom field
```
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_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.