***

title: Decision Frameworks
position: 1
excerpt: Six decision trees for picking the right action type, slot type, conversation pattern, and data transformation approach.
deprecated: false
hidden: false
---------------------

For clean Markdown of any page, append .md to the page URL. For a complete documentation index, see https://docs.moveworks.com/agent-studio/guides/getting-started/llms.txt. For full documentation content, see https://docs.moveworks.com/agent-studio/guides/getting-started/llms-full.txt.

You'll hit the same six decision points over and over when building in Agent Studio. This page gives you a tree for each one. Follow the branches, pick the right tool, move on.

## Action Type Selection

Every plugin needs at least one action. Here's how to pick the right type.

```mermaid
graph TD
    A["What does this action need to do?"] --> B{"Call an external API?"}
    B -->|Yes| D["**HTTP Action**<br />Direct API call with<br />input/output mapping"]
    B -->|No| F{"Orchestrate multiple<br />actions in sequence?"}
    F -->|Yes| G["**Compound Action**<br />Chain steps with data<br />passing between them"]
    F -->|No| H{"Need LLM reasoning<br />on unstructured text?"}
    H -->|Yes| I["**LLM Action**<br />Summarize, classify,<br />extract, or generate"]
    H -->|No| J{"Need complex data<br />transforms? (loops,<br />error handling, regex)"}
    J -->|Yes| E["**Script Action (Python)**<br />Transform data with loops,<br />conditionals, error handling"]
    J -->|No| K{"Is it a platform<br />capability?"}
    K -->|Yes| L["**Built-in Action (mw.\*)**<br />User lookup, approvals,<br />notifications, LLM calls"]
    K -->|No| D
```

### Quick Reference

| Action Type          | Use When                                                                                                 | Example                                                                                 |
| -------------------- | -------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| **HTTP**             | Straightforward API call, response maps cleanly to output                                                | GET user profile, create ticket                                                         |
| **Script (Python)**  | Need loops, conditionals, error handling, or complex data transforms that DSL/data mappers can't express | Nested filtering with fallbacks, date math across time zones, multi-step string parsing |
| **Compound**         | Orchestrating 2+ actions in sequence where intermediate results feed the next step                       | Look up calendar → find room → book meeting                                             |
| **LLM**              | Task requires language understanding — summarization, classification, extraction, generation             | Summarize ticket thread, classify sentiment                                             |
| **Built-in (mw.\*)** | Platform-native capability — user lookups, approvals, notifications, LLM generation                      | `mw.get_user_by_email`, `mw.create_generic_approval_request`, `mw.generate_text_action` |

<Callout intent="info">
  Script actions **cannot make HTTP requests** — internet access is blocked at the infrastructure level. Use HTTP actions to call external APIs, then pass the response data into a script action via `input_args` if you need complex transforms on the result.
</Callout>

## Conversation Process vs Compound Action

This decision starts with **how your plugin gets triggered**, not what it does.

* **Conversational trigger** (user sends a message) → you have a user session. Conversation processes are available.
* **System trigger** (webhook or schedule) → no user session. [Ambient agents](/agent-studio/core-concepts/ambient-agents) run compound actions only. Conversation processes are not an option because there's no user to interact with.

```mermaid
graph TD
    A["How is this plugin triggered?"] --> B{"User sends a message?<br />(conversational trigger)"}
    B -->|No| C["**Compound Action only**<br />Ambient agents have no user session.<br />CPs are not available."]
    B -->|Yes| D{"Does the user need to<br />provide input or confirm<br />between action groups?"}
    D -->|Yes| E["**Conversation Process**<br />Collects slots, shows results,<br />gets confirmation between actions.<br />Can wrap compound actions."]
    D -->|No| F["**Conversation Process<br />wrapping a Compound Action**<br />CP handles slots + confirmation.<br />CA handles the integration logic."]
    E --> G{"Tempted to chain two<br />action activities without<br />a slot in between?"}
    G -->|Yes| H["⚠️ **Golden Rule violation**<br />Move the chain into<br />a compound action"]
```

<Callout intent="warning">
  A plugin uses **one trigger type**: either conversational triggers or system triggers. If you need both, create two separate plugins. See [connecting ambient agents to conversational agents](/agent-studio/core-concepts/ambient-agents/connecting-ambient-agents-to-conversational-agents) for the handoff pattern.
</Callout>

### The Default Conversational Pattern

Most conversational plugins follow the same shape: **one conversation process wrapping one compound action**.

```yaml title="Default conversational pattern"
# Conversation Process
activities:
  - slot_activity:
      required_slots: [user_input_1, user_input_2]
  - action_activity:
      action_name: my_compound_action
      confirmation_policy: true
      output_key: result
```

