Repository: Tochemey/goakt
Pull request #175 (2023-11-12 09:56:52)

feat: add remote batch messaging

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/Tochemey/goakt/pull/175?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) Report
Attention: `244 lines` in your changes are missing coverage. Please review.
> Comparison is base [(`239798a`)](https://app.codecov.io/gh/Tochemey/goakt/commit/239798aa93769f96fb1abea2c89301e2c5a2a20e?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) 89.88% compared to head [(`fbdfa27`)](https://app.codecov.io/gh/Tochemey/goakt/pull/175?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) 84.80%.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #175      +/-   ##
==========================================
- Coverage   89.88%   84.80%   -5.08%     
==========================================
  Files          42       42              
  Lines        4132     4357     +225     
==========================================
- Hits         3714     3695      -19     
- Misses        335      579     +244     
  Partials       83       83              
```


| [Files](https://app.codecov.io/gh/Tochemey/goakt/pull/175?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene) | Coverage Δ | |
|---|---|---|
| [actors/pid.go](https://app.codecov.io/gh/Tochemey/goakt/pull/175?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-YWN0b3JzL3BpZC5nbw==) | `95.74% <ø> (-0.02%)` | :arrow_down: |
| [actors/actor\_system.go](https://app.codecov.io/gh/Tochemey/goakt/pull/175?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-YWN0b3JzL2FjdG9yX3N5c3RlbS5nbw==) | `75.44% <0.00%> (-14.38%)` | :arrow_down: |
| [actors/api.go](https://app.codecov.io/gh/Tochemey/goakt/pull/175?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene#diff-YWN0b3JzL2FwaS5nbw==) | `55.40% <0.00%> (-44.60%)` | :arrow_down: |


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/Tochemey/goakt/pull/175?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Arsene).


Patch:
diff --git a/Earthfile b/Earthfile
index 3b619bb..aa13ced 100644
--- a/Earthfile
+++ b/Earthfile
@@ -10,6 +10,7 @@ RUN apk --no-cache add git ca-certificates gcc musl-dev libc-dev binutils-gold
 pbs:
     BUILD +protogen
     BUILD +sample-pb
+    BUILD +testprotogen
 
 test:
   BUILD +lint
diff --git a/actors/actor_system.go b/actors/actor_system.go
index 741c3c3..a2fe580 100644
--- a/actors/actor_system.go
+++ b/actors/actor_system.go
@@ -683,18 +683,6 @@ func (x *actorSystem) RemoteLookup(_ context.Context, request *connect.Request[i
 		return nil, connect.NewError(connect.CodeFailedPrecondition, ErrRemotingDisabled)
 	}
 
-	// get the remoting server address
-	nodeAddr := fmt.Sprintf("%s:%d", x.remotingHost, x.remotingPort)
-
-	// let us validate the host and port
-	hostAndPort := fmt.Sprintf("%s:%d", reqCopy.GetHost(), reqCopy.GetPort())
-	if hostAndPort != nodeAddr {
-		// log the error
-		logger.Error(ErrInvalidNode.Message())
-		// here message is sent to the wrong actor system node
-		return nil, ErrInvalidNode
-	}
-
 	// construct the actor address
 	name := reqCopy.GetName()
 	actorPath := NewPath(name, NewAddress(x.Name(), reqCopy.GetHost(), int(reqCopy.GetPort())))
@@ -728,18 +716,6 @@ func (x *actorSystem) RemoteAsk(ctx context.Context, request *connect.Request[in
 		return nil, connect.NewError(connect.CodeFailedPrecondition, ErrRemotingDisabled)
 	}
 
-	// get the remoting server address
-	nodeAddr := fmt.Sprintf("%s:%d", x.remotingHost, x.remotingPort)
-
-	// let us validate the host and port
-	hostAndPort := fmt.Sprintf("%s:%d", reqCopy.GetRemoteMessage().GetReceiver().GetHost(), reqCopy.GetRemoteMessage().GetReceiver().GetPort())
-	if hostAndPort != nodeAddr {
-		// log the error
-		logger.Error(ErrInvalidNode.Message())
-		// here message is sent to the wrong actor system node
-		return nil, ErrInvalidNode
-	}
-
 	// construct the actor address
 	name := reqCopy.GetRemoteMessage().GetReceiver().GetName()
 	actorPath := NewPath(name, NewAddress(x.name, x.remotingHost, int(x.remotingPort)))
@@ -767,8 +743,6 @@ func (x *actorSystem) RemoteAsk(ctx context.Context, request *connect.Request[in
 }
 
 // RemoteTell is used to send a message to an actor remotely by another actor
-// This is the only way remote actors can interact with each other. The actor on the
-// other line can reply to the sender by using the Sender in the message
 func (x *actorSystem) RemoteTell(ctx context.Context, request *connect.Request[internalpb.RemoteTellRequest]) (*connect.Response[internalpb.RemoteTellResponse], error) {
 	// get a context log
 	logger := x.logger
@@ -782,18 +756,6 @@ func (x *actorSystem) RemoteTell(ctx context.Context, request *connect.Request[i
 		return nil, connect.NewError(connect.CodeFailedPrecondition, ErrRemotingDisabled)
 	}
 
-	// get the remoting server address
-	nodeAddr := fmt.Sprintf("%s:%d", x.remotingHost, x.remotingPort)
-
-	// let us validate the host and port
-	hostAndPort := fmt.Sprintf("%s:%d", receiver.GetHost(), receiver.GetPort())
-	if hostAndPort != nodeAddr {
-		// log the error
-		logger.Error(ErrInvalidNode.Message())
-		// here message is sent to the wrong actor system node
-		return nil, ErrInvalidNode
-	}
-
 	// construct the actor address
 	actorPath := NewPath(
 		receiver.GetName(),
@@ -819,6 +781,133 @@ func (x *actorSystem) RemoteTell(ctx context.Context, request *connect.Request[i
 	return connect.NewResponse(new(internalpb.RemoteTellResponse)), nil
 }
 
+// RemoteBatchTell is used to send a bulk of messages to an actor remotely by another actor.
+func (x *actorSystem) RemoteBatchTell(ctx context.Context, request *connect.Request[internalpb.RemoteBatchTellRequest]) (*connect.Response[internalpb.RemoteBatchTellResponse], error) {
+	// get a context log
+	logger := x.logger
+	// first let us make a copy of the incoming request
+	reqCopy := request.Msg
+
+	// get the receiver
+	receiver := reqCopy.GetReceiver()
+
+	// set the actor path with the remoting is enabled
+	if !x.remotingEnabled.Load() {
+		return nil, connect.NewError(connect.CodeFailedPrecondition, ErrRemotingDisabled)
+	}
+
+	// construct the actor address
+	actorPath := NewPath(
+		receiver.GetName(),
+		NewAddress(
+			x.Name(),
+			receiver.GetHost(),
+			int(receiver.GetPort())))
+	// start or get the PID of the actor
+	// check whether the given actor already exist in the system or not
+	pid, exist := x.actors.Get(actorPath)
+	// return an error when the remote address is not found
+	if !exist {
+		// log the error
+		logger.Error(ErrAddressNotFound(actorPath.String()).Error())
+		return nil, ErrAddressNotFound(actorPath.String())
+	}
+
+	// send the messages to the receiver mailbox
+	var messages []proto.Message
+	// let us unpack each message
+	for _, message := range reqCopy.GetMessages() {
+		// unmarshal the message
+		actual, err := message.UnmarshalNew()
+		// handle the error
+		if err != nil {
+			logger.Error(err)
+			return nil, connect.NewError(connect.CodeFailedPrecondition, err)
+		}
+		// add to the list of messages
+		messages = append(messages, actual)
+	}
+
+	// push the messages to the actor mailbox
+	if err := BatchTell(ctx, pid, messages...); err != nil {
+		// log the error
+		logger.Error(ErrRemoteSendFailure(err))
+		return nil, ErrRemoteSendFailure(err)
+	}
+
+	// return the rpc response
+	return connect.NewResponse(new(internalpb.RemoteBatchTellResponse)), nil
+}
+
+// RemoteBatchAsk is used to send a bulk messages to a remote actor with replies.
+// The replies are sent in the same order as the messages
+func (x *actorSystem) RemoteBatchAsk(ctx context.Context, request *connect.Request[internalpb.RemoteBatchAskRequest]) (*connect.Response[internalpb.RemoteBatchAskResponse], error) {
+	// get a context log
+	logger := x.logger
+	// first let us make a copy of the incoming request
+	reqCopy := request.Msg
+
+	// set the actor path with the remoting is enabled
+	if !x.remotingEnabled.Load() {
+		return nil, connect.NewError(connect.CodeFailedPrecondition, ErrRemotingDisabled)
+	}
+
+	// construct the actor address
+	name := reqCopy.GetReceiver().GetName()
+	actorPath := NewPath(name, NewAddress(x.name, x.remotingHost, int(x.remotingPort)))
+
+	// start or get the PID of the actor
+	// check whether the given actor already exist in the system or not
+	pid, exist := x.actors.Get(actorPath)
+	// return an error when the remote address is not found
+	if !exist {
+		// log the error
+		logger.Error(ErrAddressNotFound(actorPath.String()).Error())
+		return nil, ErrAddressNotFound(actorPath.String())
+	}
+
+	// send the messages to the receiver mailbox
+	var messages []proto.Message
+	// let us unpack each message
+	for _, message := range reqCopy.GetMessages() {
+		// unmarshal the message
+		actual, err := message.UnmarshalNew()
+		// handle the error
+		if err != nil {
+			logger.Error(err)
+			return nil, connect.NewError(connect.CodeFailedPrecondition, err)
+		}
+		// add to the list of messages
+		messages = append(messages, actual)
+	}
+
+	// push the messages to the actor mailbox and await replies
+	replies, err := BatchAsk(ctx, pid, x.replyTimeout, messages...)
+	// handle the error
+	if err != nil {
+		// log the error
+		logger.Error(ErrRemoteSendFailure(err))
+		return nil, ErrRemoteSendFailure(err)
+	}
+
+	// define a variable to hold responses as they trickle in
+	var responses []*anypb.Any
+	for reply := range replies {
+		// let us unpack the responses
+		actual, err := anypb.New(reply)
+		// handle the error
+		if err != nil {
+			// log the error
+			logger.Error(err)
+			return nil, connect.NewError(connect.CodeFailedPrecondition, err)
+		}
+		responses = append(responses, actual)
+	}
+
+	// return the rpc response
+	return connect.NewResponse(&internalpb.RemoteBatchAskResponse{Messages: responses}), nil
+}
+
 // handleRemoteAsk handles a synchronous message to another actor and expect a response.
 // This block until a response is received or timed out.
 func (x *actorSystem) handleRemoteAsk(ctx context.Context, to PID, message proto.Message) (response proto.Message, err error) {
diff --git a/actors/api.go b/actors/api.go
index f8f48a5..697920c 100644
--- a/actors/api.go
+++ b/actors/api.go
@@ -47,12 +47,6 @@ func Ask(ctx context.Context, to PID, message proto.Message, timeout time.Durati
 		return nil, ErrDead
 	}
 
-	// create a mutex
-	mu := sync.Mutex{}
-
-	// acquire a lock to set the message context
-	mu.Lock()
-
 	// create a receiver context
 	context := new(receiveContext)
 	// set the needed properties of the message context
@@ -84,9 +78,6 @@ func Ask(ctx context.Context, to PID, message proto.Message, timeout time.Durati
 	context.mu = sync.Mutex{}
 	context.response = make(chan proto.Message, 1)
 
-	// release the lock after config the message context
-	mu.Unlock()
-
 	// put the message context in the mailbox of the recipient actor
 	to.doReceive(context)
 
@@ -97,6 +88,9 @@ func Ask(ctx context.Context, to PID, message proto.Message, timeout time.Durati
 			return
 		case <-await:
 			err = ErrRequestTimeout
+			// push the message as a deadletter
+			to.emitDeadletter(context, err)
+			// stop the whole processing
 			return
 		}
 	}
@@ -109,11 +103,6 @@ func Tell(ctx context.Context, to PID, message proto.Message) error {
 		return ErrDead
 	}
 
-	// create a mutex
-	mu := sync.Mutex{}
-
-	// acquire a lock to set the message context
-	mu.Lock()
 	// create a message context
 	context := new(receiveContext)
 
@@ -149,15 +138,94 @@ func Tell(ctx context.Context, to PID, message proto.Message) error {
 	context.isAsyncMessage = true
 	context.mu = sync.Mutex{}
 
-	// release the lock after config the message context
-	mu.Unlock()
-
 	// put the message context in the mailbox of the recipient actor
 	to.doReceive(context)
 
 	return nil
 }
 
+// BatchTell sends bulk asynchronous messages to an actor
+func BatchTell(ctx context.Context, to PID, messages ...proto.Message) error {
+	// make sure the recipient actor is live
+	if !to.IsRunning() {
+		return ErrDead
+	}
+
+	// iterate the list messages
+	for i := 0; i < len(messages); i++ {
+		// create a message context
+		context := new(receiveContext)
+		// get the message
+		message := messages[i]
+		// set the needed properties of the message context
+		context.ctx = ctx
+		context.recipient = to
+		context.sendTime.Store(time.Now())
+		context.message = message
+		context.sender = NoSender
+		context.remoteSender = RemoteNoSender
+		// put the message context in the mailbox of the recipient actor
+		to.doReceive(context)
+	}
+	return nil
+}
+
+// BatchAsk sends a synchronous bunch of messages to the given PID and expect responses in the same order as the messages.
+// The messages will be processed one after the other in the order they are sent
+// This is a design choice to follow the simple principle of one message at a time processing by actors.
+func BatchAsk(ctx context.Context, to PID, timeout time.Duration, messages ...proto.Message) (responses chan proto.Message, err error) {
+	// make sure the actor is live
+	if !to.IsRunning() {
+		return nil, ErrDead
+	}
+
+	// create a buffered channel to hold the responses
+	responses = make(chan proto.Message, len(messages))
+
+	// close the responses channel when done
+	defer close(responses)
+
+	// let us process the messages one after the other
+	for i := 0; i < len(messages); i++ {
+		// grab the message at index i
+		message := messages[i]
+		// create a message context
+		messageContext := new(receiveContext)
+
+		// set the needed properties of the message context
+		messageContext.ctx = ctx
+		messageContext.sender = NoSender
+		messageContext.recipient = to
+		messageContext.message = message
+		messageContext.isAsyncMessage = false
+		messageContext.mu = sync.Mutex{}
+		messageContext.response = make(chan proto.Message, 1)
+		messageContext.sendTime.Store(time.Now())
+
+		// push the message to the actor mailbox
+		to.doReceive(messageContext)
+
+		// await patiently to receive the response from the actor
+	timerLoop:
+		for await := time.After(timeout); ; {
+			select {
+			case resp := <-messageContext.response:
+				// send the response to the responses channel
+				responses <- resp
+				break timerLoop
+			case <-await:
+				// set the request timeout error
+				err = ErrRequestTimeout
+				// push the message as a deadletter
+				to.emitDeadletter(messageContext, err)
+				// stop the whole processing
+				return
+			}
+		}
+	}
+	return
+}
+
 // RemoteTell sends a message to an actor remotely without expecting any reply
 func RemoteTell(ctx context.Context, to *addresspb.Address, message proto.Message) error {
 	// marshal the message
@@ -251,3 +319,80 @@ func RemoteLookup(ctx context.Context, host string, port int, name string) (addr
 	// return the response
 	return response.Msg.GetAddress(), nil
 }
+
+// RemoteBatchTell sends bulk asynchronous messages to an actor
+func RemoteBatchTell(ctx context.Context, to *addresspb.Address, messages ...proto.Message) error {
+	// define a variable holding the remote messages
+	var remoteMessages []*anypb.Any
+	// iterate the list of messages and pack them
+	for _, message := range messages {
+		// let us pack it
+		packed, err := anypb.New(message)
+		// handle the error
+		if err != nil {
+			return ErrInvalidRemoteMessage(err)
+		}
+		// add it to the list of remote messages
+		remoteMessages = append(remoteMessages, packed)
+	}
+
+	// create an instance of remote client service
+	remoteClient := internalpbconnect.NewRemotingServiceClient(
+		http.Client(),
+		http.URL(to.GetHost(), int(to.GetPort())),
+		connect.WithInterceptors(otelconnect.NewInterceptor()),
+		connect.WithGRPC(),
+	)
+
+	// prepare the remote batch tell request
+	request := connect.NewRequest(&internalpb.RemoteBatchTellRequest{
+		Messages: remoteMessages,
+		Sender:   RemoteNoSender,
+		Receiver: to,
+	})
+	// send the message and handle the error in case there is any
+	if _, err := remoteClient.RemoteBatchTell(ctx, request); err != nil {
+		return err
+	}
+	return nil
+}
+
+// RemoteBatchAsk sends bulk messages to an actor with responses expected
+func RemoteBatchAsk(ctx context.Context, to *addresspb.Address, messages ...proto.Message) (responses []*anypb.Any, err error) {
+	// define a variable holding the remote messages
+	var remoteMessages []*anypb.Any
+	// iterate the list of messages and pack them
+	for _, message := range messages {
+		// let us pack it
+		packed, err := anypb.New(message)
+		// handle the error
+		if err != nil {
+			return nil, ErrInvalidRemoteMessage(err)
+		}
+		// add it to the list of remote messages
+		remoteMessages = append(remoteMessages, packed)
+	}
+
+	// create an instance of remote client service
+	remoteClient := internalpbconnect.NewRemotingServiceClient(
+		http.Client(),
+		http.URL(to.GetHost(), int(to.GetPort())),
+		connect.WithInterceptors(otelconnect.NewInterceptor()),
+		connect.WithGRPC(),
+	)
+
+	// prepare the remote batch tell request
+	request := connect.NewRequest(&internalpb.RemoteBatchAskRequest{
+		Messages: remoteMessages,
+		Sender:   RemoteNoSender,
+		Receiver: to,
+	})
+	// send the message and handle the error in case there is any
+	response, err := remoteClient.RemoteBatchAsk(ctx, request)
+	// handle the error
+	if err != nil {
+		return nil, err
+	}
+	// return the responses
+	return response.Msg.GetMessages(), nil
+}
diff --git a/actors/pid.go b/actors/pid.go
index 7928b5a..920ef19 100644
--- a/actors/pid.go
+++ b/actors/pid.go
@@ -722,7 +722,6 @@ func (p *pid) RemoteTell(ctx context.Context, to *addresspb.Address, message pro
 			Receiver: to,
 			Message:  marshaled,
 		},
-		SendTime: timestamppb.Now(),
 	})
 
 	// add some debug logging
@@ -763,7 +762,6 @@ func (p *pid) RemoteAsk(ctx context.Context, to *addresspb.Address, message prot
 				Receiver: to,
 				Message:  marshaled,
 			},
-			SendTime: timestamppb.Now(),
 		})
 	// send the request
 	rpcResponse, rpcErr := remoteClient.RemoteAsk(ctx, rpcRequest)
diff --git a/internal/v1/internalpbconnect/remoting.connect.go b/internal/v1/internalpbconnect/remoting.connect.go
index 0870674..2f74096 100644
--- a/internal/v1/internalpbconnect/remoting.connect.go
+++ b/internal/v1/internalpbconnect/remoting.connect.go
@@ -42,20 +42,28 @@ const (
 	// RemotingServiceRemoteLookupProcedure is the fully-qualified name of the RemotingService's
 	// RemoteLookup RPC.
 	RemotingServiceRemoteLookupProcedure = "/internal.v1.RemotingService/RemoteLookup"
+	// RemotingServiceRemoteBatchTellProcedure is the fully-qualified name of the RemotingService's
+	// RemoteBatchTell RPC.
+	RemotingServiceRemoteBatchTellProcedure = "/internal.v1.RemotingService/RemoteBatchTell"
+	// RemotingServiceRemoteBatchAskProcedure is the fully-qualified name of the RemotingService's
+	// RemoteBatchAsk RPC.
+	RemotingServiceRemoteBatchAskProcedure = "/internal.v1.RemotingService/RemoteBatchAsk"
 )
 
 // RemotingServiceClient is a client for the internal.v1.RemotingService service.
 type RemotingServiceClient interface {
-	// RemoteAsk is used to send a message to an actor remotely and expect a response
-	// immediately. With this type of message the receiver cannot communicate back to Sender
-	// except reply the message with a response. This one-way communication
+	// RemoteAsk is used to send a message to an actor remotely and expect a response immediately.
 	RemoteAsk(context.Context, *connect.Request[v1.RemoteAskRequest]) (*connect.Response[v1.RemoteAskResponse], error)
-	// RemoteTell is used to send a message to an actor remotely by another actor
-	// This is the only way remote actors can interact with each other. The actor on the
-	// other line can reply to the sender by using the Sender in the message
+	// RemoteTell is used to send a message to a remote actor
+	// The actor on the other line can reply to the sender by using the Sender in the message
 	RemoteTell(context.Context, *connect.Request[v1.RemoteTellRequest]) (*connect.Response[v1.RemoteTellResponse], error)
 	// Lookup for an actor on a remote host.
 	RemoteLookup(context.Context, *connect.Request[v1.RemoteLookupRequest]) (*connect.Response[v1.RemoteLookupResponse], error)
+	// RemoteBatchTell is used to send a bulk of messages to a remote actor
+	RemoteBatchTell(context.Context, *connect.Request[v1.RemoteBatchTellRequest]) (*connect.Response[v1.RemoteBatchTellResponse], error)
+	// RemoteBatchAsk is used to send a bulk messages to a remote actor with replies.
+	// The replies are sent in the same order as the messages
+	RemoteBatchAsk(context.Context, *connect.Request[v1.RemoteBatchAskRequest]) (*connect.Response[v1.RemoteBatchAskResponse], error)
 }
 
 // NewRemotingServiceClient constructs a client for the internal.v1.RemotingService service. By
@@ -83,14 +91,26 @@ func NewRemotingServiceClient(httpClient connect.HTTPClient, baseURL string, opt
 			baseURL+RemotingServiceRemoteLookupProcedure,
 			opts...,
 		),
+		remoteBatchTell: connect.NewClient[v1.RemoteBatchTellRequest, v1.RemoteBatchTellResponse](
+			httpClient,
+			baseURL+RemotingServiceRemoteBatchTellProcedure,
+			opts...,
+		),
+		remoteBatchAsk: connect.NewClient[v1.RemoteBatchAskRequest, v1.RemoteBatchAskResponse](
+			httpClient,
+			baseURL+RemotingServiceRemoteBatchAskProcedure,
+			opts...,
+		),
 	}
 }
 
 // remotingServiceClient implements RemotingServiceClient.
 type remotingServiceClient struct {
-	remoteAsk    *connect.Client[v1.RemoteAskRequest, v1.RemoteAskResponse]
-	remoteTell   *connect.Client[v1.RemoteTellRequest, v1.RemoteTellResponse]
-	remoteLookup *connect.Client[v1.RemoteLookupRequest, v1.RemoteLookupResponse]
+	remoteAsk       *connect.Client[v1.RemoteAskRequest, v1.RemoteAskResponse]
+	remoteTell      *connect.Client[v1.RemoteTellRequest, v1.RemoteTellResponse]
+	remoteLookup    *connect.Client[v1.RemoteLookupRequest, v1.RemoteLookupResponse]
+	remoteBatchTell *connect.Client[v1.RemoteBatchTellRequest, v1.RemoteBatchTellResponse]
+	remoteBatchAsk  *connect.Client[v1.RemoteBatchAskRequest, v1.RemoteBatchAskResponse]
 }
 
 // RemoteAsk calls internal.v1.RemotingService.RemoteAsk.
@@ -108,18 +128,30 @@ func (c *remotingServiceClient) RemoteLookup(ctx context.Context, req *connect.R
 	return c.remoteLookup.CallUnary(ctx, req)
 }
 
+// RemoteBatchTell calls internal.v1.RemotingService.RemoteBatchTell.
+func (c *remotingServiceClient) RemoteBatchTell(ctx context.Context, req *connect.Request[v1.RemoteBatchTellRequest]) (*connect.Response[v1.RemoteBatchTellResponse], error) {
+	return c.remoteBatchTell.CallUnary(ctx, req)
+}
+
+// RemoteBatchAsk calls internal.v1.RemotingService.RemoteBatchAsk.
+func (c *remotingServiceClient) RemoteBatchAsk(ctx context.Context, req *connect.Request[v1.RemoteBatchAskRequest]) (*connect.Response[v1.RemoteBatchAskResponse], error) {
+	return c.remoteBatchAsk.CallUnary(ctx, req)
+}
+
 // RemotingServiceHandler is an implementation of the internal.v1.RemotingService service.
 type RemotingServiceHandler interface {
-	// RemoteAsk is used to send a message to an actor remotely and expect a response
-	// immediately. With this type of message the receiver cannot communicate back to Sender
-	// except reply the message with a response. This one-way communication
+	// RemoteAsk is used to send a message to an actor remotely and expect a response immediately.
 	RemoteAsk(context.Context, *connect.Request[v1.RemoteAskRequest]) (*connect.Response[v1.RemoteAskResponse], error)
-	// RemoteTell is used to send a message to an actor remotely by another actor
-	// This is the only way remote actors can interact with each other. The actor on the
-	// other line can reply to the sender by using the Sender in the message
+	// RemoteTell is used to send a message to a remote actor
+	// The actor on the other line can reply to the sender by using the Sender in the message
 	RemoteTell(context.Context, *connect.Request[v1.RemoteTellRequest]) (*connect.Response[v1.RemoteTellResponse], error)
 	// Lookup for an actor on a remote host.
 	RemoteLookup(context.Context, *connect.Request[v1.RemoteLookupRequest]) (*connect.Response[v1.RemoteLookupResponse], error)
+	// RemoteBatchTell is used to send a bulk of messages to a remote actor
+	RemoteBatchTell(context.Context, *connect.Request[v1.RemoteBatchTellRequest]) (*connect.Response[v1.RemoteBatchTellResponse], error)
+	// RemoteBatchAsk is used to send a bulk messages to a remote actor with replies.
+	// The replies are sent in the same order as the messages
+	RemoteBatchAsk(context.Context, *connect.Request[v1.RemoteBatchAskRequest]) (*connect.Response[v1.RemoteBatchAskResponse], error)
 }
 
 // NewRemotingServiceHandler builds an HTTP handler from the service implementation. It returns the
@@ -143,6 +175,16 @@ func NewRemotingServiceHandler(svc RemotingServiceHandler, opts ...connect.Handl
 		svc.RemoteLookup,
 		opts...,
 	)
+	remotingServiceRemoteBatchTellHandler := connect.NewUnaryHandler(
+		RemotingServiceRemoteBatchTellProcedure,
+		svc.RemoteBatchTell,
+		opts...,
+	)
+	remotingServiceRemoteBatchAskHandler := connect.NewUnaryHandler(
+		RemotingServiceRemoteBatchAskProcedure,
+		svc.RemoteBatchAsk,
+		opts...,
+	)
 	return "/internal.v1.RemotingService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		switch r.URL.Path {
 		case RemotingServiceRemoteAskProcedure:
@@ -151,6 +193,10 @@ func NewRemotingServiceHandler(svc RemotingServiceHandler, opts ...connect.Handl
 			remotingServiceRemoteTellHandler.ServeHTTP(w, r)
 		case RemotingServiceRemoteLookupProcedure:
 			remotingServiceRemoteLookupHandler.ServeHTTP(w, r)
+		case RemotingServiceRemoteBatchTellProcedure:
+			remotingServiceRemoteBatchTellHandler.ServeHTTP(w, r)
+		case RemotingServiceRemoteBatchAskProcedure:
+			remotingServiceRemoteBatchAskHandler.ServeHTTP(w, r)
 		default:
 			http.NotFound(w, r)
 		}
@@ -171,3 +217,11 @@ func (UnimplementedRemotingServiceHandler) RemoteTell(context.Context, *connect.
 func (UnimplementedRemotingServiceHandler) RemoteLookup(context.Context, *connect.Request[v1.RemoteLookupRequest]) (*connect.Response[v1.RemoteLookupResponse], error) {
 	return nil, connect.NewError(connect.CodeUnimplemented, errors.New("internal.v1.RemotingService.RemoteLookup is not implemented"))
 }
+
+func (UnimplementedRemotingServiceHandler) RemoteBatchTell(context.Context, *connect.Request[v1.RemoteBatchTellRequest]) (*connect.Response[v1.RemoteBatchTellResponse], error) {
+	return nil, connect.NewError(connect.CodeUnimplemented, errors.New("internal.v1.RemotingService.RemoteBatchTell is not implemented"))
+}
+
+func (UnimplementedRemotingServiceHandler) RemoteBatchAsk(context.Context, *connect.Request[v1.RemoteBatchAskRequest]) (*connect.Response[v1.RemoteBatchAskResponse], error) {
+	return nil, connect.NewError(connect.CodeUnimplemented, errors.New("internal.v1.RemotingService.RemoteBatchAsk is not implemented"))
+}
diff --git a/internal/v1/nats.pb.go b/internal/v1/nats.pb.go
index 7e54185..4aff810 100644
--- a/internal/v1/nats.pb.go
+++ b/internal/v1/nats.pb.go
@@ -7,11 +7,8 @@
 package internalpb
 
 import (
-	_ "github.com/tochemey/goakt/pb/address/v1"
 	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
 	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
-	_ "google.golang.org/protobuf/types/known/anypb"
-	_ "google.golang.org/protobuf/types/known/timestamppb"
 	reflect "reflect"
 	sync "sync"
 )
@@ -158,41 +155,35 @@ var File_internal_v1_nats_proto protoreflect.FileDescriptor
 var file_internal_v1_nats_proto_rawDesc = []byte{
 	0x0a, 0x16, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x61,
 	0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
-	0x61, 0x6c, 0x2e, 0x76, 0x31, 0x1a, 0x18, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2f, 0x76,
-	0x31, 0x2f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a,
-	0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
-	0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67,
-	0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65,
-	0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x8a, 0x01, 0x0a, 0x0b,
-	0x4e, 0x61, 0x74, 0x73, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68,
-	0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12,
-	0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70,
-	0x6f, 0x72, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3f, 0x0a, 0x0c, 0x6d, 0x65, 0x73, 0x73, 0x61,
-	0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e,
-	0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x61, 0x74, 0x73,
-	0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x6d, 0x65, 0x73,
-	0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x2a, 0x92, 0x01, 0x0a, 0x0f, 0x4e, 0x61, 0x74,
-	0x73, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x1a,
-	0x4e, 0x41, 0x54, 0x53, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50,
-	0x45, 0x5f, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x10, 0x00, 0x12, 0x20, 0x0a, 0x1c,
-	0x4e, 0x41, 0x54, 0x53, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50,
-	0x45, 0x5f, 0x44, 0x45, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x10, 0x01, 0x12, 0x1d,
-	0x0a, 0x19, 0x4e, 0x41, 0x54, 0x53, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54,
-	0x59, 0x50, 0x45, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x45, 0x53, 0x54, 0x10, 0x02, 0x12, 0x1e, 0x0a,
-	0x1a, 0x4e, 0x41, 0x54, 0x53, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59,
-	0x50, 0x45, 0x5f, 0x52, 0x45, 0x53, 0x50, 0x4f, 0x4e, 0x53, 0x45, 0x10, 0x03, 0x42, 0x9d, 0x01,
-	0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x76,
-	0x31, 0x42, 0x09, 0x4e, 0x61, 0x74, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x48, 0x02, 0x50, 0x01,
-	0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x6f, 0x63,
-	0x68, 0x65, 0x6d, 0x65, 0x79, 0x2f, 0x67, 0x6f, 0x61, 0x6b, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65,
-	0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x76, 0x31, 0x3b, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
-	0x70, 0x62, 0xa2, 0x02, 0x03, 0x49, 0x58, 0x58, 0xaa, 0x02, 0x0b, 0x49, 0x6e, 0x74, 0x65, 0x72,
-	0x6e, 0x61, 0x6c, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0b, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61,
-	0x6c, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x17, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5c,
-	0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02,
-	0x0c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x61, 0x6c, 0x2e, 0x76, 0x31, 0x22, 0x8a, 0x01, 0x0a, 0x0b, 0x4e, 0x61, 0x74, 0x73, 0x4d, 0x65,
+	0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72,
+	0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x12, 0x0a,
+	0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d,
+	0x65, 0x12, 0x3f, 0x0a, 0x0c, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70,
+	0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
+	0x61, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x61, 0x74, 0x73, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67,
+	0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79,
+	0x70, 0x65, 0x2a, 0x92, 0x01, 0x0a, 0x0f, 0x4e, 0x61, 0x74, 0x73, 0x4d, 0x65, 0x73, 0x73, 0x61,
+	0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x1a, 0x4e, 0x41, 0x54, 0x53, 0x5f, 0x4d,
+	0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x45, 0x47, 0x49,
+	0x53, 0x54, 0x45, 0x52, 0x10, 0x00, 0x12, 0x20, 0x0a, 0x1c, 0x4e, 0x41, 0x54, 0x53, 0x5f, 0x4d,
+	0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x45, 0x52, 0x45,
+	0x47, 0x49, 0x53, 0x54, 0x45, 0x52, 0x10, 0x01, 0x12, 0x1d, 0x0a, 0x19, 0x4e, 0x41, 0x54, 0x53,
+	0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x45,
+	0x51, 0x55, 0x45, 0x53, 0x54, 0x10, 0x02, 0x12, 0x1e, 0x0a, 0x1a, 0x4e, 0x41, 0x54, 0x53, 0x5f,
+	0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x45, 0x53,
+	0x50, 0x4f, 0x4e, 0x53, 0x45, 0x10, 0x03, 0x42, 0x9d, 0x01, 0x0a, 0x0f, 0x63, 0x6f, 0x6d, 0x2e,
+	0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x76, 0x31, 0x42, 0x09, 0x4e, 0x61, 0x74,
+	0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x48, 0x02, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68,
+	0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x6f, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x79, 0x2f,
+	0x67, 0x6f, 0x61, 0x6b, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x76,
+	0x31, 0x3b, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x70, 0x62, 0xa2, 0x02, 0x03, 0x49,
+	0x58, 0x58, 0xaa, 0x02, 0x0b, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x56, 0x31,
+	0xca, 0x02, 0x0b, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5c, 0x56, 0x31, 0xe2, 0x02,
+	0x17, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42,
+	0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x0c, 0x49, 0x6e, 0x74, 0x65, 0x72,
+	0x6e, 0x61, 0x6c, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
diff --git a/internal/v1/remoting.pb.go b/internal/v1/remoting.pb.go
index 8859639..da506b4 100644
--- a/internal/v1/remoting.pb.go
+++ b/internal/v1/remoting.pb.go
@@ -11,7 +11,6 @@ import (
 	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
 	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
 	anypb "google.golang.org/protobuf/types/known/anypb"
-	timestamppb "google.golang.org/protobuf/types/known/timestamppb"
 	reflect "reflect"
 	sync "sync"
 )
@@ -24,8 +23,7 @@ const (
 )
 
 // RemoteAsk is used to send a message to an actor remotely and expect a response
-// immediately. With this type of message the receiver cannot communicate back to Sender
-// except reply the message with a response. This one-way communication
+// immediately.
 type RemoteAskRequest struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -33,8 +31,6 @@ type RemoteAskRequest struct {
 
 	// Specifies the remote message to send
 	RemoteMessage *RemoteMessage `protobuf:"bytes,1,opt,name=remote_message,json=remoteMessage,proto3" json:"remote_message,omitempty"`
-	// Specifies the send time
-	SendTime *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=send_time,json=sendTime,proto3" json:"send_time,omitempty"`
 }
 
 func (x *RemoteAskRequest) Reset() {
@@ -76,13 +72,6 @@ func (x *RemoteAskRequest) GetRemoteMessage() *RemoteMessage {
 	return nil
 }
 
-func (x *RemoteAskRequest) GetSendTime() *timestamppb.Timestamp {
-	if x != nil {
-		return x.SendTime
-	}
-	return nil
-}
-
 type RemoteAskResponse struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -132,9 +121,7 @@ func (x *RemoteAskResponse) GetMessage() *anypb.Any {
 	return nil
 }
 
-// RemoteTell is used to send a message to an actor remotely by another actor
-// This is the only way remote actors can interact with each other. The actor on the
-// other line can reply to the sender by using the Sender in the message
+// RemoteTell is used to send a message to an actor remotely
 type RemoteTellRequest struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -142,8 +129,6 @@ type RemoteTellRequest struct {
 
 	// Specifies the remote message to send
 	RemoteMessage *RemoteMessage `protobuf:"bytes,1,opt,name=remote_message,json=remoteMessage,proto3" json:"remote_message,omitempty"`
-	// Specifies the send time
-	SendTime *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=send_time,json=sendTime,proto3" json:"send_time,omitempty"`
 }
 
 func (x *RemoteTellRequest) Reset() {
@@ -185,13 +170,6 @@ func (x *RemoteTellRequest) GetRemoteMessage() *RemoteMessage {
 	return nil
 }
 
-func (x *RemoteTellRequest) GetSendTime() *timestamppb.Timestamp {
-	if x != nil {
-		return x.SendTime
-	}
-	return nil
-}
-
 type RemoteTellResponse struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -413,6 +391,225 @@ func (x *RemoteMessage) GetMessage() *anypb.Any {
 	return nil
 }
 
+type RemoteBatchTellRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Specifies the remote message to send
+	Messages []*anypb.Any `protobuf:"bytes,1,rep,name=messages,proto3" json:"messages,omitempty"`
+	// Specifies the sender' address
+	Sender *v1.Address `protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty"`
+	// Specifies the actor address
+	Receiver *v1.Address `protobuf:"bytes,3,opt,name=receiver,proto3" json:"receiver,omitempty"`
+}
+
+func (x *RemoteBatchTellRequest) Reset() {
+	*x = RemoteBatchTellRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_internal_v1_remoting_proto_msgTypes[7]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *RemoteBatchTellRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*RemoteBatchTellRequest) ProtoMessage() {}
+
+func (x *RemoteBatchTellRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_internal_v1_remoting_proto_msgTypes[7]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use RemoteBatchTellRequest.ProtoReflect.Descriptor instead.
+func (*RemoteBatchTellRequest) Descriptor() ([]byte, []int) {
+	return file_internal_v1_remoting_proto_rawDescGZIP(), []int{7}
+}
+
+func (x *RemoteBatchTellRequest) GetMessages() []*anypb.Any {
+	if x != nil {
+		return x.Messages
+	}
+	return nil
+}
+
+func (x *RemoteBatchTellRequest) GetSender() *v1.Address {
+	if x != nil {
+		return x.Sender
+	}
+	return nil
+}
+
+func (x *RemoteBatchTellRequest) GetReceiver() *v1.Address {
+	if x != nil {
+		return x.Receiver
+	}
+	return nil
+}
+
+type RemoteBatchTellResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *RemoteBatchTellResponse) Reset() {
+	*x = RemoteBatchTellResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_internal_v1_remoting_proto_msgTypes[8]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *RemoteBatchTellResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*RemoteBatchTellResponse) ProtoMessage() {}
+
+func (x *RemoteBatchTellResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_internal_v1_remoting_proto_msgTypes[8]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use RemoteBatchTellResponse.ProtoReflect.Descriptor instead.
+func (*RemoteBatchTellResponse) Descriptor() ([]byte, []int) {
+	return file_internal_v1_remoting_proto_rawDescGZIP(), []int{8}
+}
+
+type RemoteBatchAskRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Specifies the remote message to send
+	Messages []*anypb.Any `protobuf:"bytes,1,rep,name=messages,proto3" json:"messages,omitempty"`
+	// Specifies the sender' address
+	Sender *v1.Address `protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty"`
+	// Specifies the actor address
+	Receiver *v1.Address `protobuf:"bytes,3,opt,name=receiver,proto3" json:"receiver,omitempty"`
+}
+
+func (x *RemoteBatchAskRequest) Reset() {
+	*x = RemoteBatchAskRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_internal_v1_remoting_proto_msgTypes[9]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *RemoteBatchAskRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*RemoteBatchAskRequest) ProtoMessage() {}
+
+func (x *RemoteBatchAskRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_internal_v1_remoting_proto_msgTypes[9]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use RemoteBatchAskRequest.ProtoReflect.Descriptor instead.
+func (*RemoteBatchAskRequest) Descriptor() ([]byte, []int) {
+	return file_internal_v1_remoting_proto_rawDescGZIP(), []int{9}
+}
+
+func (x *RemoteBatchAskRequest) GetMessages() []*anypb.Any {
+	if x != nil {
+		return x.Messages
+	}
+	return nil
+}
+
+func (x *RemoteBatchAskRequest) GetSender() *v1.Address {
+	if x != nil {
+		return x.Sender
+	}
+	return nil
+}
+
+func (x *RemoteBatchAskRequest) GetReceiver() *v1.Address {
+	if x != nil {
+		return x.Receiver
+	}
+	return nil
+}
+
+type RemoteBatchAskResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Specifies the message to send to the actor
+	// Any proto message is allowed to be sent
+	Messages []*anypb.Any `protobuf:"bytes,1,rep,name=messages,proto3" json:"messages,omitempty"`
+}
+
+func (x *RemoteBatchAskResponse) Reset() {
+	*x = RemoteBatchAskResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_internal_v1_remoting_proto_msgTypes[10]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *RemoteBatchAskResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*RemoteBatchAskResponse) ProtoMessage() {}
+
+func (x *RemoteBatchAskResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_internal_v1_remoting_proto_msgTypes[10]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use RemoteBatchAskResponse.ProtoReflect.Descriptor instead.
+func (*RemoteBatchAskResponse) Descriptor() ([]byte, []int) {
+	return file_internal_v1_remoting_proto_rawDescGZIP(), []int{10}
+}
+
+func (x *RemoteBatchAskResponse) GetMessages() []*anypb.Any {
+	if x != nil {
+		return x.Messages
+	}
+	return nil
+}
+
 var File_internal_v1_remoting_proto protoreflect.FileDescriptor
 
 var file_internal_v1_remoting_proto_rawDesc = []byte{
@@ -421,69 +618,99 @@ var file_internal_v1_remoting_proto_rawDesc = []byte{
 	0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x76, 0x31, 0x1a, 0x18, 0x61, 0x64, 0x64, 0x72, 0x65,
 	0x73, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x70, 0x72,
 	0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f,
-	0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f,
-	0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
-	0x8e, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41, 0x73, 0x6b, 0x52, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x12, 0x41, 0x0a, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6d,
-	0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x69,
+	0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x55,
+	0x0a, 0x10, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x12, 0x41, 0x0a, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6d, 0x65, 0x73,
+	0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x69, 0x6e, 0x74,
+	0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4d,
+	0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4d, 0x65,
+	0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x43, 0x0a, 0x11, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41,
+	0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x6d, 0x65,
+	0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f,
+	0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e,
+	0x79, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x56, 0x0a, 0x11, 0x52, 0x65,
+	0x6d, 0x6f, 0x74, 0x65, 0x54, 0x65, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
+	0x41, 0x0a, 0x0e, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
+	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
+	0x61, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4d, 0x65, 0x73, 0x73,
+	0x61, 0x67, 0x65, 0x52, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61,
+	0x67, 0x65, 0x22, 0x14, 0x0a, 0x12, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x54, 0x65, 0x6c, 0x6c,
+	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x0a, 0x13, 0x52, 0x65, 0x6d, 0x6f,
+	0x74, 0x65, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
+	0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68,
+	0x6f, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x05, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18,
+	0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x45, 0x0a, 0x14, 0x52,
+	0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f,
+	0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x76,
+	0x31, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65,
+	0x73, 0x73, 0x22, 0x9d, 0x01, 0x0a, 0x0d, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4d, 0x65, 0x73,
+	0x73, 0x61, 0x67, 0x65, 0x12, 0x2b, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x76,
+	0x31, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65,
+	0x72, 0x12, 0x2f, 0x0a, 0x08, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x76, 0x31,
+	0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x08, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76,
+	0x65, 0x72, 0x12, 0x2e, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61,
+	0x67, 0x65, 0x22, 0xa8, 0x01, 0x0a, 0x16, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, 0x74,
+	0x63, 0x68, 0x54, 0x65, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x30, 0x0a,
+	0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
+	0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
+	0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x12,
+	0x2b, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x13, 0x2e, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64,
+	0x72, 0x65, 0x73, 0x73, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x08,
+	0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13,
+	0x2e, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x72,
+	0x65, 0x73, 0x73, 0x52, 0x08, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x22, 0x19, 0x0a,
+	0x17, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, 0x74, 0x63, 0x68, 0x54, 0x65, 0x6c, 0x6c,
+	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xa7, 0x01, 0x0a, 0x15, 0x52, 0x65, 0x6d,
+	0x6f, 0x74, 0x65, 0x42, 0x61, 0x74, 0x63, 0x68, 0x41, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x12, 0x30, 0x0a, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01,
+	0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x73, 0x73,
+	0x61, 0x67, 0x65, 0x73, 0x12, 0x2b, 0x0a, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x76,
+	0x31, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x06, 0x73, 0x65, 0x6e, 0x64, 0x65,
+	0x72, 0x12, 0x2f, 0x0a, 0x08, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x18, 0x03, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x76, 0x31,
+	0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x08, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76,
+	0x65, 0x72, 0x22, 0x4a, 0x0a, 0x16, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, 0x74, 0x63,
+	0x68, 0x41, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x08,
+	0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14,
+	0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
+	0x2e, 0x41, 0x6e, 0x79, 0x52, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x32, 0xba,
+	0x03, 0x0a, 0x0f, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69,
+	0x63, 0x65, 0x12, 0x4a, 0x0a, 0x09, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41, 0x73, 0x6b, 0x12,
+	0x1d, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65,
+	0x6d, 0x6f, 0x74, 0x65, 0x41, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e,
+	0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d,
+	0x6f, 0x74, 0x65, 0x41, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d,
+	0x0a, 0x0a, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x54, 0x65, 0x6c, 0x6c, 0x12, 0x1e, 0x2e, 0x69,
+	0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74,
+	0x65, 0x54, 0x65, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x69,
 	0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74,
-	0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65,
-	0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x37, 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x64, 0x5f,
-	0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
-	0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d,
-	0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x73, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65,
-	0x22, 0x43, 0x0a, 0x11, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41, 0x73, 0x6b, 0x52, 0x65, 0x73,
-	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x07, 0x6d, 0x65,
-	0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x8f, 0x01, 0x0a, 0x11, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65,
-	0x54, 0x65, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x41, 0x0a, 0x0e, 0x72,
-	0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x76,
-	0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52,
-	0x0d, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x37,
-	0x0a, 0x09, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
-	0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x73,
-	0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x14, 0x0a, 0x12, 0x52, 0x65, 0x6d, 0x6f, 0x74,
-	0x65, 0x54, 0x65, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x51, 0x0a,
-	0x13, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74,
-	0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x12, 0x0a, 0x04,
-	0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
-	0x22, 0x45, 0x0a, 0x14, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70,
-	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72,
-	0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x61, 0x64, 0x64, 0x72,
-	0x65, 0x73, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07,
-	0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x9d, 0x01, 0x0a, 0x0d, 0x52, 0x65, 0x6d, 0x6f,
-	0x74, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2b, 0x0a, 0x06, 0x73, 0x65, 0x6e,
-	0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x61, 0x64, 0x64, 0x72,
-	0x65, 0x73, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x06,
-	0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x2f, 0x0a, 0x08, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76,
-	0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x61, 0x64, 0x64, 0x72, 0x65,
-	0x73, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x08, 0x72,
-	0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x12, 0x2e, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61,
-	0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
-	0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x07,
-	0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x32, 0x81, 0x02, 0x0a, 0x0f, 0x52, 0x65, 0x6d, 0x6f,
-	0x74, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x09, 0x52,
-	0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41, 0x73, 0x6b, 0x12, 0x1d, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72,
-	0x6e, 0x61, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41, 0x73, 0x6b,
-	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
-	0x61, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41, 0x73, 0x6b, 0x52,
-	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0a, 0x52, 0x65, 0x6d, 0x6f, 0x74,
-	0x65, 0x54, 0x65, 0x6c, 0x6c, 0x12, 0x1e, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
-	0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x54, 0x65, 0x6c, 0x6c, 0x52, 0x65,
-	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
-	0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x54, 0x65, 0x6c, 0x6c, 0x52, 0x65,
-	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x0c, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65,
-	0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x12, 0x20, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61,
-	0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4c, 0x6f, 0x6f, 0x6b, 0x75,
-	0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72,
-	0x6e, 0x61, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4c, 0x6f, 0x6f,
-	0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xa1, 0x01, 0x0a, 0x0f,
+	0x65, 0x54, 0x65, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a,
+	0x0c, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x12, 0x20, 0x2e,
+	0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f,
+	0x74, 0x65, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
+	0x21, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65,
+	0x6d, 0x6f, 0x74, 0x65, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
+	0x73, 0x65, 0x12, 0x5c, 0x0a, 0x0f, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, 0x74, 0x63,
+	0x68, 0x54, 0x65, 0x6c, 0x6c, 0x12, 0x23, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
+	0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, 0x74, 0x63, 0x68, 0x54,
+	0x65, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x69, 0x6e, 0x74,
+	0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42,
+	0x61, 0x74, 0x63, 0x68, 0x54, 0x65, 0x6c, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+	0x12, 0x59, 0x0a, 0x0e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, 0x74, 0x63, 0x68, 0x41,
+	0x73, 0x6b, 0x12, 0x22, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x76, 0x31,
+	0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, 0x74, 0x63, 0x68, 0x41, 0x73, 0x6b, 0x52,
+	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61,
+	0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x42, 0x61, 0x74, 0x63, 0x68,
+	0x41, 0x73, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xa1, 0x01, 0x0a, 0x0f,
 	0x63, 0x6f, 0x6d, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x76, 0x31, 0x42,
 	0x0d, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x48, 0x02,
 	0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74,
@@ -509,40 +736,52 @@ func file_internal_v1_remoting_proto_rawDescGZIP() []byte {
 	return file_internal_v1_remoting_proto_rawDescData
 }
 
-var file_internal_v1_remoting_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
+var file_internal_v1_remoting_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
 var file_internal_v1_remoting_proto_goTypes = []interface{}{
-	(*RemoteAskRequest)(nil),      // 0: internal.v1.RemoteAskRequest
-	(*RemoteAskResponse)(nil),     // 1: internal.v1.RemoteAskResponse
-	(*RemoteTellRequest)(nil),     // 2: internal.v1.RemoteTellRequest
-	(*RemoteTellResponse)(nil),    // 3: internal.v1.RemoteTellResponse
-	(*RemoteLookupRequest)(nil),   // 4: internal.v1.RemoteLookupRequest
-	(*RemoteLookupResponse)(nil),  // 5: internal.v1.RemoteLookupResponse
-	(*RemoteMessage)(nil),         // 6: internal.v1.RemoteMessage
-	(*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp
-	(*anypb.Any)(nil),             // 8: google.protobuf.Any
-	(*v1.Address)(nil),            // 9: address.v1.Address
+	(*RemoteAskRequest)(nil),        // 0: internal.v1.RemoteAskRequest
+	(*RemoteAskResponse)(nil),       // 1: internal.v1.RemoteAskResponse
+	(*RemoteTellRequest)(nil),       // 2: internal.v1.RemoteTellRequest
+	(*RemoteTellResponse)(nil),      // 3: internal.v1.RemoteTellResponse
+	(*RemoteLookupRequest)(nil),     // 4: internal.v1.RemoteLookupRequest
+	(*RemoteLookupResponse)(nil),    // 5: internal.v1.RemoteLookupResponse
+	(*RemoteMessage)(nil),           // 6: internal.v1.RemoteMessage
+	(*RemoteBatchTellRequest)(nil),  // 7: internal.v1.RemoteBatchTellRequest
+	(*RemoteBatchTellResponse)(nil), // 8: internal.v1.RemoteBatchTellResponse
+	(*RemoteBatchAskRequest)(nil),   // 9: internal.v1.RemoteBatchAskRequest
+	(*RemoteBatchAskResponse)(nil),  // 10: internal.v1.RemoteBatchAskResponse
+	(*anypb.Any)(nil),               // 11: google.protobuf.Any
+	(*v1.Address)(nil),              // 12: address.v1.Address
 }
 var file_internal_v1_remoting_proto_depIdxs = []int32{
 	6,  // 0: internal.v1.RemoteAskRequest.remote_message:type_name -> internal.v1.RemoteMessage
-	7,  // 1: internal.v1.RemoteAskRequest.send_time:type_name -> google.protobuf.Timestamp
-	8,  // 2: internal.v1.RemoteAskResponse.message:type_name -> google.protobuf.Any
-	6,  // 3: internal.v1.RemoteTellRequest.remote_message:type_name -> internal.v1.RemoteMessage
-	7,  // 4: internal.v1.RemoteTellRequest.send_time:type_name -> google.protobuf.Timestamp
-	9,  // 5: internal.v1.RemoteLookupResponse.address:type_name -> address.v1.Address
-	9,  // 6: internal.v1.RemoteMessage.sender:type_name -> address.v1.Address
-	9,  // 7: internal.v1.RemoteMessage.receiver:type_name -> address.v1.Address
-	8,  // 8: internal.v1.RemoteMessage.message:type_name -> google.protobuf.Any
-	0,  // 9: internal.v1.RemotingService.RemoteAsk:input_type -> internal.v1.RemoteAskRequest
-	2,  // 10: internal.v1.RemotingService.RemoteTell:input_type -> internal.v1.RemoteTellRequest
-	4,  // 11: internal.v1.RemotingService.RemoteLookup:input_type -> internal.v1.RemoteLookupRequest
-	1,  // 12: internal.v1.RemotingService.RemoteAsk:output_type -> internal.v1.RemoteAskResponse
-	3,  // 13: internal.v1.RemotingService.RemoteTell:output_type -> internal.v1.RemoteTellResponse
-	5,  // 14: internal.v1.RemotingService.RemoteLookup:output_type -> internal.v1.RemoteLookupResponse
-	12, // [12:15] is the sub-list for method output_type
-	9,  // [9:12] is the sub-list for method input_type
-	9,  // [9:9] is the sub-list for extension type_name
-	9,  // [9:9] is the sub-list for extension extendee
-	0,  // [0:9] is the sub-list for field type_name
+	11, // 1: internal.v1.RemoteAskResponse.message:type_name -> google.protobuf.Any
+	6,  // 2: internal.v1.RemoteTellRequest.remote_message:type_name -> internal.v1.RemoteMessage
+	12, // 3: internal.v1.RemoteLookupResponse.address:type_name -> address.v1.Address
+	12, // 4: internal.v1.RemoteMessage.sender:type_name -> address.v1.Address
+	12, // 5: internal.v1.RemoteMessage.receiver:type_name -> address.v1.Address
+	11, // 6: internal.v1.RemoteMessage.message:type_name -> google.protobuf.Any
+	11, // 7: internal.v1.RemoteBatchTellRequest.messages:type_name -> google.protobuf.Any
+	12, // 8: internal.v1.RemoteBatchTellRequest.sender:type_name -> address.v1.Address
+	12, // 9: internal.v1.RemoteBatchTellRequest.receiver:type_name -> address.v1.Address
+	11, // 10: internal.v1.RemoteBatchAskRequest.messages:type_name -> google.protobuf.Any
+	12, // 11: internal.v1.RemoteBatchAskRequest.sender:type_name -> address.v1.Address
+	12, // 12: internal.v1.RemoteBatchAskRequest.receiver:type_name -> address.v1.Address
+	11, // 13: internal.v1.RemoteBatchAskResponse.messages:type_name -> google.protobuf.Any
+	0,  // 14: internal.v1.RemotingService.RemoteAsk:input_type -> internal.v1.RemoteAskRequest
+	2,  // 15: internal.v1.RemotingService.RemoteTell:input_type -> internal.v1.RemoteTellRequest
+	4,  // 16: internal.v1.RemotingService.RemoteLookup:input_type -> internal.v1.RemoteLookupRequest
+	7,  // 17: internal.v1.RemotingService.RemoteBatchTell:input_type -> internal.v1.RemoteBatchTellRequest
+	9,  // 18: internal.v1.RemotingService.RemoteBatchAsk:input_type -> internal.v1.RemoteBatchAskRequest
+	1,  // 19: internal.v1.RemotingService.RemoteAsk:output_type -> internal.v1.RemoteAskResponse
+	3,  // 20: internal.v1.RemotingService.RemoteTell:output_type -> internal.v1.RemoteTellResponse
+	5,  // 21: internal.v1.RemotingService.RemoteLookup:output_type -> internal.v1.RemoteLookupResponse
+	8,  // 22: internal.v1.RemotingService.RemoteBatchTell:output_type -> internal.v1.RemoteBatchTellResponse
+	10, // 23: internal.v1.RemotingService.RemoteBatchAsk:output_type -> internal.v1.RemoteBatchAskResponse
+	19, // [19:24] is the sub-list for method output_type
+	14, // [14:19] is the sub-list for method input_type
+	14, // [14:14] is the sub-list for extension type_name
+	14, // [14:14] is the sub-list for extension extendee
+	0,  // [0:14] is the sub-list for field type_name
 }
 
 func init() { file_internal_v1_remoting_proto_init() }
@@ -635,6 +874,54 @@ func file_internal_v1_remoting_proto_init() {
 				return nil
 			}
 		}
+		file_internal_v1_remoting_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*RemoteBatchTellRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_internal_v1_remoting_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*RemoteBatchTellResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_internal_v1_remoting_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*RemoteBatchAskRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_internal_v1_remoting_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*RemoteBatchAskResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
 	}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
@@ -642,7 +929,7 @@ func file_internal_v1_remoting_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_internal_v1_remoting_proto_rawDesc,
 			NumEnums:      0,
-			NumMessages:   7,
+			NumMessages:   11,
 			NumExtensions: 0,
 			NumServices:   1,
 		},
diff --git a/protos/goakt/internal/v1/nats.proto b/protos/goakt/internal/v1/nats.proto
index ed91d05..ea504c8 100644
--- a/protos/goakt/internal/v1/nats.proto
+++ b/protos/goakt/internal/v1/nats.proto
@@ -2,10 +2,6 @@ syntax = "proto3";
 
 package internal.v1;
 
-import "address/v1/address.proto";
-import "google/protobuf/any.proto";
-import "google/protobuf/timestamp.proto";
-
 option go_package = "github.com/tochemey/goakt/internal/v1;internalpb";
 
 // NatsMessageType defines the NATs message type
diff --git a/protos/goakt/internal/v1/remoting.proto b/protos/goakt/internal/v1/remoting.proto
index 5dce857..d3d9337 100644
--- a/protos/goakt/internal/v1/remoting.proto
+++ b/protos/goakt/internal/v1/remoting.proto
@@ -4,31 +4,33 @@ package internal.v1;
 
 import "address/v1/address.proto";
 import "google/protobuf/any.proto";
-import "google/protobuf/timestamp.proto";
 
 option go_package = "github.com/tochemey/goakt/internal/v1;internalpb";
 
+// RemotingService defines the remoting service
+// With remoting one can:
+// - send messages to actors
+// - lookup actor remotely
 service RemotingService {
-  // RemoteAsk is used to send a message to an actor remotely and expect a response
-  // immediately. With this type of message the receiver cannot communicate back to Sender
-  // except reply the message with a response. This one-way communication
+  // RemoteAsk is used to send a message to an actor remotely and expect a response immediately.
   rpc RemoteAsk(RemoteAskRequest) returns (RemoteAskResponse);
-  // RemoteTell is used to send a message to an actor remotely by another actor
-  // This is the only way remote actors can interact with each other. The actor on the
-  // other line can reply to the sender by using the Sender in the message
+  // RemoteTell is used to send a message to a remote actor
+  // The actor on the other line can reply to the sender by using the Sender in the message
   rpc RemoteTell(RemoteTellRequest) returns (RemoteTellResponse);
   // Lookup for an actor on a remote host.
   rpc RemoteLookup(RemoteLookupRequest) returns (RemoteLookupResponse);
+  // RemoteBatchTell is used to send a bulk of messages to a remote actor
+  rpc RemoteBatchTell(RemoteBatchTellRequest) returns (RemoteBatchTellResponse);
+  // RemoteBatchAsk is used to send a bulk messages to a remote actor with replies.
+  // The replies are sent in the same order as the messages
+  rpc RemoteBatchAsk(RemoteBatchAskRequest) returns (RemoteBatchAskResponse);
 }
 
 // RemoteAsk is used to send a message to an actor remotely and expect a response
-// immediately. With this type of message the receiver cannot communicate back to Sender
-// except reply the message with a response. This one-way communication
+// immediately.
 message RemoteAskRequest {
   // Specifies the remote message to send
   RemoteMessage remote_message = 1;
-  // Specifies the send time
-  google.protobuf.Timestamp send_time = 2;
 }
 
 message RemoteAskResponse {
@@ -37,14 +39,10 @@ message RemoteAskResponse {
   google.protobuf.Any message = 1;
 }
 
-// RemoteTell is used to send a message to an actor remotely by another actor
-// This is the only way remote actors can interact with each other. The actor on the
-// other line can reply to the sender by using the Sender in the message
+// RemoteTell is used to send a message to an actor remotely
 message RemoteTellRequest {
   // Specifies the remote message to send
   RemoteMessage remote_message = 1;
-  // Specifies the send time
-  google.protobuf.Timestamp send_time = 2;
 }
 
 message RemoteTellResponse {}
@@ -74,3 +72,29 @@ message RemoteMessage {
   // Any proto message is allowed to be sent
   google.protobuf.Any message = 3;
 }
+
+message RemoteBatchTellRequest {
+  // Specifies the remote message to send
+  repeated google.protobuf.Any messages = 1;
+  // Specifies the sender' address
+  address.v1.Address sender = 2;
+  // Specifies the actor address
+  address.v1.Address receiver = 3;
+}
+
+message RemoteBatchTellResponse {}
+
+message RemoteBatchAskRequest {
+  // Specifies the remote message to send
+  repeated google.protobuf.Any messages = 1;
+  // Specifies the sender' address
+  address.v1.Address sender = 2;
+  // Specifies the actor address
+  address.v1.Address receiver = 3;
+}
+
+message RemoteBatchAskResponse {
+  // Specifies the message to send to the actor
+  // Any proto message is allowed to be sent
+  repeated google.protobuf.Any messages = 1;
+}


Test patch:
diff --git a/actors/actor_test.go b/actors/actor_test.go
index 71563d3..49b158e 100644
--- a/actors/actor_test.go
+++ b/actors/actor_test.go
@@ -90,7 +90,7 @@ func (p *Tester) Receive(ctx ReceiveContext) {
 		// block until timer is up
 		wg.Wait()
 	default:
-		panic(ErrUnhandled)
+		ctx.Unhandled()
 	}
 }
 
diff --git a/actors/api_test.go b/actors/api_test.go
index be8c9b6..8fc3f60 100644
--- a/actors/api_test.go
+++ b/actors/api_test.go
@@ -77,10 +77,6 @@ func TestAsk(t *testing.T) {
 		expected := &testpb.Reply{Content: "received message"}
 		assert.True(t, proto.Equal(expected, reply))
 
-		// stop the actor after some time
-		ctx, cancel := context.WithTimeout(ctx, time.Second)
-		defer cancel()
-
 		err = sys.Stop(ctx)
 	})
 	t.Run("With stopped actor", func(t *testing.T) {
@@ -121,9 +117,42 @@ func TestAsk(t *testing.T) {
 		assert.EqualError(t, err, ErrDead.Error())
 		assert.Nil(t, reply)
 
-		// stop the actor after some time
-		ctx, cancel := context.WithTimeout(ctx, time.Second)
-		defer cancel()
+		err = sys.Stop(ctx)
+	})
+	t.Run("With request timeout", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.New(log.DebugLevel, os.Stdout)
+		// create the actor system
+		sys, err := NewActorSystem("test",
+			WithLogger(logger),
+			WithReplyTimeout(receivingTimeout),
+			WithPassivationDisabled())
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		time.Sleep(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := NewTester()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		// create a message to send to the test actor
+		message := new(testpb.TestTimeout)
+		// send the message to the actor
+		reply, err := Ask(ctx, actorRef, message, receivingTimeout)
+		// perform some assertions
+		require.Error(t, err)
+		assert.EqualError(t, err, ErrRequestTimeout.Error())
+		assert.Nil(t, reply)
 
 		err = sys.Stop(ctx)
 	})
@@ -164,9 +193,124 @@ func TestAsk(t *testing.T) {
 		require.Error(t, err)
 		assert.Nil(t, reply)
 
+		err = sys.Stop(ctx)
+	})
+	t.Run("With Batch request happy path", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.New(log.DebugLevel, os.Stdout)
+		// create the actor system
+		sys, err := NewActorSystem("test",
+			WithLogger(logger),
+			WithPassivationDisabled())
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		time.Sleep(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := NewTester()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		// create a message to send to the test actor
+		// send the message to the actor
+		replies, err := BatchAsk(ctx, actorRef, receivingTimeout, new(testpb.TestReply), new(testpb.TestReply))
+		// perform some assertions
+		require.NoError(t, err)
+		assert.NotNil(t, replies)
+		assert.NotEmpty(t, replies)
+		assert.Len(t, replies, 2)
+
+		for reply := range replies {
+			expected := &testpb.Reply{Content: "received message"}
+			assert.True(t, proto.Equal(expected, reply))
+		}
+
+		err = sys.Stop(ctx)
+	})
+	t.Run("With Batch request with timeout", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.New(log.DebugLevel, os.Stdout)
+		// create the actor system
+		sys, err := NewActorSystem("test",
+			WithLogger(logger),
+			WithReplyTimeout(receivingTimeout),
+			WithPassivationDisabled())
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		time.Sleep(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := NewTester()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		// create a message to send to the test actor
+		// send the message to the actor
+		replies, err := BatchAsk(ctx, actorRef, receivingTimeout, new(testpb.TestTimeout), new(testpb.TestReply))
+		// perform some assertions
+		require.Error(t, err)
+		require.EqualError(t, err, ErrRequestTimeout.Error())
+		assert.Empty(t, replies)
+
 		// stop the actor after some time
-		ctx, cancel := context.WithTimeout(ctx, time.Second)
-		defer cancel()
+		// this is due to the actor Waitgroup to gracefully close
+		time.Sleep(time.Second)
+
+		err = sys.Stop(ctx)
+	})
+	t.Run("With Batch request with dead actor", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.New(log.DebugLevel, os.Stdout)
+		// create the actor system
+		sys, err := NewActorSystem("test",
+			WithLogger(logger),
+			WithReplyTimeout(receivingTimeout),
+			WithPassivationDisabled())
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		time.Sleep(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := NewTester()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		// stop the actor
+		require.NoError(t, actorRef.Shutdown(ctx))
+
+		// create a message to send to the test actor
+		// send the message to the actor
+		replies, err := BatchAsk(ctx, actorRef, receivingTimeout, new(testpb.TestTimeout), new(testpb.TestReply))
+		// perform some assertions
+		require.Error(t, err)
+		assert.Empty(t, replies)
 
 		err = sys.Stop(ctx)
 	})
@@ -208,8 +352,7 @@ func TestTell(t *testing.T) {
 		require.NoError(t, err)
 
 		// stop the actor after some time
-		ctx, cancel := context.WithTimeout(ctx, time.Second)
-		defer cancel()
+		time.Sleep(time.Second)
 
 		err = sys.Stop(ctx)
 		assert.NoError(t, err)
@@ -251,10 +394,6 @@ func TestTell(t *testing.T) {
 		require.Error(t, err)
 		assert.EqualError(t, err, ErrDead.Error())
 
-		// stop the actor after some time
-		ctx, cancel := context.WithTimeout(ctx, time.Second)
-		defer cancel()
-
 		err = sys.Stop(ctx)
 	})
 	t.Run("With invalid remote message", func(t *testing.T) {
@@ -290,11 +429,84 @@ func TestTell(t *testing.T) {
 		err = Tell(ctx, actorRef, message)
 		require.Error(t, err)
 
-		// stop the actor after some time
-		ctx, cancel := context.WithTimeout(ctx, time.Second)
-		defer cancel()
+		err = sys.Stop(ctx)
+	})
+	t.Run("With Batch request", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.New(log.DebugLevel, os.Stdout)
+		// create the actor system
+		sys, err := NewActorSystem("test",
+			WithLogger(logger),
+			WithPassivationDisabled())
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		time.Sleep(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := NewTester()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		time.Sleep(time.Second)
+
+		// create a message to send to the test actor
+		// send the message to the actor
+		err = BatchTell(ctx, actorRef, new(testpb.TestSend), new(testpb.TestSend))
+		// perform some assertions
+		require.NoError(t, err)
+		// wait for processing to be done
+		time.Sleep(500 * time.Millisecond)
+		require.EqualValues(t, 2, actor.counter.Load())
+
+		err = sys.Stop(ctx)
+		assert.NoError(t, err)
+	})
+	t.Run("With Batch request with a dead actor", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.New(log.DebugLevel, os.Stdout)
+		// create the actor system
+		sys, err := NewActorSystem("test",
+			WithLogger(logger),
+			WithPassivationDisabled())
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		time.Sleep(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := NewTester()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		time.Sleep(time.Second)
+		require.NoError(t, actorRef.Shutdown(ctx))
+
+		// create a message to send to the test actor
+		// send the message to the actor
+		err = BatchTell(ctx, actorRef, new(testpb.TestSend), new(testpb.TestSend))
+		// perform some assertions
+		require.Error(t, err)
+		require.EqualError(t, err, ErrDead.Error())
 
 		err = sys.Stop(ctx)
+		assert.NoError(t, err)
 	})
 }
 
@@ -446,10 +658,7 @@ func TestRemoteTell(t *testing.T) {
 		err = sys.Stop(ctx)
 		assert.NoError(t, err)
 	})
