Metadata-Version: 2.4
Name: kontiki-scheduler
Version: 1.0.0
Summary: Declarative cron scheduler for Kontiki.
License: Apache-2.0
License-File: LICENSE
Author: Julien Weber
Author-email: jpkwbr@protonmail.com
Requires-Python: >=3.11,<4.0
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Dist: croniter (>=2.0.0,<3.0.0)
Requires-Dist: kontiki (>=1.0.0)
Project-URL: Changelog, https://github.com/kontiki-org/kontiki-scheduler/blob/main/CHANGELOG.md
Project-URL: Homepage, https://github.com/kontiki-org/kontiki-scheduler
Project-URL: Repository, https://github.com/kontiki-org/kontiki-scheduler
Description-Content-Type: text/markdown

# kontiki-scheduler

Declarative cron scheduler for [Kontiki](https://github.com/kontiki-org/kontiki) deployments.

`kontiki-scheduler` replaces external crontab or systemd timers with a Kontiki-native service: schedules live in YAML, triggers go through the message bus, and operations stay aligned with the rest of the stack (configuration, logging, registry, supervision).

## Role

At each cron tick, the scheduler publishes one event on the bus. Business services react with `@on_event` handlers. The scheduler does not run domain logic and does not track success or failure — that remains the responsibility of each consumer.

Use `kontiki-scheduler` for **cross-service calendar triggers** defined centrally in configuration.

Use `@task` inside a service for **internal periodic work** (housekeeping, polling, sub-minute intervals).

## Configuration

Schedules are static and loaded at startup. Changing a schedule requires restarting the service.

```yaml
kontiki:
  amqp:
    url: "amqp://guest:guest@localhost"

scheduler:
  schedules:
    - name: billing.daily_export
      cron: "0 2 * * *"
    - name: reports.weekly_cleanup
      cron: "0 3 * * 0"
```

Each entry has:

- **`name`** — stable schedule identifier, shared with consuming services
- **`cron`** — standard 5-field expression (`minute hour day-of-month month day-of-week`)

### Timing

- **Granularity:** minute
- **Timezone:** UTC
- **Poll interval:** 60 seconds (fixed)
- **Missed runs:** skipped (next tick is computed from the current time, cron-style)
- **Deployment:** one scheduler instance per environment (no multi-instance coordination)

Invalid cron expressions or duplicate schedule names cause startup to fail immediately.

## Event contract

When a schedule fires, the scheduler publishes:

| Field | Value |
|-------|-------|
| Event type | `{name}.schedule_task.requested` |
| Payload | `{}` |

Example: schedule `billing.daily_export` publishes `billing.daily_export.schedule_task.requested` with an empty object.

The **`name` in configuration is the shared contract** between scheduler and consumers. The event type is derived from it; consumers subscribe to the full event name in their handler.

### Consumer example

```python
from kontiki.messaging import on_event


class BillingService:
    @on_event("billing.daily_export.schedule_task.requested")
    async def run_daily_export(self, payload):
        ...
```

## Quick start

```bash
make install          # install dependencies with poetry
make run-amqp         # start RabbitMQ from docker-compose.dev.yaml

make test             # run unit tests
make integration-test # start RabbitMQ (if needed) and run integration tests (behave)
```

Run the service:

```bash
kontiki-scheduler --config /path/to/config.yaml
```

## Out of scope

- Dynamic scheduling (`schedule()` / `cancel()` RPC)
- One-shot or per-request delayed jobs
- Execution tracking (`succeeded` / `failed`)
- Sub-minute precision
- Hot reload of schedules

## License

Apache License 2.0 — see [LICENSE](LICENSE).

