---
name: rules-rule-obj-activity
description: Authoring guide for Pega activity rules (Rule-Obj-Activity), including step methods, parameters, pages, and examples
---

**Prerequisite:** Load `methodology-rule-authoring` first

## Authoring Notes

### `pyActivityName` is the class key

`pyActivityName` (with `pyClassName`) is the identity key (from
`pyKeyDefList`) and the value the agent must supply.

### Queue-For-Processing: dispatch to a named Queue Processor

To call an existing Queue Processor rule from an Activity step, use the
`Queue-For-Processing` method with `QueueName` set to the Queue Processor rule
name and `QueueType: DEDICATED`. Load the **Queue-For-Processing Dispatch**
step skill for the parameter shape.

## Examples

### Rule-level

| Skill | Label | Description |
|---|---|---|
| `rules-rule-obj-activity/examples/stub` | Stub Activity | Minimal activity with a single placeholder step — smallest valid create payload. **Must include at least one step** (omitting `pySteps` causes auto-fill of an invalid empty step). |
| `rules-rule-obj-activity/examples/log-set-save` | Set Approval Status | Log-Message + Property-Set + Obj-Save |
| `rules-rule-obj-activity/examples/browse-and-return` | Get Recent Reports | Page-New + Obj-Browse + Show-Page with pyPagesAndClasses |
| `rules-rule-obj-activity/examples/java-parameter-io` | Calculate Compound Interest | Single Java step with pyParameters (IN/OUT) and ParameterPage I/O |
| `rules-rule-obj-activity/examples/data-page-orchestration` | Data Page Orchestration Activity | Sequential clipboard page operations with error checking, ERR block, Page-New/Page-Remove pairing, and fallback |
| `rules-rule-obj-activity/examples/chained-data-page-integration` | Chained Data Page Integration | Multi-API chaining with parameterized data pages (`D_Name[Param: value]`), early exit, and `pyOnException` fallback |
| `rules-rule-obj-activity/examples/enqueue-and-dispatch` | Enqueue and Dispatch | Job Scheduler dispatch activity — Obj-Browse + EMBEDDED loop + Obj-Save + Queue-For-Processing |

### Step-level skills

Step examples are organized by category. Each skill contains frontmatter + one JSON example.

#### Obj-* and Link methods
`rules-rule-obj-activity/examples/pySteps/obj-browse`, `rules-rule-obj-activity/examples/pySteps/obj-delete`, `rules-rule-obj-activity/examples/pySteps/obj-delete-by-handle`, `rules-rule-obj-activity/examples/pySteps/obj-filter`, `rules-rule-obj-activity/examples/pySteps/obj-open`, `rules-rule-obj-activity/examples/pySteps/obj-open-by-handle`, `rules-rule-obj-activity/examples/pySteps/obj-refresh-and-lock`, `rules-rule-obj-activity/examples/pySteps/obj-save`, `rules-rule-obj-activity/examples/pySteps/obj-save-cancel`, `rules-rule-obj-activity/examples/pySteps/obj-sort`, `rules-rule-obj-activity/examples/pySteps/obj-validate`, `rules-rule-obj-activity/examples/pySteps/link-objects`

#### Page-* methods
`rules-rule-obj-activity/examples/pySteps/page-change-class`, `rules-rule-obj-activity/examples/pySteps/page-clear-messages`, `rules-rule-obj-activity/examples/pySteps/page-copy`, `rules-rule-obj-activity/examples/pySteps/page-copy-data-page`, `rules-rule-obj-activity/examples/pySteps/page-merge-into`, `rules-rule-obj-activity/examples/pySteps/page-new`, `rules-rule-obj-activity/examples/pySteps/page-remove`, `rules-rule-obj-activity/examples/pySteps/page-rename`, `rules-rule-obj-activity/examples/pySteps/page-set-messages`, `rules-rule-obj-activity/examples/pySteps/page-unlock`, `rules-rule-obj-activity/examples/pySteps/page-validate`

#### Property-* methods
`rules-rule-obj-activity/examples/pySteps/property-map-decision-table`, `rules-rule-obj-activity/examples/pySteps/property-map-decision-tree`, `rules-rule-obj-activity/examples/pySteps/property-map-value`, `rules-rule-obj-activity/examples/pySteps/property-remove`, `rules-rule-obj-activity/examples/pySteps/property-set`, `rules-rule-obj-activity/examples/pySteps/property-set-from-data-page`, `rules-rule-obj-activity/examples/pySteps/property-set-messages`, `rules-rule-obj-activity/examples/pySteps/property-set-special`, `rules-rule-obj-activity/examples/pySteps/property-set-xml`, `rules-rule-obj-activity/examples/pySteps/property-validate`

#### Activity flow, Call, Branch, Exit
`rules-rule-obj-activity/examples/pySteps/activity-clear-status`, `rules-rule-obj-activity/examples/pySteps/activity-end`, `rules-rule-obj-activity/examples/pySteps/activity-set-status`, `rules-rule-obj-activity/examples/pySteps/branch`, `rules-rule-obj-activity/examples/pySteps/call`, `rules-rule-obj-activity/examples/pySteps/call-async-activity`, `rules-rule-obj-activity/examples/pySteps/call-function`, `rules-rule-obj-activity/examples/pySteps/exit-activity`, `rules-rule-obj-activity/examples/pySteps/exit-activity-conditional`, `rules-rule-obj-activity/examples/pySteps/start-end-validate`

#### Connect-* methods
`rules-rule-obj-activity/examples/pySteps/connect-rest`, `rules-rule-obj-activity/examples/pySteps/connect-soap`, `rules-rule-obj-activity/examples/pySteps/connect-wait`