The conversation process handles user interaction (slots, confirmation). The compound action handles integration logic (API calls, transforms, sequencing). Clean separation.

### When a Conversation Process Needs Multiple Action Activities

If your conversation process genuinely needs two separate action groups — for example, a lookup followed by a create — separate them with a slot barrier:

```yaml title="Two action groups with slot barrier"
activities:
  - action_activity:
      action_name: lookup_options       # First action group
      required_slots: [search_query]
      output_key: options

  - action_activity:
      action_name: create_resource      # Second action group
      required_slots: [selected_option]  # ← Slot barrier
      confirmation_policy: true
      output_key: created
```

The `required_slots` on the second activity forces the reasoning engine to pause and collect input. That's the slot barrier that satisfies the [Golden Rule](/agent-studio/guides/the-golden-rule).

## Slot Type Selection

Picking the wrong slot type is one of the most common mistakes. It works in testing but breaks in production when real users give unexpected input.

```mermaid
graph TD
    A["What data do you<br />need from the user?"] --> B{"Is it a person<br />or list of people?"}
    B -->|Yes| C["**list[User]** or **User**<br />Platform resolves identity<br />from name/email automatically"]
    B -->|No| D{"Is it a yes/no<br />choice?"}
    D -->|Yes| E["**boolean**<br />No resolver needed.<br />Cheaper than a static<br />resolver with two options"]
    D -->|No| F{"Does the display value<br />differ from the API value?"}
    F -->|Yes| G["**Static Resolver**<br />Maps display labels to<br />internal values"]
    F -->|No| H{"Need to look up<br />options from an API?"}
    H -->|Yes| I["**Dynamic Resolver**<br />Fetches options at runtime<br />via action"]
    H -->|No| J["**Free text (string)**<br />Simple input,<br />validate with DSL policy"]
```

### Common Mistakes

| Mistake                                               | Why It's Wrong                                      | Fix                                        |
| ----------------------------------------------------- | --------------------------------------------------- | ------------------------------------------ |
| `string` for a person's name                          | LLM has to parse "John" into a user ID — unreliable | Use `User` or `list[User]`                 |
| Static resolver for yes/no                            | Adds an unnecessary LLM call to resolve the option  | Use `boolean`                              |
| No validation on free text                            | User can enter anything, action fails downstream    | Add a DSL validation policy                |
| Resolver to populate a dropdown when values are fixed | Adds API call overhead for static data              | Use static resolver with hardcoded options |

<Callout intent="warning">
  Every resolver adds an LLM call. A `boolean` slot is resolved without one. If you're building a two-option choice (approve/reject, yes/no, enable/disable), always use `boolean` over a static resolver.
</Callout>

## LLM vs DSL

The fourth commandment: **LLM for language, DSL for logic.** If the task is deterministic — same input always produces same output — use DSL. If it requires understanding natural language, use an LLM action.

| Task                           | Right Tool                | Why                                        |
| ------------------------------ | ------------------------- | ------------------------------------------ |
| Lowercase a string             | DSL: `$LOWERCASE(x)`      | Deterministic string operation             |
| Format a date                  | DSL: `$FORMAT_TIME(...)`  | Deterministic transformation               |
| Map status code to label       | DSL: `LOOKUP()`           | Fixed mapping, no interpretation needed    |
| Concatenate fields             | DSL: `$CONCAT([...])`     | Deterministic — never use `+` for strings  |
| Filter a list                  | DSL: `$FILTER(items, fn)` | Condition-based, no language understanding |
| **Summarize** a paragraph      | **LLM**                   | Requires language comprehension            |
| **Classify** a ticket          | **LLM**                   | Requires semantic understanding            |
| **Extract** entities from text | **LLM**                   | Requires NLP                               |
| **Generate** a response        | **LLM**                   | Requires natural language generation       |

### The Test

Ask yourself: *"If I gave this exact input to 100 different people, would they all produce the exact same output?"*

* **Yes** → DSL. It's deterministic.
* **No** → LLM. It requires judgment.

### LLM Action Defaults

When you do use an LLM action:

* Model: `gpt-4o-mini` (fast, cheap, good enough for most tasks)
* Temperature: `0.3` for deterministic tasks (classification, extraction), `0.7` for creative tasks (generation)
* Always set `"additionalProperties": false` in the response schema to prevent hallucinated fields
* See the [LLM Actions reference](/agent-studio/actions/llm-actions) for the full list of available models and configuration options

## Data Mappers & DSL vs Python

DSL functions (`$LOWERCASE`, `$CONCAT`, `$FORMAT_TIME`, etc.) and data mappers (`MAP()`, `FILTER()`, `LOOKUP()`, `CONDITIONAL()`) work together inside output mappers and input mappers. They handle most data transformations without needing a separate action. But they have limits.

### Capability Comparison

