A Goal-oriented Cloud Native Programming Language that choreographs microservices. By removing unnecessary complexity applications can be written with pure business logic that is intuitive, transparent and syntax-light.

Table of Contents

About Storyscript

Storyscript focuses on the application logic rather than all the tape and glue that bind applications together. The underlining services have a standard for logs, metrics, fail-over, rate-limiting, tracebacks and scaling which eliminates the need to write it within the application. This cultivates a development environment primed for rapid application development in a production-ready platform.

Let's build a quick application for example. Our goals are to upload, analyse, compress and archive a video. A non-trivial application but in a couple lines of Storyscript we made it.

# Registers with Asyncy Server as an endpoint
when http server listen method:'post' path:'/upload' as client
    client write content:'Success! Processing asynchronously.'
    client set_status code:201
    client finish

    # At this we are running asynchronously

    # generate a unique id for this upload
    id = uuid uuid4

    video = client.files['my_uploaded_video']

    # using find the video topics
    topics = machinebox/videobox find_topics content:video

    # save record in mongodb
    mongodb insert db:'uploads' data:{'id': id, 'topics': topics}

    # using let's compress it to h264
    video = xiph/daala compress video:video codex:'h264'

    # upload to AWS S3
    aws/s3 put target:'/video/{id}.mp4' data:video

In comparison, the same application would likely take hundreds of lines of code, not to mention that each service above includes metrics, logging and scaling out-of-the-box.


Storytelling your first application — Build a or WeTransfer clone in 10 minutes.

Syntax Overview

Meet Storyscript

# Strings
my_string = "Hello"
"Say {my_string}!"  # string formatting
# >>> Say Hello!

# Numbers
one = 1
onethree = 1.3

# Boolean
foo = true
bar = false

# List
letters = ['a', 'b', 'c']
# >>> 1

# Object
fruit = {'apple': 'red', 'banana': 'yellow'}
# >>> yellow

# Regexp
pattern = /^foobar/
'foobar' like pattern
# >>> true

# Null
empty = null

# Conditions
if one > 1
    # then do this
else if one == 1
    # then do this
    # do this

# Loops
foreach my_list as item
    # more stuff here

while foobar
    # more stuff here

# Services
val = service action key:value
val = owner/repo action key:value

# Event-based service
when slack bot hears pattern:/hello/ as msg
    msg reply message:'world'

# Functions
function walk distance:number returns string
    # more stuff here
    return "Ok, walked {distance}km!"
# >>> Ok, walked 10km!

# Chaining calls
my_service action foo:(my_string split by:',')
                  bar:(my_object find key:(my_list random))
'abc' uppercase split
# >>> ['A', 'B', 'C']

# try and catch
  # more stuff here
catch as error
  # more stuff here
  retry  # try the block again
  # -or-
  raise  # bubble it up



output = do_third foo:(do_second (do_first ...)) bar:(do_second ...)

Parentheses MUST be used to produce inline procedures. The innermost Parentheses will be executed first moving out to the outermost.

Same level parentheses MAY be called at the same time which done by parallel processing in new threads.

First set of parentheses when assigning variables is optional. E.g., a = my_list length is the same as a = (my_list length).

Attributes, methods and entries

Objects may have attributes and methods. A Map can only have entries.

  1. Object attributes can only be accessed using a period character (.) as in
  2. Object methods can only be accessed using a space character () as in cart add item:....
  3. Map entries can only be accessed using brackets and strings (['key']) as in some_map['key']

The example below illustrates how the object tweet has attributes and methods.

tweet = twitter tweet text:'Hello world'
id =  # returns the tweet id 123456 of type int.
tweet like     # calls "like", a method of tweet


Mutations refer to running operations on the built in data types, such as strings, arrays, maps, and numbers.

# Strings
# Note: None of the string operations below change the original string in any form
str = ''
str length  # returns the number of UTF-8 characters
str replace pattern: 'ab' by: 'AB'  # returns a string by replacing 'ab' with 'AB'
str replace pattern: /ab/ by: 'AB'  # same as above
str split by: '.'  # returns an array by splitting the string with the delimiter
str uppercase  # returns a string where all characters are upper cased
str lowercase  # returns a string where all characters are lower cased
str capitalize  # returns a string where the first letter of each word is capitalized (eg: 'jane smith' becomes 'Jane Smith')

# Numbers
num = 10
num is_odd  # returns false
num is_even  # returns true
num absolute  # returns the absolute value (if num was -1, this would return 1)
num increment  # returns 11. Note that num is not changed
num decrement  # returns 9. Note that num is not changed

# Arrays
arr = [1, 2, 3, 4, 5]
arr index of: 5  # returns the index of an element, 4 in this case
arr length  # returns the length of the array, 5 in this case
arr append item: 6  # adds 6 to the end of the array
arr prepend item: 1  # adds 1 to the start of the array
arr random  # returns a random element from this array
arr reverse  # reverses the array in-place
arr sort  # sorts the array in an ascending fashion
arr min  # returns the lowest of the elements in this array (if it contains numbers)
arr max  # returns the largest of the elements in this array (if it contains numbers)
arr sum  # returns the sum of all the elements in this array (if it contains numbers)
arr unique  # reduces the array to contain only unique items
arr contains item: 3  # returns true if 3 is present, false otherwise
arr remove item: 3  # removes the item specified from the array