#### Data transforms, parsing, RDB, mapping
`rules-rule-obj-activity/examples/pySteps/apply-data-transform`, `rules-rule-obj-activity/examples/pySteps/apply-parse-xml`, `rules-rule-obj-activity/examples/pySteps/load-data-page`, `rules-rule-obj-activity/examples/pySteps/map-structured`, `rules-rule-obj-activity/examples/pySteps/rdb-list`, `rules-rule-obj-activity/examples/pySteps/rdb-open`, `rules-rule-obj-activity/examples/pySteps/rdb-save`, `rules-rule-obj-activity/examples/pySteps/save-data-page`

#### Queue-For-* methods
`rules-rule-obj-activity/examples/pySteps/queue-for-agent`, `rules-rule-obj-activity/examples/pySteps/queue-for-processing-dispatch`, `rules-rule-obj-activity/examples/pySteps/queue-for-processing-immediate`, `rules-rule-obj-activity/examples/pySteps/queue-for-processing-delayed`, `rules-rule-obj-activity/examples/pySteps/queue-for-processing-run-as-operator`

#### Show-* methods
`rules-rule-obj-activity/examples/pySteps/show-harness`, `rules-rule-obj-activity/examples/pySteps/show-html`, `rules-rule-obj-activity/examples/pySteps/show-page`, `rules-rule-obj-activity/examples/pySteps/show-property`

#### Transaction control
`rules-rule-obj-activity/examples/pySteps/commit`, `rules-rule-obj-activity/examples/pySteps/rollback`

#### History methods
`rules-rule-obj-activity/examples/pySteps/history-add`, `rules-rule-obj-activity/examples/pySteps/history-list`

#### Java, logging, wait, privilege, string buffer
`rules-rule-obj-activity/examples/pySteps/java`, `rules-rule-obj-activity/examples/pySteps/log-message`, `rules-rule-obj-activity/examples/pySteps/privilege-check`, `rules-rule-obj-activity/examples/pySteps/stringbuffer-append`, `rules-rule-obj-activity/examples/pySteps/wait`

## References

| Skill | Label | When to load |
|---|---|---|
| `rules-rule-obj-activity/references/activity-preconditions` | activity-preconditions | Pre-step "When" condition rows (`pyStepsPreCondition` + `pyStepsPreCondParams`) — canonical 7-value action-code enum, multi-row chaining (AND-gate, switch-case, loop-guard), feature-toggle gating, `pyOnException` block targeting, verification checklist |
| `rules-rule-obj-activity/references/activity-transitions` | activity-transitions | Post-step "Jump" condition rows (`pyStepsTransition` + `pyStepsTransParams`) — canonical action-code enum, block-name jump targeting, status fan-out / method-dispatcher / early-exit patterns, casing notes (`StepStatusFail` / `stepstatusgood`), block-naming conventions |
| `rules-rule-obj-activity/references/method-catalog` | activity-method-catalog | Complete catalog of all 108 activity step methods — organized by category with labels and parameter notes |
| `rules-rule-obj-activity/references/step-looping` | Step Looping | Loop authoring reference — 5 loop types, `pyStepsRepeatDef` fields, `param.pyForEachCount`, `<CURRENT>`, common patterns |

## Notes

### `pySystemParameters` is auto-injected — do not author

Every activity carries a `pySystemParameters` array that Pega populates
automatically. The standard set (tagged `pyUsage: "FLOW"`) is:

| Name | Type | Purpose |
|---|---|---|
| `flowName` | String | subscript to the pxFlow page |
| `flowType` | String | `pyFlowType` of the running flow |
| `ReferencePageName` | String | work object page name |
| `ReferenceClass` | String | class of the work object |
| `ReferenceInsKey` | String | `pzInsKey` of the work object |
| `pyDraftMode` | TrueFalse | whether the flow is in draft mode |

Reference these names directly in expressions (`param.flowName`,
`param.ReferenceClass`, etc.) without declaring them in `pyParameters`.
Do NOT add or modify entries in `pySystemParameters` when authoring —
Pega owns this list.

### Verify `pyPagesAndClasses` after creation

After creating or updating an activity, verify that every page referenced in
`pySteps` is declared in `pyPagesAndClasses` and that the persisted count
matches the payload.

If verification shows `NotDefinedP&C` warnings or missing page declarations:

1. Stop — do not wire the activity into a flow or case.
2. Update the activity to add missing page declarations.
3. Verify again before continuing.

Partial persistence usually indicates a malformed `pyPagesAndClasses` entry,
a class-name resolution issue, or a payload shape mismatch.

**Special class values:** `pyPagesAndClassesClass` accepts two
pseudo-classes beyond concrete Pega classes:
- `$ANY` — wildcard; accept any class for this page. Use when the
  page's class is not known at design time or is intentionally
  polymorphic (e.g. a `RulePage` that holds different rule types
  depending on the calling flow).
- `$None` — explicitly marks the page as having no class constraint.

`Embed-*` classes (e.g. `Embed-PagesAndClasses`) are also valid for
embedded scratch pages. Page names may use compound paths
(`pxThread.pxSecuritySnapshot`) or `local.<varName>` for dynamic
runtime resolution.

### Activity type (`pyActivityType`)

The canonical prompt list for `pyActivityType` (Pega-RULES 08-01-01) has
13 values. Pick the value that matches the activity's role; default to
`ACTIVITY` for general step-list authoring.

