Skip to content

Commit d17617b

Browse files
feat(schedules): support skipped runs and task counts
Expose task-backed run counts and exact skip/unskip operations so schedule clients can reason about individual future occurrences. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent a0d983b commit d17617b

13 files changed

Lines changed: 630 additions & 19 deletions

agentex-ui/hooks/use-agent-run-schedules.ts

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {
1313
type UpdateAgentRunScheduleRequest,
1414
} from '@/lib/agent-run-schedules';
1515

16+
import type { Agent } from 'agentex/resources';
17+
1618
export const scheduleKeys = {
1719
all: ['agentRunSchedules'] as const,
1820
byAgentId: (agentId: string | null) =>
@@ -28,6 +30,18 @@ type ScheduleMutationContext = {
2830
agentId: string;
2931
};
3032

33+
type ScheduleActionInput =
34+
| string
35+
| {
36+
scheduleId: string;
37+
scheduledTime?: string;
38+
};
39+
40+
export type AgentRunScheduleListItem = {
41+
agentId: string;
42+
schedule: AgentRunSchedule;
43+
};
44+
3145
function errorMessage(error: unknown) {
3246
return error instanceof Error ? error.message : 'Please try again.';
3347
}
@@ -46,21 +60,33 @@ export function useAgentRunSchedules(baseURL: string, agentId: string | null) {
4660
});
4761
}
4862

