Data storage
Stores are key-value databases. A store can be attached to any number of agents. Attachment happens during agent creation. A store that is attached to multiple agents can act as a shared database between them.
A store is persistent and agent deletions have no effect on it. It will keep existing and retain data until you delete it.
Create a store
A store can be created from the UI and via REST API and Python SDK.
Access a store
Store that is attached to a agent can be accessed via the store
argument of the main
function:
def main(request, store):
print(store.items())
Use a store
store
is a dict-like object. Here's how it can be used:
def main(request, store):
# get the value of "color"
color = store["color"]
# set the value of "color" to "red"
store["color"] = "red"
# delete "color"
del store["color"]
# iterate over all keys
for key in store
print(key)
# get the number of keys in store
length = len(store)
# check if "color" is in store
if "color" in store:
print("color is in store")
# get the value of "color", or the default value "red" if "color" is not in store
color = store.get("color", "red")
# get all key-value pairs in store
for key, value in store.items():
print(f"{key}: {value}")
# get all keys in store
for key in store.keys():
print(key)
# get all values in store
for value in store.values():
print(value)
# delete all keys in store
store.clear()
Supported types
When working with store
, the following rules need to be followed:
-
The root key must be a string:
store["key"] = "value"
store[1] = "value" # error -
Values must be JSON-serializable:
store["key"] = "value"
store["key"] = 1
store["key"] = 1.1
store["key"] = False
store["key"] = {"key": 1}
store["key"] = [1, 2, 3]
store["key"] = None
store["key"] = object() # error
Transactions
Stores also have transaction support. Transactions are used to ensure that a set of operations are performed atomically.
def main(request, store):
store["color"] = "red"
store["shape"] = "circle"
with store.transaction():
store["color"] = "green"
some_helper_function()
store["shape"] = "rectangle"
def some_helper_function():
raise ValueError()
In the above example, when some_helper_function
raises an exception, the store will be rolled back to its previous state. This means that the value of color
will be red
and the value of shape
will be circle
. The value of color
will not be green
.
Transactions can be used to ensure that either all the operations in a transaction are performed or none of them are. This is useful in cases when the operations are dependent on each other. For example, in banking, when transferring money from one account to another, there are two operations that need to be performed: debiting the sender's account and crediting the receiver's account. Either both of these operations need to be performed or none of them should be performed to avoid inconsistencies.
Locking
Locking is also supported. Locking makes sure that only one execution of agent can access the store at a time.
Locking is useful in cases when there are a lot of concurrent requests to a store. This can happen when a single agent is executed in very quick succession or when multiple agents are frequently accessing the same store.
def main(request, store):
with store.transaction(lock=True):
# keep track of the number of times the agent has been run
store["run"] = store.get("run", 0) + 1
print(f"run: {store['run']}")
In the above example, if we hadn't used locking, it would have been possible for two agents executed at the same time to read the same value of run
and increment it, which would have resulted in the value of run
being incremented in total by 1 instead of 2.
For example, consider the following sequence of events:
- Agent 1 reads the value of
run
(reads 0) - Agent 2 reads the value of
run
(reads 0) - Agent 1 increments the value of
run
(writes 1) - Agent 2 increments the value of
run
(writes 1)
Locking makes sure that the first agent acquires the lock, increments the value of run
and releases the lock. Only then can the second agent acquire the lock and increment the value of run
, which was already incremented by the first agent. This ensures that in total, the value of run
is incremented by 2, as expected.