| Value | When to use |
|-------|-------------|
| `ACTIVITY` | **Default.** Standard step-list activity. Use for general orchestration, data-page bodies, helpers, validation glue. |
| `ASSEMBLER` | Assembler activity (advanced; rarely authored). |
| `ASSIGN` | Activity that creates an assignment instance. |
| `ASYNCHRONOUS` | Queued / async execution wrapper. |
| `AUTOMATION` | Not currently supported for authoring. Do **not** use this type. |
| `LOADDECLARATIVEPAGE` | Body of a data page load (e.g. `@baseclass.pxCallConnector`, `Data-Portal.pxDefaultAdminContent`). |
| `NOTIFY` | Correspondence / notification activity (e.g. `NotifyAssignee`). |
| `ONCHANGE` | Declarative on-change handler. |
| `ROUTE` | Assignment router activity (e.g. `ToWorkbasket`, `ToDecisionTable`, `ToInitialAssignee`). Note: `ROUTE` (not `ROUTER`). |
| `RULECONNECT` | Activity that invokes a Connect-* rule (e.g. `pyApplyForLoan`). |
| `TRIGGER` | Declare-Trigger handler. |
| `UTILITY` | Flow utility activity callable from a flow (e.g. `Work-.UpdateStatus`, `ReassignToWorkBasket`). |
| `VALIDATE` | Obj-Validate validation activity (e.g. `Data-Party-Person.Validate`). |

`LOCATE` is a legacy value removed by `BUG-379512` and accepted only for
round-trip preservation; never author it.

`pyOldActivityType` mirrors a prior classification of the same rule and is
preserved by the server; agents should not set it explicitly.

### Activity step updates — positional merge risk

When updating an activity via the API, `pySteps` arrays are merged
**positionally** — the Nth element in the update payload overwrites the Nth
element in the existing rule. This means:

- **Inserting a step in the middle** silently shifts and overwrites all
  subsequent steps.
- **Sending fewer steps** than exist leaves trailing steps unchanged (no
  deletion).
- **Sending an empty array** does not clear steps.

**Recommended approach:** Read the full `pySteps` array with
`get-rule(detail="full")`, modify the array in memory (add, remove, or edit
steps), then send the complete array back. Always verify all steps after
update by reading back and checking each step's `pyStepsActivityName`,
`pyStepsObjectName`, and `pyStepsCallParams`.

If a sparse positional update is unavoidable, only modify existing steps in
place or append at the end — never insert in the middle.

### Conditional steps — precondition rows required

Setting `pyStepsPreCondition: "true"` alone does **not** make a step
conditional. The step also needs populated `pyStepsPreCondParams` rows with
the actual expression and branch behavior. Without the rows, readback looks
correct but the step executes unconditionally at runtime.

Always verify both fields after creation:
- `pyStepsPreCondition` is `"true"`
- `pyStepsPreCondParams` contains at least one row with a non-empty
  `pyStepsPreCondParamsWhen` expression

See `rules-rule-obj-activity/examples/pySteps/exit-activity-conditional` for the
step JSON and `rules-rule-obj-activity/references/activity-preconditions` for
the full field reference table.

### Avoid repeating the same precondition on consecutive steps

When a group of steps shares the same gate condition (e.g. a Page-New followed
by Obj-Browse and a conditional Property-Set), apply the precondition **only to
the first step** in the group. Subsequent steps in the group can rely on the
earlier gate — if the first step was skipped, operations on the page it would
have created will no-op or the page reference will be empty. Only add a
precondition to a later step if it checks a **different** condition (e.g.
`pxResultCount==0` after the browse).

**Anti-pattern — redundant preconditions:**
```
Step 4: Page-New LeadPage        precondition: Param.DoLeads=="true"
Step 5: Obj-Browse LeadPage      precondition: Param.DoLeads=="true"   ← redundant
Step 6: Property-Set (error)     precondition: Param.DoLeads=="true" && LeadPage.pxResultCount==0  ← partially redundant
```

**Correct pattern — gate once with jump past dependent steps:**
```
Step 4: Page-New LeadPage        precondition: Param.DoLeads=="true" | WhenFalse: jump to Step 7
Step 5: Obj-Browse LeadPage      (no precondition — only reached if step 4 ran)
Step 6: Property-Set (error)     precondition: LeadPage.pxResultCount==0
Step 7: (next independent step)
```

A skipped precondition does **not** skip subsequent steps — execution
always continues to the next step unless explicitly routed elsewhere.
Use action-code `"1"` (jump to later step) on WhenFalse to skip the
entire gated block.

### Prefer Exit-Activity over complex jumps

For simple conditional branches (missing input, error states, fallback),
use conditional `Exit-Activity` steps instead of jump/transition routing.
Exit-Activity is explicit, verifiable, and avoids the risk of unintended
fall-through.

**Fallback-overwrite warning:** If a fallback/error step appears after a
success path, add an explicit `Exit-Activity` before it. Without the exit,
the activity falls through into the fallback and overwrites successful
output with error state.

Recommended orchestration pattern (block names in capitals, e.g. `ERR`,
identify the fallback block referenced by `pyOnException`):

```
1.  Property-Set:    default/missing-input display state
2.  Exit-Activity:   precondition Param.RequiredInput1 == ""
3.  Exit-Activity:   precondition Param.RequiredInput2 == ""
4.  Page-New:        support pages (DataSource, etc.)
5.  Page-Copy:       upstream data page          (pyOnException: "ERR")
6.  Property-Set:    copy upstream results + compute derived params
7.  Property-Set:    failure state (precondition: upstream result missing)
8.  Exit-Activity:   same failure precondition
9.  Page-Copy:       downstream data page         (pyOnException: "ERR")
10. Property-Set:    output/display fields
11. Exit-Activity:   success (unconditional — prevents fall-through into ERR block)
12. Property-Set:    SERVICE_UNAVAILABLE fallback (pyStepsBlockName: "ERR" — only reached via pyOnException)
```