# Maps
m = {'a': 1, 'b': 2}
m size  # returns the size of the map, 2 in this case
m keys  # returns an array of all keys
m values  # returns an array of all values
m flatten  # returns a list of key/value pairs (eg: [['a', 1], ['b', 2]])
m pop key: 'a'  # removes and returns the value for key 'a'
m get key: 'b'  # returns the value for the key 'b'
m contains key: 'c'  # returns true if the key 'c' exists in the map, false otherwise

Chaining Mutations

Mutations can be chained to help reduce complexity.

'abc' uppercase split
# >>> ['A', 'B', 'C']

'abc' uppercase
      then split
# >>> ['A', 'B', 'C']

Variable Scope

Variables are not global.

n = 1

every minutes:3
  n increment
  log n
+0  INFO 2
+3m INFO 3
+6m INFO 4

Functions do not have access to local variables. All variables must be provided as arguments.

n = 1

function incr
  n increment
Syntax error. Variable "n" is not defined at line 4.


Storyscript is a dynamically compiled language. Type checking is performed at compile time, but not in a traditional way. From the perspective of the developer the following steps are performed during compile time.

Compile time consists of four primary processes:

  1. Linting is performed to check syntax and grammar.
  2. Translation is performed which translates the Storyscripts into event-logic tree.
  3. Dependancy checks are performed to ensure command and arguments exists.
  4. Type-Checking is performed on the Stories the ensure data integrity.

The type-checking includes the following checks:

  1. Type mutation method exists.
  2. Arguments are of the expected type.

Environment Variables

Environment variables are stored in a restricted keyword app.environment.

# asyncy.yml
  food: cake
  FOO: bar
if == 'cake':

if == 'bar':


Variables are ALWAYS exposed as lower-case attributes.

Execution Model

Storyscripts are executed by an interpretation engine (not compiled to C or Java).


  1. All dependencies are gathered and prepared for execution.
  2. The Asyncy Engine is prepared with the Stories as first-class assets for swift execution.
  3. Every Storyscript is executed allowing them to register with the gateway, cron, etc.


A story may execute in many ways.

  1. The Engine received notice to start a Story with or without starting arguments.
  2. The Story is executed in a single thread.
  3. When a service is called the Engine will communicate with the service passing necessary data to and from the service back into the primary thread.
  4. Asynchronous commands may generate new threads and execute in the same pattern above.
translated = service_a translate:my_string to:'spanish'
parts = translated split ' '
first_word = service_b name:parts[0]

The Story above is would perform the following operations:

  1. Translate a string to Spanish
  2. Split the translated string by whitespace
  3. Assign first_word to the first word in the parts array


data = "foobar"

long_string = "Hi Friend,
This is a lo\
ng string."
# Hi Friend, This is a long string.

