Fix 4: Content watcher now sets ClickUp task to "automation underway" after
matching, matching the behavior of the link building watcher.
Fix 5: Content watcher now moves .xlsx files to a processed/ subfolder on
success, preventing re-processing on subsequent scans.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 1 now writes OutlinePath to a ClickUp custom field via
set_custom_field_by_name(). Phase 2 reads it back with
get_custom_field_by_name(), falling back to convention path
({outline_dir}/{slug}/outline.md) if the field is empty.
Added get_task(), set_custom_field_by_name(), and
get_custom_field_by_name() helpers to ClickUpClient.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace single-day task filter with multi-pass sweep when no explicit
target_date: (1) due today, (2) overdue + current month tag, (3) last
month tag, (4) look-ahead 2 days. Deduplicate across passes.
Remove KV store from submit (dedup by job file existence) and result
poller (scan results/ folder directly, move to processed/ after handling).
Scheduler auto-submit no longer passes explicit target_date.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add date_updated field to ClickUpTask dataclass. Add _recover_stale_tasks()
to scheduler that resets tasks stuck in "automation underway" for >2 hours
back to "to do" with an explanatory comment. This prevents tasks from being
permanently stuck if CheddahBot crashes mid-execution.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Content tasks now trigger from Cora xlsx files dropped in Z:/content-cora-inbox/
instead of auto-firing from ClickUp polling. The watcher fuzzy-matches files to
ClickUp tasks and auto-detects content type from URL presence (optimization vs
new content). Adds cli_flags support for service page hints.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 1 researches competitors and generates an outline via the execution brain,
saves it to a network/local path, and pauses for human review. Phase 2 picks up
the approved outline and writes full SEO-optimized content. ClickUp integration
maps "On Page Optimization" and "Content Creation" work categories, with
"outline approved" added to poll_statuses for automatic Phase 2 triggering.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Auto-submit Cora jobs for tasks due today on each autocora loop cycle
- Move ClickUp tasks to "automation underway" at submission time
- Default to blank URL for tasks missing IMSURL (new content)
- Use task Keyword field as project_name in folder watcher (not task name)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Automates Cora SEO report workflow: queries ClickUp for qualifying tasks,
submits jobs to a shared folder queue, polls for results, and updates task
statuses. Includes two tools (submit_autocora_jobs, poll_autocora_results),
a scheduler polling loop, and 30 tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Pass scheduler instance to API router and UI for loop timestamps
and force-run endpoints
- Add interruptible waits and force_heartbeat/force_poll methods
- Record last_run timestamps for all scheduler loops in KV store
- Update press release headline examples with real client headlines
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New /api/tasks/need-cora endpoint pulls tasks needing Cora reports
across Link Building, On Page Optimization, and Content Creation work
categories, deduplicates by Keyword field, and filters to a 30-day
window. Dashboard overview now shows Cora Reports as the first section
with color-coded type badges (LB, OPO, Content).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Track per-call token usage and estimated costs across all OpenRouter models.
Switch planner agent from Claude Sonnet 4.6 ($3/$15 per M) to Grok 4.1 Fast
($0.20/$0.50 per M) for ~25x cost reduction. Add budget alerts, a dashboard
card, and a check_api_usage tool for visibility into spending.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>