Scheduler Command Action
Run a shell command on schedule and deliver its stdout verbatim — no agent, no model turn
Attach a command to a scheduled job to run a shell command on its schedule and deliver the raw stdout to your channel — no agent is resolved and no model turn is taken.
A command job is a model-free action: cheap, deterministic, and token-free — ideal for watchdogs like df -h, uptime, or a health-check curl.
Quick Start
- CLI
Add a job with
--command. Its stdout is delivered as-is — no--message, no agent, no tokens.bash praisonai schedule add "disk-watch" -s hourly \ --command "df -h /" \ --deliver telegram:-100123 - Python
Set
commandon aScheduleJob. Whencommandis present, the job takes the model-free path.python from praisonaiagents.scheduler import ScheduleJob, Schedule, DeliveryTarget job = ScheduleJob( name="disk-watch", schedule=Schedule(kind="every", every_seconds=3600), command="df -h /", command_timeout=30, delivery=DeliveryTarget.parse("telegram:-100123"), )
How It Works
The executor checks for a command before resolving any agent — a command job never touches the model.
| Detail | Behaviour |
|---|---|
| No model turn | No agent is resolved; zero tokens are spent. |
| Off the event loop | The command runs via asyncio.to_thread, so a slow command never blocks other ticks. |
| Bounded runtime | The command is killed after command_timeout seconds; a timeout returns the conventional 124 code and records the tick as failed. |
| Process-group kill | On POSIX the command runs in its own session, so a timeout kills the whole process group (shell + children) rather than orphaning them. |
| Exit codes surface | A non-zero exit is not silently dropped — the delivered text is prefixed [exit N] <output>. |
| Output cap | stdout is bounded to 8,000 characters before verbatim delivery through the existing DeliveryTarget path. |
Choosing the Right Mode
A scheduled job runs in one of three modes depending on which fields are set.
| Configuration | Mode | Model turn? |
|---|---|---|
No command, no pre_run | Normal agent job (default) | Yes |
pre_run only | Agent job with a cost gate | Yes, when the gate says go |
command only | No-LLM command action | No |
pre_run is a gate (a cheap check that still runs the model); command is an action (a shell command that runs instead of the model).
Configuration Options
ScheduleJob fields
| Option | Type | Default | Description |
|---|---|---|---|
command | Optional[str] | None | Shell command whose stdout is delivered verbatim. When set, the job runs this command on its schedule and delivers the output as-is to delivery — with no agent resolved and no model turn taken. Additive and backward-compatible: jobs without a command keep the existing agent path. |
command_timeout | float | 60.0 | Max seconds the command may run before it is killed (with its process group on POSIX) and the tick is recorded as failed. Bounds the action so a hung command cannot stall the ticker. |
provider | Optional[str] | None | Model provider snapshot for a pinned job — not applicable to --command jobs (no model turn). See Scheduler Model Pin. |
model | Optional[str] | None | Model snapshot for a pinned job — not applicable to --command jobs (no model turn). See Scheduler Model Pin. |
pin_model | bool | True | Enforce the model snapshot on drift — not applicable to --command jobs (no model turn). See Scheduler Model Pin. |
command is persisted only when set; command_timeout is persisted only when a command is configured and the timeout differs from the default 60.0. Agent-only jobs are unchanged on disk. A --command job takes no model turn, so provider / model / pin_model have no effect on it — pinning is skipped for command jobs.
Common Patterns
Disk watch
Report free space on the root filesystem every hour.
praisonai schedule add "disk-watch" -s hourly \
--command "df -h /" \
--deliver telegram:-100123Health-check curl
Poll a health endpoint and post the raw response. Use --no-continuable so a reply starts a fresh session instead of resuming.
praisonai schedule add "api-health" -s "*/5m" \
--command "curl -s https://api.example.com/health" \
--deliver slack:C012345 \
--no-continuableUptime heartbeat
Post a lightweight heartbeat so you know the host is alive.
from praisonaiagents.scheduler import ScheduleJob, Schedule, DeliveryTarget
job = ScheduleJob(
name="uptime",
schedule=Schedule(kind="every", every_seconds=300),
command="uptime",
delivery=DeliveryTarget.parse("slack:C012345"),
)Best Practices
Bound every command with command_timeout
The default command_timeout=60.0 kills a hung command with its process group on POSIX. Lower it to match a fast watchdog so a stuck command cannot stall the ticker.
job = ScheduleJob(
name="disk-watch",
schedule=Schedule(kind="every", every_seconds=3600),
command="df -h /",
command_timeout=10,
)Keep output under 8,000 characters
stdout is capped at 8,000 characters before delivery. Trim chatty commands with head, tail, or a filter so the channel gets a clean, complete message.
praisonai schedule add "top-procs" -s hourly \
--command "ps aux --sort=-%mem | head -10" \
--deliver telegram:-100123Use –no-continuable for pure alerts
A command action is a notification, not a conversation. Add --no-continuable so a reply in the channel starts a fresh session instead of trying to resume a job that has no agent context.
Command jobs are a trusted, human-only surface
command runs an arbitrary host shell command. It is not accepted by the agent-callable schedule_add tool — only a human author via CLI, YAML, or Python can persist one. This prevents a prompt-injected agent from persisting arbitrary shell commands on the host.
Related
The go/no-go gate — cheap check that still runs the model turn
Scheduler DeliveryPush scheduled results to Telegram/Discord/Slack/WhatsApp
Context ChainingFeed one job’s output into the next as bounded context
Schedule CLICLI surface — where --command and --command-timeout are configured
Pin agent jobs to a model — skipped for --command jobs