`pyOnException` holds a **block name** (matching some step's
`pyStepsBlockName`), not a step number. See
`rules-rule-obj-activity/references/activity-preconditions` for details.

#### `pyOnException` requires `pyStepsTransition: "true"`

`pyOnException` is configured in the transition/jump popup of a step. For the
exception routing to take effect, the step **must** have
`pyStepsTransition: "true"` set. Without it, the `pyOnException` value is
ignored at runtime and the exception is not routed to the target block.

**Always set both fields together:**
```json
{
  "pyStepsTransition": "true",
  "pyOnException": "ERR"
}
```

### List macros in property references

Inside loop steps (`pyStepsRepeatDef.pyStepsRepeatDefHasRepeat` = `PROPERTYLIST` / `PROPERTYGROUP`), use
these macros in the target property path to control where values are written:

| Macro | Meaning |
|---|---|
| `<APPEND>` | Append a new row to the end of the pagelist |
| `<LAST>` | Reference the last row of the pagelist |
| `<INSERT>` | Insert a new row at the beginning of the pagelist |
| `<CURRENT>` | Reference the current iteration row (read/update) |

Example: `MyList(<APPEND>).Name` inside a Property-Set step adds a new
entry to `MyList` on each iteration.

Outside of loops, `<APPEND>` and `<LAST>` can still be used in
Property-Set steps to grow a pagelist without an explicit loop wrapper.

### Activity readback verification — conditional steps

After creating or updating an activity with conditions, load
`rules-rule-obj-activity/references/activity-preconditions` and follow its
verification checklist.

### `Save-DataPage` — persist a savable Data Page

To persist a savable Data Page (`pyPageType: "savable"`), use the
`Save-DataPage` activity method with `DataPage`, a key property (e.g.
`pyGUID`), and `WriteNow: "true"`. This works for both local database
persistence and external write-back via save connectors. Do not call
internal `pzSaveDataPage` or other `pz*` activities directly — they are
private/internal and may change between platform versions. See
`rules-rule-obj-activity/examples/pySteps/save-data-page`.

Only use `Save-DataPage` against a savable Data Page — a non-savable
(`pyPageType: "normal"`) Data Page has no save plan to execute.

For **creating new records** when the lookup key may not match an existing
record, use the create-safe pattern: Page-Copy → Page-Clear-Messages →
Property-Set → Call Save (not Obj-Save). This handles both create and
update — if the lookup found a record, Property-Set overwrites it; if not,
Page-Clear-Messages ensures a clean page for the new record.

**Verifying a savable Data Page's save plan from outside Infinity Studio:**

- **Infinity Studio's Data Designer UI** — open a list View with "Allow
  edit, delete, and create" enabled. This is the primary supported path;
  see `methodology-integration`'s "Key Constraints" section for
  why this requirement exists.
