Downcity
CommandsService

Service Scheduling

Common delayed and scheduled execution for service actions

Service Action Scheduling

Downcity now supports one-shot scheduling options for any service action:

  • --delay <ms>: delayed execution
  • --time <time>: scheduled execution

This applies to:

  • city chat send
  • city memory search
  • city service command <service> <action>
  • and the dedicated action API /service/<service>/<action>

The goal is simple: if you only need to run one existing action later, you do not need to create a task.

Good Fits

Use this when you want to:

  • send a chat message 10 minutes later
  • run a memory flush at 21
  • execute one service action tomorrow morning

Do not use it for:

  • recurring schedules
  • cron
  • longer workflows
  • long-lived jobs that need richer task artifacts

For those, use city task.

Two Entry Points

There are now two user-facing entry points, and both go through the same persistent scheduler:

  1. CLI action commands
city chat send --chat-key ctx_xxx --text "remind me tonight" --time "2026-03-25T21:00:00+08:00"
city memory flush --session-id telegram-chat-123 --delay 600000
  1. HTTP action API
POST /service/chat/send
Content-Type: application/json

{
  "chatKey": "ctx_xxx",
  "text": "remind me tonight",
  "time": "2026-03-25T21:00:00+08:00"
}

No matter which entry you use, Downcity writes the scheduled job first and executes it later.

Parameter Formats

--delay <ms>

Use a non-negative integer in milliseconds.

Example:

city chat send --chat-key ctx_xxx --text "send in 10 seconds" --delay 10000

--time <time>

Supported formats:

  • Unix timestamp in seconds or milliseconds
  • timezone-aware ISO datetime

Examples:

city chat send --chat-key ctx_xxx --text "send at 9pm" --time "2026-03-25T21:00:00+08:00"
city memory flush --session-id telegram-chat-123 --time 1774443600

Notes:

  • ISO datetime must include a timezone offset such as +08:00 or Z
  • --delay and --time cannot be used together

Persistence Location

Scheduled jobs are stored in the current agent project at:

.downcity/schedule.sqlite

This means:

  • the runtime can return immediately after accepting the request
  • unfinished jobs survive runtime restart
  • the next runtime start restores and continues them

Runtime Behavior

A scheduled service action moves through these states:

  • pending: accepted and waiting
  • running: picked up by the scheduler and currently executing
  • succeeded: execution finished successfully
  • failed: execution finished with an error
  • cancelled: cancelled and no longer executed

Restart recovery rules:

  • if a job was still pending, it will be resumed later or executed immediately if overdue
  • if a job was left in running, startup resets it back to pending and retries it

Return Shape

When a command is accepted as a scheduled job, the response looks like this:

{
  "success": true,
  "data": {
    "scheduled": true,
    "jobId": "sched_xxxxxxxxxxxxxxxx",
    "runAtMs": 1774443600000,
    "status": "pending"
  }
}

This means:

  • the action was not executed immediately
  • a persistent scheduled job was created
  • jobId is the unique identifier for that scheduled record

Examples

1. Delay a chat send

city chat send \
  --chat-key ctx_xxx \
  --text "remind me in 10 minutes" \
  --delay 600000

2. Schedule a memory flush

city memory flush \
  --session-id telegram-chat-123 \
  --time "2026-03-25T23:00:00+08:00"

3. Schedule through the generic command bridge

city service command chat send \
  --payload '{"chatKey":"ctx_xxx","text":"send later"}' \
  --time "2026-03-25T21:00:00+08:00"

4. Schedule through the HTTP action API

curl -X POST http://127.0.0.1:5314/service/chat/send \
  -H 'content-type: application/json' \
  -d '{
    "chatKey": "ctx_xxx",
    "text": "send later",
    "time": "2026-03-25T21:00:00+08:00"
  }'

Difference From task

Think of the boundary like this:

  • service action scheduling: add one-shot delay/time to an existing action
  • task: define a reusable long-lived automation task

If your need is “run this existing action once, later”, use this scheduling feature.
If your need is “define an ongoing automation job”, use city task.

Inspection And Management Commands

Besides creating scheduled jobs, you can now inspect and manage the local schedule store directly.

Use:

city service schedule <subcommand>

Currently supported:

  • list
  • info <jobId>
  • cancel <jobId>

These commands read the current agent project's .downcity/schedule.sqlite directly, so they do not require the runtime to be currently online.

list

List scheduled jobs.

Key options:

  • --status <pending|running|succeeded|failed|cancelled>: filter by status
  • --limit <n>: limit result size
  • --path <path>: target agent project path
  • --agent <name>: resolve agent via console registry

Examples:

city service schedule list --path /abs/path/to/agent
city service schedule list --status pending --limit 20 --path /abs/path/to/agent

info <jobId>

Show one scheduled job in detail.

Returned fields include:

  • service/action
  • payload
  • runAtMs
  • status
  • error (when present)

Example:

city service schedule info sched_xxxxxxxxxxxxxxxx --path /abs/path/to/agent

cancel <jobId>

Cancel a job that has not started yet.

Rules:

  • only pending jobs can be cancelled
  • running / succeeded / failed / cancelled jobs are not cancelled again

Example:

city service schedule cancel sched_xxxxxxxxxxxxxxxx --path /abs/path/to/agent

Troubleshooting Flow

If one delayed or scheduled action did not behave as expected, a simple flow is:

  1. Check the lists
city service schedule list --status pending --path /abs/path/to/agent
city service schedule list --status failed --path /abs/path/to/agent
  1. Inspect one job
city service schedule info <jobId> --path /abs/path/to/agent
  1. Cancel a pending job if you want to stop it before execution
city service schedule cancel <jobId> --path /abs/path/to/agent