NOTICE: All information contained herein is, and remains
the property of TechnoCore Automate.
Database-driven task scheduler for the Axion platform. Uses cron expressions
as the scheduling language but runs entirely in-process — it does not read
or write to the system crontab.
Schedules are stored in the def_scheduler database table. The scheduler
reads those records, evaluates cron expressions using croniter, and executes
due tasks in isolated worker subprocesses. This makes the scheduler portable,
package-aware, and independent of the host OS scheduler.
SchedulerInherits from ObjSupervisor.Supervisor.
| Responsibility | Method |
|---|---|
| Poll for due schedules | poll_once(on_due) |
| Execute a schedule entry | add_schedule(guid, package) |
| Run as a continuous service | run_service(num_workers, use_tui) |
| Manual interactive execution | select_compute() |
| Run a named task directly | compute(task_name) |
| Send notifications | notify(notify_code, message_text) |
Each schedule row has a TriggerType field:
| Value | Behaviour |
|---|---|
heartbeat (or empty) |
No-op pulse — useful for monitoring |
calculation |
Runs the named ObjCalculation set |
workflow |
Activates an ObjWorkflow run |
poll_once() is the core loop tick:
def_schedulerCrontab column with croniter_next_fire cache — no DB writes per ticknext_fire <= now), calls the provided on_due_enqueue_task) and advances the next-fire timeStartWhen — skips schedules not yet activeImportant: cron expressions are the scheduling language only. The engine
that replaces system cron is the run_service() loop itself.
run_service() creates:
multiprocessing.Manager shared dict for task stateQueue for task dispatchN worker processes (default: CPU count) consuming the queueScheduler instance with its own DB connectionMAX_TASK_RETRIESAn optional Rich-based TUI (ObjSchedulerTUI) can be enabled via
--no-tui flag suppression. The TUI receives events via a separate
event_queue and displays running/queued task state in real time.
See ObjSchedulerTUI.md for details.
Uses ObjNotify for the following codes (auto-initialised on startup):
| Code | Trigger |
|---|---|
SCHEDULER_TASK_START |
Task begins execution |
SCHEDULER_TASK_COMPLETE |
Task finishes successfully |
SCHEDULER_TASK_FAILED |
Task exhausts all retries |
| Constant | Source |
|---|---|
MAX_TASK_RETRIES |
SchedulerConstants |
RETRY_BACKOFF_BASE |
SchedulerConstants — exponential: base ^ retry_count |
MAX_RUN_COUNTER |
SchedulerConstants — resets counter to 0 when exceeded |
Run via ServeScheduler.py or directly:
# Start as a service (TUI enabled by default)
python ServeScheduler.py service
# Specify worker count, disable TUI
python ServeScheduler.py service --workers 4 --no-tui
# Interactive manual task execution
python ServeScheduler.py manual
All CLI commands use typer.
# List all scheduled tasks for the active package
python factory.core/ObjScheduler.py list
python factory.core/ObjScheduler.py list --package CORE
# Add a new scheduled task
python factory.core/ObjScheduler.py add \
"My Task" "*/5 * * * *" \
--trigger-type workflow \
--workflow MY_WORKFLOW
# Disable / enable a task by name
python factory.core/ObjScheduler.py disable "My Task"
python factory.core/ObjScheduler.py enable "My Task"
# Check and execute due recurring schedules
python factory.core/ObjScheduler.py check-recurring
# Send scheduler status report email
python factory.core/ObjScheduler.py send-email \
--recipients "user@example.com"
# Run as continuous service
python factory.core/ObjScheduler.py service \
--workers 4 --no-tui
# Interactive manual execution
python factory.core/ObjScheduler.py manual
| Command | Purpose |
|---|---|
list |
Show all schedules (filterable by --package) |
add |
Insert a new def_scheduler row with cron validation |
disable |
Set Active = 'N' for a task |
enable |
Set Active = 'Y' for a task |
check-recurring |
Run check_recurring() and print results |
send-email |
Send branded HTML status report |
service |
Continuous polling service with optional TUI |
manual |
Interactive task picker |
check_recurring() -> listChecks and executes all due recurring schedules
from the def_recurring table. Delegates to
ObjRecurringMixin consumers (ObjTicket,
ObjWorkflow) via their own
recurring_check_due() / check_due_recurring()
methods.
Returns a list of dicts, each tagged with a
type key (TICKET or WORKFLOW) plus the
fields returned by the consumer.
send_email(recipients: str = "") -> NoneSends a branded HTML scheduler status report
via ObjTemplate.build_email_html(). The
report includes:
Palette colours are resolved from
self.get_palette().
All SQL is loaded from ObjScheduler.yaml via
self.get_queries(key) (the get_queries()
migration from raw YAML loading):
| Query key | Purpose |
|---|---|
get_active_schedules_detailed |
Poll loop — fetch all active schedules |
get_schedule_by_guid |
Load a single schedule for execution |
get_schedule_by_taskname |
Look up schedule by task name |
get_active_tasks_for_selection |
Interactive selection list |
reschedule_mark_done |
Mark current run complete before rescheduling |
reschedule_update_counter |
Increment run counter |
mark_schedule_complete |
Mark non-repeating schedule as done |
def_scheduler — key columns:
| Column | Purpose |
|---|---|
Guid |
Unique schedule identifier |
TaskName |
Human-readable name |
Crontab |
Cron expression (e.g. */5 * * * *) |
StartWhen |
Optional activation datetime |
EndWhen |
Optional expiry datetime |
RepeatFlag |
Y = repeating, N = run once |
TriggerType |
heartbeat, calculation, or workflow |
Workflow |
Workflow code (when TriggerType = workflow) |
CalculationName |
Calculation set (when TriggerType = calculation) |
RunCounter |
Execution count |
ForceTimeFlag |
Controls reschedule behaviour |
Package |
Package scope |
Module |
Organisational grouping |
cythonize -3 -a -i ObjScheduler.py
Compiling /home/axion/projects/axion/factory.core/ObjScheduler.py because it changed.
[1/1] Cythonizing /home/axion/projects/axion/factory.core/ObjScheduler.py
Updated: 2026-02-22