Agent routines
ProScheduled agents in one card: each routine reads as a sentence with its schedule, next run countdown, connectors and destination, and opens smoothly to edit or run it now.
- Settings or automation screens where people schedule agents to run on their own and post results to Slack.
- A card on a dashboard that shows at a glance which scheduled agents are healthy, which failed and when each runs next.
- Products that let people create automations from a sentence and confirm the parsed schedule, sources and destination.
- Use agent-run to watch a single agent work through a task in real time with its tool calls.
- Use agent-tasks for a queue of one-off jobs that run once rather than on a schedule.
- Use agent-approvals when the agent's output needs a person to approve it before anything is posted.
- Use agent-orchestrator to show several agents handing work to each other within one run.
Installation
Pro source and install commands unlock with a Pro plan.
Usage
Pass routines with onToggle, onRun and onChange to wire your scheduler; with no props it shows sample routines with live countdowns.
import { AgentRoutines, type Routine } from "@/components/arc/blocks/agent-routines/agent-routines"; export function Routines({ routines }: { routines: Routine[] }) { return ( <AgentRoutines routines={routines} onRun={async routine => { const result = await api.routines.run(routine.id); // { id, status, summary, durationMs, report } return { run: { id: result.id, status: result.status, at: Date.now(), summary: result.summary, durationMs: result.durationMs }, report: result.report, }; }} onToggle={(id, enabled) => api.routines.update(id, { enabled })} onSave={(routine, { created }) => created ? api.routines.create(routine) : api.routines.update(routine.id, routine)} /> );} // A routineconst triage: Routine = { id: "morning-triage", name: "Morning triage", instruction: "Read new Linear issues, match them to Sentry errors and post a ranked list to #triage.", schedule: { frequency: "weekdays", time: "09:00" }, sources: ["linear", "sentry"], destination: "#triage", model: "claude-sonnet-5-5", enabled: true, steps: ["Reading new Linear issues", "Matching Sentry errors", "Ranking by customer impact", "Posting to #triage"], history: [{ id: "r-1", status: "success", at: "2026-10-19T09:00:00", durationMs: 41000, summary: "Triaged 9 issues, 2 urgent" }], report: "Triage for Monday 19 October\n- ENG-2141 Checkout button stays disabled. Urgent, assigned to Priya.\n9 issues reviewed, 2 urgent.",};AgentRoutines
Scheduled agents in one card. Each row reads as a sentence: the routine's name and an on and off switch, its schedule in plain words with a live countdown to the next run and the last run on the right, then the connectors it reads, the Slack destination and the model as small colored marks in a fixed slot that never wraps, beside its last 14 runs as small tinted ticks with a tooltip each. Run now sits in a reserved slot that shows on hover or focus; a run fills a thin line under the row in the routine's series color, shimmers a Running label with the current step, then slides a new tick into the history and updates the last run. Clicking a row opens it in place, one at a time, with the latest run's output as a short formatted report, the instruction prompt and a compact schedule editor. New routine opens an inline row where a sentence is read into editable chips for the schedule, sources and destination.
routinesRoutine[]four sample routinesThe routines: id, name, instruction, schedule ({ frequency: "hourly" | "daily" | "weekdays" | "weekly", time: "HH:MM", day?: 0-6 }), sources (connector ids: linear, github, sentry, google-calendar, notion), destination ("#channel" or "Direct message"), model (claude-opus-5-5, claude-sonnet-5-5, claude-haiku-5-5, gpt-6-luna, gemini-3-8-flash), enabled, history (oldest first), an optional report, optional step labels and an optional accent (1 to 4, the series color for its progress). Passing a new array replaces the list; changes made in the card are kept until then.onRun(routine: Routine) => Promise<RoutineRunResult> | RoutineRunResult | void–Called when a run starts, from Run now or when a schedule comes due while the card is open. Return a promise of { run, report? } to drive the row from your backend: the steps advance while it is pending, the result lands when it resolves, and a rejection shows its message as the error. Return nothing to keep the built-in simulation, which says so in the status region.onToggle(id: string, enabled: boolean) => void–Called when a routine's schedule switch changes.onSave(routine: Routine, meta: { created: boolean }) => void–Called with the full routine when its schedule changes in the open row, and with created: true when New routine adds one.nownumber | Date–The current time for countdowns and run times. Without it the card uses a demo clock that starts on Tuesday 20 October 2026 at 6:45 when no routines are passed, and the real clock otherwise.defaultOpenIdstring | nullnullThe routine that starts open. Opening another closes it.titlestring"Routines"The card title, also the list's accessible name.classNamestring–Class on the root element.- Tab
- Moves through New routine, then for each row the routine name, Run now, the schedule switch and the run ticks, then the open row's schedule editor.
- Enter, Space
- On a routine name, opens or closes the row. On Run now, starts a run; while it runs the same button stops it.
- Arrow uporArrow down, Home, End
- On a routine name, moves focus to the previous, next, first or last routine.
- Arrow leftorArrow right, Home, End
- Inside the run ticks, moves between runs; each run shows its result, time, duration and summary in a tooltip. In the frequency control, changes the frequency.
- Enter
- In the New routine sentence field, adds the routine. A sentence typed a moment ago is read at once.
- Escape
- In the sentence field, cancels New routine and returns focus to its button. In a chip's popover, closes the popover.
- Each routine name is a real button with aria-expanded; the rest of the row also opens it on click, while Run now, the switch and the ticks stay separate controls.
- Status is never color alone: the last run pairs an icon with its time, each tick has a full text label and a tooltip with an icon and a label, and the strip is a group named with its counts (for example 12 succeeded, 1 failed, 1 skipped).
- Connector, destination and model marks are hidden from assistive technology and replaced by one sentence per row: what it reads, where it posts and which model runs it.
- The run ticks are one tab stop with a roving index, so a list of routines does not add 14 stops per row.
- Run starts, results, failures, stops, toggles, schedule changes and new routines are announced in a polite status region, including that the preview only simulates runs and Slack posts.
- While a run is in progress the open row lists its steps as an ordered list with the current step marked aria-current="step"; a failure is announced as an alert with the step and the error.
- The sentence parser's state and the simulation note sit in a polite live region. Chips are buttons named with their field and value, and their popovers use pressed buttons for sources and the destination.
- Pointer focus draws no ring; keyboard focus shows Arc's shared focus ring.
- A row opens from height 0 to auto and back on a critically damped spring (about 340ms, no bounce). Opacity starts a beat after the height and ends a beat before it on close, the content rises 6px into place, and the chevron and the row's quiet tint change on the same timing. Opening one row closes the other at the same time, and the rows below glide with the height.
- While a row is open its height keeps following its content on the same spring, so a streamed report or the step list never resizes the card in one frame.
- Run now fills a 2px line under the row at the pace of the steps, in the routine's series color, then the line fades. A new tick slides in from one slot to the right while the others tween one slot left and the oldest fades out, on a 240ms ease-out tween. Ticks sit in fixed slots and never measure layout, so they do not drift while rows open or close.
- Labels change with one minimal crossfade: the old one fades out in 100ms and the new one fades in over 160ms from 2px below. A schedule change slides the text after it as its width follows on a critically damped spring. The countdown uses tabular numerals in a reserved width.
- The Running label and the active step carry a calm shimmer that stops when the run ends. Run now and Stop share one button and one width, so the swap never resizes it.
- The latest report streams in chunks of 15 to 40 characters every 32ms on a requestAnimationFrame clock; each new chunk only fades in and settled text never re-renders.
- Reduced motion opens, closes, shifts and swaps at once and keeps every state, label and announcement; the shimmer stops and streamed chunks appear without the fade.
- The card is up to 680px wide and reads the same at 360px: every row keeps its three lines, and the marks slot and ticks never wrap.
- Below 540px of card width the last run moves out of line two (it stays in the ticks' tooltips and the open row), and the insets tighten.
- Below 420px Run now and Stop become icon buttons with the same accessible names, and the schedule's time field takes its own line.
- Long step labels and destinations truncate with an ellipsis; the full text is in the open row, the tooltips and the status region.
- One shared one second clock drives every countdown; nothing else re-renders on a timer.
- Runs are cancelled with an AbortController when stopped or when the card unmounts, so no timers outlive it.
- Ticks animate only transform and opacity in fixed slots; nothing in the list uses layout animation.
- Closed rows unmount their details, so a long list only renders one report and one schedule editor.
- Brand marks are inline SVG components in the block's folder; the Gemini mark scopes its gradient ids per instance.
Notes for AI
Give your coding assistant the Markdown reference instead of screenshots.
- Choose it for agents that run on a schedule and report back: daily triage, weekly digests, hourly watchers, meeting briefs. One row per routine; keep names short and specific so the first line never truncates.
- Schedules are four plain shapes (hourly at a minute, daily, weekdays, weekly on a day) with 24 hour times. The card derives the plain words, the cron expression and the next run in local time.
- History is oldest first and only the last 14 runs show. Use status skipped for runs that found nothing to post, so the strip separates quiet runs from failures.
- The report is short plain text: the first line is the title, lines starting with "- " become list items and other lines become paragraphs. Issue keys like ENG-2141, pull requests like #1842 and channels like #eng are set apart.
- Rows show up to three source marks; with four or more they show two and a +N count whose tooltip names the rest. Give each routine step labels that match what your agent really does, so the progress is honest.
- New routine parsing is a local, simulated pattern match and says so. In production, send the sentence to a model and keep the chips as the confirmation step before onSave.
The full library index for assistants is at /llms.txt.