feat: machine-owned scheduled tasks with agent-proposed schedules [risk:high] - #931
Merged
Merged
Conversation
Implement local-first Schedule definitions and Registry, durable SQLite run recovery, deterministic Session handoff, shared Task and Agent occupancy, bounded MCP and CLI commands, and beta UI across clients. Include synchronization, permission and restart regression coverage, export support, and repository formatting. Model: gpt-6
Show actionable missing requirements above Save and use the same validation for button state and submission. Add localized copy, a Storybook state, and regression coverage. Model: gpt-6
…igh] Risk: high · Confidence: high The schedules list and editor were a stack of stand-alone form fields, and a schedule could not exist without a project. This reworks both, and makes the "no project" case real end to end rather than something the UI allows and the backend rejects. Data model (pre-release, deliberately breaking): - `project` is optional on `ScheduleDefinition`, the Registry row (`projectKind`/`projectKey`), the draft schema and the mirrored Loro map. An absent project means a chat-only run: no local-project ledger lookup, no `project` on the CLI's resolved target, and `buildProjectOptions` contributes nothing to the prepared Session. A project that IS set is still validated against the owning machine before handoff. Nothing substitutes a default. Frequency: - New `packages/shared/src/schedule-recurrence.ts` maps between a named recurrence (daily / weekdays / weekly / monthly / interval / once / custom) and the persisted trigger, in both directions. The stored union is unchanged: cron already expresses all of it, and widening it would make the CLI engine, ledger and MCP each learn a second calendar. - A rule the picker cannot name opens as Custom with the expression verbatim and still editable. An untouched rule is re-emitted byte for byte, so opening a `MON-FRI` schedule and pressing Save cannot rewrite it to `1-5` or invalidate its definition fingerprint. - New schedules default to every day at 09:00. Editor: - Title and prompt carry their guidance in the placeholder; the accessible name stays on the field, so nothing takes vertical space to say "Name". - Frequency and run configuration are grouped property cards (label left, live value right) whose rows appear only when they apply. Agent and Project reuse the shared composer menus rather than a second set of pickers. - Removed every repeated confirmation on this path: the automation consent checkbox, the directory-scope checkbox (the worktree switch IS that choice, with an inline note when off), the full-access warning, and the elevated resume dialog. Ownership, machine protocol capability and an explicit permission mode still gate save, resume and run, each with a visible reason above Save. `collectScheduleSaveBlockers` is the single rule driving the button, the explanation and the submission guard. List: - Rebuilt as a real table: name, frequency in plain language, next run in the rule's own zone, target agent and project (or "Chat only"), and actions. Header and rows share one column template with a fixed-width actions column — a content-sized one redistributed the `fr` columns per row. Stacks to a readable card on narrow screens; empty, loading, paused and offline states redesigned. Tests: recurrence round-trips and lossless re-emission, chat-only persistence through the repository and Registry, the save-blocker rule, and the editor's opening/saving behaviour for each rule shape — all on injected clocks. Model: claude-opus-5[1m]
…dium] Risk: medium · Confidence: high Follow-up to 171f37c. "Custom" was still a five-field cron text box, so the one rule shape the named picker cannot express was also the one shape that dropped a person into raw cron. It is now edited field by field. - New `packages/shared/src/schedule-cron-fields.ts` reads a five-field expression as five independently editable fields, each in one of `every | step (optionally within a range) | range | list | raw`. `CronFieldRow` renders one row per field: a mode picker plus only the controls that mode needs — an interval box, a from/to pair, a searchable value multi-select, and weekday toggles for the day-of-week field. - Two rules keep this from narrowing what a person can express. Every field offers every mode, because a trimmed per-field menu leaves a stored mode unlisted — a weekday of `1-5` is a `range`, and a menu without `range` rendered that field blank and would have lost it on the next edit. And a field is only claimed by a structured mode when re-formatting reproduces its original spelling, so `MON#2`, `JAN`, `L` and `00` stay `raw` in their own text box, visible and editable. `format(parse(x)) === x` for any five-field text, and an untouched rule is still re-emitted byte for byte. - Changing a field's representation no longer changes the schedule: switching hour `9-17` to a list seeds 9…17 rather than resetting to midnight, and the raw escape hatch opens on the text that is already there. - The whole-expression text box remains, but as an opt-in "Edit as text" row under the rule rather than the default surface. The list and the editor summary now describe a custom rule in words instead of printing cron. - `withCronField` refuses a non-five-field expression or an absent field, so a controlled `<Select>` emitting an empty value while resetting cannot rewrite the rule. That path crashed during testing. Also closes a gap in the previous commit's own claim: nothing asserted the chat-only DISPATCH path, only its schema. The two decisions that place a run in a working directory are now `schedule-run-preparation.ts`, and its tests pin that a run with no project reaches `prepareSessionInput` with no `repo`, `localProject`, `branch` or `worktree` selector, with no `project` key on the target, and requiring no local-project ledger lookup. Model: claude-opus-5[1m]
Risk: medium · Confidence: high Review findings on 082a6f1..171f37c. P1 — `schedule-format.ts` passed `i18n.language` straight into `Intl`. Lody spells Chinese `zh_CN`, which `Intl.DateTimeFormat`, `Intl.ListFormat` and `Intl.RelativeTimeFormat` all reject with a RangeError, so the schedules list, the editor preview and the weekday pickers threw for every Chinese user. Every entry point now normalizes through the repository's existing `toIntlLocaleOrEn`, and this file stays the only place here that constructs an `Intl` formatter so a future caller cannot reintroduce it. `tests/schedule-locale.test.tsx` renders the real list, editor and custom field pickers under `zh_CN`; it fails on the previous code. The pure formatters were already individually correct while the pages they back crashed, which is why the regression is at the component boundary rather than only on the helpers. Also from the review: - The target column now names the machine. It was passed and never rendered, so two same-named agents on different machines — and two chat-only rows — were indistinguishable. - The last-run action defaulted to `opacity-0` until hover, which no touch device can discover. It is visible by default and hidden only behind `[@media(hover:hover)]`. - The narrow-screen New button's only text is `hidden sm:inline`, so it had no accessible name. It has an `aria-label` now. - `PropertyRow` sized its label column from a `sm:` breakpoint, which cannot see the panel a schedule renders in. Both tracks are content-driven now (`minmax(0,auto)` / `minmax(0,1fr)`), matching `settings/compact-layout.tsx`. A hugging control track let the seven weekday toggles truncate the label away in a 360px panel; a hugging label track let a long translated label eat the row. `EditorInNarrowPanel` renders that case at a desktop viewport, and the two Advanced labels are shortened so their translated values fit rather than ellipsize. Finally, the CLI regression the previous commit claimed but did not add. `schedule-chat-mode-dispatch.test.ts` drives a project-less definition through the real engine, SQLite ledger and Loro history, asserting that no `repo`, `branch`, `localProject` or `worktree` selector reaches session preparation, that the target carries no `project` key, and that the run still hands off one durable dispatched turn. Both it and the unit test fail when the pre-change "a schedule always has a project" assumption is reintroduced. Model: claude-opus-5[1m]
Risk: medium · Confidence: high Correctness review finding. `parseWeekdayToken` folded 7 onto 0 before the range it bounds was expanded, so `0-7` collapsed to the range `0-0`. Standard cron accepts both 0 and 7 for Sunday, which makes `0-7` and `1-7` every day; the editor read them as "Sundays only". An untouched rule is re-emitted verbatim, so nothing was wrong until an unrelated edit. Changing only the time or the time zone on `0 9 * * 1-7` then rewrote it to `… * * 0` and silently dropped Monday through Saturday. Verified against croner: `0-7` and `1-7` fire daily, `5-7` fires Fri/Sat/Sun. Ranges are now expanded over their raw 0..7 bounds and folded onto Sunday once, afterwards. `5-7` reads as Fri/Sat/Sun, `0-7` and `1-7` as daily, and a bare `7` still as Sunday. Coverage is the edit, not just the round trip, because the round trip was already passing while the rule was being lost: a new test replays each Sunday-7 rule after a time and a zone change and asserts the same weekdays, and those expressions joined the exact-instant round-trip corpus. Reintroducing the fold reproduces the defect in three tests. Two adjacent guards, since this is a hazard class rather than one typo: `schedule-cron-fields.ts` deliberately keeps weekday bounds at 0..6 so a Sunday-7 range stays raw editable text rather than being approximated by pickers that cannot draw a day 7 (now pinned by a test), and the weekday toggle derivation folds and de-duplicates so it stays correct if those bounds ever widen. Model: claude-opus-5[1m]
Risk: medium · Confidence: high Three defects with one root cause: the Custom editor re-derived its edit state from a serialized expression on every render. 1. Switching a field's mode rewrote execution semantics. Choosing "On selected" for Day of month replaced `*` with `1,2,…,31`. Only `*` means unrestricted, and cron ORs day-of-month with day-of-week, so a weekdays-only rule became a daily one. Verified with croner: `*/20 9-17 * * 1-5` does not match Saturday 2026-09-12T09:00:00Z; the enumerated form does. 2. Clearing the last selection produced a four-field string (`*/20 9-17 * * `). The parent failed to parse it, every picker disappeared, and the only way back was the raw cron box. 3. Custom text would not stay selected. The parent serialized and re-parsed on each keystroke, so a `raw` field snapped back to a range as soon as its text happened to parse, unmounting the input mid-edit. The fix is one design, not three patches. `ScheduleRecurrence['custom']` now carries the five `CronFields` — the fields ARE the state — plus an optional `draftText` while the whole expression is being typed. Nothing re-derives an edit mode from a string. An incomplete field is first-class rather than a short expression: `formatCronExpression` throws, `withCronField` returns `null`, and the form names the unfinished field above a disabled Save. Switching away from `*` leaves `list` and `range` UNFINISHED instead of inventing an exhaustive value; values carry over only between already-finite modes, which is semantics-preserving. When day-of-month and weekday both restrict, the editor states the OR out loud. Coverage is behavioural and mutation-checked. Reinstating the enumeration, the short-expression serialization, or the serialize-and-re-parse render each reproduce their defect in the new tests. Mode switches assert real croner `match` results rather than text. A driven browser confirms all three: the pickers survive an emptied selection, Custom text keeps its control AND focus across keystrokes, and a mode switch blocks Save naming the field. Two things found while fixing: `*/20 9-17 * * ` also arose from an emptied raw field, and every field's range inputs shared the accessible name "From", so they are now named per field. Also adds the CLI regression this branch previously described too loosely. `schedule-session-preparation.test.ts` calls the PRODUCTION `prepareSessionInput` and asserts a chat-only run yields a `SessionMeta` with no `project`, `repoFullName` or `baseBranch`, and with the engine's frozen session and turn ids. The earlier test exercised the engine and ledger but stubbed `prepare`, so it did not cover this. Model: claude-opus-5[1m]
Keep bounded interval drafts distinct from unrestricted runs, retain cleared inputs, and normalize Chinese validation labels. Cover clearing and restoring fields, and satisfy repository lint and translation checks. Model: gpt-6
…o cron [risk:high] Risk: high · Confidence: high The editor asked ordinary people to think in cron fields. This reduces it to the questions they actually have — when, and where does the result go — and adds the two things they asked for that the model could not express. Trigger: - A schedule is now timed OR manual (`trigger.kind === 'manual'`). A manual task is never planned by the clock; `nextScheduleSlot` and `latestScheduleSlot` return nothing for it and only a Run request creates a run. The editor is a two-way toggle above the frequency card, and it keeps the last timed rule while on Manual so flipping back does not reset it. - Frequency is a short menu: every day, every weekday, every week (pick days), every month (pick dates), every few hours, every few minutes, once. Rows slide in as the choice changes (framer-motion, reduced-motion aware). Steps that divide the clock are written as aligned cron (`*/15`, `0 */6`) so "every 15 minutes" means :00 :15 :30 :45; others become intervals. The persisted trigger union is unchanged. - The field-by-field cron editor is gone. A stored rule the menu cannot name is `unsupported`: shown read-only with its summary and a Replace action, and re-emitted verbatim on save. There is no cron input left in the UI. Destination (`definition.destination`, pre-release schema addition): - `new_session` — a fresh chat per run, as before. - `own_session` — ONE chat the schedule owns, created by the first run and continued by every later run. Its id is derived from the schedule id and an `epoch`, so nothing is stored, a retry or restart cannot create a second chat, and "Start a new chat" is simply `epoch + 1`. - `existing_session` — a chat the person picks. `scheduleRunIds` is the one place that chooses the Session; turn and source ids stay unique per run. Before handoff, and again on create/edit through the command service, `destinationSessionProblem` requires a shared chat to be absent (owned only) or the owner's, on this machine, driven by this Agent — exactly the fields `materializePreparedSessionInput` refuses to change — and reports `SESSION_UNAVAILABLE` as a recoverable configuration problem rather than a late identity conflict. A schedule sending into a chat has no project of its own; the editor hides the Project row, follows a picked chat's Agent, and names an Agent or machine mismatch above a disabled Save. The list shows "Manual" / "On demand" and the destination in place of a project. Tests: the engine test drives two manual runs of an owned-chat schedule through the real ledger and Loro history and asserts one Session with two prepared turns and an advanced dispatch pointer; the destination validator is covered for absent/deleted/other-owner/other-machine/other-Agent chats; the recurrence mapping round-trips every named rule to the same instants, keeps the Sunday-7 fix, and leaves unsupported rules byte-identical; component tests cover the manual toggle, the monthly date grid, replacing an unsupported rule, and the save blockers for each destination. A driven browser walks every frequency option and destination without page errors. That walk caught one defect fixed here: a controlled Select emits '' while its option list is swapped (hours → minutes), which read as a zero step. Model: claude-fable-5-1
…:low] Risk: low · Confidence: high 3eccfe4 added a Chinese render test for emptying and refilling a cron field picker. That picker was removed in 8b93859 (Custom is gone; unsupported rules are read-only), and the test merged in cleanly because it did not conflict textually. The remaining Chinese coverage still renders the list, the editor, the monthly date grid and a manual task under `zh_CN`. Model: claude-fable-5-1
… [risk:medium] Risk: medium — new persisted `schedule_proposal` system notice and a changed MCP `lody_schedule_propose` input; no existing schedule data changes shape. Confidence: high — CLI propose paths, target resolution and the card have deterministic tests; the card was rendered in Storybook in both languages. `lody_schedule_propose` now takes a NAMED rule (minutes/hours/daily/weekdays/ weekly/monthly/once/manual), an optional destination and an optional explicit target, validates the rule as a trigger, and writes one idempotent `schedule_proposal` system notice into the conversation. The tool description tells the agent to ask a short question instead of guessing when the prompt, rule or destination is unclear. `ScheduleProposalNotice` renders that notice as a card. Create IS the creation — no form follows — using the proposal id as the schedule id so a double click cannot make two. `resolveScheduleProposalTarget` defaults the Agent, effective mode, machine and project to the conversation the proposal was made in; a named Role / Agent / machine / project overrides only that part, and a named Agent without a Role gets the builtin default mode rather than another Agent's. The card runs the same save blockers as the editor and is behind the same developer + beta gate. Model: claude-fable-5-1
Main removed the workspace Tasks product (#800). Schedules keep their own pieces: the `scheduleToolsEnabled` gate now travels alone through turn config, bootstrap, and the MCP env/header; `AgentExecutionSlots` loses its Task occupancy; `buildProjectOptions` moves into `schedules/`; the shared Agent menu's copy moves from `tasks.*` to `agentRunConfig.*`. Adapted to main's API changes: `prepared` joins `SessionTurnStatus`, the history write schema, and the seen-regression guard; schedule history access uses `readSessionHistory` / `appendTurn` since `SessionDocument` no longer exposes `getHistory` / `updateHistory`; `TaskAgentRef` becomes a local `AgentRunRef`. Model: claude-opus-5-5[1m] Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adopt main's #802/#816 rules across the list, editor, detail page and proposal card: `em` sizes of `--ui-font-size` instead of fixed px/xs, `font-normal` everywhere, sentence-case section labels, 0.5px edges, and `SETTINGS_ROW_CARD_CLASS` cards inside `data-settings-surface` so light themes lift them to the popover fill. The editor's second section is now "Where it runs" so it no longer repeats its first row's "Send to" label, and the English beta help no longer describes Tasks. Model: claude-opus-5-5[1m] Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Measured in Storybook, the rows were centred but nothing shared a line: labels sat on three left edges, row endings on three right edges, rows were 44 or 32px, and centred list cells put the two-line "Runs with" above its neighbours. Now title, prompt, section and row labels share one left line; every row's last mark ends on one inset (ghost triggers bleed their padding, all menus carry the same 14px chevron, including Agent and Project); rows are 44px; hint rows pin the label to the control; list cells align to their first line and a leading status pill lines its text up with the column. Model: claude-opus-5-5[1m] Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…e problem marks [risk:medium] - Name, prompt and the run choices share one bordered box like the composer; destination, chat, Agent, project and worktree are ghost pills along its bottom edge instead of four property rows. The name autofocuses on a new schedule. - No time zone control. The CLI publishes its IANA zone as `MachineMeta.timeZone` at registration; wall-clock rules and one-off times are read on the owning machine's clock (older CLIs: the viewer's zone), and proposal cards create their trigger on that clock too. - Save problems are marked where they are fixed. `collectScheduleSaveIssues` tags each blocker with its control and `missing | invalid`; an unfinished choice is marked after a save attempt, a real conflict at once, and only reasons that belong to no control sit beside Save. - One type scale and two inks across the editor; `AnimatePresence` rows are keyed so exit animations work. - Adds the scheduled-tasks Agent Note (en/zh) and compacts the schedules AGENTS.md around the new editor. Model: claude-opus-5-5[1m] Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…k:medium] - The Agent row is the composer's `DesktopRunConfigMenu` and `DesktopPermissionModeButton` (`ScheduleAgentControls`), along the bottom of the name/prompt box; name and prompt are split by a hairline. - Machine is its own choice, like the chat landing: `DesktopMachineMenu`, the project chip and the worktree checkbox sit under the box. Changing the machine clears the Agent and a local project; machine problems get their own mark (`machine` issue field). - Where runs go is its own card above the trigger card again. - Removes the pill run bar and the now-unused shared `AgentRunConfigMenu`. Model: claude-opus-5-5[1m] Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…dium] - A new schedule opens with the chat landing's last machine and Agent and that Agent's remembered model, options and permission; a new machine or a newly picked Agent is seeded the same way (`seedScheduleAgentRunRef`). The seed fills permission exactly where `hasExplicitSchedulePermission` reads it (`_permission` option, mode list, or `mode` option) and drops credential options. - No project mark: beside the optional project chip it read as "a project is required". A local project on another machine is reported beside Save. - Time and date are typed only; the native picker button is hidden. - The prompt has no resize handle: it grows from 4 to 8 lines, then scrolls. Field marks float so they never narrow the text. Model: claude-opus-5-5[1m] Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The list is the Schedules page. On desktop, `/schedules/new` and `/schedules/$id` open over it in `ScheduleDialog`; on mobile they stay a pushed page with a back button. Saving and closing both return to the list. Routes are unchanged, so links from a Session's info bar still land on the schedule. Removes the tab strip and its atom. Model: claude-opus-5-5[1m] Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…olumns [risk:low] - Manual rows get a Run button (Zap; Play stays Resume). Run from the list starts at once with a toast; the detail dialog still confirms because its edits may be unsaved. - Every row has a context menu: open, run, pause/resume, last run, delete. Delete always confirms. - Name, Frequency and Next run resize from the header by pointer or arrow keys (double-click resets), persisted per device; tracks are `minmax(72px, width)` so a narrow panel still fits. - The list moves to `schedule-list.tsx`, re-exported from `schedule-view.tsx`. Model: claude-opus-5-5[1m] Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ium] Schedule list, editor, dialog, proposal card and field marks now use @lody/ui primitives (Button, Input, Select, Combobox, Tooltip, ContextMenu, Skeleton, Dialog/AlertDialog); cards use the settingsCard StyleX. CLI schedule tools follow the new MCP server SDK. Model: claude-opus-5-5[1m] Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The list narrows to names and the schedule slides in like a sidebar. Its header is close plus compact icon actions (Pause/Resume, Run as Play, Delete, History); run history moves to a trailing drawer. Destination and trigger share one card with timed/manual as a Tabs row; the name is larger and semibold; the prompt grows to 6 lines. Model: claude-opus-5-5[1m] Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…le split A `schedules` layout route keeps the workspace mounted so opening and closing a schedule animates instead of remounting. The list keeps its header and every column (scrolling sideways) and the boundary to the panel is a drag handle persisted per device. Top-of-pane headers join the Electron window drag strip with their buttons exempt, so close/new work. Model: claude-opus-5-5[1m] Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The split view now sits under the list header, so title, search and New stay put when a schedule opens. A click on blank page space closes the open schedule (rows, controls, the panel and portalled popups excluded). Frequency and Next run default to 150/140px. Model: claude-opus-5-5[1m] Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The header still never moves; the panel is placed against the page, so it rises over the header's trailing end instead of starting under it. Model: claude-opus-5-5[1m] Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The list defaults to 380px beside an open schedule, so the schedule gets the rest. Run history returns to the bottom of the panel; the history button and its separate drawer are gone. Model: claude-opus-5-5[1m] Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…sk:medium] Mirror hands nested maps back with an inherited `$cid`; Zod's record parser copied it into `agent.configOptionValues`. The owning machine's fingerprint then never matched the Registry's, so every schedule with Agent options stayed blocked on DEFINITION_NOT_COMMITTED - even on the machine that saved it - and re-saving from the UI would store `$cid` as an Agent option. Both Mirror readers (ScheduleRepository.read and the renderer's schedule store) now go through scheduleDocumentFromMirrorState, which rebuilds plain objects before parsing. Regression test fails on the old read path. Model: claude-opus-5-5[1m] Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Schedules are generally available. The developer-mode + beta atoms and their settings toggles are gone; navigation, commands, the mobile tab, the Registry subscription and proposal cards are always on. The per-turn `scheduleToolsEnabled` flag this branch introduced is removed end to end (shared schemas, CLI dispatch/ACP runner/MCP host, session reuse key, UI writes). The Schedule MCP tools are registered for every Agent session; they can only propose, the person still confirms the card, and the command service still requires the invoking Session's owner on this machine. Previously stored turns carrying the key still read. Model: claude-opus-5-5[1m] Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… not Create threw on most conversations: the card handed the conversation's ACP option values to the schedule as-is (booleans, and credential ids), which the definition schema rejects. resolveScheduleProposalTarget now normalizes every source (conversation or Role) to non-sensitive strings. The failure was invisible because the card and the schedules page still toasted through sonner, whose Toaster main removed; both use @/lib/toast now. Writing the card's outcome no longer returns silently either. Model: claude-opus-5-5[1m] Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Conflict in main-layout.tsx: keep both shell children — the schedule Registry subscription and main's compact-desktop layout sync. Model: claude-opus-5-5[1m] Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Earlier merges on this branch reformatted untouched electron/CLI files with a mismatched oxfmt; they are back to main's bytes (operation-coordinator keeps its real change, formatted). Lint: exhaustive `never` defaults in the schedule vocabulary, explicit `return undefined` in effect guards, and a void act() callback in the blank-click test. Model: claude-opus-5-5[1m] Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The previous commit staged stale local checkouts of these submodules. Model: claude-opus-5-5[1m] Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Edit turns the proposal card into a small form for name, prompt and the time rule (timed/manual + the editor's recurrence controls). Edits stay in the card, never written back to the notice, so an Agent retrying the same proposal still matches it; Create uses them. A created card is a receipt with no edit. Model: claude-opus-5-5[1m] Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Where schedules run (the owning machine's CLI, its local clock), creating one, triggers, where runs go, managing the list, Agent proposals with Edit/Create, missed and overlapping runs, and an FAQ. Model: claude-opus-5-5[1m] Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Risk: 🔴 high | Confidence: medium — new persisted schedule documents/registry, a new
preparedturn status, and machine-side dispatch; covered by deterministic engine/store/UI tests, not yet by a packaged end-to-end run or the adversarial review pipeline.Related issue
Problem / pressure
People want an Agent to do recurring work (a nightly review, a weekday summary) without typing the prompt each time. A cron line inside one Agent session dies with that session and machine, and nobody can see or pause it. The work has to be owned by a machine, survive restarts, be visible and pausable from any client, and never start more runs than asked for.
Summary
packages/shared/src/schedule-*): a schedule is a Loro document plus a row in the workspace Schedule Registry Flock. Triggers aremanual | once | interval | cron. A destination is a new chat per run, one chat the schedule owns (created by its first run, id derived from schedule id + epoch), or an existing chat.projectis optional, so a schedule can be a plain chat.apps/cli/src/lib/schedules): the owning machine plans slots from the injected clock. It records each run intent and cursor advance in one SQLite transaction. It freezes aprepareduser turn and hands it to the ordinary dispatch watcher. Misfire and overlap policies are bounded. Execution slots are shared per Agent, and a disconnected Registry blocks new handoffs.lody scheduleand MCPlist/show/propose/pause. An Agent can only propose.lody_schedule_proposetakes a named rule, never cron, and writes aschedule_proposalcard into the conversation. Pressing Create on the card is the creation.MachineMeta.timeZoneat registration (optional, under 50 bytes). Schedules are authored on the owning machine's clock and there is no zone picker; older CLIs fall back to the viewer's zone.packages/components/src/components/schedules, developer + beta gated): list, editor and detail. The editor is shaped like the composer: name, prompt, and a bottom row of destination / Agent / project pills in one box. Save problems appear as marks next to the control that fixes them. The frequency menu covers every day / weekdays / weekly days / monthly dates / every N hours / every N minutes / once / manual. There is no cron editor, and legacy rules are shown read-only with Replace. The editor uses the shared Agent/Project selectors and one save-blocker rule. It follows the new app chrome and one alignment grid.main: Tasks was removed upstream (chore: remove the workspace Tasks product [risk:high] #800). Schedules keep their ownscheduleToolsEnabledgate and execution slots, and use the new history API.Visual explanation
sequenceDiagram participant UI as App / Agent card participant Doc as schedule-<id> doc + Registry participant Host as Owning machine (CLI) participant DB as schedules.sqlite participant S as Session UI->>Doc: save definition, publish fingerprint Host->>Doc: subscribe, verify fingerprint & owner loop each due slot Host->>DB: IMMEDIATE tx: advance cursor + run intent Host->>S: write prepared user turn Host->>S: commit latestUserMsgId (point of no return) S-->>Host: ordinary dispatch watcher runs the turn endBefore / after
Test plan
tsgo --noEmitclean inpackages/shared,packages/components,apps/cli,apps/electron.NODE_ENV=test): shared 1222 pass; components 4038 pass; CLI 2907 pass. 5 fail intests/gh-shim-script.test.ts, which is identical tomainand depends on the host'sghsetup.0-7weekday range survives edits; productionprepareSessionInputyields no project for a chat-only schedule.check-i18n, platform/public boundary, and Code Collab import guards pass;oxlint0 errors.pnpm check, a packaged desktop end-to-end run, and the high-risk adversarial review.🤖 Generated with Claude Code