cascade-orchestrator 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. cascade_orchestrator-0.1.0/LICENSE +21 -0
  2. cascade_orchestrator-0.1.0/PKG-INFO +290 -0
  3. cascade_orchestrator-0.1.0/README.md +247 -0
  4. cascade_orchestrator-0.1.0/cascade/__init__.py +8 -0
  5. cascade_orchestrator-0.1.0/cascade/config.py +48 -0
  6. cascade_orchestrator-0.1.0/cascade/database.py +69 -0
  7. cascade_orchestrator-0.1.0/cascade/engine/__init__.py +1 -0
  8. cascade_orchestrator-0.1.0/cascade/engine/loop.py +105 -0
  9. cascade_orchestrator-0.1.0/cascade/engine/pinger.py +39 -0
  10. cascade_orchestrator-0.1.0/cascade/engine/poller.py +38 -0
  11. cascade_orchestrator-0.1.0/cascade/engine/progress_tracker.py +63 -0
  12. cascade_orchestrator-0.1.0/cascade/integrations/__init__.py +33 -0
  13. cascade_orchestrator-0.1.0/cascade/integrations/hermes_bridge.py +616 -0
  14. cascade_orchestrator-0.1.0/cascade/integrations/monitor_daemon.py +357 -0
  15. cascade_orchestrator-0.1.0/cascade/main.py +122 -0
  16. cascade_orchestrator-0.1.0/cascade/mcp/__init__.py +5 -0
  17. cascade_orchestrator-0.1.0/cascade/mcp/instructions.py +34 -0
  18. cascade_orchestrator-0.1.0/cascade/mcp/server.py +74 -0
  19. cascade_orchestrator-0.1.0/cascade/mcp/tools.py +230 -0
  20. cascade_orchestrator-0.1.0/cascade/models/__init__.py +26 -0
  21. cascade_orchestrator-0.1.0/cascade/models/dependency.py +44 -0
  22. cascade_orchestrator-0.1.0/cascade/models/event.py +58 -0
  23. cascade_orchestrator-0.1.0/cascade/models/goal.py +46 -0
  24. cascade_orchestrator-0.1.0/cascade/models/message.py +38 -0
  25. cascade_orchestrator-0.1.0/cascade/models/milestone.py +43 -0
  26. cascade_orchestrator-0.1.0/cascade/models/project.py +57 -0
  27. cascade_orchestrator-0.1.0/cascade/models/task.py +144 -0
  28. cascade_orchestrator-0.1.0/cascade/models/telemetry.py +34 -0
  29. cascade_orchestrator-0.1.0/cascade/routers/__init__.py +21 -0
  30. cascade_orchestrator-0.1.0/cascade/routers/dashboard.py +149 -0
  31. cascade_orchestrator-0.1.0/cascade/routers/events.py +61 -0
  32. cascade_orchestrator-0.1.0/cascade/routers/goals.py +70 -0
  33. cascade_orchestrator-0.1.0/cascade/routers/milestones.py +60 -0
  34. cascade_orchestrator-0.1.0/cascade/routers/pages.py +154 -0
  35. cascade_orchestrator-0.1.0/cascade/routers/projects.py +69 -0
  36. cascade_orchestrator-0.1.0/cascade/routers/tasks.py +204 -0
  37. cascade_orchestrator-0.1.0/cascade/schemas/__init__.py +51 -0
  38. cascade_orchestrator-0.1.0/cascade/schemas/event.py +50 -0
  39. cascade_orchestrator-0.1.0/cascade/schemas/goal.py +57 -0
  40. cascade_orchestrator-0.1.0/cascade/schemas/message.py +31 -0
  41. cascade_orchestrator-0.1.0/cascade/schemas/milestone.py +42 -0
  42. cascade_orchestrator-0.1.0/cascade/schemas/project.py +34 -0
  43. cascade_orchestrator-0.1.0/cascade/schemas/task.py +92 -0
  44. cascade_orchestrator-0.1.0/cascade/services/__init__.py +25 -0
  45. cascade_orchestrator-0.1.0/cascade/services/auto_decision.py +116 -0
  46. cascade_orchestrator-0.1.0/cascade/services/event_service.py +104 -0
  47. cascade_orchestrator-0.1.0/cascade/services/goal_service.py +130 -0
  48. cascade_orchestrator-0.1.0/cascade/services/milestone_service.py +125 -0
  49. cascade_orchestrator-0.1.0/cascade/services/monitor_service.py +182 -0
  50. cascade_orchestrator-0.1.0/cascade/services/project_service.py +111 -0
  51. cascade_orchestrator-0.1.0/cascade/services/scheduler_service.py +124 -0
  52. cascade_orchestrator-0.1.0/cascade/services/task_service.py +478 -0
  53. cascade_orchestrator-0.1.0/cascade/utils.py +28 -0
  54. cascade_orchestrator-0.1.0/cascade/web/static/app.js +50 -0
  55. cascade_orchestrator-0.1.0/cascade/web/templates/base.html +50 -0
  56. cascade_orchestrator-0.1.0/cascade/web/templates/board.html +55 -0
  57. cascade_orchestrator-0.1.0/cascade/web/templates/dashboard.html +98 -0
  58. cascade_orchestrator-0.1.0/cascade/web/templates/partials/goal_card.html +14 -0
  59. cascade_orchestrator-0.1.0/cascade/web/templates/partials/progress_bar.html +12 -0
  60. cascade_orchestrator-0.1.0/cascade/web/templates/partials/project_aggregate.html +41 -0
  61. cascade_orchestrator-0.1.0/cascade/web/templates/partials/task_card.html +24 -0
  62. cascade_orchestrator-0.1.0/cascade/web/templates/partials/task_list.html +15 -0
  63. cascade_orchestrator-0.1.0/cascade/web/templates/project.html +113 -0
  64. cascade_orchestrator-0.1.0/cascade/web/templates/task.html +157 -0
  65. cascade_orchestrator-0.1.0/cascade_orchestrator.egg-info/PKG-INFO +290 -0
  66. cascade_orchestrator-0.1.0/cascade_orchestrator.egg-info/SOURCES.txt +78 -0
  67. cascade_orchestrator-0.1.0/cascade_orchestrator.egg-info/dependency_links.txt +1 -0
  68. cascade_orchestrator-0.1.0/cascade_orchestrator.egg-info/entry_points.txt +3 -0
  69. cascade_orchestrator-0.1.0/cascade_orchestrator.egg-info/requires.txt +19 -0
  70. cascade_orchestrator-0.1.0/cascade_orchestrator.egg-info/top_level.txt +1 -0
  71. cascade_orchestrator-0.1.0/pyproject.toml +80 -0
  72. cascade_orchestrator-0.1.0/setup.cfg +4 -0
  73. cascade_orchestrator-0.1.0/tests/test_api.py +154 -0
  74. cascade_orchestrator-0.1.0/tests/test_event_service.py +70 -0
  75. cascade_orchestrator-0.1.0/tests/test_goal_service.py +103 -0
  76. cascade_orchestrator-0.1.0/tests/test_mcp_server.py +29 -0
  77. cascade_orchestrator-0.1.0/tests/test_milestone_service.py +61 -0
  78. cascade_orchestrator-0.1.0/tests/test_monitor_service.py +111 -0
  79. cascade_orchestrator-0.1.0/tests/test_project_service.py +51 -0
  80. cascade_orchestrator-0.1.0/tests/test_task_service.py +182 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cascade contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,290 @@