| Capability        | DSL / Data Mappers                                       | Python (Script Action)                              |
| ----------------- | -------------------------------------------------------- | --------------------------------------------------- |
| String operations | `$LOWERCASE`, `$UPPERCASE`, `$TRIM`, `$CONCAT`, `$SPLIT` | Full string library                                 |
| Date/time         | `$TIME`, `$PARSE_TIME`, `$FORMAT_TIME`, `$ADD_DATE`      | `datetime` (std lib)                                |
| List transforms   | `$MAP`, `$FILTER`, `$FIND`, `MAP()`, `FILTER()`          | List comprehensions, complex nesting                |
| Conditionals      | `CONDITIONAL()`                                          | `if/elif/else`, complex branching                   |
| Lookups           | `LOOKUP()`                                               | Dict operations, nested lookups                     |
| Field extraction  | `response.result.data.id`                                | Direct dict access                                  |
| Iteration         | `MAP()` applies a transform to each item, `$MAP` for DSL | `for`, `while`, generators, early exit with `break` |
| Error handling    | No try/catch                                             | `try/except`, custom error messages                 |
| Complex math      | Basic arithmetic only                                    | Full `math` library                                 |
| Regex             | `$MATCH(pattern, string)` for basic matching             | Full `re` module (groups, substitution, lookahead)  |

### Decision Rules

1. **Can you express it inline in a mapper?** → Use DSL/data mappers. No extra action, no latency cost.
2. **Is it a simple field extraction, lookup, or list transform?** → Data mappers handle it declaratively.
3. **Nested 3+ data mapper operations, or need regex/error handling/complex math?** → Switch to a script action.

### Example: Inline DSL vs Python Script Action

DSL runs inline in your mapper — no extra action, no latency cost:

```yaml title="DSL — inline in output mapper"
output_mapper:
  greeting: $CONCAT(["'Hello,'", data.first_name, data.last_name], "' '")
  upper_name: data.name.$UPPERCASE()
  formatted_date: $FORMAT_TIME($PARSE_TIME(data.raw_date), '%Y-%m-%d')
```

The same transforms in Python require a separate script action and add latency:

```python title="Script action — same result, more overhead"
first = input_args["first_name"]
last = input_args["last_name"]
raw_date = input_args["raw_date"]

{
    "greeting": f"Hello, {first} {last}",
    "upper_name": input_args["name"].upper(),
    "formatted_date": raw_date[:10]  # assumes ISO format
}
```

### Example: When Data Mappers Get Strained

Simple data mappers are readable and fast:

```yaml title="Good use of data mappers — simple, readable"
output_mapper:
  ticket_id: response.result.id
  status:
    LOOKUP():
      key: response.result.state
      mapping:
        '1': "'New'"
        '2': "'In Progress'"
        '3': "'Resolved'"
      default: "'Unknown'"
```

But chaining sort, filter, map, and conditional logic is where the nesting gets deep fast:

```yaml title="Data mappers getting strained — consider Python"
output_mapper:
  report:
    MAP():
      items:
        SORT():
          items:
            FILTER():
              items: response.records
              condition: item.active == true
          key: item.priority
      converter:
        title: item.name.$UPPERCASE()
        severity:
          CONDITIONAL():
            condition: item.priority > 3
            on_pass: "'Critical'"
            on_fail:
              CONDITIONAL():
                condition: item.priority > 1
                on_pass: "'Medium'"
                on_fail: "'Low'"
        owner: $CONCAT([item.assignee.first_name, item.assignee.last_name], "' '")
        link: $CONCAT(["'https://tickets.internal.com/'", item.id.$TEXT()], "")
```

A script action is cleaner once you hit this level of nesting:

```python title="Script action — same logic, easier to read and debug"
records = input_args["records"]

def severity(p):
    if p > 3: return "Critical"
    if p > 1: return "Medium"
    return "Low"

active = [r for r in records if r.get("active")]
active.sort(key=lambda r: r["priority"])

report = [
    {
        "title": r["name"].upper(),
        "severity": severity(r["priority"]),
        "owner": f"{r['assignee']['first_name']} {r['assignee']['last_name']}",
        "link": f"https://tickets.internal.com/{r['id']}"
    }
    for r in active
]
{"report": report}
```

If your mapper chains `SORT()`, `FILTER()`, `MAP()`, and `CONDITIONAL()` four or five levels deep — stop. Write a script action.

## Compound Actions vs Python

Compound actions have their own control flow: [`for`](/agent-studio/actions/compound-actions/for) loops, [`switch`](/agent-studio/actions/compound-actions/switch) conditionals, and [`parallel`](/agent-studio/actions/compound-actions/parallel) execution. These handle orchestration-level logic — iterating over items, branching between different actions, running steps concurrently. Script actions handle fine-grained data manipulation within a single step.

