Skip to content

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).

KindLLM?Tools?Description
heartbeatNo—Logs a marker. Useful as a “still alive” ping.
shellNo—Runs a shell command. Stdout captured.
exec_agentYesAgent 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_agentYesAllRuns the default APX agent with the full tool registry. Multi-iteration loop.
telegramNo—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_agent with allowed_tools: [].
  • Pure shell without LLM? → shell.
  • Fixed Telegram message on a schedule? → telegram.
FormatExamplesNotes
every:<N><unit>every:30s, every:5m, every:24h, every:7dMost common.
once:<iso-8601>once:2026-12-01T08:00:00ZFires once, then disables itself.
Cron expression*/5 * * * *, 0 8 * * *Standard 5-field cron.
Terminal window
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>]

Kind-specific JSON config:

KindRequired spec keysOptional
exec_agentpromptagent (slug, defaults to default)
super_agentprompt—
shellcmd—
telegramtext—
heartbeat(none)—

Comma-separated shell commands. They form a pipeline around the LLM call:

  1. pre_commands run sequentially. Their combined stdout is available as:

    • {{pre_output}} — substituted into spec.prompt (and spec.text) before the LLM call. Always substituted: with no pre_commands, or none that printed anything, the slot renders empty rather than reaching the model as literal braces.
    • $APX_PRE_OUTPUT — environment variable for post_commands.
    • $APX_PRE_OUTPUT_FILE — path to a temp file with the full output (for large payloads).
  2. The kind handler runs. Its text result is exposed as $APX_LLM_OUTPUT.

  3. post_commands run sequentially with $APX_LLM_OUTPUT, $APX_PRE_OUTPUT, and $APX_STATUS in the environment.

Controls when the LLM call (phase 2) is skipped, based on what pre_commands did:

ValueBehavior
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_failureSkip the LLM when the last pre command exits non-zero.
pre_successSkip 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.
alwaysAlways skip the LLM — useful for pure pre→post pipelines.
neverAlways 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:

Terminal window
apx routine add digest \
--pre 'test -s ~/report.txt || echo APX_SKIP' \
--skip-prompt-on signal

Overrides the global super_agent.permission_mode for this routine: total | automatico | permiso.

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.

Terminal window
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 now
apx routine enable <name> [--project <name|id|path>]
apx routine disable <name> [--project <name|id|path>]
apx routine remove <name> [--project <name|id|path>]
apx
$ 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
apx routine list — name, kind, schedule, enabled, next run, last run
Terminal window
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"'
Terminal window
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 automatico
Terminal window
apx routine add db-backup \
--project myapp \
--kind shell \
--schedule "every:24h" \
--spec '{"cmd":"pg_dump mydb > /backups/mydb-$(date +%F).sql"}'
Terminal window
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."}'
Terminal window
# ✗ 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"'
Terminal window
apx routine history <name> --project myapp # last N runs with status and output
apx log -f # tail the unified daemon log
apx messages tail --channel routine -n 20 # last 20 routine-channel messages

A 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.

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_agent routines run under.
  • Configuration — global model fallback and permission settings.