A long-running A2A agent that fires other agents on a schedule.
506
A long-running A2A agent that fires other agents on a schedule. Supports two trigger types:
{"triggered": true}Accepts schedule management commands as either natural language (parsed by an LLM) or structured JSON. Persists all schedules and run history in SQLite.
message/stream when a trigger fires.Register: "register the terminal agent at https://terminal:443/"
→ stored in agents table
Create: "Run the ETH price report every weekday at 9am New York time"
→ LLM parses → cron "0 9 * * 1-5" tz "America/New_York"
→ stored in schedules table
Loop tick (every 10s):
cron: now >= next_run_at → dispatch to target agent
sensor: poll_interval elapsed → call sensor agent
→ if {"triggered": true} → dispatch
app/
models/schedule.py Data models: ScheduleEntry, Trigger, AgentRegistration, ParsedScheduleRequest
store/
orm.py SQLAlchemy ORM (ScheduleRow, RunHistoryRow, AgentRow)
base.py Abstract ScheduleStore interface
sqlite.py SQLiteScheduleStore (aiosqlite)
scheduler/
loop.py SchedulerLoop — async background tick
dispatcher.py SSE streaming dispatch to target A2A agent
sensor.py query_sensor() — poll sensor agent, parse result
llm/
parser.py parse_schedule_request() — NL → ParsedScheduleRequest via LLM
server/
config.py Settings (pydantic-settings)
a2a.py ScheduleAgentExecutor — JSON + NL command routing
main.py FastAPI app with lifespan (store, LLM client, scheduler loop)
__main__.py Entry: python3 -m app.server
All settings are read from environment variables (or a .env file).
| Variable | Default | Description |
|---|---|---|
API_KEY | `` | Bearer token for A2A endpoint auth (empty = no auth) |
PORT | 8081 | HTTPS listen port |
DB_URL | sqlite+aiosqlite:///./schedule.db | SQLite database path |
TICK_INTERVAL | 10 | Scheduler loop interval in seconds |
LLM_URL | https://api.openai.com/v1 | LLM base URL for NL parsing |
LLM_API_KEY | `` | LLM API key |
LLM_MODEL | gpt-4o | Model name for NL parsing |
LLM_TIMEOUT | 30 | LLM call timeout in seconds |
docker build -t schedule-agent \
--build-context root=../.. \
-f Dockerfile ../..
docker run -d \
-p 8081:8081 \
-e LLM_API_KEY=sk-... \
-e API_KEY=mysecret \
-v schedule-data:/app \
schedule-agent
pip install -e .
LLM_API_KEY=sk-... python3 -m app.server
The schedule agent exposes a standard A2A endpoint at https://<host>:8081/.
Input can be natural language (any plain text) or a JSON command object.
Register the terminal agent at https://terminal-agent:443/ with api key secret123
Run the ETH price report every weekday at 9am New York time using the terminal agent
Watch ETH price every 2 minutes using terminal agent, fire a buy order when below $2000
Show my schedules
Disable the ETH price report
Run the price report right now
Delete schedule sched-abc123
When a required field cannot be determined, the agent replies with a clarification question instead of failing.
All JSON commands have a "command" key. Useful for programmatic / orchestrator use.
{"command": "register_agent", "name": "terminal", "url": "https://terminal-agent:443/", "api_key": "secret", "description": "runs shell commands on Linux"}
{"command": "unregister_agent", "name": "terminal"}
{"command": "list_agents"}
{
"command": "create",
"name": "Daily ETH report",
"trigger": {
"type": "cron",
"cron": {"expression": "0 9 * * 1-5", "timezone": "America/New_York"}
},
"target": {
"url": "https://terminal-agent:443/",
"api_key": "secret",
"task": "Fetch ETH price from CoinGecko and write a report to /workspace/report.md",
"timeout": 300
}
}
{
"command": "create",
"name": "Buy ETH on dip",
"trigger": {
"type": "sensor",
"sensor": {
"agent_url": "https://terminal-agent:443/",
"agent_api_key": "secret",
"task": "Check ETH price via CoinGecko. Return JSON: {\"triggered\": true, \"value\": <price>} if price < 2000, else {\"triggered\": false}",
"poll_interval": 120
}
},
"target": {
"url": "https://terminal-agent:443/",
"api_key": "secret",
"task": "Place a market buy order for 0.1 ETH",
"timeout": 60
}
}
{"command": "list"}
{"command": "get", "id": "sched-abc123"}
{"command": "update", "id": "sched-abc123", "name": "New name"}
{"command": "enable", "id": "sched-abc123"}
{"command": "disable", "id": "sched-abc123"}
{"command": "delete", "id": "sched-abc123"}
{"command": "run_now", "id": "sched-abc123"}
{"command": "list_history", "id": "sched-abc123", "limit": 20}
Any A2A agent can act as a sensor. It receives the configured task text and must return a JSON artifact:
{"triggered": true, "value": 1850.5}
or
{"triggered": false}
The value field is optional and is stored in the run history for reference.
| Expression | Meaning |
|---|---|
0 9 * * 1-5 | Every weekday at 09:00 |
0 */6 * * * | Every 6 hours |
30 8 * * 1 | Every Monday at 08:30 |
0 0 1 * * | First day of every month at midnight |
* * * * * | Every minute (useful for testing) |
Timezones use standard IANA names: UTC, America/New_York, Europe/London, Asia/Tokyo, etc.
The agent image exposes an MCP (Model Context Protocol) server on the same port as A2A at /mcp. Any MCP client — Claude Desktop, Cursor, Windsurf, ChatGPT Connectors, OpenAI Agents SDK — can list and invoke these tools using the pod's API_KEY as Bearer.
In production, Core proxies https://schedule-agent.agents.forfetch.ai/mcp → the worker pod's /mcp.
| Tool (skill_id) | Description |
|---|---|
schedule | Schedule tasks using cron expressions or sensor triggers |
Input schemas are authored in apps/agents_mcp/app/seed.py. Skills without an explicit schema advertise a single {task: str} freeform parameter.
Content type
Image
Digest
sha256:fb141a510…
Size
89.6 MB
Last updated
5 months ago
docker pull superbizon007/schedule-agent