Skip to content

feat(core): schedule prompts from agents, /schedule and the experimental API - #59457

Merged
kitlangton merged 4 commits into
devfrom
schedules-tools
Oct 29, 2026
Merged

kitlangton merged 4 commits into
devfrom
schedules-tools

Conversation

@kitlangton

@kitlangton kitlangton commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Agents, people and apps can now schedule a prompt for later: ask "check CI every hour for the next 8 hours" and the session wakes itself.
Builds on #59401 (merged), which added the storage and timer. Everything here stays off unless experimental.schedules is on where the session lives.

💡 Why
#59401 fires schedules, but nothing could create one yet. This PR gives the agent, the user and apps a way in.

🆕 Now you can

  • Let the agent schedule work itself. It gets schedule_create, schedule_list, schedule_pause, schedule_resume and schedule_delete in the opencode tool namespace. It can only see and change its own session's schedules.
  • Type /schedule every morning at 9 check CI (or just /schedule to list them). The agent turns that into a schedule.
  • Manage schedules from apps through client.experimental.schedule.{create,list,get,pause,resume,delete} (/api/experimental/schedule).
  • Write when the short way: { in: "20 minutes" }, { at: "2026-10-27T09:00" }, { every: "1 hour" } or { cron: "0 9 * * 1-5" }, with an optional tz, plus ends and prompt.

🚦 How it ships
Off by default. With experimental.schedules off, the tools and /schedule aren't registered, and the API returns 403. They appear or disappear when config changes, without a restart. Conditional checks (if / until shell commands that need approval) come in the next PR.

+ agent / /schedule / app -> schedule({ when, ends?, prompt })
+   -> flag off where the session lives? -> 403, or the tools are hidden
+   -> another session's schedule?       -> not found
+   -> the timer from #59401 fires it later as a prompt

Visual

<svg xmlns="http://www.w3.org/2000/svg" width="240" height="72" viewBox="0 0 240 72" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
  <style>
    .hand{transform-origin:36px 36px;animation:spin 4s linear infinite}
    .msg{animation:fly 4s ease-in-out infinite}
    .dot{animation:pulse 4s ease-in-out infinite}
    @keyframes spin{to{transform:rotate(360deg)}}
    @keyframes fly{0%,20%{transform:translateX(0);opacity:0}30%{opacity:1}60%,100%{transform:translateX(96px);opacity:1}}
    @keyframes pulse{0%,55%{opacity:.25}65%,85%{opacity:1}100%{opacity:.25}}
    @media (prefers-reduced-motion:reduce){.hand,.msg,.dot{animation:none}.msg{transform:translateX(96px)}}
  </style>
  <circle cx="36" cy="36" r="20" opacity=".6"/>
  <line class="hand" x1="36" y1="36" x2="36" y2="22"/>
  <line x1="36" y1="36" x2="46" y2="36" opacity=".6"/>
  <g class="msg">
    <rect x="66" y="27" width="34" height="18" rx="6" opacity=".9"/>
    <line x1="73" y1="36" x2="93" y2="36" opacity=".5"/>
  </g>
  <rect x="160" y="14" width="66" height="44" rx="8" opacity=".5"/>
  <line x1="170" y1="28" x2="208" y2="28" opacity=".35"/>
  <line x1="170" y1="44" x2="196" y2="44" opacity=".35"/>
  <circle class="dot" cx="216" cy="44" r="3" fill="currentColor" stroke="none"/>
</svg>

📊 Proof: new tool tests cover the flag gate, the full create → list → pause → resume → delete flow, /schedule, and another session's schedule reading as missing. A server test drives the real HTTP API through the generated client, including 400 for a bad duration or two triggers in one when, and 403 where the flag is off. Tools recheck the flag on every call, so a config change takes effect mid-session. 31 core and 1 server tests pass.

👀 Read: packages/core/src/tool/plugin/schedule.ts, the owned check. Schedule ids aren't secret, so every tool call checks that the schedule belongs to the calling session. Also note that duration checks moved from the schema into the service, because generated clients can't run custom schema checks. The rest is routine (generated client and OpenAPI).

Planned feature · no user reports · not measured

