# Roles

> FizzBee roles model actors and processes: role state, Init, durable vs ephemeral state, and how roles call each other, with runnable examples.

Canonical: https://fizzbee.io/reference/language/roles/
Source: https://github.com/fizzbee-io/fizzbee/blob/main/examples/references/LANGUAGE_REFERENCE.md#roles


## Definition

Roles are class-like structures representing actors, processes, or components in a distributed system.

```python
role Server:
    action Init:
        self.data = {}
        self.version = 0

    action Process:
        self.version = self.version + 1

    func get_data(key):
        return self.data.get(key, None)
```

## Characteristics

1. **Instance-based**: Create multiple instances
2. **State encapsulation**: Each instance has independent state
3. **Implicit self**: Like Java's `this`, no need to pass `self`
4. **Role Init**: Constructor-like initialization
5. **Actions and functions**: Can have both

## Role Init Action

The `Init` action in a role acts like a constructor:

```python
role Account:
    action Init:
        # Initialize instance state
        self.balance = 0
        self.owner = None

# Create instance with parameters
action Init:
    account = Account(INITIAL_BALANCE=100, ACCOUNT_ID=1)
```

## State Variables

Only `self.*` variables are part of role state:

```python
role Example:
    action Init:
        self.persisted = 0  # Part of state
        temp = 100          # NOT part of state (local variable)
```

## Special Role Fields

**`self.__id__`** - Auto-generated unique identifier:

```python
role Server:
    action Init:
        self.name = "server"
        # self.__id__ is automatically set by FizzBee

    action LogStatus:
        print(f"Server {self.__id__}: {self.status}")

action Init:
    servers = []
    for i in range(3):
        servers.append(Server())
    # servers[0].__id__ != servers[1].__id__ != servers[2].__id__
```

**Use cases**:
- Distinguishing role instances in logs
- Using instance ID in message routing
- Debugging and visualization

**Note**: `__id__` is read-only and unique across all role instances

## Durable vs Ephemeral State

Use `@state` decorator to mark ephemeral fields:

```python
@state(ephemeral=['cache'])
role Server:
    action Init:
        self.disk_data = 0  # Durable (persists across crashes)
        self.cache = 0      # Ephemeral (lost on crash)
```

**Alternatively, mark durable fields**:

```python
@state(durable=['disk_data'])
role Server:
    action Init:
        self.disk_data = 0  # Durable
        self.cache = 0      # Ephemeral (not in durable list)
```

**Rules**:
- Cannot specify both `durable` and `ephemeral`
- Default: all state is durable
- On crash: ephemeral fields reset to Init values

## Role Communication

Roles communicate by calling each other's functions:

```python
role Client:
    action SendRequest:
        response = server.process()  # RPC call

role Server:
    func process():
        return "OK"
```

**Automatic fault injection**: Message can be lost (see Fault Injection section)

## Examples

### Simple Role {#simple-role}

Basic role definition (like a class)

**Demonstrates:** role keyword, role structure

```fizz
# Example 0046: Simple Role
# Basic role definition (like a class)
# Demonstrates: role keyword, role structure

role Account:
    action Init:
        # self prefix makes it part of role state
        self.balance = 0
        self.owner = "alice"

    action Deposit:
        require self.balance < 100

        self.balance = self.balance + 10

# Create role instance
action Init:
    account = Account()

# Role instances can have their own state
# Multiple instances would have independent state

atomic action NoOp:
    if account.balance >= 100:
        pass
```
Run it in the FizzBee playground: https://fizzbee.io/play


**Expected result** (from `fizz`):

```
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2}
Nodes: 11, queued: 0
Valid Nodes: 11 Unique states: 11
IsLive: true
PASSED: Model checker completed successfully
```

