# pm_automation
{{< tier level="sovereign" note="Use agents and workflows to configure schedules, manage automation state, and inspect execution history." >}}

Use `pm_automation` to make recurring work repeatable from configuration. An agent can validate a declaration, preview its schedule, apply it, and inspect the resulting activity without recreating the rule by hand.

This reference covers declarations, MCP calls, update guards, checkpoint state, and recovery. Start with [Automations](/valdr/docs/workflows/automations/) for UI instructions and complete YAML examples.

## Call shape and permissions

Every call uses **`action` plus a nested `params` object**:

```text
pm_automation {
  action: "get",
  params: { key: "local.hourly-summary" }
}
```

Discover the tool through your connected MCP client's tool list and input schema. `pm_automation` does not accept a `help` action. Your session must allow the automation actions it needs. A workflow or tool launched by a rule also needs its normal execution prerequisites.

Successful calls return `action`, `ok: true`, and `data`. For `validate` and `preview`, also check **`data.valid`**: a completed check can return `ok: true` with `valid: false` and `diagnostics`. Domain failures return `ok: false` with `error.code`, `error.message`, and optional `error.details`. Invalid call shapes can instead arrive as an MCP error.

## Actions

| Action | Required `params` | Optional `params` | Result or effect |
| --- | --- | --- | --- |
| `validate` | `definition` **or** `definitionYaml` | — | Check the declaration and current target selection without saving or execution |
| `preview` | `definition` **or** `definitionYaml` | `count`, `sampleEvent` | Preview scheduled times or mapped event inputs without execution |
| `apply` | `definition` **or** `definitionYaml`, `clientRequestId` | `expectedActivationId`, `expectedContentHash`, `recreate` | Create or conditionally update a local rule |
| `get` | `key` | — | Read the declaration, target, current status, and next due time |
| `list` | — | `status`, `cursor`, `limit` | List rules and whether the automation engine is enabled |
| `pause` | `key`, `expectedActivationId`, `clientRequestId` | — | Stop new executions |
| `resume` | `key`, `expectedActivationId`, `clientRequestId` | `acknowledgeOccurrenceId` | Resume; optionally acknowledge a reconciled tool outcome |
| `remove` | `key`, `expectedActivationId`, `clientRequestId` | — | Remove a local rule without cancelling already-started work |
| `get_state` | `key`, `expectedActivationId` | — | Read checkpoint `state` and `stateVersion` |
| `checkpoint` | `key`, `expectedActivationId`, `expectedStateVersion`, `state`, `clientRequestId` | — | Replace checkpoint state |
| `occurrences` | `key` | `occurrenceId`, `cursor`, `limit` | Read execution history; select one occurrence for detailed tool diagnostics |

For paginated actions, `limit` defaults to 25 and accepts 1–100. Pass the returned `nextCursor` unchanged for the next page. `list.status` accepts `active`, `paused`, `removed`, `source_disabled`, or `dependency_blocked`.

## Declaration fields

Supply either a JSON object in `definition` or a YAML string in `definitionYaml`, never both. Unknown fields are rejected.

| Field | Contract |
| --- | --- |
| `schemaVersion` | `"1.0"` |
| `automation.key` | A stable, unique key of at most 200 characters. Start with a lowercase letter or number; subsequent characters can also include `.`, `_`, `:`, or `-`. |
| `automation.trigger` | One trigger from the table below |
| `automation.action` | One workflow or user-tool action |
| `automation.policy.overlap` | `skip` or `queue`; schedules default to `skip`, events to `queue` |
| `automation.policy.misfire` | `once` or `skip`; schedules default to `once`; omit for events |
| `automation.resource` | Optional `{ type, key }` strings identifying the resource your rule concerns |

The optional resource is descriptive context for your automation. It is not an event filter or an action input.

| `trigger.type` | Fields |
| --- | --- |
| `interval` | `every`: a positive whole number followed by `m`, `h`, or `d`, from `1m` through `365d` |
| `cron` | `expression`: five numeric cron fields; `timezone`: an explicit IANA timezone |
| `event` | `event: task.created`; optional `match` with exact string filters |

Event filters and mappings can use `taskId`, `taskKey`, `projectId`, `projectKey`, and `status`. All supplied filters must match.

