-
Notifications
You must be signed in to change notification settings - Fork 1
RSP Latest Specification
This is the latest version of the RSP (Remote Simulator Protocol) protocol: version 1. Details on its purpose, versioning, and more, can be found in this page. It is recommended you read what it before continuing here.
The RSP protocol allows for the communication between two entities: a "simulator", simulating a set PDDL domain-problem pair, and an agent, which will interact with said domain-problem pair, to "solve" it. This communication is described semi-abstractly, such that messages has a specific serialization format and structure, but the underlying communication channel is unspecified. In practice, RSP will generally be used over a transport such as TCP, HTTP, HTTPS, QUIC, etc. In all of these cases, RSP can also be locally, via localhost and the like.
Communication is achieved by the simulator and agent exchanging "messages" with each other. Each message has a string "type", to identify it, and a "payload", which many contain any data, according to its type. Messages are sent using CBOR, and are described here using CDDL.
RSP is a request-response oriented protocol, where almost all messages are either requests, from the agent, or responses, from the simulator. The agent cannot send a request while another is being dealt with. This makes the implementation of it extremely simple and foolproof.
In RSP, there are two main phases:
- Session setup, which is guaranteed to not change between protocol versions.
- Session operation, which may look very different depending on the protocol version.
Throughout session operation, agents send requests to observe and interact with their environment. For example:
- Problem setup, to access the domain-problem pair's definition
- Get grounded actions, to see which grounded actions an agent may perform in the environment's current state
- Perform grounded action, for sending the simulator the action the agent would like to perform in the current state
- And others.
Once the agent solves the domain-problem pair, or should simulation stop due to any other reason, a termination message may be sent. These can be sent at any time, by the agent, or the simulator, according to the type of termination message, and are therefore not requests/responses.
Note
You many benefit from first reading some parts of the full specification, as otherwise some parts here may not make sense.
Consider an example problem, with the following PDDL domain:
(define (domain simple-world)
(:predicates (at ?location) (reachable ?a ?b))
(:action move
:parameters (?from ?to)
:precondition (and (at ?from) (or (reachable ?to ?from) (reachable ?from ?to)))
:effect (and (not (at ?from))
(at ?to))))And a PDDL instance for it:
(define (problem simple-instance)
(:domain simple-world)
(:objects ?a ?b ?c)
(:init (at ?a)
(reachable ?a ?b)
(reachable ?b ?c))
(:goal (at ?c)))Given a simulator loaded with this problem, let's play the role of an agent, interacting with the simulator using the RSP protocol. This agent and simulator will only support communication via protocol version 1.
We will first request a session setup, with the following request:
{
type: "session-setup-request",
payload: { 1 => 0 },
}
Note the payload of this request. We support communication via major version 1, requiring at least the 0 minor version to be used. The simulator supports this minimum version, and so will then give a session-setup response accordingly:
{
type: "session-setup-response",
payload: 1,
}
The simulator, expectedly, chose major version 1. Session operation has now begun. Let's begin by seeing what problem we are actually dealing with (we, the humans, know this, but our hypothetical agent, does not). Thus, we will use the Session setup service, by sending a message like so:
{
type: "problem-setup-request",
payload: null,
}
The simulator will respond by returning the PDDL strings used to simulate the problem. There isn't any hidden information, so the full strings seen above will be returned, like so:
{
type: "problem-setup-response",
payload: {
domain: "
(define (domain simple-world)
(:predicates (at ?location) (reachable ?a ?b))
(:action move
:parameters (?from ?to)
:precondition (and (at ?from) (or (reachable ?to ?from) (reachable ?from ?to)))
:effect (and (not (at ?from))
(at ?to))))
",
problem: "
(define (problem simple-instance)
(:domain simple-world)
(:objects a b c)
(:init (at a)
(reachable a b)
(reachable b c))
(:goal (at ?c)))
",
}
}
We can now begin to interact with the environment. To better understand our options though, let's first see which grounded actions we may perform, using the get-grounded-actions-request message type, sending a message like so:
{
type: "get-grounded-actions-request",
payload: null
}
The simulator will then respond as expected:
{
type: "get-grounded-actions-response",
payload: [
{
name: "move",
grounding: ["a", "b"]
},
],
}
Note that one cannot do (move a a), as according to the problem, a is not reachable from a. If (move a a) was possible, the problem could end up in a broken state, with our agent technically being "nowhere", due to how we implemented move. Luckily, this isn't the case. Since we only have one valid grounded action, let's perform it, like so:
{
type: "perform-grounded-action-request",
payload: {
name: "move",
grounding: ["a", "b"]
},
}
Being a valid grounded action, the simulator will respond with an effect index, as the domain is yet to be solved. This action is deterministic, and thus has a single effect, with effect index 0. Unsuprisingly, the simulator will respond with:
{
type: "perform-grounded-action-response",
payload: 0,
}
Great! We're one step closer to solving the problem. Let's see what our surroundings look like now, using the perception service:
{
type: "perception-request",
payload: null,
}
This is the environment state returned by the simulator:
{
type: "perception-response",
payload: {
"at" => [["b"]],
"reachable" => [["a", "b"], ["b", "c"]],
"=" => [["a", "a"], ["b", "b"], ["c", "c"]]
},
}
Wait, what? What's this "=" predicate doing here? While it doesn't appear anywhere in the domain definition, PDDLSIM automatically added it to the problem state, as one can use equality constraints in preconditions. Beyond this idiosyncraticity, the current state is fairly understandable. Let's now finish the problem, by moving to c:
{
type: "perform-grounded-action-request",
payload: {
name: "move",
grounding: ["b", "c"],
},
}
As we have now finished the problem, we simulator will respond with the closing of the session, like so:
{
type: "session-termination",
payload: {
reason: "problem solved",
},
}
We should now close the communication channel.
The "Remote Simulator Protocol" (henceforth, "RSP") facilitates communication over a TCP connection between an agent and a simulator, for running a simulation session. Messages are written in CBOR and are unframed, as CBOR is self-delimiting. Throughout the specification, messages will be presented using CBOR's Concise Data Description Language (CDDL).
All messages sent will be of the form:
message = {
type: text,
payload: any,
}
When different kinds of messages will be introduced in the following sections, their type and their payload will both be detailed. Almost all messages are either a request, or a response, such that an agent may only send requests, and a simulator, only responses. Message types are unique. Types used for requests and responses, will be of the form <PREFIX>-request, or <PREFIX>-response respectively. <PREFIX> may be used to identify both of the derived types. For example, when we want to refer to the perception-request message type, we may refer to it as the perception request. The only messages without this kind of suffixing, are termination messages, which may be sent at any time, so either party must always be available to handle them. Despite this, for any given message type, the payload will always be uniquely determined.
Once the communication channel is set up, the agent must send a session-setup request, with the following payload:
session-setup-request = supported-version
supported-version = uint
essentially, the payload should be the protocol version this agent supports communication with. Upon receiving this information, it should send a session-setup response, with the following payload:
session-setup-response = bool
where assuming the simulator supports communication with this version, the payload is true, and false otherwise. When the payload is false, communcation should be terminated, similarly to a termination message.
After session setup, the simulation has officialy begun. Session operation is the final stage of an RSP session, and where the bulk of its time is spent. In this phase, an agent may use a set of provided "services", and advance the simulation by performing grounded actions. All of this, using RSP requests.
To receive the initial setup of the decision-making problem, alongside its unchanging domain, the agent can use a problem-setup request, with a null payload. The problem-setup response from the simulator will have the following payload:
problem-setup-response = {
domain: text,
problem: text,
}
where domain and problem are both in the PPDDL-like language PDDLSIM uses, but without any revealable information (:reveal). If the simulator does not however support said version, a session-termination message should be sent instead, with the reason field being undefined.
The perception request allows an agent to get from the simulator the information it perceives in the current state, which is some fraction of the full simulated state, as some information may be hidden. It has a null payload. The perception response from the simulator must have this payload:
perception-response = {
* predicate-name => [* predicate-grounding]
}
predicate-grounding = [* object]
object = text
predicate-name = text
Essentially, the returned information is information on all tuples of objects which satisfy a given predicate, for all predicates. For example, given state (west a b), (east b a), assuming all information should be known to the agent, the resulting payload would be {"west" => [["a", "b"]], "east" => [["b", "a"]]}.
The get-grounded-actions request allows the agent to receive the valid grounded actions it can perform in state, assuming the agent should be aware of them. Grounded actions relying on hidden information will not be shown. This request has a null payload. The get-grounded-actions response from the simulator has the following payload:
get-grounded-actions-response = [* grounded-action]
grounded-action = {
name: text,
grounding: [* object],
}
object = text
The goals request allows the agent to receive information on which goals of the problem it has reached, and which, it has yet to reach. This request has a null payload. The goals response from the simulator has the following payload:
goals-response = {
reached: [* goal]
unreached: [* goal],
}
goal = text
Every goal text should be a valid "condition" structure in the PPDDL-like language PDDLSIM uses. A condition is essentially an action precondition, without equality constraints, and is grounded.
For the agent to perform a grounded action, it must send a perform-grounded-action request, with the following payload:
perform-grounded-action-request = grounded-action
grounded-action = {
name: text,
grounding: [* object],
}
object = text
Then, if the grounded action did not solve the problem after applying the grounded action, if the grounded action is valid, the response from the simulator is of the same type, and with the following payload:
perform-grounded-action-response = effect-index
effect-index = uint
where effect-index is the index of the resulting effect of the action. This is only relevant for probabilistic actions, or fallible ones. If the grounded action received was invalid, it is assumed that the agent is erring, and so an external error response should be returned by the simulator.
If the grounded action instead did solve the problem, a simulation-termination message will be sent by the simulator. the reason field is not constrained by this specification.
The following messages messages all terminate the session. They are noncontextual, and so may be sent no matter the context, no matter the time.
A message of type give-up may be sent by the agent to indicate it has given up on solving the session problem. This message has a null payload.
A message of type error must be sent to indicate the session may not continue due to some error, which may originate from the sender, or from invalid data sent by receiver. The sesssion then must be terminated. This message has the following payload:
error = {
kind: "internal" / "external",
? reason: text,
}
A message of type simulation-termination may be sent by the simulator to indicate it is forcibly terminating the simulation. This may be done for any reason, and especially once the goal of a problem is reached (see [Peforming grounded actions]). This message has the following payload:
simulation-termination = {
? reason: text,
}