matrx-scheduler 0.3.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 (27) hide show
  1. matrx_scheduler-0.3.0/.gitignore +257 -0
  2. matrx_scheduler-0.3.0/CLAUDE.md +103 -0
  3. matrx_scheduler-0.3.0/PKG-INFO +144 -0
  4. matrx_scheduler-0.3.0/README.md +109 -0
  5. matrx_scheduler-0.3.0/matrx_scheduler/__init__.py +85 -0
  6. matrx_scheduler-0.3.0/matrx_scheduler/_ext.py +99 -0
  7. matrx_scheduler-0.3.0/matrx_scheduler/api/__init__.py +64 -0
  8. matrx_scheduler-0.3.0/matrx_scheduler/api/per_request.py +144 -0
  9. matrx_scheduler-0.3.0/matrx_scheduler/api/router_scheduler.py +749 -0
  10. matrx_scheduler-0.3.0/matrx_scheduler/api/schemas.py +261 -0
  11. matrx_scheduler-0.3.0/matrx_scheduler/api/user_queries.py +346 -0
  12. matrx_scheduler-0.3.0/matrx_scheduler/cron_helpers.py +76 -0
  13. matrx_scheduler-0.3.0/matrx_scheduler/models.py +170 -0
  14. matrx_scheduler-0.3.0/matrx_scheduler/next_due.py +78 -0
  15. matrx_scheduler-0.3.0/matrx_scheduler/queries.py +422 -0
  16. matrx_scheduler-0.3.0/matrx_scheduler/runner.py +367 -0
  17. matrx_scheduler-0.3.0/matrx_scheduler/scanner.py +225 -0
  18. matrx_scheduler-0.3.0/pyproject.toml +50 -0
  19. matrx_scheduler-0.3.0/tests/__init__.py +0 -0
  20. matrx_scheduler-0.3.0/tests/conftest.py +462 -0
  21. matrx_scheduler-0.3.0/tests/test_api_cron_status.py +191 -0
  22. matrx_scheduler-0.3.0/tests/test_api_per_request.py +143 -0
  23. matrx_scheduler-0.3.0/tests/test_api_tasks.py +308 -0
  24. matrx_scheduler-0.3.0/tests/test_api_triggers_runs.py +178 -0
  25. matrx_scheduler-0.3.0/tests/test_cron_helpers.py +49 -0
  26. matrx_scheduler-0.3.0/tests/test_edge_cases.py +118 -0
  27. matrx_scheduler-0.3.0/tests/test_next_due.py +60 -0