| `action.type` | Fields |
| --- | --- |
| `workflow.start` | `workflow: { key, version? }`; optional `inputs` and `eventInputs` |
| `user_tool.run` | `toolId`, `toolAction`, and absolute host `cwd`; optional `revision`, `timeoutSeconds`, `inputs`, and `eventInputs` |

`inputs` is a JSON object and defaults to `{}`. Values are literal; automation declarations do not evaluate workflow reference strings. For event triggers, `eventInputs` maps a target input name to an event field. The destination must accept a string, and cannot also be supplied in `inputs`.

Omitting a workflow version follows the highest active release. Omitting a tool revision follows the most recently installed visible revision. Use an explicit version or revision when you want to review target changes before adopting them.

For tools, `timeoutSeconds` accepts 1–300 and can shorten the configured execution limit. It cannot extend the tool's allowed limit. Use [`pm_user_tool describe`](/valdr/docs/valdr-mcp/user-tools/#find-and-inspect-an-action) to inspect the action before scheduling it.

Declarations and input objects are limited to 256 KiB of JSON; checkpoint state is limited to 64 KiB.

## Validate and preview

This example requires the installed Node text summary from the [starter pack](/valdr/docs/workflows/user-tools/starters/). Replace `cwd` with an existing absolute directory.

```text
pm_automation {
  action: "validate",
  params: {
    definition: {
      schemaVersion: "1.0",
      automation: {
        key: "local.hourly-summary",
        trigger: { type: "interval", every: "1h" },
        action: {
          type: "user_tool.run",
          toolId: "valdr-tools.user.node",
          toolAction: "summarize",
          cwd: "/absolute/path/to/your/project",
          inputs: { text: "Scheduled text summary" }
        }
      }
    }
  }
}
```

Validation checks the currently installed target and supplied inputs. When `data.valid` is `true`, validation returns the normalized `data.definition`; preview also returns the selected `data.target`. Read `data.diagnostics` when validation fails. A valid declaration does not prove future credentials, network access, or execution will succeed.

Call `preview` with the same declaration. For a schedule, `count` selects 1–20 upcoming times and defaults to 5; inspect `data.upcoming`, whose entries include `scheduledAtMs`.

For an event declaration, supply `sampleEvent` containing the fields needed by its mappings:

```text
pm_automation {
  action: "preview",
  params: {
    definitionYaml: "<complete event automation YAML>",
    sampleEvent: {
      taskKey: "EXAMPLE-42",
      projectKey: "example",
      status: "open"
    }
  }
}
```

Inspect `data.resolvedInputs`. This previews mapped inputs without creating a task or running the action. It does not check whether the sample matches your event filters; compare those values separately. Replace the YAML placeholder with your complete declaration.

## Apply and update a rule

1. Generate a fresh ULID with `pm_generate_ulid {}`.
2. Keep that ID with the exact declaration you intend to apply.
3. Call `apply` with the declaration and `clientRequestId`.

```text
pm_automation {
  action: "apply",
  params: {
    definitionYaml: "<complete automation YAML>",
    clientRequestId: "<fresh ULID>"
  }
}
```

**A newly applied automation is enabled.** Validate and preview first. The acknowledgement includes `key`, `activationId`, `revisionId`, `contentHash`, `stateVersion`, `effectiveStatus`, and `changed`.

Before updating an existing local rule, call `get`. Supply its current `activationId` as `expectedActivationId` and its current `contentHash` as `expectedContentHash`, alongside the replacement declaration and a fresh request ID. These values keep an update from silently overwriting a rule that changed since you read it.

Changing the declaration preserves the operator pause and checkpoint state. A changed trigger resets its cadence; input-only changes do not.

For every mutation, retry a lost response with the **same request ID and same arguments**. Do not reuse an ID for a different operation, or generate a new ID merely to bypass a conflict.

## Read activity and pause execution

```text
pm_automation { action: "list", params: { status: "active", limit: 25 } }
pm_automation { action: "get", params: { key: "local.hourly-summary" } }
pm_automation { action: "occurrences", params: { key: "local.hourly-summary", limit: 25 } }
```

`get` includes `effectiveStatus`, the declaration, selected `target`, `nextDueAtMs`, `engineEnabled`, and any `attention` requiring reconciliation. Times ending in `Ms` are Unix timestamps in milliseconds.

Each occurrence is one scheduled or event-triggered execution. History identifies its status, selected target, summary, and any workflow run. Pass `occurrenceId` to inspect one direct tool's available execution diagnostics.

