feat(trigger): schedule flows on cron via first-class trigger records (1.6.0) - #52
Merged
Merged
Conversation
… (1.6.0)
Adds flow_trigger — cron schedules for flows on this box — plus the scheduler
that fires them.
A trigger is a separate record (.clawflow/triggers/<id>.json), not a field on
the flow definition. A schedule is mutable operational state (pause at 2am,
retime, repoint at a version) while a published version is immutable: embedding
one in the other makes "pause" mean "publish a new version", and lets an
unrelated publish silently re-arm a schedule the draft happened to carry.
Temporal made the same move from cron-in-workflow to a separate Schedules
object. It also lets N triggers target one flow with different cadences and
different inputs, which a single embedded field cannot express.
- src/core/triggers.ts TriggerStore, one file per record (mirrors store.ts)
- src/core/scheduler.ts croner timer per record; fires runner.run() directly,
the same path the HTTP flow server takes
- flow_trigger tool create | update | list | pause | resume | delete | run_now
Behavior:
- Standard 5-field cron, minimum interval 60s. Triggers are agent-writable, so
sub-minute schedules are rejected at the trust boundary rather than becoming
a runaway cost incident.
- Missed runs are NOT replayed; replaying side-effecting flows after downtime
is the worse failure.
- Runs never overlap (croner protect).
- flow_delete PAUSES a flow's triggers rather than removing them — it is a soft
delete, and a restore must not silently re-arm unattended runs.
- Mutating actions are gated by the existing approval gate: arming a schedule
commits the box to running a flow unattended with tool access.
- The scheduler re-reads the directory every 30s, so records written
out-of-process (Clawnify hook server / dashboard, importing TriggerStore from
dist as they already do for versions) go live without a restart. Unchanged
timers are not rebuilt, so a resync never starves a slow schedule.
Also extracts resolveRunnableFlow() into manage.ts and rewires serve.ts through
it, so "which definition does a trigger run" lives in exactly one place rather
than being duplicated for the scheduler — the divergence 1.5.1 fixed.
croner@10.0.1 is the package's first runtime dependency: it is what OpenClaw
itself uses, so cron semantics match the host, and DST-correct parsing is not
something to hand-roll.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds flow_trigger — cron schedules for flows on this box — plus the scheduler
that fires them.
A trigger is a separate record (.clawflow/triggers/.json), not a field on
the flow definition. A schedule is mutable operational state (pause at 2am,
retime, repoint at a version) while a published version is immutable: embedding
one in the other makes "pause" mean "publish a new version", and lets an
unrelated publish silently re-arm a schedule the draft happened to carry.
Temporal made the same move from cron-in-workflow to a separate Schedules
object. It also lets N triggers target one flow with different cadences and
different inputs, which a single embedded field cannot express.
the same path the HTTP flow server takes
Behavior:
sub-minute schedules are rejected at the trust boundary rather than becoming
a runaway cost incident.
is the worse failure.
delete, and a restore must not silently re-arm unattended runs.
commits the box to running a flow unattended with tool access.
out-of-process (Clawnify hook server / dashboard, importing TriggerStore from
dist as they already do for versions) go live without a restart. Unchanged
timers are not rebuilt, so a resync never starves a slow schedule.
Also extracts resolveRunnableFlow() into manage.ts and rewires serve.ts through
it, so "which definition does a trigger run" lives in exactly one place rather
than being duplicated for the scheduler — the divergence 1.5.1 fixed.
croner@10.0.1 is the package's first runtime dependency: it is what OpenClaw
itself uses, so cron semantics match the host, and DST-correct parsing is not
something to hand-roll.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com