| Need                                                 | Compound Action                      | Python (Script Action)                   |
| ---------------------------------------------------- | ------------------------------------ | ---------------------------------------- |
| Loop over items and call an action per item          | `for` with `each`/`in`/`steps`       | Can't call actions from scripts          |
| Branch to different actions based on a condition     | `switch` with `cases`/`condition`    | Can't call actions from scripts          |
| Run independent actions concurrently                 | `parallel` with concurrent steps     | Single-threaded, no parallelism          |
| Complex data transform (regex, math, error handling) | No inline scripting                  | Full Python with try/except, regex, math |
| Transform data between steps                         | Output mappers with DSL/data mappers | Full Python in a script step             |

### The Key Distinction

* **Compound actions** orchestrate — they decide *which actions run* and *in what order*.
* **Script actions** transform — they manipulate *data within a single step*.

You'll often use both: a compound action orchestrates the flow, and a script action handles a tricky transform at one step.

### Example: Compound Action `for` Loop

Send a notification to each user in a list:

```yaml title="Compound action — for loop over users"
steps:
  - action:
      action_name: mw.batch_get_users_by_email
      output_key: user_results
      input_args:
        user_emails: data.email_list
  - for:
      each: user
      in: data.user_results.user_records
      output_key: notifications
      steps:
        - notify:
            output_key: notify_result
            recipient_id: user.user.record_id
            message: data.message_text

```

You can't do this in a script action — scripts can't call other actions. This is compound action territory.

### Example: Compound Action `switch`

Route to different actions based on a condition:

```yaml title="Compound action — switch on request type"
steps:
  - switch:
      cases:
        - condition: data.request_type == 'access'
          steps:
            - action:
                action_name: submit_access_request
                output_key: access_result
                input_args:
                  user_id: data.user_id
        - condition: data.request_type == 'hardware'
          steps:
            - action:
                action_name: submit_hardware_request
                output_key: hardware_result
                input_args:
                  device_type: data.device_type
      default:
        steps:
          - action:
              action_name: submit_general_request
              output_key: general_result
              input_args:
                description: data.description
```

### When to Use Python Instead

Use a script action when the problem is **data manipulation**, not action orchestration:

```python title="Script action — complex transform that compound actions can't express"
users = input_args["users"]
active_admins = []
for user in users:
    if user.get("status") == "active":
        roles = user.get("roles", [])
        if "admin" in roles and user.get("last_login"):
            try:
                last_login = parse_date(user["last_login"])
                if last_login > thirty_days_ago:
                    active_admins.append({
                        "name": user["name"],
                        "email": user["email"],
                        "last_login": format_date(last_login)
                    })
            except ValueError:
                continue  # Skip malformed dates

{"active_admins": active_admins}
```

This needs try/except, date parsing, and conditional filtering within a single list — that's a script action's job. A compound action `for` loop can iterate and call actions, but it can't do inline error handling or complex transforms.

***

## Quick Reference: All Six Decisions

| Decision                     | Default Choice                                        | Switch When                                                                                |
| ---------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| Action type                  | HTTP action                                           | Need data transforms → Script. Need sequencing → Compound. Need language → LLM.            |
| CP vs Compound Action        | Conversational → CP wrapping a CA. Ambient → CA only. | System trigger (webhook/schedule) → Compound Action is the only option.                    |
| Slot type                    | Free text `string`                                    | Person → `User`/`list[User]`. Yes/no → `boolean`. Display ≠ value → resolver.              |
| LLM vs DSL                   | DSL                                                   | Task requires language understanding → LLM                                                 |
| Data Mappers & DSL vs Python | DSL/data mappers in mapper                            | 3+ nested operations, regex, error handling, complex math → Script action                  |
| Compound Actions vs Python   | Compound action                                       | Need inline data manipulation (regex, try/except, complex transforms) → Script action step |

<CardGroup cols={2}>
  <Card title="The Golden Rule" icon="fa-solid fa-crown" href="/agent-studio/guides/the-golden-rule">
    The single most important architecture principle — never chain actions without a slot barrier.
  </Card>

  <Card title="Compound Actions" icon="fa-solid fa-layer-group" href="/agent-studio/actions/compound-actions">
    Full reference for compound actions: steps, return, for loops, switch, parallel, and output mapping.
  </Card>

  <Card title="CP vs Compound Action" icon="fa-solid fa-code-branch" href="/agent-studio/cookbooks/when-to-use-compound-actions-vs-conversational-processes">
    Deeper comparison with examples and the full decision process.
  </Card>

  <Card title="LLM Actions" icon="fa-solid fa-brain" href="/agent-studio/actions/llm-actions">
    How to configure LLM actions for summarization, classification, extraction, and generation.
  </Card>
</CardGroup>