Skip to content
Development
Skill

/scheduled-task

Use when the user wants something to run routinely / on a schedule rather than once now -- "tarea programada", "rutinariamente", "cada mañana", "cada N horas", "todas las noches", "schedule", "cron". Covers mounting, structuring, and running an unattended headless task that

From plugin
metraton-gaia
339 skills9 agents11 hooks
Install
$ npx -y skills add metraton/gaia --skill scheduled-task --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/scheduled-task

Context preview

The summary Claude sees to decide when to auto-load this skill.

Use when the user wants something to run routinely / on a schedule rather than once now -- "tarea programada", "rutinariamente", "cada mañana", "cada N horas", "todas las noches", "schedule", "cron". Covers mounting, structuring, and running an unattended headless task that

SKILL.md

scheduled-task.SKILL.md
name: scheduled-task
description: Use when the user wants something to run routinely / on a schedule rather than once now -- "tarea programada", "rutinariamente", "cada mañana", "cada N horas", "todas las noches", "schedule", "cron". Covers mounting, structuring, and running an unattended headless task that reports back, plus consuming its reports. NOT for work that runs once now in this session.

Scheduled Task

A scheduled task is a Gaia task that runs **unattended on a recurring schedule** via the OS crontab, executes `claude -p` headless, and leaves the user a report in the notifications inbox instead of asking anything mid-run. This skill covers the three flows of its lifecycle: creating one, executing it headless, and consuming what it reports. Work the user wants done once, now, in this session is an ordinary dispatch, not a scheduled task.

The load-bearing constraint that shapes everything below: a headless run has no user to answer a prompt. So a scheduled task must complete everything it can WITHOUT a T3 mutation, and when a T3 is unavoidable it must NOT try to ask -- it accumulates the `approval_id`, finishes the rest, and reports back so the user can resume and grant later. Gaia's T3 layer gates independently of Claude Code's permission dialog: `--dangerously-skip-permissions` removes the TUI prompt, but Gaia still blocks/accumulates T3 mutations exactly the same.

When to use

Trigger when the user asks for routine/scheduled execution: "cada noche corre X", "rutinariamente revisa Y", "cada 6 horas", "prográmame Z". If they want it run once now, this is the wrong skill -- that is an ordinary one-shot dispatch.

Flow A -- Creation (mount the task)

Build three artifacts, in order. Heavy mechanics and the full wrapper rationale are in `reference.md`; the runnable templates are in `scripts/`.

1. **Write the task as a read-only-first atomic prompt.** State the task's job as a self-contained prompt that opens with the headless preamble (Flow B). Front-load everything read-only; isolate any mutation as an explicit, clearly-labeled step so the headless run can skip-and-accumulate it cleanly. Store the prompt in its own file (one task = one prompt file). 2. **Copy the wrapper** `scripts/run-scheduled-task.sh` to a per-task file (e.g. `~/ws/me/scheduled-tasks/<task>.sh`) and edit its `==CONFIG==` block: `TASK_NAME`, `PROJECT_DIR`, `PROMPT_FILE`. The wrapper exports credentials and PATH **explicitly** (cron has almost no environment), runs the validated headless invocation, persists the session, and parses out the `session_id`. Do not drop `--output-format json` or add `--no-session-persistence` -- the session MUST stay resumable. 3. **Add a staggered crontab entry** from `scripts/crontab.template`. Give the wrapper an ABSOLUTE path, redirect to a per-task log, and offset the minute so no two tasks start in the same minute.

Flow B -- Headless execution (what the task's prompt instructs)

Every scheduled-task prompt begins with this preamble, verbatim in spirit:

> Eres una tarea programada headless. Nadie está mirando y no puedes preguntar > nada. Procede así: > > 1. **Intenta completar la tarea SIN ninguna mutación T3.** Haz todo el trabajo > read-only / T0-T2 que puedas. > 2. **Si hay un T3 inevitable, NO llames AskUserQuestion.** El comando se > bloqueará con un `approval_id`. NO reintentes. ACUMULA cada `approval_id` > (con el comando exacto y por qué hace falta) y sigue con TODO lo demás que > sí puedas terminar. > 3. **Captura tu `session_id`** leyendo `$CLAUDE_SESSION_ID` en la sesión > top-level (ver abajo) para poder estamparlo en el reporte. > 4. **Redacta un mensaje final GENÉRICO** (sin nombres propios ni datos > sensibles) con el formato de abajo. > 5. **Guarda ese mensaje** como último paso con `gaia notifications add` (T0).

Capturing the session_id (`$CLAUDE_SESSION_ID`)

The report must carry the run's `session_id` so the user can `claude --resume <session_id>` to grant the accumulated T3s. The headless session reads its OWN id from its shell env var -- it is NOT parsed from `--output-format json`, and NOT asked from the user:

echo $CLAUDE_SESSION_ID

**Read it at the TOP-LEVEL session, never inside a dispatched subagent.** `$CLAUDE_SESSION_ID` does not propagate to a subagent's shell, so a task that delegates its work to a subagent and reads the var there gets an empty value and the report lands with `session_id` "-". Capture the id in the main session and pass it through. If it is genuinely empty, omit `--session-id` and note in the body that the user recovers pending grants with `gaia approvals pending --all-sessions` (which does not depend on the id).

Final message format (generic, no PII)

He terminado la tarea <nombre>: <qué hizo en una línea>.
Aprobaciones pendientes: <lista de approval_id + por qué cada uno>, o "ninguna".
session_id: <valor de $CLAUDE_SESSION_ID, o "ninguno — usa gaia approvals pending --all-sessions">.

The task's LAST action is to persist that message (substitute the value you read for `$CLAUDE_SESSION_ID`):

gaia notifications add \
  --task "<nombre>" \
  --headline "He terminado la tarea <nombre>: <resumen>" \
  --body "<mensaje completo, incluidas las aprobaciones pendientes>" \
  --session-id "$CLAUDE_SESSION_ID"

`gaia notifications add` is **T0** by design, so a headless run can always record its report without stalling on a gate. The message stays generic because a notification surfaces later out of context -- proper nouns and sensitive data do not belong in an inbox line.

Flow C -- Consumption (how the user sees and acts on reports)

The report surfaces through four escalating touchpoints; the user pulls detail on demand rather than being interrupted:

1. **Per-prompt counter** -- while there are unread reports, each prompt gets a cheap one-line `🔔 N task notifications sin ver` (nothing when N=0). 2. **SessionStart li

Read more
Ships withmetraton-gaia

Generative AI Architecture

Get the whole plugin

Other skills on metraton-gaia.