Skip to content

RSP Latest Specification

Yoav Grimland edited this page Oct 14, 2024 · 31 revisions

This is the latest version of the RSP (Remote Simulator Protocol) protocol: version 1. For an introduction pertaining to the protocol's purpose, versioning guarantees, etc., see RSP Introduction. It is recommended you read what it before continuing here.

Summary

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.

Example

Note

You many benefit from first reading some parts of the full specification, as otherwise some parts here may not make sense.

Note

Additionally, this section assumes you can read CDDL well enough to understand the messages being sent. It suffices to say that it mostly looks like JSON, using ; for comments.

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 (agent) will first request a session setup, with the following request:

{ ; Agent to simulator (request)
    type: "session-setup-request",
    payload: 1,
}

Note the payload of this request. The simulator supports this version, and so will then give a session-setup response accordingly:

{ ; Simulator to agent (response)
    type: "session-setup-response",
    payload: true,
}

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:

{ ; Agent to simulator (request)
    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:

{ ; Simulator to agent (response)
    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:

{ ; Agent to simulator (request)
    type: "get-grounded-actions-request",
    payload: null
}

The simulator will then respond as expected:

{ ; Simulator to agent (response)
    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:

{ ; Agent to simulator (request)
    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:

{ ; Simulator to agent (response)
    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:

{ ; Agent to simulator (request)
    type: "perception-request",
    payload: null,
}

This is the environment state returned by the simulator:

{ ; Simulator to agent (response)
    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:

{ ; Agent to simulator (request)
    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:

{ ; Simulator to agent (termination message)
    type: "session-termination",
    payload: {
        reason: "problem solved",
    },
}

We should now close the communication channel.

Protocol sequence diagram

For the example above, we can expect a protocol sequence diagram like seen below.

sequenceDiagram
    participant Agent
    participant Simulator

    Agent->>Simulator: session-setup-request: 1
    activate Simulator
    Simulator->>Simulator: is_version_supported(): true
    Simulator-->>Agent: session-setup-response: true
    deactivate Simulator

    Agent->>Simulator: problem-setup-request
    activate Simulator
    Simulator->>Simulator: redact_hidden_info(pair_definition())
    Simulator-->>Agent: problem-setup-response: {domain: "..", problem: ".."}
    deactivate Simulator

    Agent->>Simulator: get-grounded-actions-request
    activate Simulator
    Simulator->>Simulator: get_grounded_actions()
    Simulator-->>Agent: get-grounded-actions-response: [{name: "move", grounding: ["a", "b"]}]
    deactivate Simulator

    Agent->>Simulator: perform-grounded-action-request: {name: "move", grounding: ["a", "b"]}
    activate Simulator
    Simulator->>Simulator: is_possible_grounded_action(..): true
    Simulator->>Simulator: apply_grounded_action(..)
    Simulator->>Simulator: is_pair_solved(): false
    Simulator-->>Agent: perform-grounded-action-response: 0
    deactivate Simulator

    Agent->>Simulator: perception-request
    activate Simulator
    Simulator->>Simulator: get_facts()
    Simulator->>Simulator: redact_hidden_facts(..)
    Simulator-->>Agent: perception-response: {"at": [..], "reachable": [..], "=": [..]}
    deactivate Simulator

    Agent->>Simulator: perform-grounded-action-request: {name: "move", grounding: ["b", "c"]}
    activate Simulator
    Simulator->>Simulator: is_possible_grounded_action(..): true
    Simulator->>Simulator: apply_grounded_action(..)
    Simulator->>Simulator: is_pair_solved(): true
    Simulator-->>Agent: session-termination: "problem solved"
    deactivate Simulator
Loading

Specification

Messages (invariant)

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.

Session setup (invariant)

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 = is-version-supported

is-version-supported = 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.

Session operation

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.

Problem setup

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.

Perception

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"]]}.

Get grounded actions

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

Goal tracking

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.

Perform grounded actions

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.

Termination messages (invariant)

The following messages messages all terminate the session. They are noncontextual, and so may be sent no matter the context, no matter the time.

Give up

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.

Internal error

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,
}

Simulation termination

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 Perform grounded action). This message has the following payload:

simulation-termination = {
    ? reason: text,
}

Clone this wiki locally