# Subflow & Schedule Nodes

> Applies to: ChengOS v0.2.0+ ｜ Last verified: 2026-09-25 ｜ Source: `crates/cheng-nodes/src/nodes/builtin/tools`

`tools/subflow` calls another workflow once and waits for its result — it is how a large graph is kept
readable and how one piece of logic gets reused. `tools/batch_subflow` is the same call fanned out over
a collection, which is the right tool when the item count is data-driven rather than drawn.
`tools/schedule` manages timed tasks: create, pause, resume, cancel, and read back the result of a run
that already happened.

## Sub Workflow Tool — `tools/subflow`

Call other workflows as sub-workflows (supports file path or workflow ID)

### Inputs

| Field | Type | Required | Default | Description |
|---|---|:-:|---|---|
| `workflow_path` | string | ✅ | — | Workflow file path (JSON format) or workflow UUID |
| `async_execution` | boolean | — | `false` | Whether to execute asynchronously; true returns immediately after start, false waits for completion. Control: `switch` |
| `input_data` | any | — | `null` | Input data passed to sub-workflow (optional). Put follow-up `feedback` inside this object, not at the top level. |
| `child_context` | any | — | `null` | Previous child workflow LLM context returned by an earlier subflow call. Overrides input_data.context when both are set. |
| `agent_context` | object (port) | — | `null` | Upstream AgentContext (typically connected from a routing node). Serialized to child workflow's `context` input. Priority: child_context > agent_context > input_data.context. Connection port, not a form field. |
| `conversation_id` | string | — | `null` | Ambient parent conversation id passed through to the child workflow |
| `child_session_id` | string | — | `null` | Optional stable child-session id. Copied into child execution metadata and diagnostics. |
| `response_path` | string | — | `null` | Optional dot-path in the combined result used to extract `response` before heuristic extraction. |
| `context_path` | string | — | `null` | Optional dot-path in the combined result used to extract `child_context` before heuristic extraction. |
| `return_child_context` | boolean | — | `true` | Whether to return the child workflow's updated context in agent_context. Control: `switch` |
| `return_node_outputs` | boolean | — | `true` | Whether to include child node outputs in result.outputs. Control: `switch` |
| `child_context_max_bytes` | integer | — | `100000` | Max serialized child context size (bytes) returned inline. Default 100_000. Oversized contexts are summarized. Control: `number` |
| `call_stack` | array | — | `null` | Call stack for detecting recursive calls (internal use) |
| `timeout_seconds` | integer | — | `300` | Timeout in seconds. Control: `number` |
| `max_parallelism` | integer | — | `10` | Maximum number of parallel nodes. Control: `number` |

### Outputs

| Field | Type | Description |
|---|---|---|
| `status` | string | Sub-workflow execution status |
| `agent_context` | object (port) | Sub-workflow output data as AgentContext. Connection port, not a form field. |
| `elapsed_ms` | integer | Execution time in milliseconds |
| `error` | string | Error message (if execution failed) |
| `summary` | string | Execution result summary |

## Batch Sub Workflow Tool — `tools/batch_subflow`

Run multiple sub-workflows concurrently with bounded parallelism and aggregated results

### Inputs

| Field | Type | Required | Default | Description |
|---|---|:-:|---|---|
| `items` | array&lt;object&gt; | ✅ | — | Sub-workflow invocations to run concurrently (results returned in input order) |
| `max_concurrency` | integer | — | `4` | Batch-level concurrency (concurrent in-flight sub-workflows). Distinct from per-subflow max_parallelism. Control: `number` |
| `timeout_seconds` | integer | — | `300` | Per-item sub-workflow timeout in seconds. Control: `number` |
| `max_parallelism` | integer | — | `10` | Maximum parallel nodes inside each sub-workflow (NOT batch concurrency). Control: `number` |
| `return_child_context` | boolean | — | `true` | Whether each item should return the updated child context. Control: `switch` |
| `return_node_outputs` | boolean | — | `true` | Whether to include child node outputs in result.outputs. Control: `switch` |
| `child_context_max_bytes` | integer | — | `100000` | Per-item max serialized child_context bytes returned inline. Control: `number` |
| `call_stack` | array | — | `null` | Inherited call stack used for recursion detection (internal use) |

### Outputs

| Field | Type | Description |
|---|---|---|
| `status` | string | 汇总状态：completed / partial_failed / failed |
| `results` | array&lt;object&gt; | Results |
| `total` | integer | Total |
| `succeeded` | integer | Succeeded |
| `failed` | integer | Failed |
| `elapsed_ms` | integer | 批层墙钟总耗时（毫秒） |
| `summary` | string | Summary |

## Schedule Manager — `tools/schedule`

