- Overview
- Features
- Quick Start
- API Usage
- Configuration
- Deployment
- Authentication
- Contributing
- Testing
- Troubleshooting & FAQ
- License
- Links & References
See docs/api.md for full API details and example requests/responses.
Qhronos (v0.1.0) is a developer-first scheduling and notification platform. It lets you schedule one-time or recurring events and reliably delivers webhooks at the right time. Built for reliability, security, and extensibility, Qhronos is ideal for automating workflows, orchestrating AI agents, and managing time-based triggers in distributed systems.
Event → Schedule → Occurrence Flow:
- Event: User-defined intent and configuration, stored in the database.
- Schedule: Actionable plan (stored in Redis) for when the event should be executed. Created by the expander.
- Occurrence: A record representing a specific scheduled instance of an Event. For recurring events, these are generated by the Expander. Each Occurrence tracks its lifecycle (e.g., pending, scheduled, dispatched, failed) and the outcome of its execution attempt by the Dispatcher.
For system architecture and in-depth design, see design.md.
- REST API for event scheduling
- Recurring and one-time events (iCalendar RFC 5545)
- Reliable, retryable webhook delivery
- JWT and master token authentication
- Rate limiting and audit logging
- Easy deployment (Docker/Kubernetes)
- Go 1.20+
- Docker & Docker Compose
- PostgreSQL & Redis (or use Docker Compose)
For your first run, copy the example config, build the Docker image, and start only the database dependencies and run migrations:
cp config.example.yml config.yml
make docker-build
make docker-up
make migrate-upThen, to start all services (including the app):
make docker-qupThe API will be available at http://localhost:8080.
make build
./bin/qhronosd --config config.yamlYou can use CLI flags to override config values, e.g.:
./bin/qhronosd --port 9090 --log-level debugBefore running Qhronos, set up your configuration:
- Copy the example config file:
cp config.example.yaml config.yaml
- Edit
config.yamlto match your environment (database, Redis, auth secrets, etc). - You can also override any config value using CLI flags (see above).
Before running Qhronos for the first time, initialize the PostgreSQL database and apply migrations:
- Start PostgreSQL and Redis using Make:
make docker-up
- Run database migrations:
make migrate-up
- Start all services (including Qhronos):
make docker-qup
This will create all required tables and schema in your database.
Qhronos manages database schema changes using embedded migration files. You can apply all migrations using:
./bin/qhronosd --migrate --config config.yaml- This will apply all pending migrations to your database.
- You can use any CLI flags to override config values (e.g., DB host, port, user).
- Migration files are embedded in the binary; no external migration tool is needed.
- The binary manages a
schema_migrationstable to track applied migrations. - Migration files should be named with incremental prefixes (e.g.,
001_initial_schema.sql,002_add_table.sql, etc.).
See API documentation for full details.
You can now specify an action for event delivery. The action field supports both webhook and websocket types.
{
"name": "My Event",
"description": "A test event",
"start_time": "2025-01-01T00:00:00Z",
"action": {
"type": "webhook",
"params": { "url": "https://example.com/webhook" }
},
"tags": ["api"]
}{
"name": "Websocket Event",
"description": "A test event for websocket client",
"start_time": "2025-01-01T00:00:00Z",
"action": {
"type": "websocket",
"params": { "client_name": "client1" }
},
"tags": ["api"]
}{
"name": "API Call Event",
"description": "A test event for apicall action",
"start_time": "2025-01-01T00:00:00Z",
"action": {
"type": "apicall",
"params": {
"method": "POST",
"url": "https://api.example.com/endpoint",
"headers": { "Authorization": "Bearer token", "Content-Type": "application/json" },
"body": "{ \"foo\": \"bar\" }"
}
},
"tags": ["api"]
}The legacy webhook field is still supported for backward compatibility. If you provide webhook, it will be automatically mapped to the appropriate action.
Qhronos uses an extensible action system for event delivery. Each event can specify an action object with a type and params. Supported types:
webhook: Delivers the event to an HTTP endpoint.websocket: Delivers the event to a connected websocket client.apicall: Makes a generic HTTP request with custom method, headers, body, and url.
The system is designed to be extensible for future action types.
The schedule parameter in event creation allows you to define recurring or one-time schedules using a flexible JSON structure. Here are the most common use cases:
If you omit the schedule field, the event will be scheduled only once at the specified start_time:
{
"name": "One-Time Event",
"description": "This event happens only once.",
"start_time": "2024-05-01T10:00:00Z",
"webhook": "https://example.com/webhook",
"metadata": {},
"tags": ["single"]
// No "schedule" field!
}"schedule": {
"frequency": "daily"
}This schedules the event to occur every day. (If interval is omitted, it defaults to 1.)
"schedule": {
"frequency": "weekly",
"by_day": ["MO", "FR"]
}This schedules the event to occur every Monday and Friday.
Tip: Omitting the schedule field results in a one-time event. For recurring events, specify the schedule field as shown above.
- Copy
config.example.yamltoconfig.yamland edit as needed. - Or set CLI flags to override config values.
The scheduler section in your config controls how far into the future recurring events are expanded and how often the expander runs:
scheduler:
look_ahead_duration: 24h # How far into the future to expand recurring events
expansion_interval: 5m # How often to run the expanderlook_ahead_duration: Controls the window (e.g., 24h) for which recurring event occurrences are pre-generated.expansion_interval: How frequently the expander checks and generates new occurrences.
Adjust these values in your config.yaml to tune scheduling behavior for your workload.
Qhronos supports real-time event delivery via a WebSocket endpoint at /ws.
- Client-Hook Listener: Receives events where the event webhook is
q:<client-name>. - Tag-Based Listener: Receives events that match any of the specified tags.
- Connect to the WebSocket endpoint:
ws://<host>/ws - Send an initial handshake message:
- For client-hook:
{ "type": "client-hook", "client_name": "acme-corp", "token": "<JWT>" } - For tag-listener:
{ "type": "tag-listener", "tags": ["billing", "urgent"], "token": "<JWT>" }
- For client-hook:
- On success, the server will send event messages as they occur:
{ "type": "event", "event_id": "evt_123", "occurrence_id": "occ_456", "payload": { ... }, "tags": ["foo", "bar"] } - (Client-hook only) To acknowledge receipt:
{ "type": "ack", "event_id": "evt_123", "occurrence_id": "occ_456" }- Tag-listener connections will receive an error if they send an ack message.
- All connections require authentication via JWT or master token.
- Only authorized clients receive events for their hooks or allowed tags.
See the design document for more details on message flows and security.
- Supports Docker, Docker Compose, and Kubernetes.
- See deployment guide for production tips.
You can build and run Qhronos using Docker:
make docker-build
# or
# docker build -t qhronosd:latest .make docker-qupmake docker-qdown- Custom config file:
docker run -v /path/to/your/config.yaml:/app/config.yaml -p 8080:8080 qhronosd:latest
- CLI flags (recommended):
docker run qhronosd:latest --port 9090 --log-level debug
You can combine these methods as needed.
- Use a master token or generate JWTs via the
/tokensendpoint. - See docs/auth.md for details.
Qhronos supports JWT (JSON Web Token) authentication for secure, scoped API access.
- Obtaining a JWT Token:
Use your master token to request a JWT via the
/tokensendpoint:curl -X POST http://localhost:8080/tokens \ -H "Authorization: Bearer <master_token>" \ -H "Content-Type: application/json" \ -d '{ "sub": "your-user-id", "access": "admin", "scope": ["user:your-username"], "expires_at": "2024-12-31T23:59:59Z" }'
An Occurrence represents a single execution attempt of an event, created only after a scheduled event (from Redis) is executed by the dispatcher. For recurring events, multiple occurrences are generated as each scheduled execution is processed. Each occurrence tracks its status, attempts, and delivery history.