Skip to content

Commit 0ab0854

Browse files
Kowserclaude
andcommitted
S4: docs — scheduler as the one schedule surface; verb-compat, deprecations, scoped break
- CHANGELOG: Added (lifecycle on SchedulerClient, reason=), Changed (scoped breaking get/save/list_for_agent removal with one-line migrations; typed get_schedule), Deprecated (AgentScheduleClient/ScheduleClient/ get_agent_schedule_client), Fixed (PUT-first with 405->GET fallback across server families). - docs/SCHEDULE.md: verb-compatibility note (OSS=PUT, Orkes=GET, transparent fallback), pause reason= example (OSS-only storage), new Schedule Lifecycle Helpers section (the six typed-error operations; reads/writes/lists stay on the native methods). - docs/agents/{advanced,api-reference,writing-agents}.md: schedule surface documented on SchedulerClient; samples migrated off the removed wrappers; deprecation guidance for the shim symbols. - AGENTS.md §1.2: HAND-FIX policy — never regenerate scheduler_resource_api.py without re-applying the marked fixes; the contract guard tests fail loudly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 37f9ffa commit 0ab0854

6 files changed

Lines changed: 73 additions & 17 deletions

File tree

AGENTS.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,15 @@ conductor-{language}/
122122

123123
#### 1.2 HTTP/API Layer
124124

