Skip to content

1.0.P2 Pattern: Agent's State

Julio Toboso edited this page Oct 26, 2026 · 2 revisions

Pattern 2 · State each Agent owns

When you want each Agent to carry values of its own, initialized by a Tag and able to change independently.

Every Wizard needs a spellbook and some mana. The Tag declares how those Records begin:

class Wizard(Tag):
    @Record
    def mana(agent):
        return 2

    @Record
    def spellbook(agent):
        return []

The functions marked with @Record are Record builders. When Wizard is newly applied to an Agent, each builder runs and its returned value is stored on that Agent.

Let us introduce two Wizards:

ada = Wizard(Character("Ada"))
bob = Wizard(Character("Robert"))

Both begin with two mana and an empty spellbook.

The spellbook builder runs separately for each Agent. Each execution of return [] creates a new list, so Ada and Robert receive different spellbooks.

A declaration on the Tag, a value on the Agent

The declaration tells TOP how to initialize the Record. After tagging, the Agent exposes the resulting value as an ordinary attribute:

assert ada.mana == 2
assert ada.spellbook == []

Reading ada.spellbook retrieves Ada’s stored list. It does not run the builder again.

This is why a Record is declared using a function, but its value is accessed without calling a function.

Their state can change independently

Let us change Ada’s Records:

ada.spellbook.append("Light")
ada.mana -= 1

assert ada.spellbook == ["Light"]
assert ada.mana == 1

assert bob.spellbook == []
assert bob.mana == 2

Ada has learned Light. Robert is still in the dark.

Both Agents carry Wizard, but changing Ada’s state does not change Robert’s. Their compositions now look like this:

---
title: Ada
---
swimlane-beta TD
    subgraph Wizard
        mana["mana: 1"]
        book["spellbook: ['Light']"]
    end

    subgraph Character
        name["name: Ada"]
        level["level: 1"]
    end
Loading
---
title: Robert
---
swimlane-beta TD
    subgraph Wizard
        mana["mana: 2"]
        book["spellbook: []"]
    end

    subgraph Character
        name["name: Robert"]
        level["level: 1"]
    end
Loading

The Wizard declaration is the same in both cases. The rectangles show the values currently held by each Agent.

Applying Wizard again does not refill anything

While Wizard is already active on Ada, applying it again does nothing:

Wizard(ada)

assert ada.mana == 1
assert ada.spellbook == ["Light"]

The builders do not run again merely because the Tag is called again. Ada keeps her current state.

To change an ordinary Record, change its value directly:

ada.mana = 2       # refill her mana
ada.spellbook.clear()    # empty her current spellbook

A fresh application after Ripping would run these builders again, but changing an ordinary Record does not require ending the Agent’s membership.

Clone this wiki locally