49-
export function useAgentRunScheduleDetails(
63+
export function useAgentRunSchedulesForAgents(
64+
baseURL: string,
65+
agents: Agent[],
66+
enabled: boolean
67+
) {
68+
return useQueries({
69+
queries: agents.map(agent => ({
70+
queryKey: scheduleKeys.byAgentId(agent.id),
71+
queryFn: async () => {
72+
const response = await agentRunSchedulesAPI.list(baseURL, agent.id);
73+
return response.run_schedules;
74+
},
75+
enabled,
76+
})),
77+
});
78+
}
79+
80+
export function useAgentRunScheduleDetailsForItems(
5081
baseURL: string,
51-
agentId: string | null,
52-
schedules: AgentRunSchedule[]
82+
items: AgentRunScheduleListItem[]
5383
) {
5484
return useQueries({
55-
queries:
56-
agentId == null
57-
? []
58-
: schedules.map(schedule => ({
59-
queryKey: scheduleKeys.detail(agentId, schedule.id),
60-
queryFn: () =>
61-
agentRunSchedulesAPI.get(baseURL, agentId, schedule.id),
62-
staleTime: 30_000,
63-
})),
85+
queries: items.map(({ agentId, schedule }) => ({
86+
queryKey: scheduleKeys.detail(agentId, schedule.id),
87+
queryFn: () => agentRunSchedulesAPI.get(baseURL, agentId, schedule.id),
88+
staleTime: 30_000,
89+
})),
6490
});
6591
}
6692

@@ -130,25 +156,45 @@ export function useScheduleAction({
130156
agentId,
131157
action,
132158
}: ScheduleMutationContext & {
133-
action: 'delete' | 'pause' | 'resume' | 'trigger';
159+
action: 'delete' | 'pause' | 'resume' | 'skip' | 'trigger' | 'unskip';
134160
}) {
135161
const queryClient = useQueryClient();
136162

137163
return useMutation<
138164
AgentRunSchedule | { id: string; message: string },
139165
Error,
140-
string
166+
ScheduleActionInput
141167
>({
142-
mutationFn: (scheduleId: string) => {
168+
mutationFn: input => {
169+
const scheduleId = typeof input === 'string' ? input : input.scheduleId;
170+
const scheduledTime =
171+
typeof input === 'string' ? undefined : input.scheduledTime;
143172
switch (action) {
144173
case 'delete':
145174
return agentRunSchedulesAPI.delete(baseURL, agentId, scheduleId);
146175
case 'pause':
147176
return agentRunSchedulesAPI.pause(baseURL, agentId, scheduleId);
148177
case 'resume':
149178
return agentRunSchedulesAPI.resume(baseURL, agentId, scheduleId);
179+
case 'skip':
180+
return agentRunSchedulesAPI.skip(
181+
baseURL,
182+
agentId,
183+
scheduleId,
184+
scheduledTime
185+
);
150186
case 'trigger':
151187
return agentRunSchedulesAPI.trigger(baseURL, agentId, scheduleId);
188+
case 'unskip':
189+
if (!scheduledTime) {
190+
throw new Error('scheduledTime is required to unskip a run');
191+
}
192+
return agentRunSchedulesAPI.unskip(
193+
baseURL,
194+
agentId,
195+
scheduleId,
196+
scheduledTime
197+
);
152198
}
153199
},
154200
onSuccess: result => {
@@ -163,7 +209,11 @@ export function useScheduleAction({
163209
toast.success(
164210
action === 'trigger'
165211
? 'Scheduled task triggered'
166-
: `Scheduled task ${action === 'delete' ? 'deleted' : `${action}d`}`
212+
: action === 'skip'
213+
? 'Scheduled run skipped'
214+
: action === 'unskip'
215+
? 'Scheduled run restored'
216+
: `Scheduled task ${action === 'delete' ? 'deleted' : `${action}d`}`
167217
);
168218
},
169219
onError: error => {

agentex-ui/lib/agent-run-schedules.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ export type AgentRunSchedule = {
2323
updated_at: string | null;
2424
state: 'ACTIVE' | 'PAUSED';
2525
next_action_times: string[];
26+
skipped_action_times: string[];
2627
last_action_time: string | null;
2728
num_actions_taken: number;
29+
num_tasks_created: number;
2830
};
2931

3032
export type AgentRunScheduleListResponse = {
@@ -145,4 +147,34 @@ export const agentRunSchedulesAPI = {
145147
`${schedulesPath(agentId)}/${encodeURIComponent(scheduleId)}/trigger`,
146148
{ method: 'POST' }
147149
),
150+
151+
skip: (
152+
baseURL: string,
153+
agentId: string,
154+
scheduleId: string,
155+
scheduledTime?: string
156+
) =>
157+
request<AgentRunSchedule>(
158+
baseURL,
159+
`${schedulesPath(agentId)}/${encodeURIComponent(scheduleId)}/skip`,
160+
{
161+
method: 'POST',
162+
body: JSON.stringify({ scheduled_time: scheduledTime }),
163+
}
164+
),
165+
166+
unskip: (
167+
baseURL: string,
168+
agentId: string,
169+
scheduleId: string,
170+
scheduledTime: string
171+
) =>
172+
request<AgentRunSchedule>(
173+
baseURL,
174+
`${schedulesPath(agentId)}/${encodeURIComponent(scheduleId)}/unskip`,
175+
{
176+
method: 'POST',
177+
body: JSON.stringify({ scheduled_time: scheduledTime }),
178+
}
179+
),
148180
};

agentex-ui/lib/schedule-utils.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,10 @@ describe('scheduleToCadence', () => {
9696
updated_at: null,
9797
state: 'ACTIVE',
9898
next_action_times: [],
99+
skipped_action_times: [],
99100
last_action_time: null,
100101
num_actions_taken: 0,
102+
num_tasks_created: 0,
101103
} satisfies AgentRunSchedule;
102104

103105
it('converts weekly cron to editable cadence config', () => {

agentex/openapi.yaml

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3595,6 +3595,86 @@ paths:
35953595
application/json:
35963596
schema:
35973597
$ref: '#/components/schemas/HTTPValidationError'
3598+
/agents/{agent_id}/schedules/{schedule_id}/skip:
3599+
post:
3600+
tags:
3601+
- Schedules
3602+
summary: Skip Run Schedule Action
3603+
description: Skip a recurring fire of the schedule.
3604+
operationId: skip_run_schedule_action_agents__agent_id__schedules__schedule_id__skip_post
3605+
parameters:
3606+
- name: agent_id
3607+
in: path
3608+
required: true
3609+
schema:
3610+
type: string
3611+
title: Agent Id
3612+
- name: schedule_id
3613+
in: path
3614+
required: true
3615+
schema:
3616+
type: string
3617+
title: Schedule Id
3618+
requestBody:
3619+
content:
3620+
application/json:
3621+
schema:
3622+
anyOf:
3623+
- $ref: '#/components/schemas/SkipRunScheduleRequest'
3624+
- type: 'null'
3625+
title: Request
3626+
responses:
3627+
'200':
3628+
description: Successful Response
3629+
content:
3630+
application/json:
3631+
schema:
3632+
$ref: '#/components/schemas/AgentRunScheduleResponse'
3633+
'422':
3634+
description: Validation Error
3635+
content:
3636+
application/json:
3637+
schema:
3638+
$ref: '#/components/schemas/HTTPValidationError'
3639+
/agents/{agent_id}/schedules/{schedule_id}/unskip:
3640+
post:
3641+
tags:
3642+
- Schedules
3643+
summary: Unskip Run Schedule Action
3644+
description: Remove a skip for a recurring fire of the schedule.
3645+
operationId: unskip_run_schedule_action_agents__agent_id__schedules__schedule_id__unskip_post
3646+
parameters:
3647+
- name: agent_id
3648+
in: path
3649+
required: true
3650+
schema:
3651+
type: string
3652+
title: Agent Id
3653+
- name: schedule_id
3654+
in: path
3655+
required: true
3656+
schema:
3657+
type: string
3658+
title: Schedule Id
3659+
requestBody:
3660+
required: true
3661+
content:
3662+
application/json:
3663+
schema:
3664+
$ref: '#/components/schemas/UnskipRunScheduleRequest'
3665+
responses:
3666+
'200':
3667+
description: Successful Response
3668+
content:
3669+
application/json:
3670+
schema:
3671+
$ref: '#/components/schemas/AgentRunScheduleResponse'
3672+
'422':
3673+
description: Validation Error
3674+
content:
3675+
application/json:
3676+
schema:
3677+
$ref: '#/components/schemas/HTTPValidationError'
35983678
/agents/{agent_id}/schedules/name/{name}/pause:
35993679
post:
36003680
tags:
@@ -4360,6 +4440,13 @@ components:
43604440
type: array
43614441
title: Next Action Times
43624442
description: Upcoming scheduled fire times.
4443+
skipped_action_times:
4444+
items:
4445+
type: string
4446+
format: date-time
4447+
type: array
4448+
title: Skipped Action Times
4449+
description: Skipped one-off scheduled fire times.
43634450
last_action_time:
43644451
anyOf:
43654452
- type: string
@@ -4372,6 +4459,12 @@ components:
43724459
title: Num Actions Taken
43734460
description: Number of times the schedule has fired.
43744461
default: 0
4462+
num_tasks_created:
4463+
type: integer
4464+
title: Num Tasks Created
4465+
description: Number of non-deleted tasks created by this schedule, including
4466+
manual runs.
4467+
default: 0
43754468
type: object
43764469
required:
43774470
- id
@@ -6087,6 +6180,18 @@ components:
60876180
required:
60886181
- content
60896182
title: SendMessageRequest
6183+
SkipRunScheduleRequest:
6184+
properties:
6185+
scheduled_time:
6186+
anyOf:
6187+
- type: string
6188+
format: date-time
6189+
- type: 'null'
6190+
title: Scheduled Time
6191+
description: Specific scheduled fire time to skip. If omitted, the next
6192+
fire is skipped.
6193+
type: object
6194+
title: SkipRunScheduleRequest
60906195
Span:
60916196
properties:
60926197
id:
@@ -6969,6 +7074,17 @@ components:
69697074
- name
69707075
title: ToolResponseDelta
69717076
description: Delta for tool response updates
7077+
UnskipRunScheduleRequest:
7078+
properties:
7079+
scheduled_time:
7080+
type: string
7081+
format: date-time
7082+
title: Scheduled Time
7083+
description: Specific scheduled fire time to unskip.
7084+
type: object
7085+
required:
7086+
- scheduled_time
7087+
title: UnskipRunScheduleRequest
69727088
UpdateAgentRunScheduleRequest:
69737089
properties:
69747090
name:

0 commit comments

Comments
 (0)