The
monitor field is a declarable spec the core round-trips through storage. The probe, hashing, bounded diff, state persistence, and the no_change record live in the praisonai-bot / wrapper layer — the core owns only the contract.Quick Start
1
Turn an agent into a monitor
Add
monitor to a ScheduleJob. The agent runs only when the watched URL changes.2
Watch a shell source instead
Use
command inside monitor to watch any cheap shell output — a file, a database count, a health probe.What The User Sees
Silence is the feature — an unchanged source produces no ping and spends no tokens.Three Outcomes
Every tick records exactly one status.no_change is distinct from skipped (a generic gate go/no-go) so you can tell “nothing changed” apart from “the gate said don’t run”.
How It Works
Each tick probes the source, hashes the output, and compares it to the last-seen hash held in per-job state.no_change is a distinct status from skipped. skipped means “a gate said don’t run”; no_change means “a watched source was unchanged” — so operators can tell the two apart in run history.Configuration Options
monitor spec
monitor is a small mapping naming one cheap source to probe each tick.
ScheduleJob field
GateResult — monitor outcome
The wrapper’s monitor gate returns a GateResult carrying the monitor-mode outcome.
Setting
no_change=True always forces run=False, so a monitor can never fire a tick the contract defines as suppressed.
RunRecord.status
URL Monitor Reliability & Safety
Every URL probe runs behind a bounded, hardened path. These are the guarantees an operator can rely on before pointing a monitor at anything they don’t own end-to-end.Slow-body drip protection (PR #4237): the total-deadline watchdog now aborts even on
Connection: close responses where http.client has already nulled the socket. Previously a hostile page dripping one byte per socket window could stall the entire sequential scheduler tick — the fix retains a raw socket handle so the watchdog can still shut the connection down.Monitor vs Pre-Run vs Command
Three scheduler features feel similar but solve different problems.From the SDK docstring: “monitor is distinct frompre_run(a stateless go/no-go gate) andcommand(a model-free delivery action).”
On the scheduled delivery path, a
no_change outcome is silently suppressed — no ping, no tokens. See Scheduler Delivery and Bot Intentional Silence for how unattended-monitor silence is enforced unconditionally.The heavy MonitorGate (URL/shell probe + hashing + diffing + state persistence) is a wrapper-layer feature. This page documents the core contract — the declarable monitor spec, the GateResult outcomes, and the state-store protocol.Per-Job State
A monitor needs memory across wake-ups — the last-seen hash or a watermark. The core defines theJobStateStoreProtocol contract, and the concrete ConfigYamlScheduleStore implementation now ships (PR #4057) with a 16 KiB per-job cap. The executor auto-injects a “Notepad” block into the prompt and persists GateResult.state_updates on go and no_change ticks alike.
See Cross-Run Memory for the full notepad walkthrough — the format, how the agent writes back, and the one-shot-job caveat.
PR #4205: Scheduled ticks now execute via the shared
run_sync_or_offload bridge instead of a fresh asyncio.run() per fire. This preserves per-loop LiteLLM / HTTPX / asyncpg connection pools across ticks, lets Langfuse (and other flushers) actually flush from scheduled runs, and honours scoped_bridge bindings. Long-running scheduled agents run unbounded (timeout=None) — matching pre-fix behaviour — because ScheduleLoop already claims the occurrence atomically before on_trigger fires.CLI, YAML, and Python Parity
The samemonitor spec round-trips through every front-end.
- Python
- YAML
ScheduleJob.to_dict() / from_dict() only persist monitor when it’s configured, so stateless jobs stay byte-for-byte unchanged.
Best Practices
Bound your source so a large fetch can't stall the tick
Bound your source so a large fetch can't stall the tick
The probe runs on every tick. Watch a small, cheap source — a status endpoint, a row count, a file’s modification marker — not a multi-megabyte page. A bounded source keeps the ticker responsive.
Use monitor when you want silence-when-unchanged
Use monitor when you want silence-when-unchanged
Reach for
monitor when the whole point is “only tell me when something changed” — a page, a feed, a metric. Unchanged ticks cost zero tokens and deliver nothing.Use pre_run when you want silence-when-nothing-to-do
Use pre_run when you want silence-when-nothing-to-do
If the decision is a stateless go/no-go (an inbox check, a queue depth), use
pre_run instead — it doesn’t need to remember a prior hash.Let first-run seed the baseline
Let first-run seed the baseline
The first tick has no prior state, so it always runs and stores the baseline hash. Expect one delivery when a monitor job is created, then silence until the source moves.
Watch no_change separately from skipped
Watch no_change separately from skipped
Filter run history on
no_change to see “how often was the source unchanged” without conflating it with a generic gate skip.Leave prior state on a probe error
Leave prior state on a probe error
Return
state_updates=None on an error probe so prior state is untouched — a later recovery to the prior output still suppresses correctly.Keep stored state bounded
Keep stored state bounded
State is a small scratchpad, not a database. Store cursors and hashes, not full payloads, so a runaway write can’t grow unbounded.
Prefer a small endpoint over a full page for URL monitors
Prefer a small endpoint over a full page for URL monitors
Each URL probe is capped at a total wall-clock deadline (default 30 s) covering connect and body read, so a slow-drip attacker can no longer stall the scheduler tick. A legitimate page you don’t need still costs a fetch on every tick, though — prefer a small JSON endpoint over a full page and the whole loop stays cheap.
Related
Cross-Run Memory
The notepad a monitor reads and writes across runs
Pre-Run Gate
Stateless go/no-go gate — skip when there’s nothing to do
Command Action
Model-free action — deliver a command’s stdout verbatim
Scheduler Delivery
Push scheduled results to Telegram/Discord/Slack/WhatsApp
Bot Intentional Silence
How unattended silence is enforced
Async Scheduler
Schedule agents on intervals, cron, or one-shot timestamps
Context Chaining
Fan a monitor’s output into a downstream job as bounded context
Scheduled Run Policy
Safety gates on what a scheduled run may do