[Source on GitHub](https://github.com/fizzbee-io/fizzbee/tree/main/examples/references/11-01-simple-role)

### Role Init {#role-init}

Role Init action is like a constructor

**Demonstrates:** role initialization with parameters

```fizz
# Example 0047: Role Init
# Role Init action is like a constructor
# Demonstrates: role initialization with parameters

role Account:
    # Init is called when role is instantiated
    action Init:
        # self.OWNER comes from constructor parameter
        self.balance = 0
        self.name = self.OWNER  # Use parameter

    action Deposit:
        require self.balance < 50

        self.balance = self.balance + 10

# Create role instances with parameters
action Init:
    alice_account = Account(OWNER="alice")
    bob_account = Account(OWNER="bob")
    counter = 0

atomic action IncrementCounter:
    require counter < 2
    counter = counter + 1

# Each instance has its own state
# alice_account.name = "alice"
# bob_account.name = "bob"

atomic action NoOp:
    if counter >= 2:
        pass
```
Run it in the FizzBee playground: https://fizzbee.io/play


**Expected result** (from `fizz`):

```
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2}
Nodes: 108, queued: 0
Valid Nodes: 108 Unique states: 108
IsLive: true
PASSED: Model checker completed successfully
```

[Source on GitHub](https://github.com/fizzbee-io/fizzbee/tree/main/examples/references/11-02-role-init)

### Role Actions {#role-actions}

Actions within roles

**Demonstrates:** role actions, implicit self

```fizz
# Example 0048: Role Actions
# Actions within roles
# Demonstrates: role actions, implicit self

role Account:
    action Init:
        self.balance = 0

    # Role actions don't take parameters (like top-level actions)
    # self is implicit (no need to declare)
    action Deposit:
        require self.balance < 50

        self.balance = self.balance + 10

    action Withdraw:
        require self.balance >= 10

        self.balance = self.balance - 10

action Init:
    account = Account()

# Role actions are called by the model checker
# Just like top-level actions
# The model checker explores all enabled role actions

atomic action NoOp:
    pass
```
Run it in the FizzBee playground: https://fizzbee.io/play


**Expected result** (from `fizz`):

```
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2}
Nodes: 6, queued: 0
Valid Nodes: 6 Unique states: 6
IsLive: true
PASSED: Model checker completed successfully
```

[Source on GitHub](https://github.com/fizzbee-io/fizzbee/tree/main/examples/references/11-03-role-actions)

### Role Functions {#role-functions}

Functions within roles

**Demonstrates:** role functions, implicit self

```fizz
# Example 0049: Role Functions
# Functions within roles
# Demonstrates: role functions, implicit self

role Calculator:
    action Init:
        self.total = 0

    # Functions in roles - self is implicit
    # No self parameter needed (like Java/C++)
    func add(x, y):
        return x + y

    func accumulate(value):
        self.total = self.total + value
        return self.total

    atomic action Compute:
        require self.total < 10

        # Call role functions
        sum = self.add(2, 3)
        self.accumulate(sum)

action Init:
    calc = Calculator()

atomic action NoOp:
    if calc.total >= 10:
        pass
```
Run it in the FizzBee playground: https://fizzbee.io/play


**Expected result** (from `fizz`):

```
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2}
Nodes: 6, queued: 0
Valid Nodes: 6 Unique states: 6
IsLive: true
PASSED: Model checker completed successfully
```

[Source on GitHub](https://github.com/fizzbee-io/fizzbee/tree/main/examples/references/11-04-role-functions)

### Role State {#role-state}

Only self.* variables are part of role state

**Demonstrates:** role state variables vs local variables

```fizz
# Example 0050: Role State
# Only self.* variables are part of role state
# Demonstrates: role state variables vs local variables

role Account:
    action Init:
        # State variables (persisted)
        self.balance = 0
        self.transactions = 0

        # Local variable (not persisted)
        temp = 100  # This is NOT part of state

    action Deposit:
        require self.balance < 50

        # Local variables don't affect state
        amount = 10

        # Only self.* updates are persisted
        self.balance = self.balance + amount
        self.transactions = self.transactions + 1

action Init:
    account = Account()

# Only self.balance and self.transactions are part of state
# Local variables like temp, amount are not tracked

atomic action NoOp:
    if account.balance >= 50:
        pass
```
Run it in the FizzBee playground: https://fizzbee.io/play


**Expected result** (from `fizz`):

```
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2}
Nodes: 125, queued: 0
Valid Nodes: 125 Unique states: 115
IsLive: true
PASSED: Model checker completed successfully
```

[Source on GitHub](https://github.com/fizzbee-io/fizzbee/tree/main/examples/references/11-05-role-state)

### Role Decorators {#role-decorators}

@state decorator marks durable vs ephemeral fields

**Demonstrates:** @state(durable=[...]) and @state(ephemeral=[...])

```fizz
# Example 0051: Role Decorators
# @state decorator marks durable vs ephemeral fields
# Demonstrates: @state(durable=[...]) and @state(ephemeral=[...])

# Mark cache as ephemeral (lost on crash)
@state(ephemeral=['cache'])
role Storage:
    action Init:
        # Durable state (persists across crashes)
        self.data = {}

        # Ephemeral state (lost on crash)
        self.cache = {}

    atomic action Write:
        require len(self.data) < 2

        key = "key_" + str(len(self.data))
        self.data[key] = "value"
        self.cache[key] = "cached_value"

# Can also mark durable explicitly:
# @state(durable=['data'])
# This makes everything else ephemeral

action Init:
    storage = Storage()

atomic action NoOp:
    if len(storage.data) >= 2:
        pass
```
Run it in the FizzBee playground: https://fizzbee.io/play


**Expected result** (from `fizz`):

```
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2}
Nodes: 6, queued: 0
Valid Nodes: 6 Unique states: 5
IsLive: true
PASSED: Model checker completed successfully
```

[Source on GitHub](https://github.com/fizzbee-io/fizzbee/tree/main/examples/references/11-06-role-decorators)

### Role Instances {#role-instances}

Creating and managing multiple role instances

**Demonstrates:** multiple instances with independent state

```fizz
# Example 0052: Role Instances
# Creating and managing multiple role instances
# Demonstrates: multiple instances with independent state

role Account:
    action Init:
        self.balance = self.INITIAL_BALANCE
        self.id = self.ACCOUNT_ID

    action Deposit:
        require self.balance < 100

        self.balance = self.balance + 10

action Init:
    # Create multiple independent instances
    accounts = []

    for i in range(2):
        account = Account(INITIAL_BALANCE=i*10, ACCOUNT_ID=i)
        accounts.append(account)

    total_processed = 0

atomic action CheckTotal:
    require total_processed < 2

    total_processed = total_processed + 1

# Each account has independent state
# accounts[0].balance starts at 0
# accounts[1].balance starts at 10

atomic action NoOp:
    if total_processed >= 2:
        pass
```
Run it in the FizzBee playground: https://fizzbee.io/play


**Expected result** (from `fizz`):

```
StateSpaceOptions: options:{max_actions:100 max_concurrent_actions:2}
Nodes: 330, queued: 0
Skipping dotfile generation. Too many Nodes: 330
Valid Nodes: 330 Unique states: 330
IsLive: true
PASSED: Model checker completed successfully
```

[Source on GitHub](https://github.com/fizzbee-io/fizzbee/tree/main/examples/references/11-07-role-instances)


## Patterns that use this

- [Distributed Lock Pattern](/design/patterns/distributed-lock/)
- [Leader Election (Simplified Raft)](/design/patterns/leader-election/)
- [Message Queue Pattern](/design/patterns/message-queue/)
- [Eventual Consistency Pattern](/design/patterns/eventual-consistency/)

