Save a timer
An AI can draft a reflex from a sentence: “in 40 minutes”. Saving it turns that into a date and time, and the bell still rings if the server restarts in between.
pnpm add @niscorp/tide @niscorp/prism @niscorp/strata zodA draft
A draft is a reflex as a model writes one: it may say timer where a reflex says clock, and it cannot say who it runs as. @niscorp/tide/agent has the agent that writes drafts from words. This file writes one by hand, as that agent would.
import { anchorDraft, createTide, createMemoryStore } from '@niscorp/tide';
import type { ReflexDraft } from '@niscorp/tide';
import { evaluate } from '@niscorp/prism';
const draft: ReflexDraft = {
id: 'interval-bell',
intent: 'Ring the bell five minutes before the second half.',
on: { timer: { minutes: 40 } },
effect: { name: 'bell.ring', input: { text: 'Five minutes to the second half.' } },
};Saving it
anchorDraft is what a save does. It takes the time of the save, and turns “in 40 minutes” into a date and time.
Who the reflex runs as is added by your app at the save. A model cannot choose it.
const savedAt = Date.parse('2026-10-24T19:00:00+02:00');
const reflex = { ...anchorDraft(draft, { at: savedAt, tz: 'Europe/Vienna' }), as: 'stage-manager' };
console.log(JSON.stringify(reflex.on), '· as', reflex.as);An engine, and a restart
Your app stores the reflex, and Tide’s state is in its store. So a restart is a new engine on the same store, with the reflex loaded again.
With Moss the store is Postgres. Here it is the one in memory, kept across the two engines.
const store = createMemoryStore();
const engine = () =>
createTide({
store,
transform: evaluate,
effects: { 'bell.ring': { writes: [], run: (input, ctx) => console.log('bell.ring', input, '· as', ctx.actor) } },
});
const first = engine();
await first.load([reflex], { at: savedAt });
await first.advance({ now: savedAt });
const restarted = engine();
await restarted.load([reflex]);The bell rings once
The second engine is called at 19:40 and the bell rings. Called again at 19:41, it does not ring again: that run is already recorded.
await restarted.advance({ now: savedAt + 40 * 60_000 });
await restarted.advance({ now: savedAt + 41 * 60_000 });
console.log('runs:', (await restarted.ledger.runs({})).length);Run it.
The timer became a date and time, 19:40:00 in Vienna. The bell rang once, as the stage manager, in an engine that was started after the save.
npx tsx timer.mts{"clock":{"at":"2026-10-24T19:40:00","tz":"Europe/Vienna"}} · as stage-manager
bell.ring { text: 'Five minutes to the second half.' } · as stage-manager
runs: 1