Work with Actors

In Tapis, actors are container-based functions-as-a-service that follow the actor model of concurrent computation. An actor responds to messages it receives by changing its state, performing an action, sending out response messages, or all of the above.

The function an actor performs is exposed as the default command in a container. It is typically quick and requires little processing power - i.e. an app may be configured to run FastQC, and an actor may trigger a job using that app.

The guide below is a brief introduction to interacting with actors on the Tapis platform. For a full reference guide to actors, see the Abaco Documentation.

Create a New Actor

The function of an actor is exposed as the default command in a Docker container. Here, we will create an actor from an existing Docker container image called tacc/hello-world:latest available on Docker Hub. The default command for this container simply prints the message “Hello, World” or the message sent to it, which will be captured in the actor logs.

Create the actor as:

$ tapis actors create --repo tacc/hello-world:latest \
                      -n example-actor \
                      -d "Test actor that says Hello, World"
| Field          | Value                       |
| id             | NN5N0kGDvZQpA               |
| name           | example-actor               |
| owner          | taccuser                    |
| image          | tacc/hello-world:latest     |
| lastUpdateTime | 2021-07-14T22:25:06.171534  |
| status         | SUBMITTED                   |
| cronOn         | False                       |

The --repo flag points to the Docker Hub repo on which this actor is based, the -n flag and -d flag attach a human-readable name and description to the actor, the -e flags demonstrate how to set (optional) environment variables for the actor.

The resulting actor is assigned an id: NN5N0kGDvZQpA. The actor id can be queried by:

