ワンクリックで
grain-periodic-task
Create scheduled background jobs that run on cron schedules
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Create scheduled background jobs that run on cron schedules
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Give your AI agents something more useful than a prompt. Velocity through clarity.
Visual quality gate — screenshot each primary screen, critique it against the baseline rubric, and refine the components until it passes. Use before declaring a build done.
Implement command handlers that validate state, emit events, and return signals using the Grain framework
Implement query handlers that read from event-sourced read models and render hiccup
Build read models by reducing events into queryable state with defreadmodel
Define Malli schemas for commands, events, and queries using defschemas
| name | grain-periodic-task |
| description | Create scheduled background jobs that run on cron schedules |
Periodic tasks are scheduled background jobs that run on cron or interval schedules. They operate outside the request/response cycle and are used for maintenance, cleanup, reminders, and other time-based operations.
In this compact app, periodic triggers are managed through the periodic-task interface
(ai.obney.grain.periodic-task.interface). A service exposes a periodic_tasks.clj
file with start/stop functions that wrap pt/start-periodic-triggers! and
pt/stop-periodic-triggers!. The root system wires this lifecycle through the
::periodic-triggers Integrant key in src/cjbarre/grain_todo_list.clj.
service/<svc>-service/periodic_tasks.clj)(ns cjbarre.grain-todo-list.service.<svc>-service.periodic-tasks
"Scheduled background triggers for <svc>-service."
(:require [ai.obney.grain.event-store-v3.interface :as es]
[ai.obney.grain.periodic-task.interface :as pt]))
(defn start
[{:keys [event-store tenant-id]}]
(pt/start-periodic-triggers!
{:append-fn (partial es/append event-store)
:tenant-ids-fn (constantly #{tenant-id})}))
(defn stop
[triggers]
(pt/stop-periodic-triggers! triggers))
pt/start-periodic-triggers! takes an :append-fn (used to append the trigger event
into the event store) and a :tenant-ids-fn returning the set of tenant ids to run for.
It returns a handle that pt/stop-periodic-triggers! shuts down. The individual triggers
(their schedules and the events they emit) are defined via the periodic-task interface;
prefer the real pt/start-periodic-triggers! / pt/stop-periodic-triggers! names over
any ad-hoc registry.
A periodic task may run twice (e.g. overlapping ticks or a restart). Make each run safe to repeat. Two strategies:
Use a compare-and-swap append so the work only happens once for a given key/period:
(require '[ai.obney.grain.event-store-v3.interface :as es :refer [->event]])
(es/append event-store
{:tenant-id tenant-id
:events [(->event {:type :todo/digest-sent
:tags #{[:user user-id]}
:body {:user-id user-id
:date (str (java.time.LocalDate/now))}})]
:cas {:types #{:todo/digest-sent}
:tags #{[:user user-id]}
:predicate-fn (fn [existing]
(empty? (into [] existing)))}})
The CAS predicate checks no :todo/digest-sent event already exists for this user with
these tags. If another run already emitted one, the append silently fails — at most once.
Alternatively, let the triggered command's own validation reject a duplicate (e.g. the command refuses if the entity is already in the target state). Either way, a task running twice must not double-apply its effect.
tenant-ids-fn returns the set of tenants the triggers run for. In this single-tenant
dev app it is (constantly #{tenant-id}). To enumerate tenants dynamically, read them
from the event store: es/tenants (from ai.obney.grain.event-store-v3.interface)
returns {tenant-id {:tenant/last-event-id uuid-or-nil}} — use (keys ...) for the ids.
The root system already wires periodic triggers via the ::periodic-triggers Integrant
key in src/cjbarre/grain_todo_list.clj:
;; in the system map:
::periodic-triggers {:event-store (ig/ref ::event-store)
:tenant-id tenant-id}
;; init / halt:
(defmethod ig/init-key ::periodic-triggers [_ config]
(periodic-tasks/start config))
(defmethod ig/halt-key! ::periodic-triggers [_ triggers]
(periodic-tasks/stop triggers))
with [cjbarre.grain-todo-list.service.todo-list-service.periodic-tasks :as periodic-tasks]
in the ns require. To add periodic tasks for another service, either add triggers to the
existing periodic-tasks/start, or add a new service periodic_tasks.clj and a parallel
Integrant key. After changing it, restart the system:
(app/stop app)
(def app (app/start))
src/cjbarre/grain_todo_list/service/todo_list_service/periodic_tasks.clj — working start/stopsrc/cjbarre/grain_todo_list.clj — the ::periodic-triggers Integrant wiring