Commit c045ad0
fix(schedules): load live fields in schedule lists (#368)
## Problem
The schedules UI loads persisted schedule definitions first, then sends
one detail request per schedule to obtain live Temporal fields. In
authenticated deployments, failures in that detail-request fan-out leave
`next_action_times` empty without surfacing an error. As a result, valid
schedules disappear from Upcoming and "Skip next run" is unavailable
even though their Temporal clocks have future actions.
## Solution
- add an opt-in `include_live` query parameter to schedule listing
- enrich authorized rows with live Temporal fields using a concurrency
limit of 10
- keep the existing database-only list behavior as the default for
compatibility and performance
- have the UI request enriched lists and remove the per-schedule
detail-request fan-out
- surface a notice when an active schedule still lacks next-run data
## Test plan
- [x] Backend schedule service unit tests
- [x] Backend schedule authorization route unit tests
- [x] Frontend schedule hook tests
- [x] Frontend TypeScript typecheck
- [x] Frontend lint and formatting checks
- [x] Local smoke test confirms enriched lists return future action
times
Made with [Cursor](https://cursor.com)
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR moves live Temporal field enrichment from a per-schedule
client-side fan-out to a single server-side batch operation gated by an
opt-in `include_live` query parameter. The previous approach silently
dropped schedules from the "Upcoming" view when any per-schedule detail
request failed in authenticated deployments.
- **Backend**: `list_schedules` now accepts `include_live=True`,
collects visible rows, then concurrently enriches up to
`MAX_LIVE_ENRICHMENT_ROWS` (200) via a semaphore-bounded
`asyncio.gather(return_exceptions=True)`; rows beyond the cap are
returned with `live_data_available=None` rather than a misleading
`False`. A new `live_data_available: bool | None` field on the response
schema distinguishes enrichment success, enrichment failure, and
enrichment not requested.
- **Frontend**: Both list hooks now pass `include_live: true` and
`staleTime: 30_000`, replacing the removed
`useAgentRunScheduleDetailsForItems` fan-out. The "Upcoming" tab shows a
targeted status notice when any non-paused schedule has
`live_data_available === false`, directing users to the Schedules tab
for definitions that are still intact.
- **Tests**: Four new service-layer tests cover the live path, semaphore
concurrency bound, fan-out cap via monkeypatch, and the guarantee that
all sibling tasks settle before re-raising an error.
<details><summary><h3>Confidence Score: 5/5</h3></summary>
Safe to merge — the change is opt-in (default include_live=False
preserves existing behavior), the fan-out ceiling and semaphore prevent
overload, and all previously identified issues have been addressed.
All three concerns from the prior review round (return_exceptions=True,
staleTime on enriched queries, live_data_available distinguishing
enrichment failures from genuinely empty schedules) were addressed. The
new logic is well-covered by four targeted service tests. The DB-only
default path is unchanged, and the include_live=True path degrades
gracefully per row rather than failing the whole request.
No files require special attention.
agentex/src/domain/services/agent_run_schedule_service.py carries the
most new logic but is thoroughly tested.
</details>
<details><summary><h3>Important Files Changed</h3></summary>
| Filename | Overview |
|----------|----------|
| agentex/src/domain/services/agent_run_schedule_service.py | Core
enrichment logic: bounded gather with semaphore,
MAX_LIVE_ENRICHMENT_ROWS cap, return_exceptions=True, and
live_data_available tri-state field all implemented correctly |
| agentex/src/api/routes/agent_run_schedules.py | Adds include_live
query parameter with Annotated[bool, Query(...)] annotation and threads
it through to the use case |
| agentex/src/api/schemas/agent_run_schedules.py | Adds
live_data_available: bool | None field to AgentRunScheduleResponse with
correct nullable default and clear docstring |
| agentex-ui/hooks/use-agent-run-schedules.ts | Removes per-schedule
detail fan-out; both list hooks now pass include_live:true and
staleTime:30_000; AgentRunScheduleListItem export removed cleanly |
| agentex-ui/components/scheduled-tasks/scheduled-tasks-page.tsx |
Removes detailQueries fan-out; computes unavailableLiveDataCount from
live_data_available===false and shows a targeted status notice on the
upcoming tab |
| agentex-ui/lib/agent-run-schedules.ts | Adds live_data_available:
boolean | null to AgentRunSchedule type and normalizeAgentRunSchedule
with correct null coalescing |
| agentex/tests/unit/services/test_agent_run_schedule_service.py | Four
new tests cover include_live=True path, bounded concurrency, fan-out cap
(via monkeypatch), and waiting for all rows before re-raising — solid
coverage |
| agentex/tests/unit/api/test_agent_run_schedules_authz.py | Updates
authz route test to pass and assert include_live=True through the use
case call |
| agentex-ui/hooks/use-agent-run-schedules.test.tsx | New test verifies
the hook passes include_live:true and that live_data_available is
surfaced on the returned schedule object |
| agentex-ui/lib/schedule-utils.test.ts | Test fixtures updated to
include live_data_available: null to match the new field; no logic
changes |
| agentex/openapi.yaml | Adds include_live query parameter and
live_data_available response field to the OpenAPI spec; nullable boolean
uses anyOf correctly |
| agentex/src/domain/use_cases/agent_run_schedules_use_case.py | Minimal
pass-through change: adds include_live parameter to list_schedules and
forwards it to the service |
</details>
<details><summary><h3>Sequence Diagram</h3></summary>
<a href="#gh-light-mode-only">
```mermaid
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant UI as ScheduledTasksPage
participant Hook as useAgentRunSchedules
participant API as GET /agents/{id}/schedules
participant Svc as AgentRunScheduleService
participant DB as Postgres
participant T as Temporal (xN semaphore10)
UI->>Hook: mount / staleTime expires
Hook->>API: "?include_live=true&limit=50"
API->>Svc: "list_schedules(include_live=True)"
Svc->>DB: list_by_agent_id()
DB-->>Svc: rows (all)
Note over Svc: auth filter to visible_rows
Note over Svc: live_rows = visible_rows[:200]
par asyncio.gather semaphore 10
Svc->>T: describe_schedule(id_1)
T-->>Svc: ScheduleDescription
Svc->>T: describe_schedule(id_2)
T-->>Svc: ScheduleDescription or Exception
end
Note over Svc: live_data_available True or False per row
Svc-->>API: AgentRunScheduleListResponse
API-->>Hook: JSON run_schedules total
Hook-->>UI: data staleTime 30s
Note over UI: upcoming filter next_action_times not null
Note over UI: notice if live_data_available is false
```
</a>
<a href="#gh-dark-mode-only">
```mermaid
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant UI as ScheduledTasksPage
participant Hook as useAgentRunSchedules
participant API as GET /agents/{id}/schedules
participant Svc as AgentRunScheduleService
participant DB as Postgres
participant T as Temporal (xN semaphore10)
UI->>Hook: mount / staleTime expires
Hook->>API: "?include_live=true&limit=50"
API->>Svc: "list_schedules(include_live=True)"
Svc->>DB: list_by_agent_id()
DB-->>Svc: rows (all)
Note over Svc: auth filter to visible_rows
Note over Svc: live_rows = visible_rows[:200]
par asyncio.gather semaphore 10
Svc->>T: describe_schedule(id_1)
T-->>Svc: ScheduleDescription
Svc->>T: describe_schedule(id_2)
T-->>Svc: ScheduleDescription or Exception
end
Note over Svc: live_data_available True or False per row
Svc-->>API: AgentRunScheduleListResponse
API-->>Hook: JSON run_schedules total
Hook-->>UI: data staleTime 30s
Note over UI: upcoming filter next_action_times not null
Note over UI: notice if live_data_available is false
```
</a>
</details>
<sub>Reviews (5): Last reviewed commit: ["Merge branch 'main'
into
jerome/schedule..."](4cf2629)
| [Re-trigger
Greptile](https://app.greptile.com/api/retrigger?id=45786376)</sub>
<!-- /greptile_comment -->
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>1 parent d0ba8bf commit c045ad0
12 files changed
Lines changed: 304 additions & 58 deletions
File tree
- agentex-ui
- components/scheduled-tasks
- hooks
- lib
- agentex
- src
- api
- routes
- schemas
- domain
- services
- use_cases
- tests/unit
- api
- services
Lines changed: 21 additions & 14 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
30 | | - | |
31 | 30 | | |
32 | 31 | | |
33 | 32 | | |
| |||
90 | 89 | | |
91 | 90 | | |
92 | 91 | | |
93 | | - | |
94 | | - | |
95 | | - | |
96 | | - | |
97 | | - | |
| 92 | + | |
98 | 93 | | |
99 | | - | |
100 | | - | |
101 | | - | |
102 | | - | |
103 | | - | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
104 | 100 | | |
105 | 101 | | |
106 | 102 | | |
107 | 103 | | |
108 | 104 | | |
109 | | - | |
| 105 | + | |
110 | 106 | | |
111 | 107 | | |
112 | 108 | | |
113 | 109 | | |
114 | | - | |
| 110 | + | |
115 | 111 | | |
116 | | - | |
| 112 | + | |
117 | 113 | | |
118 | 114 | | |
119 | 115 | | |
| |||
181 | 177 | | |
182 | 178 | | |
183 | 179 | | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
184 | 191 | | |
185 | 192 | | |
186 | 193 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
8 | 12 | | |
9 | 13 | | |
10 | 14 | | |
| |||
16 | 20 | | |
17 | 21 | | |
18 | 22 | | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
19 | 64 | | |
20 | 65 | | |
21 | 66 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
30 | 35 | | |
31 | 36 | | |
32 | 37 | | |
| |||
40 | 45 | | |
41 | 46 | | |
42 | 47 | | |
43 | | - | |
44 | | - | |
45 | | - | |
46 | | - | |
47 | | - | |
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
| |||
59 | 59 | | |
60 | 60 | | |
61 | 61 | | |
62 | | - | |
63 | | - | |
64 | | - | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
65 | 66 | | |
66 | 67 | | |
67 | 68 | | |
| 69 | + | |
68 | 70 | | |
69 | 71 | | |
70 | 72 | | |
| |||
77 | 79 | | |
78 | 80 | | |
79 | 81 | | |
80 | | - | |
81 | | - | |
82 | | - | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
83 | 86 | | |
84 | 87 | | |
85 | 88 | | |
86 | | - | |
87 | | - | |
88 | | - | |
89 | | - | |
90 | | - | |
91 | | - | |
92 | | - | |
93 | | - | |
94 | | - | |
95 | | - | |
96 | | - | |
97 | | - | |
98 | | - | |
99 | | - | |
100 | | - | |
101 | | - | |
102 | | - | |
103 | | - | |
| 89 | + | |
104 | 90 | | |
105 | 91 | | |
106 | 92 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| 26 | + | |
26 | 27 | | |
27 | 28 | | |
28 | 29 | | |
| |||
69 | 70 | | |
70 | 71 | | |
71 | 72 | | |
| 73 | + | |
72 | 74 | | |
73 | 75 | | |
74 | 76 | | |
| |||
100 | 102 | | |
101 | 103 | | |
102 | 104 | | |
| 105 | + | |
103 | 106 | | |
104 | 107 | | |
105 | 108 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
131 | 131 | | |
132 | 132 | | |
133 | 133 | | |
| 134 | + | |
134 | 135 | | |
135 | 136 | | |
136 | 137 | | |
| |||
179 | 180 | | |
180 | 181 | | |
181 | 182 | | |
| 183 | + | |
182 | 184 | | |
183 | 185 | | |
184 | 186 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3359 | 3359 | | |
3360 | 3360 | | |
3361 | 3361 | | |
| 3362 | + | |
| 3363 | + | |
| 3364 | + | |
| 3365 | + | |
| 3366 | + | |
| 3367 | + | |
| 3368 | + | |
| 3369 | + | |
| 3370 | + | |
3362 | 3371 | | |
3363 | 3372 | | |
3364 | 3373 | | |
| |||
4492 | 4501 | | |
4493 | 4502 | | |
4494 | 4503 | | |
| 4504 | + | |
| 4505 | + | |
| 4506 | + | |
| 4507 | + | |
| 4508 | + | |
| 4509 | + | |
| 4510 | + | |
4495 | 4511 | | |
4496 | 4512 | | |
4497 | 4513 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| |||
106 | 106 | | |
107 | 107 | | |
108 | 108 | | |
109 | | - | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
110 | 114 | | |
111 | 115 | | |
112 | 116 | | |
| |||
117 | 121 | | |
118 | 122 | | |
119 | 123 | | |
| 124 | + | |
120 | 125 | | |
121 | 126 | | |
122 | 127 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
151 | 151 | | |
152 | 152 | | |
153 | 153 | | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
154 | 161 | | |
155 | 162 | | |
156 | 163 | | |
| |||
0 commit comments