Downcity
ServicesTask Service

Task Definition and Action Contracts

task.md schema, when trigger semantics, and action contracts

Task Definition and Action Contracts

Task definition file

.downcity/task/<taskId>/task.md

taskId is derived from normalized title, and title should stay unique.

Frontmatter fields

FieldRequiredDescription
titleYesUnique task name
descriptionYesGoal description
sessionIdYesSession id that receives the final task result
whenYesTrigger: @manual / cron / time:ISO8601
statusYesenabled / paused / disabled
kindNoagent (default) or script
reviewNoOnly used by kind=agent; enables user-simulator multi-round review when true, otherwise single-round

when semantics

  • @manual: manual trigger only.
  • cron expression: periodic scheduling.
  • time:2026-03-08T10:30:00+08:00: one-shot trigger (timezone required).

Cron expressions are interpreted in the local timezone of the runtime process. If the runtime runs in Asia/Shanghai, 0 13 * * * means 13

Beijing time every day. HTTP proxies do not change this timezone; they can only affect IP-based location detection.

After one-shot execution succeeds:

  • status -> paused
  • when -> @manual

Actions

ActionPurposeKey response
listlist task definitionstasks[]
createcreate task definitionmay return reusedExisting=true
updateupdate definitionwrites task.md
runmanual run oncereturns accepted=true + executionId immediately
statusexplicit status setenabled/paused/disabled
enable/disablestatus shortcutssame as status update
deleteremove definition and run historyreturns task dir path

Create de-dup logic

create now deduplicates by exact title only:

  • same title exists: reuse existing task (reusedExisting=true)
  • otherwise: create a new task

No semantic-similarity dedup is applied.

run contract

run is async-accepted:

  1. verify task exists
  2. start runTaskNow(...) in background
  3. return immediately with accepted=true, message, and executionId

The message explicitly tells the caller:

  • the task has started
  • the task result will be sent to the user automatically when it finishes
  • the agent can continue the next steps immediately
  • there is no need to wait for the task to finish

Automatic delivery after completion:

  • Downcity uses chat service send to deliver the task's final result body back to the chat bound to sessionId.
  • Because of that, task bodies usually should not ask the executor to call city chat send again.
  • Only add explicit send steps for cross-session delivery, extra notifications, or multi-target fanout.

Caller behavior rules:

  • do not manually forward the task output again after accepted=true
  • do not proactively read the run directory, run-progress.json, or output.md to poll
  • only inspect run artifacts when the user explicitly asks for debugging or execution details

Current agent rules:

  • single-round by default
  • multi-round review only when review=true
  • valid output text is enough by default; successful chat_send delivery is no longer required

How to write task bodies

Treat body as an execution spec, not a vague reminder.

# Goal

- State the final deliverable clearly.

# Context and Inputs

- Add sources, scope, constraints, assumptions, and references.

# Steps

1. Understand the completion criteria.
2. Gather information or execute the work.
3. Save key intermediate artifacts into the run directory.
4. Produce the final result body.

# Output Requirements

- Return the final result directly.
- Specify whether it should be Markdown, JSON, a table, etc.
- Avoid long raw logs.
- Do not call `city chat send` again unless extra delivery is required.

# Trigger and Status Guidance

- draft/manual flow: `@manual` + `paused`
- stable recurring flow: cron + `enabled`
- one-shot execution: `time:<timezone-aware ISO datetime>`

# Notes

- Only send extra messages when cross-session or additional notification is truly required.
# Goal: one-line summary
# Inputs: env vars, files, command dependencies
# Failure conditions: when the script should exit 1

set -euo pipefail

# 1. Prepare inputs

# 2. Run main logic

# 3. Print final result (this output is what gets sent back to the user)

Default guidance

  • If the user did not explicitly ask for automatic triggering, prefer @manual + paused.
  • Task bodies should describe what the final result should look like, not just say "go do it".
  • Write key intermediate artifacts into the run directory when possible.

CLI and API

CLI:

city task run daily-check --reason "manual"

API:

city service command task run --payload '{"title":"daily-check","reason":"manual"}'

city task ... now uses local IPC by default, so it does not need a Bearer token for normal local usage. Only explicit --host or --port switches it into remote HTTP mode; in that case, authentication identity stays separate from sessionId:

  • the Bearer Token answers "who is calling"
  • sessionId answers "which execution context this task belongs to"