@@ -0,0 +1,257 @@
1
+ *.pyc
2
+ secrets/
3
+ ignore/
4
+ temp/
5
+ logs/
6
+ todo
7
+ text_notes/
8
+ aidream/secrets/2.env
9
+ automation_matrix/matrix_processing/temp/*
10
+ cd
11
+ # Byte-compiled / optimized / DLL files
12
+ __pycache__/
13
+ *.py[cod]
14
+ *$py.class
15
+
16
+ # C extensions
17
+ *.so
18
+ .venv/
19
+
20
+ # Distribution / packaging
21
+ .Python
22
+ build/
23
+ develop-eggs/
24
+ dist/
25
+ downloads/
26
+ eggs/
27
+ .eggs/
28
+ lib/
29
+ lib64/
30
+ # The blanket lib/ rule above is from the standard Python .gitignore template
31
+ # and was silently swallowing TS source under the SPA `src/lib/` folders.
32
+ # Re-allow them explicitly so frontend builds don't ship without their lib layer.
33
+ !dashboard/src/lib/
34
+ !dashboard/src/lib/**
35
+ !workflow-studio/src/lib/
36
+ !workflow-studio/src/lib/**
37
+ parts/
38
+ sdist/
39
+ var/
40
+ wheels/
41
+ share/python-wheels/
42
+ *.egg-info/
43
+ .installed.cfg
44
+ *.egg
45
+ MANIFEST
46
+
47
+ # PyInstaller
48
+ # Usually these files are written by a python script from a template
49
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
50
+ *.manifest
51
+ *.spec
52
+
53
+ # Installer logs
54
+ pip-log.txt
55
+ pip-delete-this-directory.txt
56
+
57
+ # Unit test / coverage reports
58
+ ai/tests/clean_response.json
59
+ ai/tests/cx_storage_response.json
60
+ ai/tests/execution_test.py
61
+ ai/tests/final_response.json
62
+ htmlcov/
63
+ .tox/
64
+ .nox/
65
+ .coverage
66
+ .coverage.*
67
+ .cache
68
+ nosetests.xml
69
+ coverage.xml
70
+ *.cover
71
+ *.py,cover
72
+ .hypothesis/
73
+ .pytest_cache/
74
+ cover/
75
+
76
+ # Translations
77
+ *.mo
78
+ *.pot
79
+
80
+ # Django stuff:
81
+ *.log
82
+ local_settings.py
83
+ db.sqlite3
84
+ db.sqlite3-journal
85
+
86
+ # Flask stuff:
87
+ instance/
88
+ .webassets-cache
89
+
90
+ # Scrapy stuff:
91
+ .scrapy
92
+
93
+ # Sphinx documentation
94
+ docs/_build/
95
+
96
+ # PyBuilder
97
+ .pybuilder/
98
+ target/
99
+
100
+ # Jupyter Notebook
101
+ .ipynb_checkpoints
102
+
103
+ # IPython
104
+ profile_default/
105
+ ipython_config.py
106
+
107
+ # pyenv
108
+ # For a library or package, you might want to ignore these files since the code is
109
+ # intended to run in multiple environments; otherwise, check them in:
110
+ # .python-version
111
+
112
+ # pipenv
113
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
114
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
115
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
116
+ # install all needed dependencies.
117
+ #Pipfile.lock
118
+
119
+ # poetry
120
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
121
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
122
+ # commonly ignored for libraries.
123
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
124
+
125
+ # pdm
126
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
127
+ #pdm.lock
128
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
129
+ # in version control.
130
+ # https://pdm.fming.dev/#use-with-ide
131
+ .pdm.toml
132
+
133
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
134
+ __pypackages__/
135
+
136
+ # Celery stuff
137
+ celerybeat-schedule
138
+ celerybeat.pid
139
+
140
+ # SageMath parsed files
141
+ *.sage.py
142
+
143
+ # Environments
144
+ .env
145
+ .env_remote
146
+ .venv
147
+ env/
148
+ venv/
149
+ ENV/
150
+ env.bak/
151
+ venv.bak/
152
+ .env.armanonly
153
+
154
+ # Spyder project settings
155
+ .spyderproject
156
+ .spyproject
157
+
158
+ # Rope project settings
159
+ .ropeproject
160
+
161
+ # mkdocs documentation
162
+ /site
163
+
164
+ # mypy
165
+ .mypy_cache/
166
+ .dmypy.json
167
+ dmypy.json
168
+
169
+ # Pyre type checker
170
+ .pyre/
171
+
172
+ # random armani files
173
+ /armani_dev/secrets/
174
+ /armani/
175
+ /_armani/
176
+
177
+
178
+
179
+ # pytype static type analyzer
180
+ .pytype/
181
+
182
+ # Cython debug symbols
183
+ cython_debug/
184
+
185
+ .idea/
186
+ .vscode/
187
+ /node_modules/
188
+
189
+ dump.rdb
190
+
191
+ frontend/
192
+
193
+ # AME Temp Files and directory structure
194
+ # Ignore all files in the temp directory and its subdirectories
195
+ /temp/**/*
196
+ /tmp/**/*
197
+
198
+ # Allow .gitkeep files to retain directory structure
199
+ !/temp/**/.gitkeep
200
+ !/tmp/**/.gitkeep
201
+
202
+ # Armani
203
+ .history*
204
+ .history/
205
+ local_data/
206
+ local_reports_data/
207
+ webscraper/quick_scrapes/temp/
208
+ automation_matrix/ai_apis/fireworks/_dev/*
209
+ automation_matrix/ai_apis/fireworks/_dev/fireworks_sample.py
210
+ *.pdf
211
+ *.flac
212
+ *.mp3
213
+ *.wav
214
+ miniconda.sh
215
+ /database/python_sql/temp_data/
216
+ .history*
217
+ .history/
218
+ .history/
219
+
220
+ _dev/
221
+ /_dev/
222
+ requirements_filtered.txt
223
+
224
+ # matrx-dev-tools backups
225
+ .env-backups/
226
+ # Matrx Ship config (contains API key)
227
+ .matrx-ship.json
228
+
229
+ # Matrx config (contains API keys)
230
+ .matrx.json
231
+ .matrx-tools.conf
232
+
233
+ # Claude Code local worktrees and per-user settings
234
+ .claude/worktrees/
235
+ .claude/settings.local.json
236
+
237
+ # Append-only snapshots from matrx_utils.update_history (unbounded; do not commit)
238
+ common/utils/data_in_code/data_history.json
239
+ packages/matrx-utils/matrx_utils/data_in_code/data_history.json
240
+
241
+ # Tool-dispatch debug logs — one file per server start, never committed
242
+ .matrx-debug/
243
+
244
+ # macOS Finder metadata
245
+ .DS_Store
246
+ **/.DS_Store
247
+
248
+ # Environment files
249
+ .env
250
+ .env.*
251
+ *.env
252
+ *.env.*
253
+
254
+ # Keep safe templates trackable
255
+ !.env.example
256
+ !.env.sample
257
+ !.env.template
@@ -0,0 +1,103 @@
1
+ # matrx-scheduler — CLAUDE.md
2
+
3
+ Standalone PyPI-publishable Python package. Server-side executor for the
4
+ `sch_*` scheduling spine shared with matrx-frontend and matrx-extend.
5
+
6
+ ## Package independence
7
+
8
+ Per `aidream/CLAUDE.md` (top-level): packages MUST NOT import from `aidream/`
9
+ or root-level modules. matrx-scheduler reads `_ext.py` for host-supplied
10
+ dependencies via `configure(...)` — never reaches out.
11
+
12
+ Dependency graph (inter-package):
13
+
14
+ ```
15
+ matrx-utils (foundation — no matrx deps)
16
+
17
+ matrx-scheduler (needs: matrx-utils + host-injected
18
+ supabase / context / agent_runner)
19
+ ```
20
+
21
+ ## Public API
22
+
23
+ ```python
24
+ import matrx_scheduler
25
+
26
+ matrx_scheduler.configure(
27
+ supabase_client=..., # supabase-py async client
28
+ surface="server",
29
+ agent_runner=..., # async callable(AgentRunInput) -> AgentRunResult
30
+ get_app_context=..., # () -> AppContext | None
31
+ scan_interval_seconds=5.0,
32
+ lease_seconds=600,
33
+ )
34
+
35
+ # Background loop
36
+ await matrx_scheduler.start_scanner()
37
+ await matrx_scheduler.stop_scanner()
38
+
39
+ # Cron helpers (used by the /scheduling/* routes)
40
+ matrx_scheduler.validate_cron("0 9 * * 1-5", "America/Los_Angeles")
41
+ matrx_scheduler.next_n_fires("0 9 * * 1-5", "UTC", n=5)
42
+ matrx_scheduler.compute_next_due_at("interval", {"every_seconds": 3600})
43
+
44
+ # Health
45
+ status: ScannerStatus = matrx_scheduler.status()
46
+ ```
47
+
48
+ ## Module layout
49
+
50
+ - `__init__.py` — public API exports
51
+ - `_ext.py` — host-injection registry (`configure`, `get_ext`)
52
+ - `models.py` — Pydantic models for sch_task / sch_agent_task / sch_trigger /
53
+ sch_run + the AgentRunInput/Result contract
54
+ - `queries.py` — Supabase access (the only place that calls `.table('sch_*')`)
55
+ - `scanner.py` — background asyncio loop
56
+ - `runner.py` — drives a single claimed run end-to-end
57
+ - `next_due.py` — `compute_next_due_at(...)` per trigger type
58
+ - `cron_helpers.py` — `croniter`-backed validators
59
+
60
+ ## Invariants
61
+
62
+ 1. **Atomic claim, not "read-then-write".** Scanner finds candidates, then
63
+ each call to `queries.claim_task` does the concurrency check (active runs
64
+ count) and inserts the sch_run row in one go. Multiple scanners racing
65
+ the same task can't double-run because `max_concurrent` is enforced
66
+ inside the claim.
67
+ 2. **DB owns `sch_task.next_due_at`.** After every recurring fire, the
68
+ scheduler calls `advance_trigger_next_due_at` on `sch_trigger`; the DB
69
+ trigger `sch_trigger_cascade_next_due_at` updates `sch_task.next_due_at`.
70
+ Don't UPDATE the parent column directly from the package.
71
+ 3. **Lease expiry sweeps run every tick.** Crashed runners leave behind
72
+ `status IN (claimed,running) AND claim_expires_at < now()` rows. Scanner
73
+ marks them `failed` so the trigger&apos;s next firing can re-enqueue.
74
+ 4. **Heartbeat conversation continuity.** First run of a heartbeat trigger
75
+ sets `sch_agent_task.persistent_conversation_id` from the runner&apos;s
76
+ `result.conversation_id`. Subsequent runs use it. Don&apos;t recreate.
77
+ 5. **Host-injected agent_runner is fire-and-forget from the scheduler's POV.**
78
+ It must complete or raise; the scheduler doesn&apos;t poll progress.
79
+ Inside the runner, use matrx-connect&apos;s streaming infra normally.
80
+
81
+ ## Where this is wired
82
+
83
+ - aidream `package_integration.py::_configure_matrx_scheduler` — startup config.
84
+ - aidream `api/app.py` — `start_scanner()` in lifespan when
85
+ `AIDREAM_SCHEDULER=1`. Stops on shutdown.
86
+ - aidream `api/routers/scheduling.py` — 5 HTTP endpoints under `/scheduling`
87
+ consumed by matrx-frontend.
88
+
89
+ ## Tests
90
+
91
+ `tests/test_next_due.py` and `tests/test_cron_helpers.py` cover all 5 trigger
92
+ types and the cron parser. Run with:
93
+
94
+ ```bash
95
+ uv run pytest packages/matrx-scheduler/tests
96
+ ```
97
+
98
+ ## Related
99
+
100
+ - Spec: matrx-frontend `docs/SCHEDULING.md`
101
+ - Migrations: matrx-frontend `migrations/sch_*.sql` (the spine is shared)
102
+ - FE control plane: matrx-frontend `features/scheduling/FEATURE.md`
103
+ - Browser-context executor: matrx-extend `src/lib/agenda/*`
@@ -0,0 +1,144 @@
1
+ Metadata-Version: 2.4
2
+ Name: matrx-scheduler
3
+ Version: 0.3.0
4
+ Summary: Server-side execution engine for the matrx scheduling spine (sch_* tables): scanner, claim, runner, cron parser, HTTP API.
5
+ Author-email: Matrx <admin@aimatrx.com>
6
+ License: MIT
7
+ Keywords: agents,cron,matrx,scheduler,supabase
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
+ Requires-Python: >=3.13
15
+ Requires-Dist: croniter>=2.0
16
+ Requires-Dist: matrx-utils
17
+ Requires-Dist: pydantic>=2.12
18
+ Requires-Dist: supabase>=2.0
19
+ Provides-Extra: api
20
+ Requires-Dist: fastapi>=0.115; extra == 'api'
21
+ Requires-Dist: httpx>=0.27; extra == 'api'
22
+ Requires-Dist: matrx-connect; extra == 'api'
23
+ Provides-Extra: dev
24
+ Requires-Dist: fastapi>=0.115; extra == 'dev'
25
+ Requires-Dist: freezegun>=1.4; extra == 'dev'
26
+ Requires-Dist: httpx>=0.27; extra == 'dev'
27
+ Requires-Dist: matrx-connect; extra == 'dev'
28
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
29
+ Requires-Dist: pytest>=8.0; extra == 'dev'
30
+ Provides-Extra: host
31
+ Requires-Dist: matrx-ai; extra == 'host'
32
+ Requires-Dist: matrx-connect; extra == 'host'
33
+ Requires-Dist: matrx-orm; extra == 'host'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # matrx-scheduler
37
+
38
+ Server-side execution engine for the matrx scheduling spine (`sch_*` tables in
39
+ Supabase). Provides:
40
+
41
+ - a scanner loop that claims due tasks atomically,
42
+ - a runner that drives the host's agent_runner / tool_runner,
43
+ - an authoritative cron parser + next-due computation,
44
+ - and (since 0.3) an HTTP API surface (FastAPI) for task / trigger / run CRUD,
45
+ manual fires, scanner status, and cron preview.
46
+
47
+ Any host application (aidream is the reference) wires it up via `configure()`
48
+ and either runs the scanner directly, mounts the HTTP routes, or both.
49
+
50
+ ## What it does (under the hood)
51
+
52
+ - Polls `sch_task` for rows where `next_due_at <= now()` and the host surface
53
+ is in `surfaces[]` (or `'any'`).
54
+ - Atomically claims a candidate by inserting a `sch_run` with `claim_token`
55
+ and `claim_expires_at` (the lease).
56
+ - Drives the matrx-ai agent runner (host-injected) against the claimed task,
57
+ or the host's tool_runner for `kind='tool'`.
58
+ - Writes back results (`status`, `result_summary`, `output_ref`, etc.).
59
+ - Recomputes the next fire time on recurring triggers; the DB cascade
60
+ updates `sch_task.next_due_at` automatically.
61
+
62
+ ## Capability-within, injection-without
63
+
64
+ `matrx-scheduler` doesn't import from a host app. It exposes a `configure()`
65
+ function the host calls at startup, passing in:
66
+
67
+ - `supabase_client` — service-role client used by the SCANNER to read sch_task
68
+ across users and update lease state.
69
+ - `surface` — the string this host identifies as in `sch_task.surfaces[]`
70
+ (e.g. `'server'` for aidream, `'desktop'` for matrx-local).
71
+ - `agent_runner` — callable that runs an agent (kind='agent' tasks).
72
+ - `tool_runner` — optional callable for kind='tool' tasks. Hosts that don't
73
+ claim tool tasks (e.g. aidream itself) leave this unset.
74
+ - `user_supabase_factory` — optional callable `(user_jwt) -> AsyncClient`
75
+ used by the HTTP routes to build per-request user-scoped clients. If not
76
+ provided, the package falls back to an env-based factory keyed on
77
+ `SUPABASE_MATRIX_URL` + `SUPABASE_MATRIX_PUBLISHABLE_KEY`.
78
+ - `get_app_context`, `emitter_factory`, `scan_interval_seconds`,
79
+ `lease_seconds` — see the `_ext.configure` docstring.
80
+
81
+ Install with the `host` extra (`pip install matrx-scheduler[host]`) when
82
+ using it inside a full host app. Add the `api` extra
83
+ (`pip install matrx-scheduler[api]`) to mount the HTTP routes.
84
+
85
+ ## HTTP API
86
+
87
+ Available since 0.3. Optional — drop the import if you only want the scanner.
88
+
89
+ ```python
90
+ from fastapi import FastAPI
91
+ import matrx_scheduler
92
+
93
+ app = FastAPI()
94
+
95
+ # Host startup wiring (any host).
96
+ matrx_scheduler.configure(
97
+ supabase_client=service_role_client,
98
+ surface="server", # or "desktop", "extension", ...
99
+ agent_runner=my_agent_runner,
100
+ user_supabase_factory=my_user_client_factory, # optional
101
+ )
102
+ await matrx_scheduler.start_scanner()
103
+
104
+ # Mount the HTTP routes.
105
+ matrx_scheduler.api.include_routers(app, prefix="/scheduler")
106
+ ```
107
+
108
+ ### Endpoints
109
+
110
+ All routes use `matrx_connect.AppContext` via `Depends(context_dep)`. Every
111
+ write goes through a per-request Supabase client built from the caller's JWT;
112
+ RLS is the only line of authority on row ownership. The service-role client
113
+ (used by the scanner) is **never** accessible from these routes.
114
+
115
+ | Method | Path | Purpose |
116
+ | ------ | --------------------------------------- | ---------------------------------------------------- |
117
+ | POST | `/scheduler/tasks` | Create a task (optionally with agent_task + trigger) |
118
+ | GET | `/scheduler/tasks` | List tasks (filter by kind, enabled) |
119
+ | GET | `/scheduler/tasks/{id}` | Get task hydrated with agent_task, triggers, runs |
120
+ | PATCH | `/scheduler/tasks/{id}` | Patch task fields (title, enabled, tags, etc.) |
121
+ | DELETE | `/scheduler/tasks/{id}` | Soft-delete (set enabled=false) |
122
+ | POST | `/scheduler/tasks/{id}/run-now` | Enqueue a manual run via `sch_enqueue_manual_run` |
123
+ | GET | `/scheduler/triggers?task_id=...` | List triggers for a task |
124
+ | POST | `/scheduler/triggers` | Create a trigger on an existing task |
125
+ | PATCH | `/scheduler/triggers/{id}` | Patch a trigger (recomputes next_due_at if type/config changed) |
126
+ | DELETE | `/scheduler/triggers/{id}` | Hard-delete a trigger |
127
+ | GET | `/scheduler/runs?task_id=...&status=...`| List run history |
128
+ | GET | `/scheduler/runs/{id}` | Get one run |
129
+ | POST | `/scheduler/cron/validate` | Validate a cron expression + preview next N fires |
130
+ | POST | `/scheduler/cron/preview-fires` | Preview next-fire times for any trigger config |
131
+ | POST | `/scheduler/compute-next-due-at` | Compute the single next due_at for a trigger config |
132
+ | GET | `/scheduler/status` | Scanner health (admin only) |
133
+
134
+ ### Co-existence with aidream's `/scheduling/*`
135
+
136
+ aidream had a small router at `/scheduling/*` (cron validation, run-now,
137
+ admin force-disable) before this surface existed. The package routes use
138
+ `/scheduler/*` (singular) so they don't collide. aidream can mount both
139
+ simultaneously, or migrate clients to the package routes opportunistically.
140
+ matrx-local mounts only the package routes; it has no `/scheduling/*` routes
141
+ to begin with.
142
+
143
+ See `docs/SCHEDULING.md` in the matrx-frontend repo for the full data-model
144
+ spec, trigger taxonomy, and lifecycle.
@@ -0,0 +1,109 @@
1
+ # matrx-scheduler
2
+
3
+ Server-side execution engine for the matrx scheduling spine (`sch_*` tables in
4
+ Supabase). Provides:
5
+
6
+ - a scanner loop that claims due tasks atomically,
7
+ - a runner that drives the host's agent_runner / tool_runner,
8
+ - an authoritative cron parser + next-due computation,
9
+ - and (since 0.3) an HTTP API surface (FastAPI) for task / trigger / run CRUD,
10
+ manual fires, scanner status, and cron preview.
11
+
12
+ Any host application (aidream is the reference) wires it up via `configure()`
13
+ and either runs the scanner directly, mounts the HTTP routes, or both.
14
+
15
+ ## What it does (under the hood)
16
+
17
+ - Polls `sch_task` for rows where `next_due_at <= now()` and the host surface
18
+ is in `surfaces[]` (or `'any'`).
19
+ - Atomically claims a candidate by inserting a `sch_run` with `claim_token`
20
+ and `claim_expires_at` (the lease).
21
+ - Drives the matrx-ai agent runner (host-injected) against the claimed task,
22
+ or the host's tool_runner for `kind='tool'`.
23
+ - Writes back results (`status`, `result_summary`, `output_ref`, etc.).
24
+ - Recomputes the next fire time on recurring triggers; the DB cascade
25
+ updates `sch_task.next_due_at` automatically.
26
+
27
+ ## Capability-within, injection-without
28
+
29
+ `matrx-scheduler` doesn't import from a host app. It exposes a `configure()`
30
+ function the host calls at startup, passing in:
31
+
32
+ - `supabase_client` — service-role client used by the SCANNER to read sch_task
33
+ across users and update lease state.
34
+ - `surface` — the string this host identifies as in `sch_task.surfaces[]`
35
+ (e.g. `'server'` for aidream, `'desktop'` for matrx-local).
36
+ - `agent_runner` — callable that runs an agent (kind='agent' tasks).
37
+ - `tool_runner` — optional callable for kind='tool' tasks. Hosts that don't
38
+ claim tool tasks (e.g. aidream itself) leave this unset.
39
+ - `user_supabase_factory` — optional callable `(user_jwt) -> AsyncClient`
40
+ used by the HTTP routes to build per-request user-scoped clients. If not
41
+ provided, the package falls back to an env-based factory keyed on
42
+ `SUPABASE_MATRIX_URL` + `SUPABASE_MATRIX_PUBLISHABLE_KEY`.
43
+ - `get_app_context`, `emitter_factory`, `scan_interval_seconds`,
44
+ `lease_seconds` — see the `_ext.configure` docstring.
45
+
46
+ Install with the `host` extra (`pip install matrx-scheduler[host]`) when
47
+ using it inside a full host app. Add the `api` extra
48
+ (`pip install matrx-scheduler[api]`) to mount the HTTP routes.
49
+
50
+ ## HTTP API
51
+
52
+ Available since 0.3. Optional — drop the import if you only want the scanner.
53
+
54
+ ```python
55
+ from fastapi import FastAPI
56
+ import matrx_scheduler
57
+
58
+ app = FastAPI()
59
+
60
+ # Host startup wiring (any host).
61
+ matrx_scheduler.configure(
62
+ supabase_client=service_role_client,
63
+ surface="server", # or "desktop", "extension", ...
64
+ agent_runner=my_agent_runner,
65
+ user_supabase_factory=my_user_client_factory, # optional
66
+ )
67
+ await matrx_scheduler.start_scanner()
68
+
69
+ # Mount the HTTP routes.
70
+ matrx_scheduler.api.include_routers(app, prefix="/scheduler")
71
+ ```
72
+
73
+ ### Endpoints
74
+
75
+ All routes use `matrx_connect.AppContext` via `Depends(context_dep)`. Every
76
+ write goes through a per-request Supabase client built from the caller's JWT;
77
+ RLS is the only line of authority on row ownership. The service-role client
78
+ (used by the scanner) is **never** accessible from these routes.
79
+
80
+ | Method | Path | Purpose |
81
+ | ------ | --------------------------------------- | ---------------------------------------------------- |
82
+ | POST | `/scheduler/tasks` | Create a task (optionally with agent_task + trigger) |
83
+ | GET | `/scheduler/tasks` | List tasks (filter by kind, enabled) |
84
+ | GET | `/scheduler/tasks/{id}` | Get task hydrated with agent_task, triggers, runs |
85
+ | PATCH | `/scheduler/tasks/{id}` | Patch task fields (title, enabled, tags, etc.) |
86
+ | DELETE | `/scheduler/tasks/{id}` | Soft-delete (set enabled=false) |
87
+ | POST | `/scheduler/tasks/{id}/run-now` | Enqueue a manual run via `sch_enqueue_manual_run` |
88
+ | GET | `/scheduler/triggers?task_id=...` | List triggers for a task |
89
+ | POST | `/scheduler/triggers` | Create a trigger on an existing task |
90
+ | PATCH | `/scheduler/triggers/{id}` | Patch a trigger (recomputes next_due_at if type/config changed) |
91
+ | DELETE | `/scheduler/triggers/{id}` | Hard-delete a trigger |
92
+ | GET | `/scheduler/runs?task_id=...&status=...`| List run history |
93
+ | GET | `/scheduler/runs/{id}` | Get one run |
94
+ | POST | `/scheduler/cron/validate` | Validate a cron expression + preview next N fires |
95
+ | POST | `/scheduler/cron/preview-fires` | Preview next-fire times for any trigger config |
96
+ | POST | `/scheduler/compute-next-due-at` | Compute the single next due_at for a trigger config |
97
+ | GET | `/scheduler/status` | Scanner health (admin only) |
98
+
99
+ ### Co-existence with aidream's `/scheduling/*`
100
+
101
+ aidream had a small router at `/scheduling/*` (cron validation, run-now,
102
+ admin force-disable) before this surface existed. The package routes use
103
+ `/scheduler/*` (singular) so they don't collide. aidream can mount both
104
+ simultaneously, or migrate clients to the package routes opportunistically.
105
+ matrx-local mounts only the package routes; it has no `/scheduling/*` routes
106
+ to begin with.
107
+
108
+ See `docs/SCHEDULING.md` in the matrx-frontend repo for the full data-model
109
+ spec, trigger taxonomy, and lifecycle.
@@ -0,0 +1,85 @@
1
+ """
2
+ matrx-scheduler — server-side execution engine for the sch_* scheduling spine.
3
+
4
+ Public API:
5
+ configure(...) — wire host-supplied dependencies (call at startup)
6
+ start_scanner() — spawn the background scanner asyncio task
7
+ stop_scanner() — graceful shutdown
8
+ status() — current scanner health (last tick, queue depth, etc.)
9
+ validate_cron(...) — server-authoritative cron expression validator
10
+ next_n_fires(...) — preview a cron expression's next N fires
11
+ compute_next_due_at(...) — compute the next fire time for any trigger config
12
+
13
+ HTTP API (optional, requires matrx-connect + fastapi):
14
+ matrx_scheduler.api.include_routers(app, prefix="/scheduler")
15
+
16
+ Models (Pydantic):
17
+ SchTask, SchAgentTask, SchTrigger, SchRun
18
+ HydratedTask, AgentRunInput, AgentRunResult
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ from ._ext import configure, has_ext, is_configured
24
+ from .cron_helpers import (
25
+ fire_count_in_window,
26
+ next_n_fires,
27
+ validate_cron,
28
+ )
29
+ from .models import (
30
+ AgentRunInput,
31
+ AgentRunResult,
32
+ AuthMode,
33
+ HydratedTask,
34
+ RunStatus,
35
+ SchAgentTask,
36
+ SchRun,
37
+ SchTask,
38
+ SchTrigger,
39
+ ToolRunInput,
40
+ TriggerType,
41
+ )
42
+
43
+
44
+ __version__ = "0.3.0"
45
+ from .next_due import compute_next_due_at
46
+ from .scanner import (
47
+ ScannerStatus,
48
+ is_running,
49
+ start_scanner,
50
+ status,
51
+ stop_scanner,
52
+ )
53
+
54
+
55
+ __all__ = [
56
+ # configuration
57
+ "configure",
58
+ "is_configured",
59
+ "has_ext",
60
+ # scanner
61
+ "start_scanner",
62
+ "stop_scanner",
63
+ "status",
64
+ "is_running",
65
+ "ScannerStatus",
66
+ # cron helpers
67
+ "validate_cron",
68
+ "next_n_fires",
69
+ "fire_count_in_window",
70
+ "compute_next_due_at",
71
+ # models
72
+ "SchTask",
73
+ "SchAgentTask",
74
+ "SchTrigger",
75
+ "SchRun",
76
+ "HydratedTask",
77
+ "AgentRunInput",
78
+ "AgentRunResult",
79
+ "ToolRunInput",
80
+ "TriggerType",
81
+ "RunStatus",
82
+ "AuthMode",
83
+ # version
84
+ "__version__",
85
+ ]