- New report_issue tool logs bugs/improvements to memory/improvement_requests.md
- Planner agent (Sonnet via OpenRouter) for architecture and debugging tasks
- Heartbeat checks for pending improvement requests to surface to Bryan
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- _maybe_set_title sets a quick truncated fallback immediately
- Then fires a background thread to ask the LLM for a 5-8 word summary
- Background thread doesn't block the streaming response
- Title appears in sidebar on first chunk, then upgrades when LLM responds
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- agent.new_conversation() now just resets conv_id to None; DB row is
created lazily by ensure_conversation() when user sends first message
- on_app_load no longer eagerly creates a conversation on page load
- list_conversations filters out conversations with no messages so
orphaned empty rows don't appear in the sidebar
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move _maybe_set_title() to run at start of respond() generator (before streaming)
so titles are set even if the generator is closed mid-stream by Gradio
- Refresh sidebar conv list on first streaming chunk for immediate title display
- Backfilled 34 existing conversation titles from their first user messages
- Add scheduler loop status cards (heartbeat, poll, clickup, folder_watch) to
both System Health and Notifications tabs in the HTML dashboard
- Loop cards show relative time (e.g. "3m ago") with color-coded status
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Link building runs that finish successfully are already deployed,
so they go straight to complete. PRs still go to internal review.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Only show To Do, In Progress, Error, Automation Underway counts.
Exclude tasks due more than 1 month ago. Add "Scheduled next month"
card showing upcoming work.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevents the watcher from picking up ~$*.xlsx lock files created by
Excel, which would match the same ClickUp task and corrupt its status.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Filters out automation-touched tasks (error, automation underway, complete,
closed, done, internal review) and sorts by due date. Renders with 10-at-a-time
pagination and responsive mobile layout.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tasks now show "automation underway" when the bot picks them up and "error"
on failure, replacing the old "in progress" / "to do" fallbacks that were
invisible on Bryan's ClickUp board. Folder watcher also syncs ClickUp status
on match, missing IMSURL, pipeline failure, success, and exceptions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove status filter from folder watcher so tasks in any open status
(including "internal review") are matched by keyword
- Add retry logic for stuck processing/blocked/unmatched KV states
- Fix notification ordering (newest first, limit 50) and date parsing
- Use BLM's own .venv Python instead of uv run for subprocess calls
- Document external tools venv convention in CLAUDE.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove placeholder URL fallback from ingest-cora args. Add early
validation in run_cora_backlinks and folder watcher — if IMSURL is
empty, block the task with a notification instead of running with a
fake URL. Update tests to pass money_site_url and add missing-URL test.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove status gate from Cora filter — LB Method is the discriminator,
not status. Derive Overview stats and Cora section from /tasks data
directly (no slow /tasks/link-building fetch). Add click-to-copy
Z:\cora-inbox path next to Cora header. Show due dates in This Month.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ClickUp "Client" custom field was deleted; switch all references to
"Customer" across config, tools, tests, and docs. Refine Overview tab:
rename Due Soon to Up Next (today/tomorrow, fallback next 5), expand
This Month to include both month tag and due-date-in-month matches.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch from get_tasks_from_space (all lists) to get_tasks_from_overall_lists
(only "Overall" list per folder) to reduce noise. Add tags and date_done
fields to ClickUpTask. Redesign Overview tab with Due Soon, This Month,
and Cora Reports Needed sections. Add Recently Completed and In Progress
Not Started sections to Link Building tab.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New cheddahbot/api.py: FastAPI router with endpoints for tasks,
link building, press releases, agents, system health, notifications,
KV states, and cache management (all cached 5min)
- Rewrote dashboard/index.html: replaces all hardcoded data with JS
that fetches from /api/ endpoints. Tabs: Overview, Link Building,
Press Releases, By Company, System Health, Agents, Notifications
- Updated __main__.py: mounts API router, removes old inline
/api/linkbuilding/status endpoint
- Fixed run_link_building to reject empty LB Method instead of
defaulting to Cora Backlinks
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Display tool arguments in the calling indicator so user can see what
each tool call is doing (e.g. Calling delegate_task(prompt='...')...)
- Bump MAX_TOOL_ITERATIONS from 5 to 15 for complex chat interactions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Core fixes:
- Rewrite router.py format_messages_for_llm() to properly handle tool
call/result message pairs in OpenAI format instead of faking them as
user messages — root cause of most LLM API errors
- Fix scheduler ignoring auto_execute:false flag, which caused all Link
Building tasks to be incorrectly executed and moved to internal review
- Add safety check so Skipped/Error tool results don't get marked as
completed in ClickUp
Additional improvements:
- Add LLM retry logic (2 retries on transient 5xx/timeout/rate-limit)
- Replace raw LLM tracebacks with friendly error messages
- Fix ghost assistant bubble in UI by deferring append to first chunk
- Auto-title conversations from first user message
- Consistent tool_call_id generation (resolve once, reuse everywhere)
- Reduce pipeline status polling from 3s to 10s
- Update CLAUDE.md: remove stale watchdog/autostart docs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Ruff format: consistent dict/call wrapping in agent.py, db.py,
skills.py, delegate.py
- Replace clickup_approve_task/clickup_decline_task with
clickup_reset_task/clickup_reset_all (simpler state machine)
- Add kv_delete() method to Database
- Add due_date and field filter tests to test_clickup.py
- Update test_clickup_tools.py for new reset tools
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove f-prefix from strings with no placeholders
- Use list unpacking instead of concatenation
- Fix import sorting in test file
- Remove unused Path import
- Use contextlib.suppress instead of try/except/pass
- Wrap long lines to stay under 100 chars
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- /api/linkbuilding/status endpoint returns pending, in-progress,
completed, and failed pipeline states for dashboard consumption
- skills/linkbuilding.md with YAML frontmatter linking tools and agents
- Skill body documents workflow, triggers, default flags, and ClickUp fields
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 4th daemon thread _folder_watch_loop scans Z:/cora-inbox
- Fuzzy-matches .xlsx filename stems to ClickUp Keyword fields
- On match: runs run_cora_backlinks, moves file to processed/
- On failure: marks in KV store, notifies via NotificationBus
- Uses existing _get_clickup_client and notification infrastructure
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ClickUpClient.create_custom_field() for POST /list/{id}/field
- 40+ tests covering output parsers, CLI arg builder, pipeline,
ClickUp state machine, and folder scanning
- All tests mock subprocess.run, never call Big-Link-Man
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New LinkBuildingConfig dataclass (blm_dir, watch_folder, interval, ratio)
- YAML loading block and BLM_DIR env var override in load_config()
- Link Building skill_map entry with field mappings in config.yaml
- link_building section in config.yaml
- link_builder agent definition in agents section
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New linkbuilder agent that handles ClickUp "Link Building" tasks.
For each keyword/company, generates three content pieces via the
execution brain: a guest article (500-700 words), a directory
listing, and a social media post — each with proper SEO anchor
text and backlinks. Integrates with ClickUp for status updates,
comments, and file attachments.
- cheddahbot/tools/linkbuilding.py: build_links tool with full pipeline
- skills/linkbuilding.md: skill prompt for SEO content generation
- config.yaml: linkbuilder agent config + Link Building skill_map entry
- tests/test_linkbuilding.py: 36 tests covering helpers, prompts,
pipeline, file output, error handling, and ClickUp sync
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Removed clickup_task_id from write_press_releases function signature
so the LLM cannot see or fabricate a task ID. The parameter is now
passed through ctx by the ToolRegistry — the scheduler sets it in
args, and execute() moves it into the ctx dict before filtering.
Only system-injected task IDs can reach the tool.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The press release tool now handles its own ClickUp sync lifecycle when
a clickup_task_id is provided — sets status to "in progress" with a
starting comment, uploads docx attachments after creation, then sets
status to "internal review" with a completion comment. The scheduler
now passes clickup_task_id to tools and defers to tool-level sync when
detected, falling back to scheduler-level sync for other tools.
ToolRegistry.execute() now filters args to accepted params to prevent
TypeError when extra keys (like clickup_task_id) are passed to tools
that don't accept them.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add sidebar layout with agent selector (Radio), conversation history
(gr.render), and BrowserState for localStorage session persistence.
Conversations tagged by agent_name for per-agent history filtering.
Sidebar auto-closes on mobile viewports via JS. 11 new tests (135 total).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New tool in delegate.py routes tasks to named agents via
agent.respond_to_prompt(). Includes thread-local depth counter
(max 3) to prevent infinite A->B->A delegation loops. Extended
ctx injection in ToolRegistry to include agent_registry. Wired
agent_registry into ToolRegistry from __main__.py.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Loop over config.agents to create per-agent LLM (when model override set),
Agent, MemorySystem (with scope), and register in AgentRegistry. Shared
ToolRegistry and SkillRegistry are wired to all agents. Scheduler and UI
use the default agent.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MemorySystem now accepts optional scope parameter. When set:
- Memory files go to memory/{scope}/ subdirectory
- Fallback search covers both scoped and shared directories
Unscoped agents (scope="") use the shared memory/ root directory.
This enables agents to have private memory while still searching
shared knowledge.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Agent changes:
- Accept optional AgentConfig in __init__
- Add name property
- Filter tools via agent_config.tools whitelist in respond()
- Use agent-specific personality file when configured
- Pass agent name to skills registry for filtering
ToolRegistry changes:
- get_tools_schema() accepts filter_names parameter
- get_tools_description() accepts filter_names parameter
- When filter_names is None, all tools are returned (backward compat)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New file cheddahbot/agent_registry.py. Holds multiple Agent instances
keyed by name with methods: register(), get(), list_agents(), and
default property. First registered agent is the default.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New AgentConfig dataclass in config.py with fields:
- name, display_name, personality_file, model
- tools (whitelist), skills (filter), memory_scope
Loaded from config.yaml under agents: key. Defaults to single
agent for backward compatibility when section is omitted.
config.yaml now includes 4 agent configs: default, writer,
researcher, ops — each with appropriate tool/skill whitelists.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The _load_skill() function now strips YAML frontmatter (--- ... ---)
before returning skill content. This prevents the execution brain
from receiving metadata intended for the skill registry.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- router.py: build_system_prompt() gets skills_context parameter,
injected between memory and tools sections
- agent.py: Agent gets set_skills_registry(), calls it in respond()
to get skills prompt section
- __main__.py: Creates SkillRegistry from skills_dir, wires to agent
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New module (not package) that discovers .md files with YAML frontmatter
in the skills/ directory. Provides:
- SkillDef dataclass: name, description, content, tools, agents, file_path
- SkillRegistry: discovers skills, filters by agent, builds prompt sections
- _parse_frontmatter(): splits YAML frontmatter from markdown body
- get_prompt_section(agent_name): builds system prompt injection
- get_body(name): returns skill content without frontmatter
Files without frontmatter (data files) are automatically skipped.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously tool results were injected as role:user messages which
confuses some models. Now the live agent loop uses proper OpenAI
function-calling format:
- Assistant messages include tool_calls array with IDs
- Tool results use role:tool with matching tool_call_id
History replay in router.py is unchanged (no tool_call_ids in DB).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When config.shell.require_approval is True, run_command now refuses
execution and directs the user to delegate_task instead. The execution
brain (Claude Code CLI) has its own approval controls.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously respond() accepted files but silently dropped them.
Now when files are attached:
- Images are base64-encoded as image_url content parts
- Text files are read and inlined as text content parts
- The last user message is converted to multipart format
Follows the same encoding pattern used in tools/image.py.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace 4 standalone sqlite3.connect()/conn.close() pairs with a
thread-local _embed_conn property, matching the pattern in db.py.
Adds WAL mode for better concurrent read/write performance.
This prevents potential collisions between scheduler threads and
Gradio request threads accessing the embedding database.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove unused modules that were never called at startup:
- cheddahbot/skills/__init__.py (dead @skill decorator system)
- cheddahbot/providers/__init__.py (empty placeholder)
- cheddahbot/tools/build_skill.py (depends on dead skills system)
- cheddahbot/tools/build_tool.py (security risk: generates arbitrary Python)
Also fix all pre-existing ruff lint errors across the codebase:
- Fix import sorting, unused imports, line length violations
- Fix type comparisons (use `is` instead of `==`)
- Fix implicit Optional types (dict -> dict | None)
- Fix unused variables, ambiguous variable names
- Apply ruff format for consistent style
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a press release is triggered via chat with a ClickUp task ID, the
tool now uploads .docx attachments, posts a result comment, and updates
task status — matching what the scheduler does automatically. ClickUp
sync is wrapped in try/except so failures don't lose PR results.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add _retry() helper to ClickUpClient (3 attempts, exponential backoff,
only retries 5xx/transport errors)
- Apply retry to update_task_status, add_comment, upload_attachment
- Fix review_status from "review" to "internal review" in config and defaults
- Update SOUL.md personality and USER.md profile
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The LLM was delegating write_press_releases to the execution brain
(Claude Code CLI subprocess), bypassing the Python tool's orchestration,
skill prompts, and pipeline status updates. Add an exception to the
system prompt so specialized tools are called directly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Database.disable_task() public method and replace direct _conn
access in scheduler._run_due_tasks().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a press release task completes, docx file paths are extracted from
the tool output and uploaded as attachments to the ClickUp task. The
completion comment now includes an attachment count note.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Strip unused UI components: voice chat accordion, conversation history
panel, and settings accordion. Removes associated event handlers and
helper functions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>