Checks run
  • bun run script/test.ts test/schedule.test.ts test/tool-schedule.test.ts (core): 31 pass
  • bun test test/schedule.test.ts (server): 1 pass
  • bun typecheck in schema, core, protocol, server and client: pass
  • bun run generate in client and protocol: regenerated
  • oxlint on changed files: 0 errors; bun script/sdk-docs.ts: 0 problems

Added by the opencode bot (AI-generated).

Related: #41906, #53960

Public API: adds experimental /api/experimental/schedule endpoints, client.experimental.schedule., Schedule schema types, opencode.schedule_ agent tools and /schedule (backwards compatible)

@kitlangton
kitlangton force-pushed the schedules-tools branch 2 times, most recently from 522d54f to 5158f2f Compare October 28, 2026 04:26
Base automatically changed from schedules-core to dev October 28, 2026 20:07

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works as described, and I found nothing that blocks a merge. I ran it from source with the simulated model:

  • With experimental.schedules on, /schedule every hour for 8 hours check CI expanded into the template. The model's opencode.schedule_create call returned an active hourly schedule with the right next run and end time.
  • With the flag off, /schedule isn't registered and the tool isn't listed. The base branch behaves the same way.
  • Core (31) and server (1) tests pass, and tsc -b passes in schema, core, protocol, server and client.

The flag is checked on create and on every other tool and HTTP call. The owned check makes another session's schedules read as not found, and a schedule whose session is gone also reads as not found.

Public API: everything is additive and experimental, so existing callers aren't affected.

  • HTTP: POST/GET /api/experimental/schedule, plus GET and DELETE /api/experimental/schedule/{scheduleID} and POST .../{scheduleID}/pause and /resume. These are in OpenAPI under the experimental tag, with a new 404 ScheduleNotFoundError.
  • Client: client.experimental.schedule.{create,list,get,pause,resume,delete}, and the Schedule types are now exported from the generated client.
  • Schema: adds Schedule.WhenInput, Schedule.Request and Schedule.CreatePayload. Schedule.Duration loses its format check and becomes a plain annotated string (looser, still compatible).
  • Agent tools: opencode.schedule_create, schedule_list, schedule_pause, schedule_resume and schedule_delete.
  • Command: /schedule.

One design point for before this leaves experimental: schedule_create doesn't ask for permission and has no per-session limit. A prompt-injected or confused agent could set up recurring prompts, and a schedule that fires could create more. A permission prompt or a cap might be worth it.

Related: #41906, #53960

Before

After

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new commit only merges dev. Its conflicts were in the generated promise client and the client test, and both were settled by adding schedule to the new client.experimental grouping from #59072. Re-running generate in client and protocol changes nothing.

I checked it again on the new head:

  • The client, protocol identifier, core and server schedule tests pass.
  • tsc -b passes in schema, core, protocol, server and client.
  • In the TUI, /schedule with schedule_create still creates an active schedule.

The public API is the same as in my last review: additive, experimental and backwards compatible. Nothing new to flag.

@kitlangton
kitlangton merged commit fbb47c9 into dev Oct 29, 2026
20 checks passed
@kitlangton
kitlangton deleted the schedules-tools branch October 29, 2026 02:37
@amrkmn amrkmn mentioned this pull request Oct 29, 2026
1 task
R44VC0RP added a commit that referenced this pull request Oct 29, 2026
Resolves conflicts in generated migration files after #59457 and newer
migrations landed on dev. The branch's migration is regenerated as
20261029140544_schedule_condition so it sorts after dev's migrations; it
still only adds the nullable schedule.if and schedule.state columns.

Verified: bun typecheck (core), test/schedule.test.ts and
test/tool-schedule.test.ts (41 pass).
R44VC0RP added a commit that referenced this pull request Oct 29, 2026
…route

#59457 added the experimental schedule.create route, and Schedule.create can
now fail with Permission.BlockedError or Permission.CorrectedError when a
code condition's allowlisted tools are denied or rejected at creation. Map
both to ForbiddenError so the route's error type stays within the API
contract.

Verified: bun typecheck (server, core), server test/schedule.test.ts.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant