Manage Schedules over REST
Use raw HTTP to create, list, read, update, enable, disable, trigger, page through run history, and delete a schedule served by 01_run_in_agentos.py.
"""
Manage Schedules over REST
==========================
Use raw HTTP to create, list, read, update, enable, disable, trigger, page
through run history, and delete a schedule served by 01_run_in_agentos.py.
Prerequisites: 01_run_in_agentos.py running with OPENAI_API_KEY
Run: .venvs/demo/bin/python cookbook/05_agent_os/12_scheduler/02_rest_api.py
Try: Inspect the data and meta objects returned for run-history pages 1 and 2
"""
import os
import httpx
# ---------------------------------------------------------------------------
# Create REST Client Helpers
# ---------------------------------------------------------------------------
BASE_URL = os.getenv("AGENT_OS_BASE_URL", "http://127.0.0.1:7777")
AGENT_ID = "scheduled-greeter"
SCHEDULE_NAME = "rest-api-greeting"
def delete_existing(client: httpx.Client) -> None:
"""Remove an earlier copy so the walkthrough is repeatable."""
response = client.get("/schedules", params={"limit": 100, "page": 1})
response.raise_for_status()
for schedule in response.json()["data"]:
if schedule["name"] == SCHEDULE_NAME:
delete_response = client.delete(f"/schedules/{schedule['id']}")
delete_response.raise_for_status()
def trigger(client: httpx.Client, schedule_id: str) -> dict:
"""Trigger one schedule and require a completed executor record."""
response = client.post(f"/schedules/{schedule_id}/trigger")
response.raise_for_status()
run = response.json()
if run["status"] != "success":
raise RuntimeError(f"Triggered run ended with {run['status']}")
return run
# ---------------------------------------------------------------------------
# Run REST Schedule Lifecycle
# ---------------------------------------------------------------------------
def run_rest_lifecycle() -> None:
"""Exercise the complete schedule REST lifecycle."""
with httpx.Client(base_url=BASE_URL, timeout=180.0) as client:
health_response = client.get("/health")
health_response.raise_for_status()
config_response = client.get("/config")
config_response.raise_for_status()
config = config_response.json()
delete_existing(client)
create_response = client.post(
"/schedules",
json={
"name": SCHEDULE_NAME,
"cron_expr": "0 0 1 1 *",
"endpoint": f"/agents/{AGENT_ID}/runs",
"description": "A schedule managed through raw HTTP.",
"payload": {
"message": "Confirm this manually triggered scheduler run.",
"session_id": "rest-scheduler-session",
},
"timezone": "UTC",
"timeout_seconds": 120,
"max_retries": 1,
"retry_delay_seconds": 5,
},
)
create_response.raise_for_status()
schedule = create_response.json()
schedule_id = schedule["id"]
try:
list_response = client.get(
"/schedules",
params={"limit": 1, "page": 1},
)
list_response.raise_for_status()
listed = list_response.json()
if set(listed) != {"data", "meta"} or listed["meta"]["page"] != 1:
raise RuntimeError("Schedule listing did not use data/meta pagination")
detail_response = client.get(f"/schedules/{schedule_id}")
detail_response.raise_for_status()
if detail_response.json()["name"] != SCHEDULE_NAME:
raise RuntimeError("Schedule detail returned the wrong row")
update_response = client.patch(
f"/schedules/{schedule_id}",
json={
"description": "Updated through PATCH.",
"cron_expr": "0 9 1 1 *",
},
)
update_response.raise_for_status()
updated = update_response.json()
if updated["cron_expr"] != "0 9 1 1 *":
raise RuntimeError("PATCH did not persist cron_expr")
disable_response = client.post(f"/schedules/{schedule_id}/disable")
disable_response.raise_for_status()
if disable_response.json()["enabled"]:
raise RuntimeError("Schedule remained enabled")
enable_response = client.post(f"/schedules/{schedule_id}/enable")
enable_response.raise_for_status()
if not enable_response.json()["enabled"]:
raise RuntimeError("Schedule remained disabled")
first_run = trigger(client, schedule_id)
second_run = trigger(client, schedule_id)
first_page_response = client.get(
f"/schedules/{schedule_id}/runs",
params={"limit": 1, "page": 1},
)
first_page_response.raise_for_status()
first_page = first_page_response.json()
second_page_response = client.get(
f"/schedules/{schedule_id}/runs",
params={"limit": 1, "page": 2},
)
second_page_response.raise_for_status()
second_page = second_page_response.json()
if first_page["meta"]["total_count"] < 2:
raise RuntimeError("Expected two persisted trigger records")
if first_page["meta"]["page"] != 1 or second_page["meta"]["page"] != 2:
raise RuntimeError("Run-history pagination did not preserve page")
if not first_page["data"] or not second_page["data"]:
raise RuntimeError("Expected one run on each requested history page")
page_ids = {
first_page["data"][0]["id"],
second_page["data"][0]["id"],
}
if len(page_ids) != 2:
raise RuntimeError("History pages returned the same run")
print(f"Health: {health_response.json()['status']}")
print(f"AgentOS: {config['os_id']}")
print(f"Agent: {config['agents'][0]['id']}")
print(f"Created: {schedule_id}")
print(f"Updated cron: {updated['cron_expr']}")
print(f"Triggered runs: {first_run['id']}, {second_run['id']}")
print(f"History keys: {sorted(first_page)}")
print(
"History pages: "
f"{first_page['meta']['page']}, {second_page['meta']['page']}"
)
print(f"History total: {first_page['meta']['total_count']}")
finally:
delete_response = client.delete(f"/schedules/{schedule_id}")
if delete_response.status_code not in {204, 404}:
delete_response.raise_for_status()
if __name__ == "__main__":
run_rest_lifecycle()Run the Example
Set up your virtual environment
uv venv --python 3.12
source .venv/bin/activateInstall dependencies
uv pip install -U "agno[os]" "psycopg[binary]" httpx openaiExport your API keys
export OPENAI_API_KEY="your_openai_api_key_here"Clone Agno
Clone the pinned Agno source and run the remaining commands from its root:
git clone https://github.com/agno-agi/agno.git
cd agno
git checkout v3.0.4Start PostgreSQL and AgentOS
Open another terminal in the parent directory where .venv and the cloned agno directory are siblings. Start PostgreSQL, then keep AgentOS running on port 7777:
docker run -d -e POSTGRES_DB=ai -e POSTGRES_USER=ai -e POSTGRES_PASSWORD=ai -e PGDATA=/var/lib/postgresql -v pgvolume:/var/lib/postgresql -p 5532:5432 --name pgvector agnohq/pgvector:18
source .venv/bin/activate
export OPENAI_API_KEY="your_openai_api_key_here"
cd agno
python cookbook/05_agent_os/12_scheduler/01_run_in_agentos.pyRun the REST client
Run the example from the repository root:
python cookbook/05_agent_os/12_scheduler/02_rest_api.pyFull source: cookbook/05_agent_os/12_scheduler/02_rest_api.py