1
+ Metadata-Version: 2.4
2
+ Name: cascade-orchestrator
3
+ Version: 0.1.0
4
+ Summary: Cascade — agent task orchestration platform combining Leantime's strategic coherence with AgentRQ's agent orchestration
5
+ Author: Cascade contributors
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/nguyenminhduc9988/cascade
8
+ Project-URL: Repository, https://github.com/nguyenminhduc9988/cascade
9
+ Project-URL: Issues, https://github.com/nguyenminhduc9988/cascade/issues
10
+ Keywords: agents,orchestration,task-queue,fastapi,mcp,llm-agents,autonomous-agents,workflow
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Framework :: FastAPI
14
+ Classifier: Framework :: AsyncIO
15
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
16
+ Classifier: Topic :: Software Development :: Build Tools
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Requires-Python: >=3.11
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: fastapi
25
+ Requires-Dist: uvicorn[standard]
26
+ Requires-Dist: sqlalchemy[asyncio]
27
+ Requires-Dist: aiosqlite
28
+ Requires-Dist: alembic
29
+ Requires-Dist: pydantic
30
+ Requires-Dist: pydantic-settings
31
+ Requires-Dist: python-ulid
32
+ Requires-Dist: jinja2
33
+ Requires-Dist: python-multipart
34
+ Requires-Dist: apscheduler
35
+ Requires-Dist: croniter
36
+ Requires-Dist: sse-starlette
37
+ Requires-Dist: httpx
38
+ Provides-Extra: dev
39
+ Requires-Dist: pytest; extra == "dev"
40
+ Requires-Dist: pytest-asyncio; extra == "dev"
41
+ Requires-Dist: httpx; extra == "dev"
42
+ Dynamic: license-file
43
+
44
+ # 🌀 Cascade
45
+
46
+ **An agent task orchestration platform** that combines Leantime's *strategic
47
+ coherence* (task–goal–milestone links) with AgentRQ's *agent orchestration*
48
+ (dequeue, status state machine, MCP tools, continuous monitoring loop).
49
+
50
+ Cascade is a from-scratch Python reimplementation and significant improvement
51
+ of AgentRQ (Go), built on FastAPI + SQLAlchemy 2.0 with an HTMX/Tailwind UI and
52
+ real-time SSE updates.
53
+
54
+ ---
55
+
56
+ ## ✨ Highlights
57
+
58
+ - **Strategic coherence** — every task links explicitly to a goal and milestone;
59
+ goal progress is *computed at read-time* from linked tasks (never denormalised).
60
+ - **Pull-based work queue** — agents dequeue the highest-priority `not_started`
61
+ task whose DAG dependencies are all completed (`idx_tasks_dequeue` composite index).
62
+ - **Status state machine** — `not_started → ongoing → completed|blocked|rejected`,
63
+ validated centrally in `TaskService.update_status`.
64
+ - **Continuous monitoring loop** — a 10-second tick (not hourly) runs the poller,
65
+ pinger and scheduler concurrently; stalls are detected and nudged.
66
+ - **Autonomy-first** — `AutoDecisionService` resolves choices automatically and
67
+ only escalates to a human for genuinely irreversible/destructive operations.
68
+ - **Cross-project choreography** — events + triggers materialise tasks on publish.
69
+ - **MCP agent tools** — `get_task`, `create_task`, `reply`, `update_status`,
70
+ `get_mission`, `get_project_context`, `publish_event`, `get_dependencies`.
71
+ - **Real-time UI** — SSE-powered dashboard with agent liveness dots, goal
72
+ progress bars, drag-and-drop Kanban, and per-task conversation logs.
73
+
74
+ ---
75
+
76
+ ## 🧱 Tech stack
77
+
78
+ | Concern | Choice |
79
+ |----------------|------------------------------------------|
80
+ | Runtime | Python 3.11+, FastAPI, Uvicorn |
81
+ | ORM | SQLAlchemy 2.0 (async, `Mapped[]`) + aiosqlite |
82
+ | Migrations | Alembic |
83
+ | Schemas | Pydantic v2 |
84
+ | IDs | `python-ulid` (time-ordered, sortable) |
85
+ | Real-time | `sse-starlette` + in-memory pub/sub |
86
+ | Scheduling | APScheduler + croniter (cron templates) |
87
+ | UI | HTMX + Tailwind (CDN, no build step) |
88
+ | Templating | Jinja2 |
89
+
90
+ ---
91
+
92
+ ## 🚀 Quick start
93
+
94
+ ```bash
95
+ # from /home/minguyen/.hermes
96
+ cd cascade
97
+
98
+ # run with the project venv
99
+ hermes-agent/venv/bin/python -m uvicorn cascade.main:app --reload --port 8100
100
+ ```
101
+
102
+ Open **http://localhost:8100** for the dashboard. The API docs are at
103
+ `/docs`, and `/api/health` reports service health.
104
+
105
+ On first boot the database is created automatically (`init_db`). For managed
106
+ schema changes, use Alembic:
107
+
108
+ ```bash
109
+ hermes-agent/venv/bin/python -m alembic upgrade head
110
+ hermes-agent/venv/bin/python -m alembic revision --autogenerate -m "describe change"
111
+ ```
112
+
113
+ ---
114
+
115
+ ## 🗂️ Project structure
116
+
117
+ ```
118
+ cascade/
119
+ ├── pyproject.toml # dependencies + pytest config
120
+ ├── alembic.ini # migration config (async)
121
+ ├── alembic/ # env.py + versions/
122
+ ├── cascade/
123
+ │ ├── main.py # FastAPI app factory + lifespan (monitoring loop)
124
+ │ ├── config.py # Pydantic Settings (CASCADE_ env prefix)
125
+ │ ├── database.py # async engine, session factory, Base
126
+ │ ├── utils.py # ULID + JSON helpers
127
+ │ ├── models/ # SQLAlchemy 2.0 typed models
128
+ │ ├── schemas/ # Pydantic v2 request/response
129
+ │ ├── services/ # business logic (thin controllers → services)
130
+ │ ├── routers/ # FastAPI route handlers (REST + SSE + HTMX pages)
131
+ │ ├── mcp/ # MCP server factory + tools + agent instructions
132
+ │ ├── engine/ # monitoring loop, poller, pinger, progress tracker
133
+ │ └── web/ # Jinja2 templates + static app.js
134
+ └── tests/ # pytest-asyncio (23 tests)
135
+ ```
136
+
137
+ ---
138
+
139
+ ## 🧠 Data model (the core)
140
+
141
+ ```
142
+ Project ─┬─< Goal ────< Task
143
+ ├─< Milestone─< Task
144
+ └─< Task >─ TaskDependency (DAG edges)
145
+ > Message (append-only conversation)
146
+ > Telemetry (audit trail)
147
+ Event / EventTrigger ── publish ──> auto-create Task
148
+ ```
149
+
150
+ `Task` is the **unified work item** (polymorphic: epic/story/task/subtask) with
151
+ a status state machine, bidirectional human/agent delegation, self-referential
152
+ hierarchy, strategic goal/milestone links, cron-template spawning and event
153
+ choreography — a single model doing what Leantime spreads across many.
154
+
155
+ ---
156
+
157
+ ## ⚙️ Key behaviours
158
+
159
+ ### Dequeue (agent pull queue)
160
+ `GET /api/tasks/dequeue?project_id=…&assignee=agent` returns the highest-priority
161
+ `not_started` task whose every `depends_on` is `completed`. Backed by the
162
+ `idx_tasks_dequeue (project_id, assignee, status)` composite index.
163
+
164
+ ### Status state machine
165
+ All transitions go through `TaskService.update_status`, which validates against
166
+ `VALID_TRANSITIONS`, sets `started_at`/`completed_at`, records telemetry, posts
167
+ a system message and broadcasts an SSE `status_change` event.
168
+
169
+ ### Goal progress (read-time)
170
+ `GoalService.get_progress` counts linked tasks completed/total when
171
+ `auto_aggregate` is True — progress is **never stored/denormalised**.
172
+
173
+ ### Continuous monitoring loop
174
+ `engine/loop.monitoring_loop` runs every **10 seconds**, concurrently executing
175
+ the poller (stall nudging), pinger (dead-session eviction) and scheduler (cron
176
+ template spawning). Stall detection runs on its own slower cadence.
177
+
178
+ ### Autonomy
179
+ `AutoDecisionService.should_ask_human` returns `True` only for destructive
180
+ operations (`delete`, `drop`, `production-deploy`, …); everything else is
181
+ auto-resolved via `auto_resolve_choice` (prefers low-risk, low-effort,
182
+ reversible options).
183
+
184
+ ---
185
+
186
+ ## 🤖 MCP tools
187
+
188
+ | Tool | Purpose |
189
+ |---------------------|--------------------------------------------------|
190
+ | `get_task` | Dequeue next task (no ID) or fetch a specific one|
191
+ | `create_task` | Decompose / delegate (parent_id + depends_on) |
192
+ | `reply` | Post progress/reply/permission messages |
193
+ | `update_status` | Transition task status |
194
+ | `get_mission` | Big-picture mission + active goals |
195
+ | `get_project_context` | Full project state for coherence |
196
+ | `publish_event` | Emit a cross-project choreography event |
197
+ | `get_dependencies` | Dependency tree status |
198
+ | `auto_decide` | Auto-resolve a choice |
199
+
200
+ See [`cascade/mcp/instructions.py`](cascade/mcp/instructions.py:1) for the
201
+ agent operating contract served as the MCP server instructions.
202
+
203
+ ---
204
+
205
+ ## 🧪 Tests
206
+
207
+ ```bash
208
+ cd cascade
209
+ hermes-agent/venv/bin/python -m pytest -q
210
+ ```
211
+
212
+ 23 tests cover the task state machine + dequeue + DAG resolution, goal progress
213
+ aggregation, agent liveness/stall detection, auto-decision, and the REST API +
214
+ HTMX page rendering (isolated in-memory SQLite per test).
215
+
216
+ ---
217
+
218
+ ## 🔧 Configuration
219
+
220
+ All settings are overridable via `CASCADE_`-prefixed env vars or a `.env` file
221
+ (see [`cascade/config.py`](cascade/config.py:1)):
222
+
223
+ | Setting | Default |
224
+ |---------------------------------|--------------------------|
225
+ | `CASCADE_DATABASE_URL` | `sqlite+aiosqlite:///./cascade.db` |
226
+ | `CASCADE_PORT` | `8100` |
227
+ | `CASCADE_LOOP_TICK_SECONDS` | `10` |
228
+ | `CASCADE_STALL_THRESHOLD_MINUTES` | `30` |
229
+ | `CASCADE_SESSION_TIMEOUT_SECONDS` | `60` |
230
+ | `CASCADE_ENABLE_MONITORING_LOOP` | `true` |
231
+
232
+ ---
233
+
234
+ ## 🛠️ Development
235
+
236
+ ### Install dev dependencies
237
+
238
+ ```bash
239
+ cd cascade
240
+ hermes-agent/venv/bin/pip install -e ".[dev]"
241
+ ```
242
+
243
+ ### Run the test suite
244
+
245
+ ```bash
246
+ hermes-agent/venv/bin/python -m pytest -q
247
+ ```
248
+
249
+ Tests use `pytest-asyncio` with an isolated in-memory SQLite database per test,
250
+ so they are fast and side-effect-free. Coverage spans the task state machine and
251
+ dequeue/DAG resolution, goal progress aggregation, agent liveness and stall
252
+ detection, the auto-decision engine, and the REST API + HTMX page rendering.
253
+
254
+ ### Architecture notes
255
+
256
+ - **Thin controllers → services.** Routers only parse + serialise; all business
257
+ logic lives in the `services/` package, keeping endpoints trivial to test.
258
+ - **Read-time aggregation.** Goal progress is *computed* on read (never stored),
259
+ so there is no denormalisation drift to repair.
260
+ - **Centralised state transitions.** Every status change funnels through
261
+ `TaskService.update_status`, which enforces `VALID_TRANSITIONS` and records
262
+ telemetry + SSE broadcasts in one place.
263
+
264
+ ### Database migrations
265
+
266
+ Schema changes are managed with Alembic. Create a new revision, review the
267
+ autogenerated diff, then upgrade:
268
+
269
+ ```bash
270
+ hermes-agent/venv/bin/python -m alembic revision --autogenerate -m "add new table"
271
+ hermes-agent/venv/bin/python -m alembic upgrade head
272
+ ```
273
+
274
+ ### Contributing
275
+
276
+ 1. Fork the repo and create a feature branch.
277
+ 2. Add or update tests for any behaviour change.
278
+ 3. Ensure `pytest -q` passes and no lint regressions.
279
+ 4. Open a pull request describing the change and its rationale.
280
+
281
+ ---
282
+
283
+ ## 📄 License
284
+
285
+ Released under the **MIT License** — see [`LICENSE`](LICENSE:1).
286
+
287
+ Cascade reinterprets ideas from [Leantime](https://github.com/Leantime/leantime)
288
+ (strategic task–goal–milestone coherence) and AgentRQ (agent dequeue + status
289
+ state machine + monitoring loop), reimplemented from scratch in Python. It is an
290
+ independent work and is not affiliated with or endorsed by either project.
@@ -0,0 +1,247 @@
1
+ # 🌀 Cascade
2
+
3
+ **An agent task orchestration platform** that combines Leantime's *strategic
4
+ coherence* (task–goal–milestone links) with AgentRQ's *agent orchestration*
5
+ (dequeue, status state machine, MCP tools, continuous monitoring loop).
6
+
7
+ Cascade is a from-scratch Python reimplementation and significant improvement
8
+ of AgentRQ (Go), built on FastAPI + SQLAlchemy 2.0 with an HTMX/Tailwind UI and
9
+ real-time SSE updates.
10
+
11
+ ---
12
+
13
+ ## ✨ Highlights
14
+
15
+ - **Strategic coherence** — every task links explicitly to a goal and milestone;
16
+ goal progress is *computed at read-time* from linked tasks (never denormalised).
17
+ - **Pull-based work queue** — agents dequeue the highest-priority `not_started`
18
+ task whose DAG dependencies are all completed (`idx_tasks_dequeue` composite index).
19
+ - **Status state machine** — `not_started → ongoing → completed|blocked|rejected`,
20
+ validated centrally in `TaskService.update_status`.
21
+ - **Continuous monitoring loop** — a 10-second tick (not hourly) runs the poller,
22
+ pinger and scheduler concurrently; stalls are detected and nudged.
23
+ - **Autonomy-first** — `AutoDecisionService` resolves choices automatically and
24
+ only escalates to a human for genuinely irreversible/destructive operations.
25
+ - **Cross-project choreography** — events + triggers materialise tasks on publish.
26
+ - **MCP agent tools** — `get_task`, `create_task`, `reply`, `update_status`,
27
+ `get_mission`, `get_project_context`, `publish_event`, `get_dependencies`.
28
+ - **Real-time UI** — SSE-powered dashboard with agent liveness dots, goal
29
+ progress bars, drag-and-drop Kanban, and per-task conversation logs.
30
+
31
+ ---
32
+
33
+ ## 🧱 Tech stack
34
+
35
+ | Concern | Choice |
36
+ |----------------|------------------------------------------|
37
+ | Runtime | Python 3.11+, FastAPI, Uvicorn |
38
+ | ORM | SQLAlchemy 2.0 (async, `Mapped[]`) + aiosqlite |
39
+ | Migrations | Alembic |
40
+ | Schemas | Pydantic v2 |
41
+ | IDs | `python-ulid` (time-ordered, sortable) |
42
+ | Real-time | `sse-starlette` + in-memory pub/sub |
43
+ | Scheduling | APScheduler + croniter (cron templates) |
44
+ | UI | HTMX + Tailwind (CDN, no build step) |
45
+ | Templating | Jinja2 |
46
+
47
+ ---
48
+
49
+ ## 🚀 Quick start
50
+
51
+ ```bash
52
+ # from /home/minguyen/.hermes
53
+ cd cascade
54
+
55
+ # run with the project venv
56
+ hermes-agent/venv/bin/python -m uvicorn cascade.main:app --reload --port 8100
57
+ ```
58
+
59
+ Open **http://localhost:8100** for the dashboard. The API docs are at
60
+ `/docs`, and `/api/health` reports service health.
61
+
62
+ On first boot the database is created automatically (`init_db`). For managed
63
+ schema changes, use Alembic:
64
+
65
+ ```bash
66
+ hermes-agent/venv/bin/python -m alembic upgrade head
67
+ hermes-agent/venv/bin/python -m alembic revision --autogenerate -m "describe change"
68
+ ```
69
+
70
+ ---
71
+
72
+ ## 🗂️ Project structure
73
+
74
+ ```
75
+ cascade/
76
+ ├── pyproject.toml # dependencies + pytest config
77
+ ├── alembic.ini # migration config (async)
78
+ ├── alembic/ # env.py + versions/
79
+ ├── cascade/
80
+ │ ├── main.py # FastAPI app factory + lifespan (monitoring loop)
81
+ │ ├── config.py # Pydantic Settings (CASCADE_ env prefix)
82
+ │ ├── database.py # async engine, session factory, Base
83
+ │ ├── utils.py # ULID + JSON helpers
84
+ │ ├── models/ # SQLAlchemy 2.0 typed models
85
+ │ ├── schemas/ # Pydantic v2 request/response
86
+ │ ├── services/ # business logic (thin controllers → services)
87
+ │ ├── routers/ # FastAPI route handlers (REST + SSE + HTMX pages)
88
+ │ ├── mcp/ # MCP server factory + tools + agent instructions
89
+ │ ├── engine/ # monitoring loop, poller, pinger, progress tracker
90
+ │ └── web/ # Jinja2 templates + static app.js
91
+ └── tests/ # pytest-asyncio (23 tests)
92
+ ```
93
+
94
+ ---
95
+
96
+ ## 🧠 Data model (the core)
97
+
98
+ ```
99
+ Project ─┬─< Goal ────< Task
100
+ ├─< Milestone─< Task
101
+ └─< Task >─ TaskDependency (DAG edges)
102
+ > Message (append-only conversation)
103
+ > Telemetry (audit trail)
104
+ Event / EventTrigger ── publish ──> auto-create Task
105
+ ```
106
+
107
+ `Task` is the **unified work item** (polymorphic: epic/story/task/subtask) with
108
+ a status state machine, bidirectional human/agent delegation, self-referential
109
+ hierarchy, strategic goal/milestone links, cron-template spawning and event
110
+ choreography — a single model doing what Leantime spreads across many.
111
+
112
+ ---
113
+
114
+ ## ⚙️ Key behaviours
115
+
116
+ ### Dequeue (agent pull queue)
117
+ `GET /api/tasks/dequeue?project_id=…&assignee=agent` returns the highest-priority
118
+ `not_started` task whose every `depends_on` is `completed`. Backed by the
119
+ `idx_tasks_dequeue (project_id, assignee, status)` composite index.
120
+
121
+ ### Status state machine
122
+ All transitions go through `TaskService.update_status`, which validates against
123
+ `VALID_TRANSITIONS`, sets `started_at`/`completed_at`, records telemetry, posts
124
+ a system message and broadcasts an SSE `status_change` event.
125
+
126
+ ### Goal progress (read-time)
127
+ `GoalService.get_progress` counts linked tasks completed/total when
128
+ `auto_aggregate` is True — progress is **never stored/denormalised**.
129
+
130
+ ### Continuous monitoring loop
131
+ `engine/loop.monitoring_loop` runs every **10 seconds**, concurrently executing
132
+ the poller (stall nudging), pinger (dead-session eviction) and scheduler (cron
133
+ template spawning). Stall detection runs on its own slower cadence.
134
+
135
+ ### Autonomy
136
+ `AutoDecisionService.should_ask_human` returns `True` only for destructive
137
+ operations (`delete`, `drop`, `production-deploy`, …); everything else is
138
+ auto-resolved via `auto_resolve_choice` (prefers low-risk, low-effort,
139
+ reversible options).
140
+
141
+ ---
142
+
143
+ ## 🤖 MCP tools
144
+
145
+ | Tool | Purpose |
146
+ |---------------------|--------------------------------------------------|
147
+ | `get_task` | Dequeue next task (no ID) or fetch a specific one|
148
+ | `create_task` | Decompose / delegate (parent_id + depends_on) |
149
+ | `reply` | Post progress/reply/permission messages |
150
+ | `update_status` | Transition task status |
151
+ | `get_mission` | Big-picture mission + active goals |
152
+ | `get_project_context` | Full project state for coherence |
153
+ | `publish_event` | Emit a cross-project choreography event |
154
+ | `get_dependencies` | Dependency tree status |
155
+ | `auto_decide` | Auto-resolve a choice |
156
+
157
+ See [`cascade/mcp/instructions.py`](cascade/mcp/instructions.py:1) for the
158
+ agent operating contract served as the MCP server instructions.
159
+
160
+ ---
161
+
162
+ ## 🧪 Tests
163
+
164
+ ```bash
165
+ cd cascade
166
+ hermes-agent/venv/bin/python -m pytest -q
167
+ ```
168
+
169
+ 23 tests cover the task state machine + dequeue + DAG resolution, goal progress
170
+ aggregation, agent liveness/stall detection, auto-decision, and the REST API +
171
+ HTMX page rendering (isolated in-memory SQLite per test).
172
+
173
+ ---
174
+
175
+ ## 🔧 Configuration
176
+
177
+ All settings are overridable via `CASCADE_`-prefixed env vars or a `.env` file
178
+ (see [`cascade/config.py`](cascade/config.py:1)):
179
+
180
+ | Setting | Default |
181
+ |---------------------------------|--------------------------|
182
+ | `CASCADE_DATABASE_URL` | `sqlite+aiosqlite:///./cascade.db` |
183
+ | `CASCADE_PORT` | `8100` |
184
+ | `CASCADE_LOOP_TICK_SECONDS` | `10` |
185
+ | `CASCADE_STALL_THRESHOLD_MINUTES` | `30` |
186
+ | `CASCADE_SESSION_TIMEOUT_SECONDS` | `60` |
187
+ | `CASCADE_ENABLE_MONITORING_LOOP` | `true` |
188
+
189
+ ---
190
+
191
+ ## 🛠️ Development
192
+
193
+ ### Install dev dependencies
194
+
195
+ ```bash
196
+ cd cascade
197
+ hermes-agent/venv/bin/pip install -e ".[dev]"
198
+ ```
199
+
200
+ ### Run the test suite
201
+
202
+ ```bash
203
+ hermes-agent/venv/bin/python -m pytest -q
204
+ ```
205
+
206
+ Tests use `pytest-asyncio` with an isolated in-memory SQLite database per test,
207
+ so they are fast and side-effect-free. Coverage spans the task state machine and
208
+ dequeue/DAG resolution, goal progress aggregation, agent liveness and stall
209
+ detection, the auto-decision engine, and the REST API + HTMX page rendering.
210
+
211
+ ### Architecture notes
212
+
213
+ - **Thin controllers → services.** Routers only parse + serialise; all business
214
+ logic lives in the `services/` package, keeping endpoints trivial to test.
215
+ - **Read-time aggregation.** Goal progress is *computed* on read (never stored),
216
+ so there is no denormalisation drift to repair.
217
+ - **Centralised state transitions.** Every status change funnels through
218
+ `TaskService.update_status`, which enforces `VALID_TRANSITIONS` and records
219
+ telemetry + SSE broadcasts in one place.
220
+
221
+ ### Database migrations
222
+
223
+ Schema changes are managed with Alembic. Create a new revision, review the
224
+ autogenerated diff, then upgrade:
225
+
226
+ ```bash
227
+ hermes-agent/venv/bin/python -m alembic revision --autogenerate -m "add new table"
228
+ hermes-agent/venv/bin/python -m alembic upgrade head
229
+ ```
230
+
231
+ ### Contributing
232
+
233
+ 1. Fork the repo and create a feature branch.
234
+ 2. Add or update tests for any behaviour change.
235
+ 3. Ensure `pytest -q` passes and no lint regressions.
236
+ 4. Open a pull request describing the change and its rationale.
237
+
238
+ ---
239
+
240
+ ## 📄 License
241
+
242
+ Released under the **MIT License** — see [`LICENSE`](LICENSE:1).
243
+
244
+ Cascade reinterprets ideas from [Leantime](https://github.com/Leantime/leantime)
245
+ (strategic task–goal–milestone coherence) and AgentRQ (agent dequeue + status
246
+ state machine + monitoring loop), reimplemented from scratch in Python. It is an
247
+ independent work and is not affiliated with or endorsed by either project.
@@ -0,0 +1,8 @@
1
+ """Cascade — agent task orchestration platform.
2
+
3
+ Combines Leantime's strategic coherence (task-goal-milestone links) with
4
+ AgentRQ's agent orchestration (dequeue, status state machine, MCP tools,
5
+ continuous monitoring loop).
6
+ """
7
+
8
+ __version__ = "0.1.0"
@@ -0,0 +1,48 @@
1
+ """Application configuration via Pydantic Settings.
2
+
3
+ All settings can be overridden with environment variables prefixed ``CASCADE_``
4
+ (e.g. ``CASCADE_PORT=8100``) or via a ``.env`` file in the working directory.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from pydantic_settings import BaseSettings, SettingsConfigDict
10
+
11
+
12
+ class Settings(BaseSettings):
13
+ """Central configuration for the Cascade platform."""
14
+
15
+ model_config = SettingsConfigDict(
16
+ env_prefix="CASCADE_",
17
+ env_file=".env",
18
+ env_file_encoding="utf-8",
19
+ extra="ignore",
20
+ )
21
+
22
+ # --- Database ---
23
+ database_url: str = "sqlite+aiosqlite:///./cascade.db"
24
+
25
+ # --- Server ---
26
+ host: str = "0.0.0.0"
27
+ port: int = 8100
28
+ reload: bool = True
29
+
30
+ # --- Engine ---
31
+ # Seconds between monitoring-loop ticks. Continuous monitoring, not hourly.
32
+ loop_tick_seconds: int = 10
33
+ # Seconds between stall-detection sweeps.
34
+ stall_check_interval_seconds: int = 300
35
+ # Minutes of silence before an ongoing task is considered stalled.
36
+ stall_threshold_minutes: int = 30
37
+ # Seconds before an agent session with no heartbeat is considered dead.
38
+ session_timeout_seconds: int = 60
39
+
40
+ # --- Scheduling ---
41
+ scheduler_interval_seconds: int = 60
42
+
43
+ # --- Feature flags ---
44
+ enable_monitoring_loop: bool = True
45
+ enable_scheduler: bool = True
46
+
47
+
48
+ settings = Settings()
@@ -0,0 +1,69 @@
1
+ """Async database engine, session factory and declarative Base.
2
+
3
+ Provides the single :data:`engine`, the :data:`async_session_factory` and the
4
+ dependency :func:`get_db` used by FastAPI route handlers.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from collections.abc import AsyncGenerator
10
+
11
+ from sqlalchemy import event
12
+ from sqlalchemy.ext.asyncio import (
13
+ AsyncSession,
14
+ async_sessionmaker,
15
+ create_async_engine,
16
+ )
17
+ from sqlalchemy.orm import DeclarativeBase
18
+
19
+ from cascade.config import settings
20
+
21
+
22
+ class Base(DeclarativeBase):
23
+ """Declarative base for all SQLAlchemy 2.0 typed models."""
24
+
25
+
26
+ engine = create_async_engine(
27
+ settings.database_url,
28
+ echo=False,
29
+ future=True,
30
+ )
31
+
32
+ if engine.url.get_backend_name() == "sqlite":
33
+ # The monitoring loop and API requests hit SQLite from several concurrent
34
+ # connections. Without WAL + a busy timeout, SQLite's default rollback
35
+ # journal serialises writers and raises "database is locked" almost
36
+ # immediately under any real concurrency.
37
+ @event.listens_for(engine.sync_engine, "connect")
38
+ def _set_sqlite_pragmas(dbapi_connection, connection_record) -> None: # noqa: ANN001
39
+ cursor = dbapi_connection.cursor()
40
+ cursor.execute("PRAGMA journal_mode=WAL")
41
+ cursor.execute("PRAGMA busy_timeout=30000")
42
+ cursor.execute("PRAGMA foreign_keys=ON")
43
+ cursor.close()
44
+
45
+ async_session_factory: async_sessionmaker[AsyncSession] = async_sessionmaker(
46
+ bind=engine,
47
+ class_=AsyncSession,
48
+ expire_on_commit=False,
49
+ autoflush=False,
50
+ )
51
+
52
+
53
+ async def get_db() -> AsyncGenerator[AsyncSession, None]:
54
+ """FastAPI dependency that yields an :class:`AsyncSession`."""
55
+ async with async_session_factory() as session:
56
+ try:
57
+ yield session
58
+ except Exception:
59
+ await session.rollback()
60
+ raise
61
+
62
+
63
+ async def init_db() -> None:
64
+ """Create all tables. Intended for dev/bootstrap; Alembic is used in prod."""
65
+ # Importing here ensures every model is registered on ``Base.metadata``.
66
+ from cascade import models # noqa: F401 (register models)
67
+
68
+ async with engine.begin() as conn:
69
+ await conn.run_sync(Base.metadata.create_all)
@@ -0,0 +1 @@
1
+ """Engine: continuous monitoring loop, polling, pinger and progress tracking."""