Use the `activationId` from `get` when changing its status:

```text
pm_automation {
  action: "pause",
  params: {
    key: "local.hourly-summary",
    expectedActivationId: "<activationId from get>",
    clientRequestId: "<fresh ULID>"
  }
}
```

Use `resume` with the same field names and a new request ID for a deliberate resume. Pausing does not cancel started work. See [overlap and missed schedules](/valdr/docs/workflows/automations/#control-overlap-and-missed-schedules) before resuming.

`remove` uses the same required fields and is available for local rules. To recreate a removed local rule, use `apply` with `recreate: true`, its old `expectedActivationId`, a complete declaration, and a fresh request ID. Recreation starts enabled with a new activation and empty checkpoint state.

For pack-managed rules, change or withdraw the declaration in its owning pack and reimport it. Local `apply` and `remove` reject pack ownership conflicts.

## Keep state between executions

Use checkpoint state for a small cursor or the last item your automation processed. Call `get_state` with `key` and `expectedActivationId`, then replace the state with `checkpoint`:

```text
pm_automation {
  action: "checkpoint",
  params: {
    key: "local.hourly-summary",
    expectedActivationId: "<activationId from get>",
    expectedStateVersion: 0,
    state: { lastProcessedKey: "EXAMPLE-42" },
    clientRequestId: "<fresh ULID>"
  }
}
```

Replace `0` with the `stateVersion` returned by `get_state`. A checkpoint replaces the entire JSON value. On `stale_state_version`, read the latest state and recompute your update.

Workflow **Valdr Tool** steps can call `pm_automation` too. Set `tool.id: pm_automation`, select the action in `tool.action`, and place its parameters under `inputs.params`. This lets a setup workflow create a rule or a recurring workflow record progress and pause itself.

Generate the mutation ID in a preceding `pm_generate_ulid` step and capture `$.raw.ulid` as a named output. Pass that output as `clientRequestId`; `${runtime.clientRequestId}` is not a ULID. Keep the generated ID stable when retrying the same operation.

To find the rule that launched the current workflow, call `pm_workflow get_run` with `${runtime.runUlid}`. Its `run.automationOrigin` supplies `automationKey` and `activationId`. Use those values for `get_state`, then use the returned state version for `checkpoint`. In workflow output mappings, `pm_automation` data is available directly under `$.raw`, such as `$.raw.stateVersion` and `$.raw.state`. See [Valdr Tool steps](/valdr/docs/workflows/steps/tool/) for output mapping syntax.

## Recover a user-tool automation

Some tool failures pause the automation because execution may already have affected an external system. Read `get.attention`, then inspect the identified occurrence. Check the tool's result and external effects before resuming.

After reconciliation, call `resume` with `acknowledgeOccurrenceId` set to that occurrence:

```text
pm_automation {
  action: "resume",
  params: {
    key: "local.hourly-summary",
    expectedActivationId: "<activationId from get>",
    acknowledgeOccurrenceId: "<attention.occurrenceId from get>",
    clientRequestId: "<fresh ULID>"
  }
}
```

Acknowledging confirms that you have checked the outcome; it does not undo external effects or rerun that occurrence. After successful acknowledgement, the occurrence can still show `outcome_unknown` in its history. That retained result does not itself block future executions; check the resume response and the rule's current status.

If resume returns `invocation_in_flight`, the tool is still executing. Wait for it to finish before acknowledging its outcome. For another rejected resume, resolve the reported error before trying again.

| Error | Recovery |
| --- | --- |
| `invalid_definition`, `invalid_schedule`, `invalid_event_mapping` | Correct the declaration or mapping and validate again. |
| `dependency_unavailable` | Restore the selected target, required inputs, runtime, or working directory; inspect the message for the failed prerequisite. |
| `source_conflict` | Use a unique local key, or edit and reimport the owning pack. |
| `stale_activation`, `stale_content_hash`, `stale_state_version` | Read the current rule or state and reconsider the change before sending a new operation. |
| `request_conflict` | Recover the original arguments for that request ID. Use a fresh ID only for an intentionally different operation. |

## Next step

Ask your agent to prepare a rule from a workflow or tool you already trust:

{{< prompts >}}
Find my installed reporting workflow, inspect its inputs, and draft a weekday automation for 09:00 in my timezone. Validate and preview it, then show me the declaration before applying it.
{{< /prompts >}}

