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:
“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 onScheduleJob control chaining.
ScheduleRunner.resolve_context(job) returns a (context, missing) tuple.
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.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 independentmonitor jobs’ outputs.
Best Practices
Bound context_max_chars per stage
Bound context_max_chars per stage
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.
Refs by name read well; ids are stable
Refs by name read well; ids are stable
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.
Use on_missing_context='skip' when empty inputs are meaningless
Use on_missing_context='skip' when empty inputs are meaningless
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.Declared order is preserved verbatim
Declared order is preserved verbatim
Sections appear in the exact order you list
context_from. Put the most important upstream first so it leads the assembled prompt.resolve_context is pure — call it in a dry run
resolve_context is pure — call it in a dry run
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.Related
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

