Skip to content

Example: manage two sets of accounts

Combine types, functions, memory and Agent calls in a small project. Maintain two independent sets of account balances, then process numbers with two different increment functions.

You will learn to split packages, reference identically named types, call another Agent from a skill and read fields from returned values.

Prepare the project

Download the account example (ZIP) and extract it into /tmp to get /tmp/aal-composition. First complete the compiler setup in AAL introduction.

aal-composition/
  left/
    aal.toml
    src/main.aal
  right/
    aal.toml
    src/main.aal
  app/
    aal.toml
    aal.lock
    src/main.aal

left and right are account packages; app calls both. Each has its own manifest. The application declares dependencies using relative paths. The lock file is included with the source.

Define account types and memory

Open left/src/main.aal:

struct Account
  balance: Int

agent Worker is Agent
  memory scope "account" strict
    balance: Int = 10
  public
    ability add(amount: Int) -> Account effects(memory.read, memory.write)
      balance = memory.require("balance") + amount
      memory.store("balance", balance)
      return Account(balance: balance)

Account is the returned result; its balance field holds the balance. The balance in memory is Worker state, initially 10.

Each add reads the old balance, adds amount, stores the new value, and returns an Account. Reading and writing require memory.read and memory.write respectively.

The right package uses the same type and Agent names but starts at 100. Package aliases distinguish these objects.

Compose calls in the application

Open app/src/main.aal:

agent Main is Agent
  private
    fn left_balance(account: left.Account) -> Int effects()
      return account.balance
    skill delegation
      fn increment() -> right.Account effects(agent)
        return agent("right.Worker").add(1)
  public
    ability run() -> List<Int> effects(agent)
      factory = (offset: Int) => (value: Int) => value + offset
      add_two = factory(2)
      add_ten = factory(10)
      a = agent("left.Worker").add(1)
      b = self.delegation.increment()
      again = agent("left.Worker").add(2).balance
      return [self.left_balance(a), again, b.balance, add_two(3), add_ten(3), factory(20)(1)]

Read run in this order:

  1. factory accepts offset and returns a function that retains it. add_two and add_ten retain 2 and 10.
  2. agent("left.Worker").add(1) increments the left balance by 1 and assigns the returned Account to a.
  3. self.delegation.increment() enters the private skill, which calls the right Worker. Skills organize steps; external callers still use public abilities.
  4. agent("left.Worker").add(2).balance adds another 2 on the left and immediately reads the returned balance field.
  5. The final line collects balances and closure results.

Check and run

Run from the AAL repository:

cargo run --locked --bin aalc -- lock /tmp/aal-composition/app
cargo run --locked --bin aalc -- check /tmp/aal-composition/app
cargo run --locked --bin aalc -- run /tmp/aal-composition/app --agent Main --ability run --args '{}' --state-dir /tmp/aal-composition/state --invocation composition-1

On first use of this state directory, the result is:

[11, 13, 101, 5, 13, 21]
ResultSource
11Left starts at 10, then adds 1
13Left adds another 2
101Right starts at 100, then adds 1
5add_two(3): 3 + 2
13add_ten(3): 3 + 10
21factory(20)(1): 1 + 20

The right operation does not change the left balance. The closures also retain different offsets.

Try a change

Change add_two's offset to 5. Update the lock and run with a new invocation ID; observe the fourth result. Keeping the state directory retains prior balances. Use a new directory to start from initial values.

Then replace self.left_balance(a) with self.left_balance(b). Update the lock and run check. You will get type error E210: the function requires left.Account, but b is right.Account. Identical fields do not make these types interchangeable. Restore a and update the lock to continue.

Continue learning

The two Workers are defined separately in the project, not created by a runtime instance factory. Read closure factories for functions returning functions, or projects and distribution to share a project.