Glass Box Terrarium, explained from the inside

Last updated · Latest source on GitHub ↗

Claude CodeLesson

Scheduled tasks

A Skill says what a job does. Something else has to say when it runs. Claude Code offers several timers, which differ in where they run and how long they last, from a cloud task that needs no open session to a loop that ends with the session it runs in. Terrarium keeps each job's steps in the repo and its timer outside it, and reads what ran back from the session logs.

Know scheduled tasks and /loop already? Skip to In Terrarium.

Outside the box · General

The concept

Claude Code has three ways to run a prompt later, compared side by side in the docs' scheduling options table:

  • A cloud scheduled task, called a routine until the docs renamed it, runs on Anthropic's cloud and needs neither your machine nor an open session.
  • A Desktop scheduled task runs on your own machine, with its local files.
  • /loop runs inside one open session and lasts only as long as it does.

A cloud scheduled task is a saved prompt you create at claude.ai/code/routines, or with /schedule in the CLI. Each run is a new cloud session on a fresh clone of each repository it names, so it has the Skills committed there and nothing from your machine. The run starts in auto mode, where a classifier model allows or blocks each action instead of asking you, but some actions still stop and wait for an answer. The table and each option's docs cover the rest, such as what can start a run and how often it may fire.

/loop runs a prompt again and again in the current session:

/loop 5m check whether the deploy finished
/loop check whether CI passed and address any review comments

With an interval, the prompt runs on a fixed schedule. Without one, Claude picks the wait after each run, and ends the loop with the ScheduleWakeup tool once the work is done. Underneath, tools such as CronCreate manage these session tasks. A fire runs only Skills Claude may start on its own: a Skill with disable-model-invocation: true reaches Claude as plain text instead.

Full referenceClaude Code docs: Automate work with scheduled tasks

Looking inside

In Terrarium

Terrarium calls a cloud scheduled task that runs one of its a , the docs' former name. The Routine lives in a claude.ai account; its prompt is the Skill's slash command, and the Skill in .agents/skills/ holds the steps. A scheduled Skill may say that it is scheduled, and never how often. dependabot-sweep opens like this:

.agents/skills/dependabot-sweep/SKILL.md ↗7–9as of
7A Routine runs this Skill; its schedule lives there, not here (ADR-0005).
8Its approval covers only the steps below (ADR-0003): it opens no branch and
9edits no file.

scripts/gate/validate-skill-cadence.ts, part of pnpm validate:content, enforces that for one Collection: it fails any sentence that pairs "Routine" with a cadence word such as "weekly" or a phrase such as "every 2 hours".

No file declares the Routines or their timers: a Routine's run shows only in its , whose trigger scripts/session/transcript-trace.ts takes from the run's first turn, and the audit-skills Skill adds each run's cost to the Routine costs page and grades a Skill routine only when scheduled sessions run it.

A scheduled Skill turns model invocation off (Claude Code Skills). A Routine's prompt arrives as the run's first turn, the way a person's slash command does, and the Skill runs. A /loop fire of it arrives as plain text instead, and each Skill that /loop may drive, such as guest-intake, says to follow its steps directly.

Inside a session, Terrarium waits without /loop. A session that must check a PR's CI hours later schedules its own check-in with the cloud host's send_later tool, which delivers a message back into the same session (docs/agents/pr-workflow.md). ScheduleWakeup belongs to /loop alone. scripts/hooks/loop-only-tool-guard.ts, a PreToolUse hook, denies it unless the session's transcript shows a /loop, and its message names what to use instead; it allows a call with stop: true, which only cancels a wakeup.

Why it's built this way

  • The timer outside the Skill. Claude Code Skills gives the reason a schedule lives in the Routine and never in the Skill. ADR-0005 (docs/adr/0005-skill-model.md).
  • Observed, not declared. The timers sit in an account the repo can't read, and a run's transcript names no Routine. So the log takes the Skill's name from the run's first turn, and the audit counts what actually fired. The cost: a Routine that never fires leaves no trace, and one whose prompt isn't a slash command is named by its first line. scripts/session/transcript-trace.ts.
  • A check where the mistake happened. A schedule changes with no commit, so a sentence saying how often a Routine runs goes stale without anyone noticing. A Skill Inventory entry is the one place that had happened, so the check scans only those entries. The cost: the same sentence in a Skill, doc or page passes pnpm validate:content and goes stale at the next schedule change. scripts/gate/validate-skill-cadence.ts.
  • Each Skill states its own limits. Nobody watches a scheduled run, so each scheduled Skill writes down what it may change, as dependabot-sweep does; Human-in-the-loop agents says why no central table holds it. ADR-0003 (docs/adr/0003-agent-operating-model-and-governance.md).
  • A guard instead of a note. Sessions outside /loop kept calling ScheduleWakeup, and a fired wakeup arrives as an unwanted turn that can re-run the session's whole prompt. Two fixes that wrote the rule into a doc failed (Claude Code hooks says why), so a hook now denies the call. The cost: when it can't read the transcript it denies too. scripts/hooks/loop-only-tool-guard.ts.

What to watch for

  • Session timers can vanish on resume. docs/agents/environment-caveats.md records a /loop or CronCreate job gone after a session resumed, with no error, and the docs say a self-paced /loop is never restored. Check that it is still there before you rely on it.
  • A scheduled wake-up may not show as a turn. A scheduled task that wakes an existing session can fire without a visible turn; the same caveats doc says to check the task's last run before you decide it didn't fire.
  • An unattended run can wait forever. Auto mode still stops for some actions, and in an unattended run a subagent's call that needs approval blocks with no signal to the session that dispatched it (docs/agents/environment-caveats.md). Treat a long silence as possibly stuck.

Try it in your project

Pick a chore you repeat, such as a dependency check. Write it as a Skill with disable-model-invocation: true and a first line saying a schedule runs it. Then run /schedule and give the new task the Skill's slash command as its prompt. Keep the timer there alone: search your repo for the Skill's name next to "daily" or "weekly", and expect no hit.

Where next

Last updated · Latest source on GitHub ↗

Something went wrong

This content couldn’t be loaded. Reloading the page usually fixes it.

Technical details
route: /t/glassbox/guide/claude-code/routines