Skip to content

feat: recurring/scheduled task support#73

Open
marktoda wants to merge 12 commits into
earendil-works:mainfrom
marktoda:schedules
Open

feat: recurring/scheduled task support#73
marktoda wants to merge 12 commits into
earendil-works:mainfrom
marktoda:schedules

Conversation

@marktoda

@marktoda marktoda commented Mar 5, 2026

Copy link
Copy Markdown

Summary

Adds cron and interval-based recurring tasks

Schedules are ticked lazily inside claim_task(). When a worker polls for work, tick_schedules checks for due schedules and spawns tasks before returning. Epoch-based idempotency keys (sched::) prevent double-firing across concurrent workers.

Two catchup policies handle downtime recovery:

  • skip (default) — jump to the next future occurrence; stale runs are discarded
  • all — drip-feed missed runs (up to 5 per tick) until caught up

What changed

SQL — s_{queue} table, parse_cron_field (full 5-field cron with wrap-around ranges, named days/months, @daily/@every shorthands), next_cron_time, tick_schedules, and CRUD functions (create_schedule, get_schedule, list_schedules, update_schedule, delete_schedule).

TypeScript & Python SDKs — createSchedule, getSchedule, listSchedules, updateSchedule, deleteSchedule. Spawned tasks inherit all schedule metadata
(params, headers, retry strategy, cancellation, max attempts).

absurdctl — create-schedule, list-schedules, update-schedule, delete-schedule commands.

Habitat — GET /api/queues/{queue}/schedules endpoint and a new Schedules view with queue filtering, status badges, and relative timestamps.

Test plan

538-line test suite (tests/test_schedules.py) covering cron parsing, next-time computation, full CRUD lifecycle, tick behavior for both catchup policies, and idempotency.

AI Disclosure

This feature was developed with the use of Claude Code

Contribution Agreement

By submitting this pull request, I confirm the following:

I understand that the entity Earendil Inc. (incorporated in the state of Delaware in 2025) needs some rights from me in order to utilize my contributions in this PR. As a contributor I retain all rights, title and interest in and to my contributions, and by keeping this boilerplate intact I confirm that Earendil Inc. can use, modify, copy, and redistribute my contributions, under Earendil Inc.'s choice of terms.

Fixes #72

marktoda and others added 12 commits March 2, 2026 11:57
Database-level recurring task support via per-queue s_{queue} tables,
PL/pgSQL cron parser, and tick_schedules called inside claim_task.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
10 bite-sized tasks covering cron parser, schedule table, CRUD
procedures, tick engine, claim_task integration, TypeScript SDK
methods, migration, and integration tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Expands a single cron field (wildcards, ranges, steps, lists, named
days/months) into a sorted integer array. Foundation for the cron
parser needed by recurring schedules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement database-level schedule management with PL/pgSQL cron engine.
Schedules are per-queue (s_{queue} table), ticked automatically when
workers poll via claim_task, with skip/all catchup policies.

New stored procedures:
- parse_cron_field: expands cron field to integer array
- next_cron_time: 5-field cron + shorthands + @every N
- create/get/list/delete/update_schedule: full CRUD
- tick_schedules: spawns tasks from due schedules

Modified: claim_task (ticks before claiming), ensure_queue_tables and
drop_queue (include s_ table lifecycle).

Also adds TypeScript SDK methods, migration file (0.0.7-0.0.8), and
35 schedule-specific tests (68 total passing).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two fixes from code review:

1. Idempotency keys in tick_schedules used timestamptz::text which
   renders differently depending on session TimeZone, allowing
   duplicate task spawns across workers with different TZ settings.
   Changed to extract(epoch from ...)::bigint::text for a
   timezone-independent key.

2. Migration file used bare "create function" for new functions,
   causing failure if re-run after partial application. Changed all
   8 new functions to "create or replace function" to match the
   pattern already used for modified functions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three fixes from code review:

1. parse_cron_field now handles wrap-around ranges (e.g. FRI-MON /
   5-1) by expanding both ends of the range around the boundary.
   Previously this produced an empty array and a cryptic "no match
   found within 4 years" error.

2. next_cron_time now validates that @every is followed by a plain
   integer. Previously @every 300s or @every 5m hit a raw Postgres
   cast error.

3. Added index on s_{queue}(enabled, next_run_at) so tick_schedules
   uses an index scan instead of a sequential scan on every
   claim_task poll.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Nanoclaw-inspired personal AI assistant built on Absurd as the durable
task management layer. Key design decisions: Telegram via grammY,
Claude API for reasoning, containerized plugin execution, markdown
agent notes, Postgres for history/state, no slash commands.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add schedule management commands to absurdctl (create-schedule,
update-schedule, delete-schedule, list-schedules), a schedules API
endpoint and view to the habitat web UI, and schedule CRUD methods
to the Python SDK (both sync and async clients).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ipts

The original Docker-per-plugin architecture was too rigid for self-extending
behaviors. Replace with a nanoclaw-inspired model where the agent runs
entirely in a Docker container and extends itself by writing markdown skill
files, executable tool scripts, and creating Absurd schedules. Add the full
implementation plan (14 tasks across 6 phases).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
#	absurdctl
#	habitat/ui/src/App.tsx
#	sdks/typescript/package-lock.json
#	sql/absurd.sql
@mitsuhiko

Copy link
Copy Markdown
Member

I'm definitely open to it, I'm not sure if I am happy with that approach but I will review it.

@gakonst

gakonst commented Jun 1, 2026

Copy link
Copy Markdown

Hey @mitsuhiko (apologies for over-pinging per the other PR) - could you unpack what you'd prefer for this approach? Happy to take it over / work with Mark (we've worked together before) on getting this over the line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Interest in recurring/scheduled task support?

3 participants