Directly list, create, update, pause, resume, cancel, and inspect scheduled tasks

### Inputs

| Field | Type | Required | Default | Description |
|---|---|:-:|---|---|
| `mode` | enum | — | `manual` | Execution mode: Manual=user fills parameters, Llm=LLM auto-fills parameters. Control: `select`. Options: `manual`, `llm` |
| `action` | string | — | `""` | Action type: list_tasks / get_task / get_execution_result / create_task / update_task / pause_task / resume_task / cancel_task / delete_task. Tip: when using create_task, always set a descriptive `label` so the task list is readable at a glance instead of showing only IDs. Control: `select`. Options: `list_tasks` (List Tasks), `get_task` (Get Task), `get_execution_result` (Get Execution Result), `create_task` (Create Task), `update_task` (Update Task), `pause_task` (Pause Task), `resume_task` (Resume Task), `cancel_task` (Cancel Task), `delete_task` (Delete Task) |
| `id` | string | — | `null` | Numeric scheduled task ID |
| `workflow_id` | string | — | `null` | Workflow UUID |
| `status` | string | — | `null` | Filter status for list_tasks: pending, running, completed, failed, cancelled, paused |
| `limit` | integer | — | `50` | Max return count (default 50, max 500) |
| `offset` | integer | — | `0` | Pagination offset (default 0) |
| `run_at` | string | — | `null` | The schedule expression. Accepts THREE formats: (1) a relative delay to run once after a wait — '2m', '30m', '1h', '1d'; (2) a cron expression for a recurring schedule — '0 9 * * *' (daily 09:00), '0 9 * * 1' (Mondays 09:00), '0 9 * * 0,6' (weekends 09:00); (3) a LOCAL wall-clock datetime to run once at a specific time — '2026-06-30T09:00' or '2026-06-30T09:00:00' (no timezone/offset; the node applies the server timezone itself). For a one-off datetime this MUST be in the future — do not guess the current date; any validation error states the current server time, so read it and correct run_at. Leave `timezone` empty to use the server's system timezone (recommended). |
| `frequency` | string | — | `null` | Frequency: once, every_minute, hourly, daily, weekly, monthly, custom |
| `custom_cron` | string | — | `null` | Custom cron expression |
| `timezone` | string | — | `null` | IANA timezone for interpreting run_at. Leave empty to use the server's system timezone (recommended). |
| `task_text` | string | — | `null` | The workflow INPUT for the scheduled run, as plain text. For a chat/input-fronted workflow this becomes its `raw_input`. Put the actual content to run here (or use `params`). Do NOT put it in `label`. |
| `target_node_id` | string | — | `null` | Node ID for raw_input routing |
| `params` | any | — | `null` | Explicit workflow input parameters object, e.g. {"raw_input": "hello"}. This is exactly what the workflow's entry node receives. Use this OR `task_text` to provide the input — not `label`. |
| `priority` | integer | — | `null` | Execution priority (default 0) |
| `max_retries` | integer | — | `null` | Maximum retries (default 3) |
| `label` | string | — | `null` | Strongly recommended: a short descriptive NAME for the task, shown in lists and logs so you can identify tasks at a glance without querying each one by ID. Always set a label when creating tasks. Good labels: '每日新闻摘要', '数据备份-周一', '天气查询测试'. Bad labels: '任务1', 'test', '123'. Keep it under 20 characters and describe the task's core purpose. This is NOT the workflow input — never put the task content here; use `task_text` or `params`. |
| `agent_prompt` | string | — | `null` | Schedule an AGENT TURN in the workspace this run is already using, instead of a bare workflow run. Use it to give your future self work: 'check whether the build passed and summarize the failures'. The machine and directory are taken from the current execution and cannot be chosen here — a scheduled turn always lands in the workspace it was created from. Requires the current run to be client-local (a `cheng` session on the user's own machine). |
| `agent_unattended` | string | — | `null` | What the scheduled turn does when it needs an approval: fail_fast (default, abort with a reason) or wait (park until a person answers or it times out). |
| `agent_on_busy` | string | — | `null` | What to do when the workspace is already running a scheduled turn: skip (default), queue, or parallel. |

### Outputs

| Field | Type | Description |
|---|---|---|
| `status` | string | Operation status: ok / not_found / error / llm_config |
| `data` | any | Query result or action result |
| `count` | integer | Current page result count |
| `total` | integer | Total count before pagination |
| `error` | string | Error message |

## Next steps

- [Subflows & Batch Subflows](/docs/editor-subflows)
- [Scheduling & Triggers](/docs/exec-scheduling-triggers)
- [Workflow Introspection Nodes](/docs/nodes-workflow-inspect)
