Stateful Autonomous Agents

in #obyte5 years ago

obyte-stateful-aa.jpg

In the previous post we built a very simple Hello World autonomous agent on the Obyte platform that only responded with a static value. In this article we will learn how to store state and execute different actions based on user input and the AA state.

Agent Greeter

This example is gonna be an autonomous agent that greets the user differently depending on whether the user already registered. The user will have to first send their name which the AA will store. Next, every time the user interacts with the AA, it will greet them with a warm welcome back message.

Requesting user input

This AA will require that the user first supply their name. But how can we do that? It's easy, the Obyte wallet allows sending data fields along with a payment which the AA can easily read via the trigger.data variable:

{
    bounce_fees: { base: 10000 },
    messages: [
        {
            app: 'state',
            state: `{
                var[trigger.address] = trigger.data.name;
                response['message'] = 'Hello ' || var[trigger.address];
            }`
        }
    ]
}

A couple of things to notice here:

  • we use the state message type because that is the only place where state variables can be set. State variables can be accessed (read) in any part of the script, but you can only assign values in the state message.
  • state variables are assigned using the var associative array-like variable. The var[trigger.address] = trigger.data.name maps the value of trigger.address to the value of trigger.data.name, for example 53LAFVHRJ7C2SNNEMLUCJCZCZFXZNKV4 to John Doe.
  • trigger.data refers to a data payload object of the triggering Obyte unit and we can access its properties with the dot operator. In the above example the trigger.data.name reads the value of the name property.
  • finally, when setting the response message, we can see that the state variable can be immediately read after we assigned a value to it.

And here is how the user would call Agent Greeter from the Send screen of the wallet:

obyte-stateful-aa-user-data.png

As we can see, the wallet immediately predicts the outcome of the call and displays what state variables would be set upon execution.

Handling errors

What happens if the user does not send the name data field? Rather than storing no name, we should instead give an error feedback to the user indicating the missing data element. Let's make some changes:

{
    bounce_fees: { base: 10000 },
    messages: [
        {
            init: `{
                $name = trigger.data.name otherwise bounce("name is required!");
            }`,
            app: 'state',
            state: `{
                var[trigger.address] = $name;
                response['message'] = 'Hello ' || var[trigger.address];
            }`
        }
    ]
}

A few new things are introduced above:

  • an init property which can have a value with Oscript statements. This is a great place to initialize local variables and handle preconditions and erroneous situations.
  • a local variable called $name is initialized with the value of the name data property. It is important to note, that local variables cannot be re-assigned once initialized with a value.
  • the otherwise keyword which is a binary operator that returns the left side if that is "truthy" , otherwise it returns the expression on the right side. Another example would be $name = trigger.data.name otherwise 'anonymous';
  • the bounce command which is used to terminate the AA execution with an error.

Here is how it looks like in the wallet when no name data field is provided:

obyte-stateful-aa-bounce.png

Adding multiple behaviors

Our goal is to recognize and greet the user once they introduced themselves to Agent Greeter. To achieve that we will have to branch our code and alter the behavior if we already stored a name for the calling user. For that, we are going to use the cases keyword. See the final script below:

{
    bounce_fees: { base: 10000 },
    messages: {
        cases: [
            {
                if: `{ !var[trigger.address] }`,
                messages: [{
                    init: `{
                        $name = trigger.data.name otherwise bounce("name is required!");
                    }`,
                    app: 'state',
                    state: `{
                        var[trigger.address] = $name;
                        response['message'] = 'Hello ' || var[trigger.address];
                    }`
                }]
            },
            {
                messages: [{
                    app: 'state',
                    state: `{
                        response['message'] = 'Welcome back ' || var[trigger.address];
                    }`
                }]
            }
        ]
    }
}

The cases keyword is used for parameterizing JSON objects. The cases takes an array and picks the first element of which if condition is truthy or the last one without an if. The final script has two branches: the first executes only if the user has not registered their name yet, while the second executes otherwise when the AA already knows the user and so it can welcome them by their name.

Important to note that recognizing the same user only works if the user triggers the AA with the same address. For regular users that means they are supposed to use a single address wallet for interacting with Agent Greeter. Bots and programmed headless wallets of course can construct triggering units that would pick a specific address even if they use a multi-address wallet.

As an exercise, try to modify the script to return the user most of the fees they paid for interacting with the agent.

Further reading and resources

Sort:  

Congratulations @pmiklos! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :

You received more than 50 upvotes. Your next target is to reach 100 upvotes.

You can view your badges on your Steem Board and compare to others on the Steem Ranking
If you no longer want to receive notifications, reply to this comment with the word STOP

Vote for @Steemitboard as a witness to get one more award and increased upvotes!