- **The DX API data-object-action endpoint** —
  `GET`/`PATCH /data/{dataObjectName}/actions/{actionID}` (see the
  `pega-dx-api-endpoints` skill's endpoint table, row "Read or edit a data
  object"). No tool in `infinity-rules-mcp` currently exposes this
  endpoint — the MCP server's tools cover cases, assignments, data views,
  and rules, but not data-object actions. This means an MCP-based agent
  currently cannot verify a savable Data Page's connector-based write-back
  (PATCH/DELETE/CREATE save options) purely through its own tool calls.
  This is a real capability gap — flag it to the user rather than implying
  a workaround exists.

### Post-refactor cleanup checklist

After modifying an activity via deep-merge updates (especially when
changing step types or removing steps), verify:

- [ ] No unused entries in `pyPagesAndClasses` (pages no longer referenced)
- [ ] No stale `pxNamedPageReferences` entries
- [ ] `pyDescription` reflects current behavior (not a prior iteration)
- [ ] No placeholder `pyMemo` or warning justification text
- [ ] No stale `pyParamArray` rows left from positional merge edits
- [ ] No leftover step fields from prior action types (see DT skill:
      "Step action change via positional merge — leftover fields")

### Best practices

#### Activity names should be short — 3 to 5 words max

Keep `pyActivityName` concise: **3–4 words is ideal, 5 words maximum.**
Use PascalCase with no spaces (e.g. `ValidateOrderData`,
`GetClientDetails`, `CheckEligibility`). Long names are harder to
reference in Call/Branch steps and clutter tracer output.

**Good:** `ValidateAddress`, `GetClaimStatus`, `InitializeFields`
**Bad:** `ValidateAndProcessIncomingOrderRequestData`

#### Page-New must pair with Page-Remove and pyPagesAndClasses

Every temporary page created with `Page-New` must:

1. **Be declared in `pyPagesAndClasses`** at the activity level with its
   class name. Missing declarations produce `NotDefinedP&C` warnings.
2. **Have a matching `Page-Remove` step** at the end of the activity to
   clean up the clipboard. Missing cleanup produces the
   `pzCleanUpCreatedPages` guardrail warning (Data Integrity, severity 2)
   and can cause memory leaks.
3. If the activity has an error block (e.g. `pyStepsBlockName: "ERR"`),
   add a `Page-Remove` step at the end of the error block too — ensure
   cleanup happens on both success and failure paths.

**Anti-pattern — do NOT do this:**
```
Step 1: Page-New TempPage
Step 2: (use TempPage)
Step 3: Obj-Save
(no Page-Remove — clipboard leak!)
```

**Correct pattern:**
```
Step 1: Page-New TempPage
Step 2: (use TempPage)
Step 3: Obj-Save
Step 4: Page-Remove TempPage
```

#### Use single Property-Set for multiple fields

When setting multiple properties, use a **single Property-Set step** with
multiple `pyParamArray` entries. Do NOT create separate Property-Set steps
for each field.

**Anti-pattern — do NOT do this:**
```
Step 1: Property-Set .Status = "Open"
Step 2: Property-Set .Priority = "High"
Step 3: Property-Set .AssignedTo = "WorkBasket"
```

**Correct pattern — one step, multiple entries:**
```
Step 1: Property-Set
  pyParamArray[0]: .Status = "Open"
  pyParamArray[1]: .Priority = "High"
  pyParamArray[2]: .AssignedTo = "WorkBasket"
```

#### Property-Set value expression types

`PropertiesValue()` in `pyParamArray` accepts these expression forms:

| Form | Example | Meaning |
|------|---------|---------|
| `"\"Literal\""` | `"\"Approved\""` | String literal (must be double-quoted inside the expression) |
| `.PropertyName` | `.pyStatusWork` | Property reference on the step page |
| `Param.X` | `Param.InputValue` | Parameter page reference |
| `@Function()` | `@CurrentDateTime()` | Built-in function call |
| `local.varName` | `local.myBuffer` | Local variable reference |
| `.Page.Property` | `.MyPage.pyLabel` | Property on a named page |

Unquoted values are treated as property references — always double-quote
string literals.

#### pyParametersParamReq values are NOT boolean

`pyParametersParamReq` in `pyParameters` accepts these values:
- `"-1"` — parameter is **required**
- `"0"` — parameter is **not required** (optional)
- `""` (blank) — same as `"0"` (optional)

Do NOT use `"true"` or `"false"` — these are not valid values.

#### pyParametersParamType valid enum values

`pyParametersParamType` in `pyParameters` must be one of:
`STRING`, `INTEGER`, `TrueFalse`, `BOOLEAN`, `Date`, `DateTime`,
`Decimal`, `Double`, `JAVAOBJECT`, `PAGE`, `TimeOfDay`.

Note the mixed casing — `TrueFalse`, `Date`, `DateTime`, `TimeOfDay`
are title-case while `STRING`, `INTEGER`, `BOOLEAN`, `DOUBLE`,
`JAVAOBJECT`, `PAGE` are uppercase. `StringBuffer` is also valid for
`pyLocalParameters` (local variables) but not for `pyParameters`.

#### Activity-Set-Status Severity values and Message parameter

The `Severity` parameter of `Activity-Set-Status` only accepts:
`Good`, `Info`, `Warn`, `Fail`. Using `Error` or other values causes
a creation failure. `Status` accepts `Good` or `Fail`.

The `Message` parameter (not `StatusMessage`) sets the status message
text and is mandatory — omitting it produces no visible status output.

#### ERR block must be defined before it is referenced

When using `pyOnException: "ERR"` or precondition jumps to `"ERR"`, the
target step with `pyStepsBlockName: "ERR"` must exist in the activity. The
ERR block must be placed **after** an `Exit-Activity` step so the success
path never falls through into it.

**Pattern:**
```
Step N:   (risky step)          pyOnException: "ERR"
Step N+1: Property-Set          (success results)
Step N+2: Exit-Activity         (prevents fall-through into ERR block)
Step N+3: Log-Message           pyStepsBlockName: "ERR" (first step of error block)
Step N+4: Property-Set          (error status)
Step N+5: Page-Remove           (cleanup on error path too)
```

#### `pyStepsBlockName: "//"` comments out a step

Setting `pyStepsBlockName` to `"//"` on a step marks it as **commented out**.
A commented step is skipped both at design time and at runtime:

- **Design time:** The rule saves even if the step is misconfigured or
  incomplete — no validation errors are raised for commented steps.
- **Runtime:** The step is never executed; the activity proceeds to the
  next non-commented step.

This is the Pega equivalent of commenting out a line of code. Use it to
temporarily disable a step without deleting it.

```json
{
  "pyStepsBlockName": "//",
  "pyStepsActivityName": "Property-Set",
  "pyStepsObjectName": "..."
}
```

**Warning:** Do not leave `"//"` steps in production activities — they are
invisible to reviewers reading the rule in Designer Studio unless the
Block Name column is explicitly checked.

#### Obj-Browse uses pyParamArray as a column/filter grid — NOT key-value pairs

Unlike most methods, `Obj-Browse` uses `pyParamArray` as a **row-based filter
grid**. Each entry represents a column to select, filter, and/or sort with
fields: `Field`, `Select`, `Condition`, `Value`, `Sort`, `Label`. This is
completely different from the key-value `PropertyName`/`PropertyValue` pattern
used by `Obj-Open`.

**Supported `Condition` values:** *(blank)* (value only), `Contains`,
`StartsWith`, `=` (Is Equal), `IsNotEqual`, `IsGreaterThan`,
`IsGreaterThanOrEqual`, `IsLessThan`, `IsLessThanOrEqual`,
`NotStartsWith`, `EndsWith`, `NotEndsWith`, `NotContain`, `IsNull`,
`IsNotNull`, `IsTrue`, `IsFalse`.

Conditions `IsNull`, `IsNotNull`, `IsTrue`, `IsFalse` do not require a
`Value`. Use `Label` (e.g. `"A"`, `"B"`) with `Logic` (e.g. `"A AND B"`)
in `pyStepsCallParams` for compound filter expressions.

The server auto-populates `pyStepsParamUI` sub-arrays on both the first
`pyParamArray` entry (6 items describing the grid column schema) and
`pyStepsCallParams` (7 items describing main params like PageName, ObjClass).
Do NOT author `pyStepsParamUI` manually — it is generated on save.

See `rules-rule-obj-activity/examples/pySteps/obj-browse` for the full condition table
and correct structure.

#### Obj-Browse requires PageName and RowKey

`Obj-Browse` requires both `PageName` (the clipboard page to populate
with results) and `RowKey` — a property reference that uniquely
identifies each result row (e.g. `.pyID`, `.pzInsKey`). Omitting either
causes runtime errors. Columns to select, filter, and sort can be
specified either via the row-based `pyParamArray` grid (see
`rules-rule-obj-activity/examples/pySteps/obj-browse`) or via the optional `SelectList`, `OrderByList`, and
`MaxRecords` parameters in `pyStepsCallParams`.
Use `ReadOnly: "true"` for read-only queries (no locking).

#### Obj-Filter requires ListPage and ResultClass

`Obj-Filter` requires `ListPage` (the source page list to filter) and
`ResultClass` (the class of the result page) as mandatory parameters.
Omitting either causes a creation or runtime failure.

#### Obj-Sort requires Class and PageListProperty

`Obj-Sort` requires `Class` (the class of pages in the list) and
`PageListProperty` (the page list property to sort) as mandatory
parameters. Omitting either causes a creation or runtime failure.

#### Activity must have at least one step

An activity must have at least one step with a valid
`pyStepsActivityName`. Omitting `pySteps` causes the builder to auto-fill
an empty step with a blank method name, which fails enum validation.

#### Server validates rule references at creation time

Many step methods reference other rules (privileges, decision tables, data
pages, Rule-Messages, queue processors, etc.). The server validates these
references **at activity creation time** — not just at runtime. If the
referenced rule does not exist in the application's ruleset stack, creation
fails with a validation error.

Affected methods include:
- `Privilege-Check` — `PrivilegeName` must exist as a privilege rule
- `Property-Map-DecisionTable` — `DecisionTableName` must exist
- `Property-Map-DecisionTree` — `DecisionTreeName` must exist
- `Property-Map-Value` — `MapName` must exist
- `Property-Set-Messages` / `Page-Set-Messages` — `Message` must be an existing Rule-Message
- `Queue-For-Processing` — `Type` must reference an existing queue processor
- `Obj-Validate` — `Validate` must reference an existing Rule-Obj-Validate
- `Apply-DataTransform` — `DataTransform` must exist
- `Load-DataPage` — `DataPage` must exist and be thread/requestor/node scoped

**Best practice:** Always verify that referenced rules exist before
including them in activity step parameters. When creating test/example
activities, use well-known OOTB rule names rather than fictional ones.

#### StringBuffer-Append requires a local variable declaration

`StringBuffer-Append` (and `StringBuffer-Insert`) reference buffer names
that must be declared as local variables with type `StringBuffer` in
`pyLocalParameters`. Without this declaration, the generated Java code
fails to compile.

```json
"pyLocalParameters": [
  { "pyParametersParamName": "myBuffer", "pyParametersParamType": "StringBuffer", "pyParametersParamDesc": "Output buffer" }
]
```

#### Page-Change-Class Keep parameter is NOT boolean

The `Keep` parameter for `Page-Change-Class` accepts numeric string values:
- `""` (blank) — default behavior (new-style, keep properties)
- `"0"` — old behavior (discard properties not in new class)
- `"1"` — new behavior (keep all properties)
- `"2"` — data transform (apply Model to reclassed page)

Do NOT use `"true"` or `"false"` — these cause Java compilation errors.

#### Load-DataPage scope restriction

`Load-DataPage` only works for **thread-level**, **requestor-level**, and
**node-level** data pages. It cannot load operator-scoped data pages like
`D_pxCurrentOperator`. Attempting to load an unsupported scope produces:
"Only thread, requestor and node level data pages can be loaded."

#### Log-Message LoggingLevel valid values

The `LoggingLevel` parameter of `Log-Message` accepts only:
`Debug`, `Info`, `Warn`, `Error`, `InfoForced`.

`InfoForced` always writes to the log regardless of the logging level
configuration. Do NOT use `WarnForced` or other variants — they are
not valid values.

#### Show-HTML HTMLStream expects a Rule-Obj-HTML name

The `HTMLStream` parameter of `Show-HTML` expects the **name of a
`Rule-Obj-HTML` rule instance** (e.g. `"ShowMessage"`), not raw HTML
content. The referenced HTML rule must exist in the ruleset stack at
creation time.

#### Call and Branch use composite pyStepsActivityName

The `Call`, `Branch`, `Queue`, and `Collect` keywords embed the target
activity name directly in `pyStepsActivityName` as a composite value:
- `"Call MyActivityName"` — calls activity in same class
- `"Call Work-.pzApplyPageInstructions"` — cross-class call (dot-qualified)
- `"Branch MyActivityName"` — branch to activity in same class
- `"Branch Work-.pzApplyPageInstructions"` — cross-class branch

The authoring contract accepts both the fixed methods (e.g. `"Property-Set"`)
and the composite pattern `^(Call|Branch|Queue|Collect) .+$`.

**Server validation for Call/Branch:**
- The target activity must exist in the ruleset stack at creation time.
- For cross-class calls, the server also validates that all required
  parameters of the target activity are satisfied via `pyStepsCallParams`.
- Use `pyPassCurrentParameterPage: "true"` to forward the caller's
  parameter page instead of explicit `pyStepsCallParams`.

#### Loop type selection — EMBEDDED vs PROPERTYLIST vs PAGE

Choose the correct `pyStepsRepeatDefHasRepeat` value based on what you are
iterating over:

| Loop Type | Use When | Example |
|-----------|----------|---------|
| `EMBEDDED` | Iterating over a **page list** (embedded pages, e.g. `pxResults` from Obj-Browse, any PageList property) | Loop over `BrowseList.pxResults` |
| `PROPERTYLIST` | Iterating over a **value list** (list of scalars — strings, integers, decimals) | Loop over a list of email addresses |
| `PAGE` | Iterating over top-level named pages on the clipboard | Loop over multiple top-level pages |
| `REPEAT` | Fixed count iteration (N times) | Execute a step exactly 5 times |
| `PROPERTYGROUP` | Iterating over a property group's keys | Loop over dynamic key-value pairs |

**Common mistake:** Using `PROPERTYLIST` for a page list like `pxResults`.
`PROPERTYLIST` is for scalar value lists only. Use `EMBEDDED` for page lists.

#### EMBEDDED loop step page must reference the page list path

When using an `EMBEDDED` loop, `pyStepsObjectName` must reference the
**page list property path** (e.g., `BrowseList.pxResults`), not a standalone
page name like `CurrentOpp`. The loop iterates over embedded pages within
that list — the step page tells Pega which list to walk.

**Correct:**
```
pyStepsObjectName: "BrowseList.pxResults"
pyStepsRepeatDef.pyStepsRepeatDefHasRepeat: "EMBEDDED"
pyStepsRepeatDef.pyForEachValidClasses: [{ "pyForEachClass": "MyOrg-MyApp-Work-Order" }]
```

**Wrong — causes "Step page should reference a list or group property":**
```
pyStepsObjectName: "CurrentOpp"
pyStepsRepeatDef.pyStepsRepeatDefHasRepeat: "EMBEDDED"
```

#### `pyForEachProperty` requires a dot prefix

The `pyForEachProperty` field must be dot-prefixed to indicate a property
reference. Without the dot, the server rejects it as a literal string.

- **Correct:** `.pxResults`
- **Wrong:** `pxResults` — error: "pxResults is a literal String but
  Property Name should reference a ClipboardProperty"

#### `pyPagesAndClasses` must include page list paths used as step pages

When the step page is a compound path like `BrowseList.pxResults`, that
path must be declared in `pyPagesAndClasses` with the class of the
embedded pages. Without this declaration, property references on the step
page resolve against `@baseclass` and fail validation.

```json
"pyPagesAndClasses": [
  { "pyPagesAndClassesPage": "BrowseList", "pyPagesAndClassesClass": "Code-Pega-List" },
  { "pyPagesAndClassesPage": "BrowseList.pxResults", "pyPagesAndClassesClass": "MyOrg-MyApp-Work-MyCase" }
]
```

#### Obj-Save WriteNow vs Commit — transaction pattern in loops

`Obj-Save` with `WriteNow: "true"` bypasses the deferred-save queue and
writes to the database immediately, but does **not** commit the
transaction. A subsequent `Commit` step is still required to make the
write permanent (commit the transaction boundary). When `WriteNow` is
empty/unchecked, the save is deferred until a `Commit` step executes.

**Best practice in loops:** Use `WriteNow: ""` (unchecked) inside the loop
and a single `Commit` step **after** the loop. This provides:
- Transactional integrity — all saves succeed or all roll back
- Better performance — one commit instead of N writes

**Anti-pattern:**
```
Step 3: Obj-Save (WriteNow: "true", in loop)  ← writes per iteration (no transactional grouping)
Step 4: Commit                                 ← still needed to commit
```

**Correct pattern:**
```
Step 3: Obj-Save (WriteNow: "", in loop)       ← defers each save
Step 4: Commit (after loop)                    ← commits all at once
```

#### Obj-Save and Commit step page in EMBEDDED loops

There are two common patterns for saving inside an EMBEDDED loop:

**Pattern A — Save per iteration (inside the loop):**
Place `Obj-Save` inside the EMBEDDED loop with the same step page as the
loop (`BrowseList.pxResults`). During each iteration, the step page
resolves to the current embedded element, so each page is saved
individually. Follow with a single `Commit` after the loop.

| Method | Step Page | Loop |
|--------|-----------|------|
| Property-Set | `BrowseList.pxResults` | EMBEDDED (modifies each page) |
| Obj-Save | `BrowseList.pxResults` | EMBEDDED (saves each page per iteration) |
| Commit | `BrowseList` | None (single commit after loop) |

**Pattern B — Batch save after the loop (recommended for transactional integrity):**
Defer all saves until after the loop completes by placing `Obj-Save` and
`Commit` outside the loop on the parent page. This saves all modified
embedded pages in one operation.

| Method | Step Page | Loop |
|--------|-----------|------|
| Property-Set | `BrowseList.pxResults` | EMBEDDED (modifies each page) |
| Obj-Save | `BrowseList` | None (single save after loop) |
| Commit | `BrowseList` | None (single commit after loop) |

Choose Pattern B when all-or-nothing transactional semantics are needed.
Choose Pattern A when individual records must be persisted independently
(e.g., partial progress on large batches).

### Methods not supported by the activity generator

The following methods cannot be created via the API:

| Method | Reason |
|--------|--------|
| `Assign-EstablishContext` | Server returns "Unsupported or unknown operation" |
| `ExecuteAport` | Server returns "Unsupported or unknown operation" |
| `Call-Automation` | Requires `pyActivityType: "AUTOMATION"` which is not supported for authoring |

### Prefer Param.X references over class properties

When authoring reusable or portable activities, reference activity
**parameters** (`Param.X`) instead of class properties in step fields
(Property-Set values, precondition When expressions, Log-Message messages).

- `Param.X` references are **not validated** against class property definitions at
  creation time — the server only validates that the syntax is valid.
- Class property references (e.g. `.pyStatusWork`) **are validated** at
  creation time — the property must exist on the step page's class.
- Declare-expression properties (e.g. `.pxUrgencyWork`) **cannot** be set
  via Property-Set — the server rejects the assignment at creation time.

This pattern is especially useful for test/example activities that should
work across different application classes.

### Call-Function invokes library functions — use `library-function-builder` for signatures

The `Call-Function` step method calls Pega library functions using the
`@(RuleSet:Library).functionName(args)` signature pattern. The function
expression goes in `pyParamArray[N].PropertiesValue` and the optional
result property in `pyParamArray[N].PropertiesName` (blank if void).

**Multiple functions can be batched in one step** by adding multiple
`pyParamArray` entries. Each entry is one function call with its own
`PropertiesValue` and `PropertiesName`.

**All `pyParamArray` entries** use `pyTempPlaceHolder: "TempPlaceHolder"`
and `pyStepsCallParams: { "pxObjClass": "" }`. Do NOT use `pyStepsParamUI`
— it is irrelevant for Call-Function rendering.

**CRITICAL: `pyStepsCallParamsList` with `pyFunctionData` is required for
UI rendering.** The Pega designer uses `pyStepsCallParamsList` (one entry
per function in `pyParamArray`) with nested `pyFunctionData` to render the
function grid. The server does **NOT** auto-generate this when creating
rules via API. Without it, the function grid appears blank in the designer
(though the activity persists and executes correctly).

**Constructing `pyFunctionData`:** Load the corresponding function alias
from the `library-function-builder` skill (e.g.,
`library-function-builder/examples/string-equals`,
`library-function-builder/examples/math-greater-than`,
`library-function-builder/examples/is-in-the-past-date`) and map its fields to
`pyFunctionData`:
- `pyPurpose` → `pyCity` and `pyName`
- `pySignature` → `pySignature`
- `pyPatternText` → `pyEcho`
- `pyPattern` (with `[]` instead of `{}`) → `pyLabel`
- `pyOutputType` → `pyReturnType`
- `pyLibraryRuleSet` → `pyRuleSet`
- `pyParameters[]` → `pyParameters[]` (with actual argument values in `pyParametersParamValue`)
- Derive `pyUIParameters[]` from the pattern (input fields for placeholders, Label entries for text between)

See `rules-rule-obj-activity/examples/pySteps/call-function` for the full
`pyFunctionData` structure, mapping table, and batching examples.

**All available function signatures are defined in the
`library-function-builder` skill.** Load that skill to find the correct
`pySignature` for any function alias. The function families include:
date/time (`DateTime`), string (`String`), math (`ExpressionEvaluators`),
collection operations (`Utilities`), and more.

**Common Libraries Quick Reference:**

| Library | RuleSet | Common Functions |
|---------|---------|-----------------|
| `String` | `Pega-RULES` | `equals`, `contains`, `pxIsInListOfValues`, `pxIsNotInListOfValues`, `toLowerCase`, `toUpperCase`, `length` |
| `DateTime` | `Pega-RULES` | `CurrentDateTime`, `compareDatesByDays`, `isDateInThePast`, `isWithinDaysOfNow`, `pxDateTimeisPastOrFuture` |
| `Math` | `Pega-RULES` | `greaterThan`, `greaterThanEqualTo`, `lessThan`, `lessThanEqualTo` |
| `Utilities` | `Pega-RULES` | `PropertyHasValue` |
| `ExpressionEvaluators` | `Pega-RULES` | `compareTwoValues`, `evaluateWhen`, `compareTwoStrings` |
| `Property` | `Pega-RULES` | `setPropertyValue`, `getPropertyValue` |
| `Page` | `Pega-RULES` | `pxPageListLength`, `pxValueIsInPageList`, `pxValueIsNotInPageList` |
| `WorkUtilities` | `Pega-ProcessEngine` | `pxAssignedToMyStaff` |
| `DateTimeUtils` | `Pega-RulesEngine` | `pxCompareDateTimeToSymbolicDate`, `pxDateTimeisPastOrFuture` |
| `BusinessCalendar` | `Pega-RULES` | calendar-based date calculations |

**Signature pattern:** `@(RuleSet:Library).functionName(arg1, arg2, ...)`
- Property refs: `.PropertyName` (dot prefix, no quotes)
- String literals: `\"text\"` (escaped double quotes in JSON)
- Numbers: `42`, `3.14` (bare)
- Booleans: `true`, `false` (bare)
- Parameters: `Param.X` (no dot prefix, no quotes)
- No return capture: set `PropertiesName` to `""`

**Multiple functions in one step:** Add multiple `pyParamArray` entries. Each entry is one function call. `pyStepsCallParamsList` must have one entry per function, in the same order as `pyParamArray`.

**Common pitfalls:**
- `lessThanEqualTo` / `greaterThanEqualTo` — NOT `lessThanOrEqualTo` / `greaterThanOrEqualTo`
- `isDateInThePast` (DateTime library) — NOT `pxIsInThePastDateTime` (that's a When rule alias, not callable)
- `@(Pega-RULES:Property).setPropertyValue` expects a property **reference** arg, not a string literal

### Obj-Open pyParamArray keys must match the class key definition

When using `Obj-Open` with `pyParamArray` key-value pairs, the
`PropertyName` values must match the **class key definition** of the
target class. For Work classes, use `.pyID` (not `.pzInsKey`). The server
validates key names against the class key definition at creation time.

### Java steps — use only when built-in methods cannot solve the problem

Pega provides **108 built-in step methods** that cover the vast majority
of activity use cases. Java steps should only be used when no combination
of built-in methods can achieve the desired result.

**Prefer Property-Set with preconditions** for conditional logic instead
of Java if/else blocks. The AND-gate precondition pattern (multiple rows
with `WhenTrue="2"` / `WhenFalse="3"`) handles most branching needs.

Java steps:
- Require compilation at save time (slower, more error-prone)
- Are harder to maintain and debug than declarative steps
- Cannot be traced/inspected the same way as built-in methods
- May introduce security and upgrade risks
