Skip to main content
Turn isolated scheduled jobs into a composable pipeline — a downstream job names its upstreams, and the runner prepends each upstream’s most-recent successful output to the downstream message as bounded, clearly-delimited context.
resolve_context is pure and side-effect-free — it returns the resolved text; a wrapper executor is what prepends it to message. The core owns only the shape (three ScheduleJob fields) and the resolution contract.

Quick Start

1

One upstream feeds one downstream

Point a downstream job at an upstream by name. At fire time the runner resolves the upstream’s last successful output and prepends it as context.
2

Bare-string upstream in YAML

For a single upstream, write context_from as a plain string — from_dict coerces it to a one-element list.
3

Multiple upstreams + skip on empty

Name several upstreams, tighten the per-stage budget, and refuse to run when an input is missing.

How It Works

Each downstream tick resolves its upstreams from execution history, bounds each output, and formats one section per upstream. Each resolved upstream becomes a section formatted exactly as ### Context from '{ref}' followed by its output, and sections join with a blank line:
Two upstreams preserve declared order:
“Most-recent successful” is strict: a failed record does not satisfy a ref, and a succeeded record with an empty result does not either — the ref is reported as missing.

Configuration Options

Three declarable fields on ScheduleJob control chaining. ScheduleRunner.resolve_context(job) returns a (context, missing) tuple.
A zero or negative context_max_chars clamps to 0 — an empty body, not the full upstream output. This preserves the bounded contract even when the budget is misconfigured.
ScheduleJob.to_dict() only emits context_from when it’s truthy, and emits context_max_chars / on_missing_context only when they differ from their defaults. A job that never sets context_from serialises byte-for-byte the same as before.

Common Patterns

Morning briefing pipeline

Three chained jobs — fetch, summarise, deliver — each small and independently observable in run history.
The final stage typically delivers — see Scheduler Delivery.

Fail-closed downstream

Refuse to draft a briefing when the inbox fetch produced nothing.
on_missing_context="skip" records the tick as the existing skipped outcome — no tokens, no delivery — so the downstream never runs on empty inputs.

Fan-in from two watchers

One downstream consumes two independent monitor jobs’ outputs.

Best Practices

Each upstream is truncated independently, so set a budget that fits the downstream prompt. A tight budget on a chatty upstream keeps the assembled prompt small and cheap.
A name is readable in YAML and history, but it changes if you rename the job. An id never changes. Use names for hand-authored pipelines and ids when the reference must survive a rename.
If a downstream has nothing useful to say without its upstream, skip records the tick as skipped (no tokens, no delivery) — consistent with the pre-run gate. Leave the default "run" when partial context is still useful.
Sections appear in the exact order you list context_from. Put the most important upstream first so it leads the assembled prompt.
Because it has no side effects, a wrapper can call resolve_context to preview exactly what a downstream tick would see — and which upstreams are missing — before spending any tokens.

Async Scheduler

Schedule agents on intervals, cron, or one-shot timestamps

Scheduler Delivery

Push a pipeline’s final stage to Telegram/Discord/Slack/WhatsApp

Scheduler Monitor

Wake a job only when a watched source changed — fan its output in

Pre-Run Gate

Stateless go/no-go gate — skip when there’s nothing to do