-}
-
-func TestRemoteAsk(t *testing.T) {
-	t.Run("With happy path", func(t *testing.T) {
+	t.Run("With remoting disabled", func(t *testing.T) {
 		// create the context
 		ctx := context.TODO()
 		// define the logger to use
@@ -481,25 +690,20 @@ func TestRemoteAsk(t *testing.T) {
 		require.NoError(t, err)
 		assert.NotNil(t, actorRef)
 
-		// get the address of the actor
 		addr, err := RemoteLookup(ctx, host, remotingPort, actorName)
 		require.NoError(t, err)
 
+		// let us disable remoting
+		actorsSystem := sys.(*actorSystem)
+		actorsSystem.remotingEnabled.Store(false)
+
 		// create a message to send to the test actor
-		message := new(testpb.TestReply)
+		message := new(testpb.TestSend)
 		// send the message to the actor
-		reply, err := RemoteAsk(ctx, addr, message)
+		err = RemoteTell(ctx, addr, message)
 		// perform some assertions
-		require.NoError(t, err)
-		require.NotNil(t, reply)
-		require.True(t, reply.MessageIs(new(testpb.Reply)))
-
-		actual := new(testpb.Reply)
-		err = reply.UnmarshalTo(actual)
-		require.NoError(t, err)
-
-		expected := &testpb.Reply{Content: "received message"}
-		assert.True(t, proto.Equal(expected, actual))
+		require.Error(t, err)
+		require.EqualError(t, err, "failed_precondition: remoting is not enabled")
 
 		// stop the actor after some time
 		time.Sleep(time.Second)
@@ -507,7 +711,7 @@ func TestRemoteAsk(t *testing.T) {
 		err = sys.Stop(ctx)
 		assert.NoError(t, err)
 	})
-	t.Run("With invalid message", func(t *testing.T) {
+	t.Run("With Batch request", func(t *testing.T) {
 		// create the context
 		ctx := context.TODO()
 		// define the logger to use
@@ -542,12 +746,19 @@ func TestRemoteAsk(t *testing.T) {
 		// get the address of the actor
 		addr, err := RemoteLookup(ctx, host, remotingPort, actorName)
 		require.NoError(t, err)
-
+		// create a message to send to the test actor
+		messages := make([]proto.Message, 10)
 		// send the message to the actor
-		reply, err := RemoteAsk(ctx, addr, nil)
-		// perform some assertions
-		require.Error(t, err)
-		require.Nil(t, reply)
+		for i := 0; i < 10; i++ {
+			messages[i] = new(testpb.TestSend)
+		}
+
+		err = RemoteBatchTell(ctx, addr, messages...)
+		require.NoError(t, err)
+
+		// wait for processing to complete on the actor side
+		time.Sleep(500 * time.Millisecond)
+		require.EqualValues(t, 10, actor.counter.Load())
 
 		// stop the actor after some time
 		time.Sleep(time.Second)
@@ -555,7 +766,7 @@ func TestRemoteAsk(t *testing.T) {
 		err = sys.Stop(ctx)
 		assert.NoError(t, err)
 	})
-	t.Run("With remote service failure", func(t *testing.T) {
+	t.Run("With Batch service failure", func(t *testing.T) {
 		// create the context
 		ctx := context.TODO()
 		// define the logger to use
@@ -587,32 +798,27 @@ func TestRemoteAsk(t *testing.T) {
 		require.NoError(t, err)
 		assert.NotNil(t, actorRef)
 
-		// get the address of the actor
+		// create a wrong address
 		addr := &addresspb.Address{
 			Host: host,
 			Port: 2222,
 			Name: "",
 			Id:   "",
 		}
-
 		// create a message to send to the test actor
-		message := new(testpb.TestReply)
+		message := new(testpb.TestSend)
 		// send the message to the actor
-		reply, err := RemoteAsk(ctx, addr, message)
+		err = RemoteBatchTell(ctx, addr, message)
 		// perform some assertions
 		require.Error(t, err)
-		require.Nil(t, reply)
 
 		// stop the actor after some time
 		time.Sleep(time.Second)
-		t.Cleanup(func() {
-			assert.NoError(t, sys.Stop(ctx))
-		})
-	})
-}
 
-func TestAPIRemoteLookup(t *testing.T) {
-	t.Run("When remoting is not enabled", func(t *testing.T) {
+		err = sys.Stop(ctx)
+		assert.NoError(t, err)
+	})
+	t.Run("With Batch when remoting is disabled", func(t *testing.T) {
 		// create the context
 		ctx := context.TODO()
 		// define the logger to use
@@ -626,6 +832,666 @@ func TestAPIRemoteLookup(t *testing.T) {
 		sys, err := NewActorSystem("test",
 			WithLogger(logger),
 			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		time.Sleep(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := NewTester()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		addr, err := RemoteLookup(ctx, host, remotingPort, actorName)
+		require.NoError(t, err)
+
+		// let us disable remoting
+		actorsSystem := sys.(*actorSystem)
+		actorsSystem.remotingEnabled.Store(false)
+
+		// create a message to send to the test actor
+		message := new(testpb.TestSend)
+		// send the message to the actor
+		err = RemoteBatchTell(ctx, addr, message)
+		// perform some assertions
+		require.Error(t, err)
+		require.EqualError(t, err, "failed_precondition: remoting is not enabled")
+
+		// stop the actor after some time
+		time.Sleep(time.Second)
+
+		err = sys.Stop(ctx)
+		assert.NoError(t, err)
+	})
+	t.Run("With actor not found", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.New(log.DebugLevel, os.Stdout)
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
+
+		// create the actor system
+		sys, err := NewActorSystem("test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		time.Sleep(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := NewTester()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		// get the address of the actor
+		addr, err := RemoteLookup(ctx, host, remotingPort, actorName)
+		require.NoError(t, err)
+
+		// stop the actor when wait for cleanup to take place
+		require.NoError(t, actorRef.Shutdown(ctx))
+		time.Sleep(time.Second)
+
+		// create a message to send to the test actor
+		message := new(testpb.TestSend)
+		// send the message to the actor
+		err = RemoteTell(ctx, addr, message)
+		// perform some assertions
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "not found")
+
+		// stop the actor after some time
+		time.Sleep(time.Second)
+
+		err = sys.Stop(ctx)
+		assert.NoError(t, err)
+	})
+	t.Run("With Batch actor not found", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.New(log.DebugLevel, os.Stdout)
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
+
+		// create the actor system
+		sys, err := NewActorSystem("test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		time.Sleep(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := NewTester()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		// get the address of the actor
+		addr, err := RemoteLookup(ctx, host, remotingPort, actorName)
+		require.NoError(t, err)
+
+		// stop the actor when wait for cleanup to take place
+		require.NoError(t, actorRef.Shutdown(ctx))
+		time.Sleep(time.Second)
+
+		// create a message to send to the test actor
+		message := new(testpb.TestSend)
+		// send the message to the actor
+		err = RemoteBatchTell(ctx, addr, message)
+		// perform some assertions
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "not found")
+
+		// stop the actor after some time
+		time.Sleep(time.Second)
+
+		err = sys.Stop(ctx)
+		assert.NoError(t, err)
+	})
+}
+
+func TestRemoteAsk(t *testing.T) {
+	t.Run("With happy path", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.New(log.DebugLevel, os.Stdout)
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
+
+		// create the actor system
+		sys, err := NewActorSystem("test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		time.Sleep(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := NewTester()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		// get the address of the actor
+		addr, err := RemoteLookup(ctx, host, remotingPort, actorName)
+		require.NoError(t, err)
+
+		// create a message to send to the test actor
+		message := new(testpb.TestReply)
+		// send the message to the actor
+		reply, err := RemoteAsk(ctx, addr, message)
+		// perform some assertions
+		require.NoError(t, err)
+		require.NotNil(t, reply)
+		require.True(t, reply.MessageIs(new(testpb.Reply)))
+
+		actual := new(testpb.Reply)
+		err = reply.UnmarshalTo(actual)
+		require.NoError(t, err)
+
+		expected := &testpb.Reply{Content: "received message"}
+		assert.True(t, proto.Equal(expected, actual))
+
+		// stop the actor after some time
+		time.Sleep(time.Second)
+
+		err = sys.Stop(ctx)
+		assert.NoError(t, err)
+	})
+	t.Run("With invalid message", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.New(log.DebugLevel, os.Stdout)
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
+
+		// create the actor system
+		sys, err := NewActorSystem("test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		time.Sleep(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := NewTester()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		// get the address of the actor
+		addr, err := RemoteLookup(ctx, host, remotingPort, actorName)
+		require.NoError(t, err)
+
+		// send the message to the actor
+		reply, err := RemoteAsk(ctx, addr, nil)
+		// perform some assertions
+		require.Error(t, err)
+		require.Nil(t, reply)
+
+		// stop the actor after some time
+		time.Sleep(time.Second)
+
+		err = sys.Stop(ctx)
+		assert.NoError(t, err)
+	})
+	t.Run("With remote service failure", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.New(log.DebugLevel, os.Stdout)
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
+
+		// create the actor system
+		sys, err := NewActorSystem("test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		time.Sleep(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := NewTester()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		// get the address of the actor
+		addr := &addresspb.Address{
+			Host: host,
+			Port: 2222,
+			Name: "",
+			Id:   "",
+		}
+
+		// create a message to send to the test actor
+		message := new(testpb.TestReply)
+		// send the message to the actor
+		reply, err := RemoteAsk(ctx, addr, message)
+		// perform some assertions
+		require.Error(t, err)
+		require.Nil(t, reply)
+
+		// stop the actor after some time
+		time.Sleep(time.Second)
+		t.Cleanup(func() {
+			assert.NoError(t, sys.Stop(ctx))
+		})
+	})
+	t.Run("With remoting disabled", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.New(log.DebugLevel, os.Stdout)
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
+
+		// create the actor system
+		sys, err := NewActorSystem("test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		time.Sleep(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := NewTester()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		// get the address of the actor
+		addr, err := RemoteLookup(ctx, host, remotingPort, actorName)
+		require.NoError(t, err)
+
+		// let us disable remoting
+		actorsSystem := sys.(*actorSystem)
+		actorsSystem.remotingEnabled.Store(false)
+
+		// create a message to send to the test actor
+		message := new(testpb.TestReply)
+		// send the message to the actor
+		reply, err := RemoteAsk(ctx, addr, message)
+		// perform some assertions
+		require.Error(t, err)
+		require.EqualError(t, err, "failed_precondition: remoting is not enabled")
+		require.Nil(t, reply)
+
+		// stop the actor after some time
+		time.Sleep(time.Second)
+
+		err = sys.Stop(ctx)
+		assert.NoError(t, err)
+	})
+	t.Run("With Batch request", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.New(log.DebugLevel, os.Stdout)
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
+
+		// create the actor system
+		sys, err := NewActorSystem("test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		time.Sleep(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := NewTester()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		// get the address of the actor
+		addr, err := RemoteLookup(ctx, host, remotingPort, actorName)
+		require.NoError(t, err)
+
+		// create a message to send to the test actor
+		message := new(testpb.TestReply)
+		// send the message to the actor
+		replies, err := RemoteBatchAsk(ctx, addr, message)
+		// perform some assertions
+		require.NoError(t, err)
+		require.Len(t, replies, 1)
+		require.NotNil(t, replies[0])
+		require.True(t, replies[0].MessageIs(new(testpb.Reply)))
+
+		actual := new(testpb.Reply)
+		err = replies[0].UnmarshalTo(actual)
+		require.NoError(t, err)
+
+		expected := &testpb.Reply{Content: "received message"}
+		assert.True(t, proto.Equal(expected, actual))
+
+		// stop the actor after some time
+		time.Sleep(time.Second)
+
+		err = sys.Stop(ctx)
+		assert.NoError(t, err)
+	})
+	t.Run("With Batch service failure", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.New(log.DebugLevel, os.Stdout)
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
+
+		// create the actor system
+		sys, err := NewActorSystem("test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		time.Sleep(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := NewTester()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		// get the address of the actor
+		addr := &addresspb.Address{
+			Host: host,
+			Port: 2222,
+			Name: "",
+			Id:   "",
+		}
+
+		// create a message to send to the test actor
+		message := new(testpb.TestReply)
+		// send the message to the actor
+		reply, err := RemoteBatchAsk(ctx, addr, message)
+		// perform some assertions
+		require.Error(t, err)
+		require.Nil(t, reply)
+
+		// stop the actor after some time
+		time.Sleep(time.Second)
+		t.Cleanup(func() {
+			assert.NoError(t, sys.Stop(ctx))
+		})
+	})
+	t.Run("With Batch when remoting is disabled", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.New(log.DebugLevel, os.Stdout)
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
+
+		// create the actor system
+		sys, err := NewActorSystem("test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		time.Sleep(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := NewTester()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		addr, err := RemoteLookup(ctx, host, remotingPort, actorName)
+		require.NoError(t, err)
+
+		// let us disable remoting
+		actorsSystem := sys.(*actorSystem)
+		actorsSystem.remotingEnabled.Store(false)
+
+		// create a message to send to the test actor
+		message := new(testpb.TestReply)
+		// send the message to the actor
+		reply, err := RemoteBatchAsk(ctx, addr, message)
+		// perform some assertions
+		require.Error(t, err)
+		require.EqualError(t, err, "failed_precondition: remoting is not enabled")
+		require.Nil(t, reply)
+
+		// stop the actor after some time
+		time.Sleep(time.Second)
+		t.Cleanup(func() {
+			assert.NoError(t, sys.Stop(ctx))
+		})
+	})
+	t.Run("With actor not found", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.New(log.DebugLevel, os.Stdout)
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
+
+		// create the actor system
+		sys, err := NewActorSystem("test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		time.Sleep(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := NewTester()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		// get the address of the actor
+		addr, err := RemoteLookup(ctx, host, remotingPort, actorName)
+		require.NoError(t, err)
+
+		// stop the actor when wait for cleanup to take place
+		require.NoError(t, actorRef.Shutdown(ctx))
+		time.Sleep(time.Second)
+
+		// create a message to send to the test actor
+		message := new(testpb.TestReply)
+		// send the message to the actor
+		reply, err := RemoteAsk(ctx, addr, message)
+		// perform some assertions
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "not found")
+		require.Nil(t, reply)
+
+		// stop the actor after some time
+		time.Sleep(time.Second)
+
+		err = sys.Stop(ctx)
+		assert.NoError(t, err)
+	})
+	t.Run("With Batch actor not found", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.New(log.DebugLevel, os.Stdout)
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
+
+		// create the actor system
+		sys, err := NewActorSystem("test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
+		)
+		// assert there are no error
+		require.NoError(t, err)
+
+		// start the actor system
+		err = sys.Start(ctx)
+		assert.NoError(t, err)
+
+		time.Sleep(time.Second)
+
+		// create a test actor
+		actorName := "test"
+		actor := NewTester()
+		actorRef, err := sys.Spawn(ctx, actorName, actor)
+		require.NoError(t, err)
+		assert.NotNil(t, actorRef)
+
+		// get the address of the actor
+		addr, err := RemoteLookup(ctx, host, remotingPort, actorName)
+		require.NoError(t, err)
+
+		// stop the actor when wait for cleanup to take place
+		require.NoError(t, actorRef.Shutdown(ctx))
+		time.Sleep(time.Second)
+
+		// create a message to send to the test actor
+		message := new(testpb.TestReply)
+		// send the message to the actor
+		reply, err := RemoteBatchAsk(ctx, addr, message)
+		// perform some assertions
+		require.Error(t, err)
+		require.Contains(t, err.Error(), "not found")
+		require.Nil(t, reply)
+
+		// stop the actor after some time
+		time.Sleep(time.Second)
+
+		err = sys.Stop(ctx)
+		assert.NoError(t, err)
+	})
+}
+
+func TestAPIRemoteLookup(t *testing.T) {
+	t.Run("When remoting is not enabled", func(t *testing.T) {
+		// create the context
+		ctx := context.TODO()
+		// define the logger to use
+		logger := log.New(log.DebugLevel, os.Stdout)
+		// generate the remoting port
+		nodePorts := dynaport.Get(1)
+		remotingPort := nodePorts[0]
+		host := "0.0.0.0"
+
+		// create the actor system
+		sys, err := NewActorSystem("test",
+			WithLogger(logger),
+			WithPassivationDisabled(),
+			WithRemoting(host, int32(remotingPort)),
 		)
 		// assert there are no error
 		require.NoError(t, err)
@@ -636,11 +1502,16 @@ func TestAPIRemoteLookup(t *testing.T) {
 
 		time.Sleep(time.Second)
 
+		// let us disable remoting
+		actorsSystem := sys.(*actorSystem)
+		actorsSystem.remotingEnabled.Store(false)
+
 		// create a test actor
 		actorName := "test"
 		// get the address of the actor
 		addr, err := RemoteLookup(ctx, host, remotingPort, actorName)
 		require.Error(t, err)
+		require.EqualError(t, err, "failed_precondition: remoting is not enabled")
 		require.Nil(t, addr)
 
 		t.Cleanup(func() {
diff --git a/actors/pid_test.go b/actors/pid_test.go
index d8b53cf..ec21f47 100644
--- a/actors/pid_test.go
+++ b/actors/pid_test.go
@@ -726,6 +726,52 @@ func TestActorToActor(t *testing.T) {
 		err = Tell(ctx, pid1, new(testpb.TestBye))
 		require.NoError(t, err)
 	})
+	t.Run("With Ask timeout", func(t *testing.T) {
+		ctx := context.TODO()
+		// create a Ping actor
+		opts := []pidOption{
+			withInitMaxRetries(1),
+			withCustomLogger(log.DiscardLogger),
+			withSendReplyTimeout(receivingTimeout),
+		}
+
+		// create the actor path
+		actor1 := &Exchanger{}
+		actorPath1 := NewPath("Exchange1", NewAddress("sys", "host", 1))
+		pid1, err := newPID(ctx, actorPath1, actor1, opts...)
+
+		require.NoError(t, err)
+		require.NotNil(t, pid1)
+
+		actor2 := NewTester()
+		actorPath2 := NewPath("Exchange2", NewAddress("sys", "host", 1))
+		pid2, err := newPID(ctx, actorPath2, actor2, opts...)
+		require.NoError(t, err)
+		require.NotNil(t, pid2)
+
+		err = pid1.Tell(ctx, pid2, new(testpb.TestSend))
+		require.NoError(t, err)
+
+		// send an ask
+		reply, err := pid1.Ask(ctx, pid2, new(testpb.TestTimeout))
+		require.Error(t, err)
+		require.EqualError(t, err, ErrRequestTimeout.Error())
+		require.Nil(t, reply)
+
+		// wait a while because exchange is ongoing
+		time.Sleep(time.Second)
+
+		err = Tell(ctx, pid1, new(testpb.TestBye))
+		require.NoError(t, err)
+
+		time.Sleep(time.Second)
+		assert.False(t, pid1.IsRunning())
+		assert.True(t, pid2.IsRunning())
+
+		time.Sleep(time.Second)
+		assert.NoError(t, pid2.Shutdown(ctx))
+		assert.False(t, pid2.IsRunning())
+	})
 }
 func TestActorRemoting(t *testing.T) {
 	// create the context