Quick Start
YAML (simplest)
Add
pre_run to your agents.yaml schedule block. The agent only runs when the script exits 0 with output.How It Works
The gate runs inasyncio.to_thread so a slow check does not block other scheduled ticks. If the gate itself raises an exception, the executor falls back to running the agent (defensive default).
Configuration Options
ScheduleJob fields
| Field | Type | Default | Description |
|---|---|---|---|
pre_run | Optional[str] | None | Shell command run before each tick. Set via YAML schedule.pre_run or --pre-run |
condition | Optional[str] | None | Advisory natural-language label (round-tripped, not enforced by the default gate) |
ScheduledAgentExecutor parameter
| Parameter | Type | Default | Description |
|---|---|---|---|
condition_resolver | None | False | callable | None | None = auto shell gate when pre_run set; False = gating disabled; callable (job) → gate | None = custom resolver |
ShellConditionGate options
| Option | Type | Default | Description |
|---|---|---|---|
timeout | float | 30.0 | Seconds before the gate is treated as a skip |
_MAX_CONTEXT_CHARS | int | 8000 | Cap on captured stdout appended as context |
_MAX_REASON_CHARS | int | 500 | Cap on stderr surfaced in the skip reason |
Gate decision table
| Gate command exit | stdout | Decision | What happens |
|---|---|---|---|
0 | non-empty | run=True, context=stdout | Model runs, stdout appended to message |
0 | empty | run=True, context=None | Model runs with original message only |
| non-zero | — | run=False | Tick recorded as skipped — no tokens, no delivery |
timeout (>timeouts) | — | run=False | Same as skipped |
| gate raises | — | falls back to run=True | Model runs (defensive) |
Pre-Run Gate vs RunPolicy
Two complementary gates apply on every tick when both are configured:| Gate | Purpose | When to configure |
|---|---|---|
Pre-run gate (pre_run) | Efficiency — should a run happen at all? | When most ticks have nothing to do |
| RunPolicy | Safety — what is an unattended run allowed to do? | Always in production unattended runs |
Common Patterns
Inbox watcher
Only fire when new mail arrives — cuts 288 daily runs to just the count of mail bursts.scripts/new_mail.sh exits 0 and prints new messages when mail is present; exits non-zero when the inbox is quiet.
CI babysitter
Alert a channel only when a build breaks.Custom Python gate
Use a callable resolver for richer logic than a shell exit code.Best Practices
Keep the gate cheap and deterministic
Keep the gate cheap and deterministic
The gate runs on every tick. Aim for under 1 second — a fast file check, an API ping, or a database row count. Avoid heavy computation.
Always set a sensible timeout
Always set a sensible timeout
The default
timeout=30.0 is conservative. A runaway gate stalls that tick’s slot. Set it to match your gate’s expected worst case.Use stdout to seed the prompt
Use stdout to seed the prompt
The same check that gates the run can feed it the data — the gate’s stdout is automatically appended to the message.The agent then receives:
"Summarise these new emails... <new mail subjects>".Don't put secrets in pre_run
Don't put secrets in pre_run
The
pre_run string is stored verbatim in the job record. Use environment variables or a secrets manager instead of inline credentials.pre_run is CLI/YAML/Python-only — never LLM-callable
pre_run is CLI/YAML/Python-only — never LLM-callable
The agent-callable
schedule_add tool deliberately does not accept pre_run. This prevents a prompt-injected agent from persisting arbitrary shell commands on the host. Always configure pre_run through the CLI, YAML, or Python code.Understand skipped vs failed
Understand skipped vs failed
A gate returning non-zero records the tick as
skipped (not failed) — this is expected normal operation when there’s nothing to do. Check your run history with praisonai schedule logs to see skip rates.Related
Async Agent Scheduler
The scheduler this gate plugs into
Schedule CLI
CLI surface — where
--pre-run and --condition are configured
