# Schema v2 Migration Guide ## Field Mapping | v1 | v2 | |----|----| | `command: echo hello` | `run: echo hello` | | multi-line `command: |` | `run: |` | | `script: |` | `run: |` | | `shell`, `shell_args`, `shell_packages` | move under `with:` on the `run:` step | | `exec:` | `action: exec` with `with.command` and `with.args` | | `type: http` | `action: http.request` | | `type: ssh` + `command` | `action: ssh.run` with `with.command` | | `type: postgres` + `command` | `action: postgres.query` with `with.query` | | `type: sqlite` + `command` | `action: sqlite.query` with `with.query` | | SQL import config | `action: postgres.import` or `action: sqlite.import` | | `type: jq` + `command` | `action: jq.filter` with `with.filter` | | `type: docker` | `action: docker.run` | | `type: container` | `action: container.run` | | `type: k8s` or `type: kubernetes` | `action: k8s.run` or `action: kubernetes.run` | | `call:` + `params:` | `action: dag.run` with `with.dag` and `with.params` | | `parallel:` with `call:` | `parallel:` with `action: dag.run` | | `type: router`, `value`, `routes` | `action: router.route` with `with.value` and `with.routes` | | `type: chat`, `messages`, `llm` | `action: chat.completion` with `with.prompt` or `with.messages` plus LLM keys | | `type: harness` + prompt in `command` | `action: harness.run` with `with.prompt` | | `type: template` + `script` | `action: template.render` with `with.template` | | `type: log` | `action: log.write` | | `type: mail` | `action: mail.send` | | `type: archive` | `action: archive.create`, `archive.extract`, or `archive.list` | | `type: s3` | `action: s3.upload`, `s3.download`, `s3.list`, or `s3.delete` | | `type: sftp` | `action: sftp.upload` or `sftp.download` | | `type: redis` | `action: redis.` | | `type: noop` | `action: noop` | | `step_types:` | `actions:` | | `config:` | `with:` | ## Local Command Steps Before: ```yaml steps: - id: test command: go test ./... ``` After: ```yaml steps: - id: test run: go test ./... ``` Multi-line command/script before: ```yaml steps: - id: build script: | set -e npm ci npm test ``` After: ```yaml steps: - id: build run: | set -e npm ci npm test ``` Shell settings before: ```yaml steps: - id: bash command: echo "$SHELL" shell: bash -e shell_args: [-c] ``` After: ```yaml steps: - id: bash run: echo "$SHELL" with: shell: bash -e shell_args: [-c] ``` Only `shell`, `shell_args`, and `shell_packages` are allowed in `with:` for a `run:` step. ## Direct Exec Before: ```yaml steps: - id: direct exec: command: /usr/bin/python3 args: [-u, app.py, --limit, 10] ``` After: ```yaml steps: - id: direct action: exec with: command: /usr/bin/python3 args: [-u, app.py, --limit, 10] ``` ## HTTP Before: ```yaml steps: - id: request type: http with: method: POST url: https://api.example.com/jobs body: '{"mode":"daily"}' ``` After: ```yaml steps: - id: request action: http.request with: method: POST url: https://api.example.com/jobs body: '{"mode":"daily"}' ``` ## SQL Query Before: ```yaml steps: - id: query type: postgres command: SELECT * FROM users with: dsn: ${DATABASE_URL} ``` After: ```yaml steps: - id: query action: postgres.query with: dsn: ${DATABASE_URL} query: SELECT * FROM users ``` For SQLite, use `action: sqlite.query`. ## SSH Before: ```yaml steps: - id: deploy type: ssh command: cd /srv/app && git pull && systemctl restart app with: host: prod.example.com user: deploy ``` After: ```yaml steps: - id: deploy action: ssh.run with: host: prod.example.com user: deploy command: cd /srv/app && git pull && systemctl restart app ``` ## Child DAGs Before: ```yaml steps: - id: child call: workflows/process-account params: ACCOUNT_ID: acct_123 ``` After: ```yaml steps: - id: child action: dag.run with: dag: workflows/process-account params: ACCOUNT_ID: acct_123 ``` Parallel before: ```yaml steps: - id: fanout call: workflows/process-account params: ACCOUNT_ID: ${ITEM.account_id} parallel: items: - account_id: acct_1 - account_id: acct_2 ``` Parallel after: ```yaml steps: - id: fanout action: dag.run with: dag: workflows/process-account params: ACCOUNT_ID: ${ITEM.account_id} parallel: items: - account_id: acct_1 - account_id: acct_2 ``` `parallel:` currently requires `action: dag.run` or `action: dag.enqueue`. ## Router Before: ```yaml steps: - id: route type: router value: ${STATUS} routes: success: [deploy] failed: [notify] ``` After: ```yaml steps: - id: route action: router.route with: value: ${STATUS} routes: success: [deploy] failed: [notify] ``` ## jq Before: ```yaml steps: - id: pick_name type: jq command: .name script: '{"name":"Alice"}' ``` After: ```yaml steps: - id: pick_name action: jq.filter with: filter: .name data: name: Alice ``` Use `with.input` for a file path. Do not set both `with.data` and `with.input`. ## Harness Before: ```yaml steps: - id: review type: harness command: Review the current branch and list actionable issues. with: provider: codex ``` After: ```yaml steps: - id: review action: harness.run with: provider: codex prompt: Review the current branch and list actionable issues. ``` If the v1 harness used `script:` for stdin, use `with.stdin` in v2. ## Chat Chat before: ```yaml steps: - id: summarize type: chat messages: - role: user content: Summarize ${REPORT_PATH} llm: provider: openai model: gpt-4.1 ``` Chat after: ```yaml steps: - id: summarize action: chat.completion with: provider: openai model: gpt-4.1 messages: - role: user content: Summarize ${REPORT_PATH} ``` ## Template Before: ```yaml steps: - id: render type: template with: data: name: Alice script: | Hello, {{ .name }}! ``` After: ```yaml steps: - id: render action: template.render with: data: name: Alice template: | Hello, {{ .name }}! ``` ## `step_types` to `actions` Before: ```yaml step_types: greet: type: command description: Print a greeting input_schema: type: object additionalProperties: false required: [message] properties: message: type: string template: command: echo {{ json .input.message }} steps: - id: say_hello type: greet with: message: hello ``` After: ```yaml actions: greet: description: Print a greeting input_schema: type: object additionalProperties: false required: [message] properties: message: type: string template: run: echo {{ json .input.message }} steps: - id: say_hello action: greet with: message: hello ``` Important differences: - `actions..type` is not supported. - `actions..template` must contain exactly one of `run` or `action`. - Legacy execution fields are rejected inside action templates. - Custom action names may contain dots, such as `slack.notify`. - Custom action names cannot conflict with builtin action names. ## Mixed Syntax to Avoid Do not leave v1 execution fields beside v2 fields: ```yaml steps: - id: bad run: echo hello command: echo legacy ``` ```yaml steps: - id: bad action: http.request type: http with: method: GET url: https://example.com ``` The parser rejects these because `run:` and `action:` cannot be used with legacy execution fields on the same step.