Schedules and monitors
Run work on a clock or react when a file or HTTP source changes.
A schedule starts work on a clock. A monitor polls files or an HTTP source
and starts work when it detects a change. Both need a running Marbles loop.
roll runs one target or poll immediately and exits.
The CLI owns the polling and clock loops. A launched step or workflow runs through Workflows, with its own results and configured retry policies. Trigger continuity and run durability are related: a later poll or schedule tick creates a new run, while a retry is another attempt within the current run.
Schedules
This complete module logs a heartbeat every 30 seconds:
import { schedule, step } from "@foundry.sh/marbles";
const heartbeat = step("heartbeat").do(({ log }) => {
log("Marbles is running");
return { alive: true };
});
schedule(heartbeat).every("30s");
Open marbles and press Enter to start the clock. Replace .every("30s")
with .at({ weekday: "mon", hour: 9, minute: 30 }) for a Monday slot.
Calendar times use the machine’s local timezone. Intervals are positive
integers followed by s, m, h, or d.
A schedule takes a named definition, or a childless locked definition
carrying input. For example, schedule(count({}, { extension: ".ts" }))
can target the count step from Using Marbles.
Finish the declaration with .every(...) or .at(...).
Configured interval schedules count from completion, preserve their last completion across restarts, and do not queue missed ticks. An overlapping tick of the same schedule key on the same machine is skipped. A direct launch by definition name is not protected by that schedule lock.
File monitors
Save this module to observe repository instructions:
import { monitor } from "@foundry.sh/marbles";
monitor("**/{AGENTS,CLAUDE}.md")
.every("30s")
.do(({ change, log }) => {
if (change.kind === "files") log(change);
});
Run marbles list, copy the monitor key, and run marbles roll <key>.
The first poll reports existing matches as added. Edit a matching file and
poll again to see a modification. The handler receives added, modified,
and removed paths with their checksums, relative to the workspace root.
File monitors poll; they do not subscribe to filesystem events. Their default cadence is one minute. The stored observation advances only after the handler resolves. A throwing handler sees the same change next time.
The scan uses Workspaces for the project’s file inventory. Digests identify changes; preserving the observation does not keep a copy of every file’s previous bytes.
HTTP monitors
A source beginning with http:// or https:// uses HTTP polling. This
complete module observes Bun’s public release metadata:
import { monitor } from "@foundry.sh/marbles";
monitor("https://api.github.com/repos/oven-sh/bun/releases/latest")
.every("6h")
.do(({ change, log }) => {
if (change.kind === "http") log(change.status, change.current);
});
The first response counts as a change. Later polls compare the body with the last handled observation. Fields such as timestamps can change even when nothing relevant changed; filter them in the handler, as the release monitor does.
.request(init) supplies request options or a function evaluated on every
poll. A function can read the current token from the environment. Request
options are not included in the monitor key or stored observation. Network
errors and 401/403 fail the poll; other error statuses are observations
the handler can inspect. Dry execution still performs monitor I/O.
Launch work from a change
This complete module launches a named step when Markdown files change:
import { monitor, step } from "@foundry.sh/marbles";
const inventory = step("markdown-inventory").do(async ({ workspaces }) => ({
files: (await workspaces.current.files()).filter((file) =>
file.path.endsWith(".md")
).length,
}));
monitor("**/*.md")
.every("1m")
.do(() => inventory({}));
The same form works with a workflow. Return a childless lock of a named
target, or return undefined to launch nothing. Returning the bare
definition does not launch it.
A pending launch is kept until a start succeeds. A crash between starting and acknowledging it can cause redelivery, so make external effects idempotent. A launched run that later fails is not automatically launched again for the same observation.
Agent-created triggers
Sandbox agents can also create durable schedules and monitors through approved tools. They retain ownership by agent and session and appear in the dashboard. Their creation-time cadence differs from the completion-based intervals above. See managed automation.
The trigger reference covers exact forms and keys; state covers locks and persisted observations.
Next: Local execution and state connects these launches to the CLI process and its persistent stores.