on: block, and
timeless playbooks run renders its prompt through your
configured harness to produce an artifact.
Your machine has to be on. Playbooks run locally — the desktop app’s timer and
your agent both live on your computer, so nothing fires while it is shut down or
asleep. Event-driven playbooks catch up (the events wait in the queue and the first
pass after wake leases them) and
interval: playbooks resume on their next tick,
but an at: time that passes while the machine is off is skipped — it does not
run late.Two kinds of trigger
Every playbook sets exactly one ofon.event or on.schedule — that choice is what
makes it event-driven or scheduled.
Event-driven (on: { event: <type> }) binds the playbook to an ack-queue event
type. A run leases pending events of that type, renders the prompt once per event, and
acknowledges them. The event-type namespace is an open set; two types reach the queue
today — meeting.transcript_ready (what playbooks init binds to) and
meeting.initial_summary_ready. A pass is scoped to one type and never sees the other’s
events.
Scheduled (on: { schedule: … }) fires on a timer and never touches the queue.
There is no triggering event, so the prompt fetches whatever data it needs itself (for
example timeless meetings upcoming). Set one of:
interval: 30m— a positive Go duration.at: ["09:00"]— one or moreHH:MM24-hour local times, optionally narrowed withweekdays: [mon, tue, wed, thu, fri].
Timing lives inline, in the playbook. There is no
timeless schedule command and
no schedule.yaml: the desktop app’s resident timer derives what to run, and when,
from the playbooks: map directly. Seeding a playbook is scheduling it.An event-driven playbook is polled by the app on a built-in 5-minute cadence.
Override it per playbook with on.every: 1m. An empty playbooks: map runs
nothing — there is no implicit default.Where they live
Playbooks live under theplaybooks: key in your config file at
~/.config/timeless/config.yaml (respects TIMELESS_CONFIG_DIR / XDG_CONFIG_HOME).
Run timeless playbooks init to seed the defaults — it is
idempotent and never overwrites a prompt you have edited. On a fresh machine it
installs transcript_notes. timeless playbooks list --json shows what’s configured
and reports each trigger as event=<type> or schedule=….
Prompt variables differ by kind
The prompt is a Go text/template. The variable set depends on the trigger, so a scheduled prompt that references an event-only variable fails at run time. Event-driven:{{.EventID}}— the queue event id{{.EventType}}— e.g.meeting.transcript_ready{{.MeetingID}}— the payload’smeeting_id, falling back to itsid(which is the key real payloads use); empty only if the payload carries neither{{.Payload}}— the full event payload as compact JSON{{.OutPath}}— the absolute path the CLI expects the artifact written to
{{.Today}}— the run’s local date,2006-01-02{{.Now}}— the run’s local timestamp, RFC3339{{.OutPath}}— as above
A run succeeds when a non-empty file exists at
{{.OutPath}} — the harness’s exit
code is ignored. Every prompt should end by writing there. Artifacts land in
~/.local/share/timeless/artifacts and the desktop app surfaces each one to you
in-app; the CLI never opens a browser, so keep them self-contained.{{.OutPath}} rather than composing a filename. It
takes one of two shapes: <event type>-<event id>-<playbook>.html for an event-driven
run (the playbook name is in there precisely so several playbooks on one type cannot
overwrite each other) and <playbook>-<YYYY-MM-DD>.html for a scheduled one, where
there is no event id to key on.
Run them
--event-type prevents.
Stale events are skipped, not run
An event whose meeting time is older thanevents.max_age (default 24h) is acknowledged and
skipped: consumed for good, but no playbook runs and no artifact is written. It is
reported as expired and the pass still exits 0 — a skip is not a failure and never
means a recap went missing. This is what stops three days away from producing a burst of
dozens of recaps for meetings you have moved on from.
Widen the window when you want a backlog processed on purpose — --max-age 168h for one
pass, or timeless config set events.max_age 168h to persist it. There is no undo once
an event has been retired.
Turn one off without deleting it
enabled key. Nothing is deleted — the prompt and trigger
survive, so enable restores it exactly. Both are idempotent, and they govern future
runs only: a pass already in flight still finishes, so one more recap can land right
after you disable something. That run already consumed its event; let it land.
playbooks list does not show enabled state — it reports name, trigger, harness,
and prompt only. Read config.yaml, or trust the changed field disable returns.Several playbooks can share one event type
This is the point most often gotten wrong, and older guidance said the opposite. Sharing a trigger is correct — it is the supported way to get several deliverables from one meeting.timeless playbooks run --event-type <type> acknowledges each event once, then runs
every enabled playbook bound to that type, in name-sorted order, each writing its
own artifact. Paths are keyed <event type>-<event id>-<playbook name>.html, so they
cannot collide. Add a CRM sync and a Slack digest alongside transcript_notes — all
three on meeting.transcript_ready — and one transcript produces three artifacts.
Nothing needs to be renamed or removed to make room.
Two related guarantees: events pull is read-only and can
never cost an event its attempt, and scheduled playbooks never touch the queue at all.
Each playbook is a full agent run and they execute sequentially, so cost and
wall-clock grow with the size of the set for one event type.
Let your agent customize them
There is notimeless playbook command — playbooks are just YAML, so you edit them
directly or ask your agent to. The agent already knows the file location and the
template contract, so you can describe the change in plain language:
- “Explain my playbooks” — the agent reads the config and summarizes each one.
- “Have the recap include action items with owners” — the agent edits the
promptin place, preserving the template variables and the write-to-{{.OutPath}}step. - “Poll for transcripts every minute” — the agent sets
on.every: 1m. - “Pause the weekly brief” — the agent runs
timeless playbooks disable weekly_brief. - “Run transcript recaps on Codex” — the agent sets
harness: codexon that playbook. - “Add a Monday-morning digest” — the agent adds a playbook with an
on.scheduletrigger.
timeless playbooks list --json to confirm the trigger parsed as
you intended, then exercise it with timeless playbooks run.