Cascade — agent task orchestration platform combining Leantime's strategic coherence with AgentRQ's agent orchestration
Project description
Cascade is a from-scratch, production-hardened reimplementation that fuses Leantime's strategic coherence (task → goal → milestone) with AgentRQ's agent orchestration (dequeue, status state machine, continuous monitoring) — rebuilt in async Python on FastAPI + SQLAlchemy 2.0, with a live HTMX/SSE dashboard.
Quick start · Architecture · MCP tools · Tests · Contributing
✨ Why Cascade
🧱 Tech stack
| Layer | Choice |
|---|---|
| Runtime | Python 3.11+ · FastAPI · Uvicorn |
| ORM | SQLAlchemy 2.0 (async, Mapped[]) · aiosqlite · WAL mode |
| Migrations | Alembic |
| Schemas | Pydantic v2 |
| IDs | python-ulid — time-ordered, sortable |
| Real-time | sse-starlette + in-memory pub/sub |
| Scheduling | croniter (cron-template spawning on the monitoring-loop tick) |
| UI | HTMX + Tailwind (CDN, zero build step) |
| Agent protocol | Model Context Protocol (MCP) tool registry |
🚀 Quick start
pip install cascade-orchestrator
# launch the API + dashboard
cascade
Or from source:
git clone https://github.com/nguyenminhduc9988/cascade.git
cd cascade
python -m venv venv && source venv/bin/activate
pip install -e ".[dev]"
python -m uvicorn cascade.main:app --reload --port 8100
Open http://localhost:8100 for the live dashboard. Interactive API docs
live at /docs, and /api/health reports service health.
The database is created automatically on first boot (init_db). For managed
schema changes, use Alembic:
python -m alembic upgrade head
python -m alembic revision --autogenerate -m "describe change"
🏗️ Architecture
flowchart LR
subgraph Agents["🤖 Agents"]
A1[Agent A]
A2[Agent B]
end
subgraph Cascade["🌀 Cascade Core"]
MCP[MCP Tool Registry<br/>get_task · reply · update_status]
API[FastAPI REST + SSE]
Loop[Monitoring Loop — 10s tick]
DB[(SQLite / Postgres)]
end
subgraph UI["📊 Dashboard"]
Board[Kanban Board]
SSE[Live SSE Stream]
end
A1 -- dequeue / claim --> MCP
A2 -- dequeue / claim --> MCP
MCP --> API
API <--> DB
Loop -- poller / pinger / scheduler --> DB
Loop -- broadcasts --> SSE
SSE --> Board
API --> Board
style Cascade fill:#6366f1,stroke:#4338ca,color:#fff
style Agents fill:#0ea5e9,stroke:#0284c7,color:#fff
style UI fill:#f59e0b,stroke:#d97706,color:#fff
Data model
erDiagram
Project ||--o{ Goal : has
Project ||--o{ Milestone : has
Project ||--o{ Task : contains
Project ||--o{ Event : defines
Goal ||--o{ Task : "linked (read-time progress)"
Milestone ||--o{ Task : "linked (rollup)"
Task ||--o{ Task : "parent / subtasks"
Task ||--o{ TaskDependency : "DAG edges"
Task ||--o{ Message : "conversation log"
Task ||--o{ Telemetry : "audit trail"
Event ||--o{ EventTrigger : "fires"
EventTrigger }o--|| Task : "materialises"
Task is the unified work item (polymorphic: epic / story / task /
subtask) — a status state machine, bidirectional human/agent delegation,
self-referential hierarchy, strategic goal/milestone links, cron-template
spawning and event choreography, all in one model.
⚙️ How it works
🔄 Dequeue = atomic claim
GET /api/tasks/dequeue?project_id=…&assignee=agent scans not_started
tasks in priority order, checks DAG readiness via check_dependencies, and
claims the winner with a single conditional UPDATE:
UPDATE tasks SET status='ongoing' WHERE id=? AND status='not_started'
If the row-count is zero, another agent already won — Cascade moves to the next candidate instead of handing out duplicate work.
🧮 Status state machine
Every transition funnels through TaskService.update_status, validated
against an explicit transition table:
not_started → ongoing → completed | blocked | rejected
blocked → ongoing | rejected | completed
completed → ongoing | not_started (reopen)
rejected → not_started (re-queue)
Each transition sets started_at/completed_at, records telemetry, posts a
system message to the task's conversation log, broadcasts an SSE event —
and if the task declares emit_event_id, fires that event on completion,
driving cross-project choreography automatically.
📈 Goal progress — computed, never stored
GoalService.get_progress counts linked tasks completed/total live, every
time. There is no progress_pct column to fall out of sync — the number you
see is always true.
💓 Continuous monitoring loop
engine/loop.monitoring_loop runs every 10 seconds (not hourly), firing
the poller, pinger and scheduler concurrently — each on its own database
session, since AsyncSession isn't safe to share across coroutines:
- Poller — finds
ongoingtasks with no recent message and nudges them. - Pinger — evicts agent sessions past their heartbeat timeout and broadcasts liveness changes.
- Scheduler — spawns child tasks from due
cron-status templates, idempotently (never double-spawns while a child is still active).
🛡️ Autonomy without recklessness
AutoDecisionService.should_ask_human returns True only for
destructive keywords (delete, drop, purge, production-deploy,
force-push, refund, …). Everything else is auto-resolved by
auto_resolve_choice, which scores options on risk / effort /
reversibility and picks the safest, fastest one — recording its reasoning
as a message on the task.
🤖 MCP tools
Cascade exposes a per-workspace MCP tool
registry — each server instance is bound to one project_id and force-scopes
every call to it, so an agent connected to one project can never read or
write another's data.
| Tool | Purpose |
|---|---|
get_task |
Dequeue + atomically claim the next ready task, or fetch by ID |
create_task |
Decompose / delegate work (parent_id + depends_on) |
reply |
Post progress / reply / permission messages |
update_status |
Transition task status through the state machine |
get_mission |
Big-picture mission + active goals |
get_project_context |
Full project state for strategic coherence |
publish_event |
Emit a cross-project choreography event |
get_dependencies |
Dependency tree — what a task waits on / blocks |
auto_decide |
Auto-resolve a choice without asking a human |
See cascade/mcp/instructions.py for the full
agent operating contract served as the MCP server's system instructions.
🧪 Tests
pip install -e ".[dev]"
pytest -q
40 tests run against an isolated in-memory SQLite database per test (with
PRAGMA foreign_keys=ON to match production), covering the status state
machine, DAG dependency resolution, concurrent dequeue race safety,
concurrent status-transition race safety, cron template spawn integrity,
goal/milestone progress aggregation, cascade-delete referential integrity,
event-trigger choreography, MCP workspace isolation, and the full REST +
HTMX page surface.
🔧 Configuration
All settings are overridable via CASCADE_-prefixed env vars or a .env
file (see cascade/config.py):
| Setting | Default |
|---|---|
CASCADE_DATABASE_URL |
sqlite+aiosqlite:///./cascade.db |
CASCADE_PORT |
8100 |
CASCADE_LOOP_TICK_SECONDS |
10 |
CASCADE_STALL_THRESHOLD_MINUTES |
30 |
CASCADE_SESSION_TIMEOUT_SECONDS |
60 |
CASCADE_ENABLE_MONITORING_LOOP |
true |
CASCADE_ENABLE_SCHEDULER |
true |
🗂️ Project structure
cascade/
├── pyproject.toml # packaging + pytest config
├── alembic/ # async migrations (env.py + versions/)
├── cascade/
│ ├── main.py # FastAPI app factory + lifespan (monitoring loop)
│ ├── config.py # Pydantic Settings (CASCADE_ env prefix)
│ ├── database.py # async engine (WAL + busy_timeout + FK enforcement)
│ ├── models/ # SQLAlchemy 2.0 typed models
│ ├── schemas/ # Pydantic v2 request/response
│ ├── services/ # business logic (thin routers → services)
│ ├── routers/ # REST + SSE + HTMX page handlers
│ ├── mcp/ # MCP server factory + tools + agent instructions
│ ├── engine/ # monitoring loop, poller, pinger, progress tracker
│ ├── integrations/ # Hermes bridge client + standalone monitor daemon
│ └── web/ # Jinja2 templates + static app.js
└── tests/ # pytest-asyncio, 40 tests
🛠️ Contributing
|
📄 License
Released under the MIT License — see LICENSE.
Cascade reinterprets ideas from Leantime (strategic task–goal–milestone coherence) and AgentRQ (agent dequeue + status state machine + monitoring loop), reimplemented from scratch in async Python. It is an independent work and is not affiliated with or endorsed by either project.
⭐ Star this repo if Cascade helps you orchestrate your agents.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cascade_orchestrator-0.1.3.tar.gz.
File metadata
- Download URL: cascade_orchestrator-0.1.3.tar.gz
- Upload date:
- Size: 77.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6eb66f5f42501f3a9a9e55cc431bf3d1a6e2c057402d3d75c56571cbcceecc7c
|
|
| MD5 |
5f920cb29551a0e10521e5e3470544a5
|
|
| BLAKE2b-256 |
c2167e85a9e658d5ce3885ca5db1c3d454ae00924f9ca956a5b7a91ecab68eb7
|
File details
Details for the file cascade_orchestrator-0.1.3-py3-none-any.whl.
File metadata
- Download URL: cascade_orchestrator-0.1.3-py3-none-any.whl
- Upload date:
- Size: 92.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3c49951dc3bf84ea3d82fe7be85cc937ddbe9e9ec5989c134b4a1d5d6dda267d
|
|
| MD5 |
c9346a341094e4d57bd106c38badb811
|
|
| BLAKE2b-256 |
e137da02f5cfcd6ee8b9afae965163d1d67c40e49e182ce016c984895c9da769
|