$ tapis actors show -v NN5N0kGDvZQpA
 "id": "NN5N0kGDvZQpA",
 "name": "example-actor",
 "description": "Test actor that says Hello, World",
 "owner": "sgopal",
 "image": "tacc/hello-world:latest",
 "createTime": "2021-07-14T22:25:06.171Z",
 "lastUpdateTime": "2021-07-14T22:25:06.171Z",
 "defaultEnvironment": {},
 "gid": 862347,
 "hints": [],
 "link": "",
 "mounts": [],
 "privileged": false,
 "queue": "default",
 "stateless": true,
 "status": "READY",
 "statusMessage": " ",
 "token": true,
 "uid": 862347,
 "useContainerUid": false,
 "webhook": "",
 "cronOn": false,
 "cronSchedule": null,
 "cronNextEx": null,
 "_links": {
   "executions": "",
   "owner": "",
   "self": ""

Above, you can see the plain text name, description, and any default environment variables that were passed on the command line. In addition, you can see the “status” of the actor is “READY”, meaning it is ready to receive and act on messages. Finally, you can list all actors visible to you with:

$ tapis actors list
| id            | name          | owner    | image                       | lastUpdateTime             | status | cronOn|
| NN5N0kGDvZQpA | example-actor | taccuser | tacc/hello-world:latest     | 2021-07-14T22:25:06.171Z   | READY  | False |

Probe the Underlying Container

An actor now exists and is waiting for a message to respond to. But, how will the actor respond when sent a message? We can probe the underlying container to figure out what this specific actor will do. First pull the container locally:

$ docker pull tacc/hello-world:latest
latest: Pulling from tacc/hello-world
Digest: sha256:baf7241b9d6fb1b123825021b831337307b9fa0aa4d45b14c9405ebf2a36a929
Status: Image is up to date for tacc/hello-world:latest

Then find the default command for the container:

$ docker inspect tacc/hello-world:latest | jq ".[].ContainerConfig.Cmd"
 "#(nop) ",
 "CMD [\"python\" \"/\"]"

It runs at the root level. Print out the contents of to inspect:

$ docker run --rm tacc/hello-world:latest cat /
1   """Say Hello, World or the message received from user input"""
2   from agavepy.actors import get_context
4   def say_hello_world(m):
5   """Print message from user if present, else echo "Hello, World"""
6       if m == " ":
7           print("Actor says: Hello, World")
8       else:
9           print("Actor received message: {}".format(m))
11  def main():
12  """Main entry to grab message context from user input"""
13      context = get_context()
14      message = context['raw_message']
15      say_hello_world(message)
17  if __name__ == '__main__':
18      main()

This container, when run, will first get the message that was passed to it (from the get_context() function, line 10). Then it will print various parts of the message and the environment.

Submit a Message to the Actor

Next, let’s craft a simple message to send to the reactor. Messages can be plain text or in JSON format. When using the python actor libraries as in the example above, JSON-formatted messages are made available as python dictionaries.

# Write a message
$ export MESSAGE='Hello, World'
$ echo $MESSAGE
Hello, World

$ Submit the message to the actor
$ tapis actors submit -m "$MESSAGE" NN5N0kGDvZQpA
|  Field      | Value         |
| executionId | N4xQ5WM5Np1X0 |
| msg         | Hello, World  |

The id of the actor (N4xQ5WM5Np1X0) was used on the command line to specify which actor should receive the message. In response, an “execution id” (N4xQ5WM5Np1X0) is returned. An execution is a specific instance of an actor. List all the executions for a given actor as:

The above execution has already completed. Show detailed information for the execution with:

$ tapis actors execs show -v boEg3mEvrKO5w ayB45Oe8GJvAA
   "actorId": "NN5N0kGDvZQpA",
   "apiServer": "",
   "cpu": 121748743,
   "exitCode": 0,
   "finalState": {
     "Dead": false,
     "Error": "",
     "ExitCode": 0,
     "FinishedAt": "2021-07-14T22:32:45.602Z",
     "OOMKilled": false,
     "Paused": false,
     "Pid": 0,
     "Restarting": false,
     "Running": false,
     "StartedAt": "2021-07-14T22:32:45.223Z",
     "Status": "exited"
   "id": "N4xQ5WM5Np1X0",
   "io": 176,
   "messageReceivedTime": "2021-07-14T22:32:37.051Z",
   "runtime": 1,
   "startTime": "2021-07-14T22:32:44.752Z",
   "status": "COMPLETE",
   "workerId": "JABKl4BeDwXJD",
   "_links": {
     "logs": "",
     "owner": "",
     "self": ""

Check the Logs for an Execution

An execution’s logs will contain whatever was printed to STDOUT / STDERR by the actor. In our demo actor, we just expect the actor to print the message passed to it.

$ tapis actors execs logs NN5N0kGDvZQpA N4xQ5WM5Np1X0
Logs for execution N4xQ5WM5Np1X0
 Actor received message: Hello, World

Sure enough, the information in the execution logs match what we expected to print. The message was pulled in by the get_context() function. It was not done in this script, but in a normal scenario, the actor would then act on the contents of that message to, e.g., kick off a job, perform some data management, send messages to other actors, or more.

Run Synchronously

The previous message submission (with tapis actors submit) was an asynchronous run, meaning the command prompt detached from the process after it was submitted to the actor. In that case, it was up to us to check the execution to see if it had completed and manually print the logs.

There is also a mode to run actors synchronously using tapis actors run, meaning the command line stays attached to the process awaiting a response after sending a message to the actor. For example:

$ tapis actors run -m "$MESSAGE" NN5N0kGDvZQpA
  "username": "taccuser",
  "HOSTNAME": "33d4dd334ef9",
  "_abaco_worker_id": "X5xGkZ0lol0D3",
  "raw_message": "Hello, World",
  "actor_dbid": "TACC-PROD_boEg3mEvrKO5w",
  "new_foo": "new_bar",
  "_abaco_container_repo": "tacc/hello-world:latest",
  "content_type": null,
  "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
  "MSG": "{\"key1\":\"value1\", \"key2\":\"value2\"}",
  "HOME": "/",
  "_abaco_actor_state": "{}",
  "_abaco_actor_name": "example-actor",
  "_abaco_Content_Type": "str",
  "execution_id": "jP3RExQW108wM",
  "_abaco_synchronous": "True",
  "_abaco_access_token": "de6d11bdbb5a16bdd85beec692b1b283",
  "message_dict": {
    "key2": "value2",
    "key1": "value1"
  "_abaco_api_server": "",
  "_abaco_actor_dbid": "TACC-PROD_boEg3mEvrKO5w",
  "_abaco_jwt_header_name": "X-Jwt-Assertion-Tacc-Prod",
  "_abaco_actor_id": "boEg3mEvrKO5w",
  "_abaco_execution_id": "jP3RExQW108wM",
  "state": "{}",
  "_abaco_username": "taccuser",
  "actor_id": "boEg3mEvrKO5w"

The output above is truncated because it is mostly the same response as our first execution of the actor. This time, however, we did not need to query the logs for this execution for them to print to screen - that was done automatically. In addition, the new environment variable settings can be seen in the context (see highlighted line).

Delete an Actor

Similar to other resources in Tapis, actors can be deleted with the following:

$ tapis actors delete NN5N0kGDvZQpA
| Field    | Value             |
| deleted  | ['NN5N0kGDvZQpA'] |
| messages | []                |

This will delete the actor and any associated executions.