125+
> **HAND-FIX policy:** the bundled scheduler API spec is out of date, so
126+
> `scheduler_resource_api.py` carries hand-applied fixes marked with `HAND-FIX`
127+
> comments (per-schedule pause/resume verbs are `PUT` — the OSS Conductor dialect,
128+
> with a 405→`GET` fallback for Orkes servers living in `OrkesSchedulerClient`
129+
> plus an optional `reason` query param and `get_schedule`
130+
> `response_type='WorkflowSchedule'`). Never regenerate that file without
131+
> re-applying them; `tests/unit/orkes/test_scheduler_resource_contract.py` fails
132+
> loudly if a regeneration reverts them.
133+
125134
- [ ] Generate models from OpenAPI specification
126135
- [ ] Generate resource API classes
127136
- [ ] Implement ApiClient with:

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Canonical metrics mode: opt-in harmonized metric surface via `WORKER_CANONICAL_METRICS=true` -- [details](METRICS.md#detailed-technical-notes--unreleased)
1313
- `MetricsSettings` gains `clean_directory` and `clean_dead_pids` for opt-in stale `.db` file cleanup (both default to `False`)
14+
- `SchedulerClient` now carries the schedule lifecycle operations itself: `pause(reason=)`, `resume`, `delete`, `run_now`, `preview_next`, `reconcile` (declarative tri-state sync) — with typed errors (`ScheduleNotFound`, `InvalidCronExpression`, ...). `pause_schedule` gains an optional `reason=` (stored by OSS Conductor servers; ignored by Orkes servers)
1415

1516
### Changed
1617

1718
- **Multiprocessing start method now defaults to `spawn` on all platforms** (was `fork` everywhere except Windows). `fork` caused silently-restarting `SIGSEGV` worker subprocesses on macOS (exitcode `-11`) and fork-with-held-lock deadlocks on POSIX.
1819
- Legacy metrics emit unchanged by default; no env var required
1920
- `metrics_collector.py` is now a compatibility shim; `from conductor.client.telemetry.metrics_collector import MetricsCollector` continues to work
21+
- **Breaking (scoped):** schedule client objects no longer expose the mapped `get`/`save`/`list_for_agent` wrappers — `SchedulerClient`'s native methods are the source of truth: `get(wire)``get_schedule(wire)`, `save(schedule, agent)``save_schedule(SaveScheduleRequest)`, `list_for_agent(agent)``get_all_schedules(workflow_name=agent)`. The module-level `schedules.list/get/save` functions (and their `ScheduleInfo` returns) are unchanged
22+
- `get_schedule` returns a typed `WorkflowSchedule` (or `None` for missing schedules) instead of a raw camelCase dict, matching its declared annotation and [docs/SCHEDULE.md](docs/SCHEDULE.md); dict-consumers should switch to attribute access or `to_dict()`
23+
24+
### Deprecated
25+
26+
- `AgentScheduleClient`/`ScheduleClient` and `OrkesClients.get_agent_schedule_client()` — fully functional delegation shims over `SchedulerClient`; use `get_scheduler_client()`. Removal planned for the next major release
2027

2128
### Fixed
2229

2330
- `@worker_task` workers are now picklable, making the decorator path work with the `spawn`/`forkserver` start methods (fixes `TypeError: cannot pickle '_thread.lock' object` and `PicklingError: it's not the same object as ...`; issues #264, #271): `Worker.api_client` is created lazily in the worker process, runtime state (locks, pending async tasks, background loop) is excluded from pickling and rebuilt in the child, and decorated functions are pickled as importable references resolved in the child
2431
- `TaskHandler.start_processes()` no longer hangs the interpreter when a worker fails to start (e.g., unpicklable state under `spawn`); it now cleans up already-started subprocesses and raises with actionable guidance
2532
- Worker processes killed by a signal now log a diagnostic hint (signal number, `PYTHONFAULTHANDLER=1` guidance) instead of restarting silently
33+
- Per-schedule pause/resume now work on both Conductor server families: the client sends `PUT` (the OSS Conductor dialect — the spec-generated `GET` failed there) and transparently falls back to `GET` on a 405 for Orkes servers, caching the working dialect per client instance

docs/SCHEDULE.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,31 @@ Operations for controlling schedule execution state.
3535
| `resume_schedule()` | `PUT /api/scheduler/schedules/{name}/resume` | Resume a specific schedule | [Example](#resume-schedule) |
3636
| `resume_all_schedules()` | `PUT /api/scheduler/schedules/resume` | Resume all schedules | [Example](#resume-all-schedules) |
3737

38+
> **Verb compatibility:** OSS Conductor maps per-schedule pause/resume as `PUT`;
39+
> Orkes Conductor servers map them as `GET`. The client sends `PUT` first and
40+
> transparently falls back to `GET` on a 405, remembering the working dialect for
41+
> the rest of the client's lifetime — both server families work without configuration.
42+
> The optional `reason` parameter on `pause_schedule` is stored by OSS servers only.
43+
44+
## Schedule Lifecycle Helpers
45+
46+
Beyond the endpoint methods above, `SchedulerClient` carries higher-level lifecycle
47+
operations (shared with the agents layer). They raise typed errors
48+
(`ScheduleNotFound`, `InvalidCronExpression`, `ScheduleNameConflict` from
49+
`conductor.client.ai.schedule_errors`) instead of raw `ApiException`.
50+
51+
| Method | Signature | Description |
52+
|--------|-----------|-------------|
53+
| `pause()` | `(wire_name, reason=None)` | Pause with typed errors |
54+
| `resume()` | `(wire_name)` | Resume with typed errors |
55+
| `delete()` | `(wire_name)` | Delete with typed errors |
56+
| `preview_next()` | `(cron, n=5, start_at=None, end_at=None) -> list[int]` | Next `n` epoch-ms fire times |
57+
| `run_now()` | `(info: ScheduleInfo) -> str` | Fire the schedule's workflow once, returns execution id |
58+
| `reconcile()` | `(workflow_name, desired: list[Schedule] \| None)` | Declarative sync: `None` no-op, `[]` purge, `[...]` upsert+prune |
59+
60+
Reads, writes, and lists have no duplicated helper forms — `get_schedule()`,
61+
`save_schedule()`, and `get_all_schedules()` are the source of truth.
62+
3863
## Schedule Execution APIs
3964

4065
APIs for managing and querying schedule executions.
@@ -169,6 +194,9 @@ Pause a specific schedule to stop executions.
169194
```python
170195
scheduler_client.pause_schedule("daily_order_processing")
171196
print("Schedule paused")
197+
198+
# Optionally record why (stored by OSS Conductor servers; ignored by Orkes servers):
199+
scheduler_client.pause_schedule("daily_order_processing", reason="maintenance window")
172200
```
173201

174202
#### Pause All Schedules

docs/agents/advanced.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,16 @@ with AgentRuntime() as runtime:
126126

127127
Key methods: `run`/`run_async`, `start`/`start_async`, `deploy`/`deploy_async`,
128128
`schedule(agent, schedules)`, `get_status`, `respond`, `stop`, `signal`,
129-
`stream_sse`, and `.schedules` (the `AgentScheduleClient`). Both sync and async forms
130-
exist. Most users call `runtime.run/start/deploy` instead, which add local-worker
131-
management on top of this client.
129+
`stream_sse`, and `.schedules` (the schedule lifecycle — `pause`/`resume`/`delete`/
130+
`run_now`/`preview_next`/`reconcile`, now carried by `SchedulerClient` itself). Both
131+
sync and async forms exist. Most users call `runtime.run/start/deploy` instead,
132+
which add local-worker management on top of this client.
132133

133134
The raw `/agent/*` HTTP transport behind this client is
134135
`conductor.client.ai.AgentApiClient` — also reachable without the agents layer via
135-
`OrkesClients.get_agent_client()` (and `get_agent_schedule_client()` for the cron
136-
lifecycle). `AgentClient` composes that transport and adds the agent-level surface.
136+
`OrkesClients.get_agent_client()` (and `get_scheduler_client()` for the cron
137+
lifecycle; `get_agent_schedule_client()` is deprecated). `AgentClient` composes that
138+
transport and adds the agent-level surface.
137139

138140
## Structured output
139141

@@ -284,10 +286,12 @@ nightly = Schedule(name="nightly", cron="0 0 * * *", timezone="UTC",
284286
runtime.deploy(agent, schedules=[nightly]) # upsert; [] purges; omit leaves as-is
285287

286288
sc = runtime.schedules_client() # or runtime.client.schedules
287-
sc.list_for_agent(agent.name)
288-
sc.pause("greeter-nightly")
289-
sc.run_now(sc.get("greeter-nightly"))
289+
sc.get_all_schedules(workflow_name=agent.name) # list — source-of-truth read
290+
sc.pause("greeter-nightly", reason="ship freeze")
290291
print(sc.preview_next("0 0 * * *", n=5)) # next 5 fire times (epoch ms)
292+
293+
from conductor.ai.agents.schedule import schedules
294+
schedules.run_now("greeter-nightly", runtime=runtime) # fire once -> execution id
291295
```
292296

293297
## Skills

docs/agents/api-reference.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ Context manager (sync and async: `with` / `async with`).
4646
| `signal` | `(execution_id, message) -> None` | Inject persistent context |
4747
| `shutdown` | `() -> None` | Stop all workers |
4848
| `client` (property) | `-> AgentClient` | Control-plane client |
49-
| `schedules_client` | `() -> AgentScheduleClient` | Shared schedule client |
49+
| `schedules_client` | `() -> SchedulerClient` | Shared schedule client (a deprecated `AgentScheduleClient` shim) |
5050

5151
Async variants exist for status/respond/approve/reject/send/stop/shutdown
5252
(`*_async`). Module-level wrappers using a singleton runtime: `run`, `run_async`,
@@ -190,21 +190,27 @@ start_at=None, end_at=None, description=None)` — `cron` is a 5- or 6-field exp
190190
`ScheduleInfo` (read model) fields include `name`, `short_name`, `agent`, `cron`,
191191
`timezone`, `input`, `paused`, `catchup`, `next_run`, `create_time`, `update_time`, ...
192192

193-
`AgentScheduleClient` (alias `ScheduleClient`; canonical home
194-
`conductor.client.ai` — via `runtime.schedules_client()`, `runtime.client.schedules`,
195-
or `OrkesClients.get_agent_schedule_client()`):
193+
The schedule lifecycle lives on `SchedulerClient` itself (via
194+
`runtime.schedules_client()`, `runtime.client.schedules`, or
195+
`OrkesClients.get_scheduler_client()`):
196196

197197
| Method | Signature |
198198
|---|---|
199-
| `save` | `(schedule: Schedule, agent_name) -> None` |
200-
| `get` | `(wire_name, agent_name=None) -> ScheduleInfo` |
201-
| `list_for_agent` | `(agent_name) -> list[ScheduleInfo]` |
202199
| `pause` / `resume` | `(wire_name[, reason])` / `(wire_name)` |
203200
| `delete` | `(wire_name) -> None` |
204201
| `run_now` | `(info: ScheduleInfo) -> str` (execution_id) |
205202
| `preview_next` | `(cron, n=5, start_at=None, end_at=None) -> list[int]` |
206203
| `reconcile` | `(agent_name, desired: list[Schedule] | None) -> None` |
207204

205+
Reads/writes/lists use the native source-of-truth methods: `get_schedule(wire) ->
206+
WorkflowSchedule | None`, `save_schedule(SaveScheduleRequest)`,
207+
`get_all_schedules(workflow_name=...) -> list[WorkflowSchedule]`. The mapped
208+
`ScheduleInfo` view is returned by the module-level `schedules.list/get`.
209+
210+
`AgentScheduleClient` (alias `ScheduleClient`; `conductor.client.ai`) and
211+
`OrkesClients.get_agent_schedule_client()` are **deprecated** delegation shims,
212+
kept for backward compatibility and planned for removal in the next major release.
213+
208214
Errors: `ScheduleError`, `ScheduleNameConflict`, `ScheduleNotFound`,
209215
`InvalidCronExpression`.
210216

docs/agents/writing-agents.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -422,8 +422,9 @@ with AgentRuntime() as runtime:
422422

423423
`schedules=[]` purges all schedules for the agent; omitting `schedules` leaves them
424424
untouched. The schedule lifecycle client (`runtime.schedules_client()` or
425-
`runtime.client.schedules`) exposes `save`, `get`, `list_for_agent`, `pause`,
426-
`resume`, `delete`, `run_now`, `preview_next`, `reconcile`. See
425+
`runtime.client.schedules`) exposes `pause`, `resume`, `delete`, `run_now`,
426+
`preview_next`, `reconcile`, plus the native `get_schedule`/`save_schedule`/
427+
`get_all_schedules` for reads, writes, and lists. See
427428
[Advanced](advanced.md) and the [API reference](api-reference.md#schedule).
428429

429430
## Agents from a class

0 commit comments

Comments
 (0)