more_data = """
    The quick brown fox
    jumps over the lazy dog.
# The quick brown fox\njumps over the lazy dog.

where = "Earth"
data_formatted = "Hello, {where}"
# Hello, Earth

Like many traditional programming languages, Storyscript supports strings as delimited by the " or ' characters. Storyscript also supports string interpolation within "-quoted strings, using { variable }. Single-quoted strings are literal. You may even use interpolation in object keys.

Multiline strings are allowed in Storyscript. Lines are joined by a single space unless they end with a backslash. Indentation is ignored.

Block strings, delimited by """ or ''', can be used to hold formatted or indentation-sensitive text (or, if you just don’t feel like escaping quotes and apostrophes). The indentation level that begins the block is maintained throughout, so you can keep it all aligned with the body of your code.

Double-quoted block strings, like other double-quoted strings, allow interpolation.


int = 1
number = 1.2



# Inline comment

foo = "bar"  # end of line comment

In Storyscript, comments are denoted by the # character to the end of a line, or from ### to the end of the line of the next appearance of ###.


happy = true
sad = false


list_inline = ["string", 1, 2]
list_multiline = [


object_inline = {'foo': 'bar', 'apples': 'oranges'}
object_multiline = {
  'foo': 'bar',
  'apples': 'oranges'


if foo == bar
  # more stuff here
else if foo > bar
  # more stuff here
  # more stuff here

if (foo > 0 and cat == dog) or foobar contains item: /regexp/
  # more stuff here

if/else statements can be written without the use of parentheses and curly brackets. As with functions and other block expressions, multi-line conditionals are delimited by indentation.


Looping through index and/or object keys.

foreach my_list as item
    # ...

foreach my_list as index, item
    # ...

foreach my_object as key
    # ...

foreach my_object as key, value
    # ...

while foobar
    # ...

Loops have reserved keywords for ending and continuing loops.

foreach my_list as item
    # more stuff here
    if do_end_loop
    if do_skip_to_next_item
    # ...


function get_user id:int returns map
    someone = (psql exec query:'select * from users where id={id} limit 1;')[0]
    someone['contact'] = fullcontact person email:someone['email']
    return someone

user_a = get_user(id:7)
user_b = get_user(id:10)

The example above is a function what queries the database and also downloads their FullContact profile.

Function must define their inputs and outputs which help with transparency, autocomplete and type checking during the Asyncy CI process.

Functions MAY declare one output and identify it's type. If an output type is used the function MUST use return and return that type.

function add this:int that:int returns int
   return this + that

Functions that do not have an output may not use return. An error is thrown if a return is used.

function do_this
    # ...

function do_that
    return 1
>>> ERROR: Function must set type of return.


A service is a containerize microservice that is registered in the Asyncy Hub. Discover hundreds of services in the Hub or build your own in any language, submit to the Asyncy Hub and call it in your Storyscript like this:

# Call a service with a command and all arguments named
service cmd key:value foo:bar
org/repo cmd key:value foo:bar

# Service output assigned to variable
foobar = service cmd key:value

# Arguments may by indented under the service
service cmd key:value

In Storyscript, the syntax to run a service appears natural and arguments are named for transparency.

tweet = "hello"
twitter tweet message:tweet
# would result in ```twitter tweet message:"hello"```

Containers, commands and argument names are static grammar and interpreted literally.

Event-Based Services

Services may publish events which run a new block of logic.

service cmd foo:bar as client
    when client event foo:bar as data
        # ...

A good example of this is streaming Tweets by hashtag.

twitter stream as client
    when client tweet hashtag:'asyncy' as tweet
        res = machinebox/language data:tweet.message
        if res.tone == 'good'
            tweet reply message:'Thank you!'
            tweet retweet
            tweet like

Every new tweet will be passed into the block below in the variable tweet. Then machine learning will determine if the tone of the tweet's message is good or bad. The streaming service will wait for new tweets until the story is ended.

If no output is defined, it will be implicitly default to the name of the command. Furthermore, if only a command name is used in when blocks, it will use the output of its parent as subscribing service. This allows this to shorten the example from above:

twitter stream
    when tweet hashtag:'asyncy'
        res = machinebox/language data:tweet.message
        if res.tone == 'good'
            tweet reply message:'Thank you!'
            tweet retweet
            tweet like

Note that the service call twitter stream has an implicit output of stream. In the when block this output gets automatically inferred to when <stream> tweet hashtag: 'asyncy'. As this a service call as well, it has an implicit output too (tweet) and thus tweet.message gets correctly resolved.



if something_went_wrong
    end story

Use end story to stop the story and exit now.

Exception Handling

  # more stuff here
catch as error
  # more stuff here
  # more stuff here

In Storyscript, the try expressions catch exceptions and pass the error to the catch block.

The finally block is always entered regardless of an exception being raised or not, use it for cleanup commands.

You may omit both the catch and finally.

  # more stuff here
catch as error
  # more stuff here

Use the throw keyword to raise the exception, bubbling up to the next try block or stopping the story.

Regular Expressions

pattern = /^foo/i

Regular expressions are supported without any special characters of escaping necessary.

Regular Expressions Methods

pattern = /(?P<key>\w):(?P<value>\w)/


Learn the methods you can call with regular expression in the Asyncy Hub


1 type
# int

true type
# bool

"" type
# string

[] type
# list

{} type
# object

null type
# null

(date now) type
# date

(interval days:1) type
# interval

(range from:foo to:bar) type
# range

/^foobar/ type
# regexp

function foobar returns int
    return 1

function foobar
    # ... no return allowed

foobar type
# function

Use the method type to get the type of a variable as a string.

(1 is int) and (true is bool) and ("" is string)
# true

([] is list) and ({} is object)
# true

(1 is number) and (1.2 is number)
# true

{} is string
# false

Type checking can be applied to any type.

Application Information

Storyscript has access to application level information under the keyword app, containing the following details:

app.secrets   # map of environment variables set via the CLI (more below)
app.hostname  # the full http dns hostname where your application is located
              # e.g, ""
app.version   # the release number of the application (see "asyncy releases list" for releases)
              # e.g, "v1"

Secrets (aka environment variables, configuration)

There are two types of secrets Storyscript Secrets, which may be used in your Storyscript only, and Service Secrets, which are provided to the specific service during runtime. Use the Asyncy CLI to set, list, update, and delete secrets.


Secrets are read-only and case-insensative. The secrets foobar, FOOBAR and FooBar all return the same value.

Storyscript Secrets

  1. Create a secret
asyncy config set foo=bar
  1. Access the secret in your Storyscript
# Your Storyscript
if == 'bar'

Service Secrets

To set secrets for services, prepend the varible name with the service name, like this example:

asyncy config set github.client_secret=some-secret-value

The value client_secret will be set to some-secret-value only for the service github.


Service Secrets are only readable by the service they belong to. They cannot be accessed in your Storyscript or by any other service. When the service is started by Asyncy it will be assigned the secrets as environment varibles at runtime.

Listing Secrets

Retrieve a list of secrets in your application call the following command:

asyncy config list

The result will organize the secrets in the following way:

# Storyscript variables:
FOO: bar

# Service variables:
  CLIENT_SECRET: some-secret-value