Routines
A routine is a scheduled APX task. The daemon runs a scheduler tick every 5 seconds and fires
any routine that is due. Each routine has a kind, a schedule, an optional spec JSON blob,
and optional shell hook arrays (pre_commands / post_commands).
| Kind | LLM? | Tools? | Description |
|---|---|---|---|
heartbeat | No | — | Logs a marker. Useful as a “still alive” ping. |
shell | No | — | Runs a shell command. Stdout captured. |
exec_agent | Yes | Agent tools: | Loads a project agent, runs spec.prompt with that agent’s allowlist (not the full super-agent registry), persists a conversation. Pass allowed_tools: [] for the old one-shot text path. |
super_agent | Yes | All | Runs the default APX agent with the full tool registry. Multi-iteration loop. |
telegram | No | — | Sends a hardcoded spec.text via the Telegram plugin. |
Picking rule of thumb:
- Need a project agent to actually do the work (files, Asana, tools)? →
exec_agent. - Need the default APX agent (full tool registry, persona)? →
super_agent. - Need text from a model with no tool calls? →
exec_agentwithallowed_tools: []. - Pure shell without LLM? →
shell. - Fixed Telegram message on a schedule? →
telegram.
Schedule grammar
Section titled “Schedule grammar”| Format | Examples | Notes |
|---|---|---|
every:<N><unit> | every:30s, every:5m, every:24h, every:7d | Most common. |
once:<iso-8601> | once:2026-12-01T08:00:00Z | Fires once, then disables itself. |
| Cron expression | */5 * * * *, 0 8 * * * | Standard 5-field cron. |
apx routine add
Section titled “apx routine add”apx routine add <name> \ --kind <kind> \ --schedule <schedule> \ [--spec '<json>'] \ [--pre-commands 'cmd1,cmd2'] \ [--post-commands 'cmd'] \ [--skip-prompt-on signal|pre_failure|pre_success|always|never] \ [--permission-mode total|automatico|permiso] \ [--allowed-tools tool1,tool2] \ [--project <name|id|path>]--spec
Section titled “--spec”Kind-specific JSON config:
| Kind | Required spec keys | Optional |
|---|---|---|
exec_agent | prompt | agent (slug, defaults to default) |
super_agent | prompt | — |
shell | cmd | — |
telegram | text | — |
heartbeat | (none) | — |
--pre-commands and --post-commands
Section titled “--pre-commands and --post-commands”Comma-separated shell commands. They form a pipeline around the LLM call:
-
pre_commandsrun sequentially. Their combined stdout is available as:{{pre_output}}— substituted intospec.prompt(andspec.text) before the LLM call. Always substituted: with nopre_commands, or none that printed anything, the slot renders empty rather than reaching the model as literal braces.$APX_PRE_OUTPUT— environment variable forpost_commands.$APX_PRE_OUTPUT_FILE— path to a temp file with the full output (for large payloads).
-
The kind handler runs. Its text result is exposed as
$APX_LLM_OUTPUT. -
post_commandsrun sequentially with$APX_LLM_OUTPUT,$APX_PRE_OUTPUT, and$APX_STATUSin the environment.
--skip-prompt-on
Section titled “--skip-prompt-on”Controls when the LLM call (phase 2) is skipped, based on what pre_commands did:
| Value | Behavior |
|---|---|
signal (default) | Skip the LLM when the pre commands’ stdout contains APX_SKIP. Exit codes are ignored. This is a content signal, not an OS signal — the pre command decides. |
pre_failure | Skip the LLM when the last pre command exits non-zero. |
pre_success | Skip the LLM when the last pre command exits zero. The exact opposite of pre_failure — useful when the pre command’s success means there is nothing left to think about. |
always | Always skip the LLM — useful for pure pre→post pipelines. |
never | Always run the LLM, whatever the pre commands did. |
post_commands always run, whether or not the LLM was skipped; $APX_SKIPPED is 1 when it was.
Emitting the skip signal from a pre command looks like this:
apx routine add digest \ --pre 'test -s ~/report.txt || echo APX_SKIP' \ --skip-prompt-on signal--permission-mode
Section titled “--permission-mode”Overrides the global super_agent.permission_mode for this routine:
total | automatico | permiso.
Which model runs it
Section titled “Which model runs it”A routine runs on its agent’s own model (exec_agent) or the super-agent’s own model, and falls
back to the router. To give one job a different model, set --model provider:model (spec.model,
panel: Routine model). If that model fails, the run continues with the agent’s own model, then
the router — a routine model is a choice for that job, it never replaces the agent’s.
Other subcommands
Section titled “Other subcommands”apx routine list [--project <name|id|path>]apx routine get <name> [--project <name|id|path>]apx routine history <name> [--project <name|id|path>]apx routine run <name> [--project <name|id|path>] # force-trigger nowapx routine enable <name> [--project <name|id|path>]apx routine disable <name> [--project <name|id|path>]apx routine remove <name> [--project <name|id|path>]$ apx routine list --project myapp project #2 routines: NAME EN KIND SCHEDULE NEXT_RUN LAST morning-standup ✓ exec_agent every:1h 2026-06-14 10:00:00 ✓ 2026-06-14 09:00:02 daily-weather ✓ telegram once:08:00 2026-06-15 08:00:00 ✓ 2026-06-14 08:00:01 healthcheck ✓ heartbeat every:5m 2026-06-14 09:35:00 ✓ 2026-06-14 09:30:00 nightly-backup ✗ shell once:02:00 — ✗ 2026-06-13 02:00:04
Examples
Section titled “Examples”Plain text + Telegram delivery
Section titled “Plain text + Telegram delivery”apx routine add daily-weather \ --project myapp \ --kind exec_agent \ --schedule "every:24h" \ --spec '{"agent":"default","prompt":"El clima es {{pre_output}}. Una frase amigable, sin saludos."}' \ --pre-commands "curl -s 'https://wttr.in/London?format=%t+%C+viento+%w'" \ --post-commands 'apx telegram send "$APX_LLM_OUTPUT"'Super-agent with tools, morning schedule
Section titled “Super-agent with tools, morning schedule”apx routine add morning-standup \ --project myapp \ --kind super_agent \ --schedule "0 9 * * *" \ --spec '{"prompt":"List open tasks across projects and send me a short summary via Telegram."}' \ --permission-mode automaticoPure shell, no LLM
Section titled “Pure shell, no LLM”apx routine add db-backup \ --project myapp \ --kind shell \ --schedule "every:24h" \ --spec '{"cmd":"pg_dump mydb > /backups/mydb-$(date +%F).sql"}'One-shot future run
Section titled “One-shot future run”apx routine add deploy-prod \ --project myapp \ --kind super_agent \ --schedule "once:2026-07-01T10:00:00Z" \ --spec '{"prompt":"Run the production deploy checklist and report status."}'The double-reply gotcha
Section titled “The double-reply gotcha”# ✗ Fragile — relies on suppression heuristic--kind super_agent \--spec '{"prompt":"El clima es {{pre_output}}. Mandalo por Telegram."}' \--post-commands 'apx telegram send "$APX_LLM_OUTPUT"'
# ✓ Clean — model writes text, shell delivers it--kind exec_agent \--spec '{"prompt":"El clima es {{pre_output}}. Una frase amigable, sin saludos."}' \--post-commands 'apx telegram send "$APX_LLM_OUTPUT"'Debugging
Section titled “Debugging”apx routine history <name> --project myapp # last N runs with status and outputapx log -f # tail the unified daemon logapx messages tail --channel routine -n 20 # last 20 routine-channel messagesA routine that “sends nothing” most often means: enabled: false, next_run_at is in the
future, or the LLM returned empty text. Check apx routine history first — the result.text
field shows exactly what the model produced.
Watching a run
Section titled “Watching a run”The panel’s Routines screen shows the run happening now, not only the ones that finished. A running routine is marked as such from the moment it starts, the executions list draws its steps as the agent takes them, and each finished run links to the chat it filed into. That state lives in the daemon, so it survives a page refresh, looks the same on every open panel, and shows runs the scheduler started while nobody was watching. A daemon restart clears it — the run died with the process.
- Tasks — per-project TODO list that routines can read and write.
- Super-agent — the mode that
super_agentroutines run under. - Configuration — global model fallback and permission settings.