SLIM Group Communication Tutorial
This tutorial shows how to set up secure group communication using SLIM. The group is created by defining a group session and inviting participants. Messages are sent to a shared channel where every member can read and write. All messages are end-to-end encrypted using the MLS protocol. This tutorial is based on the group.py example in the SLIM repo.
Key Features
- Name-based Addressing: In SLIM, all endpoints (channels and clients) have a name, and messages use a name-based addressing scheme for content routing.
- Session Management: Allows for the creation and management of sessions using both the SLIM Python Bindings and the SLIM Controller.
- Broadcast Messaging: Facilitates broadcast messaging to multiple subscribers.
- End-to-End Encryption: Ensures secure communication using the MLS protocol.
Configure Client Identity and Implement the SLIM App
Every participant in a group requires a unique identity for authentication and for use by the MLS protocol. This section explains how to set up identity and create a SLIM application instance.
SLIM App
Every SLIM application requires both a unique identity and an authentication mechanism. The identity is used for end-to-end encryption via the MLS protocol, while authentication verifies the application to the SLIM network. In this tutorial, we use shared secret authentication for simplicity. For more advanced authentication methods (JWT, SPIRE), see the SLIM documentation.
The example code provides a create_local_app helper function (from common.py) that simplifies the app creation and connection process.
The create_local_app function handles three main tasks:
- Initialize the Global Service: Sets up the SLIM runtime and global service instance
- Create Authentication: Determines the authentication mode (SPIRE, JWT, or shared secret) based on configuration
- Connect to SLIM Server: Establishes connection to the SLIM network
Here's how the function works:
async def create_local_app(config: BaseConfig) -> tuple[slim_bindings.App, int]:
"""
Build a Slim application instance using the global service.
Resolution precedence for auth:
1. If SPIRE options provided -> SPIRE dynamic identity flow.
2. Else if jwt + bundle + audience provided -> JWT/JWKS flow.
3. Else -> shared secret (must be provided).
Args:
config: BaseConfig instance containing all configuration.
Returns:
tuple[App, int]: Slim application instance and connection ID.
"""
# Initialize tracing and global state
service = setup_service()
# Convert local identifier to a strongly typed Name.
local_name = split_id(config.local)
# Connect to SLIM server (returns connection ID)
client_config = slim_bindings.new_insecure_client_config(config.slim)
conn_id = await service.connect_async(client_config)
# Determine authentication mode
auth_mode = config.get_auth_mode()
if auth_mode == AuthMode.SPIRE:
print("Using SPIRE dynamic identity authentication.")
provider_config, verifier_config = spire_identity(
socket_path=config.spire_socket_path,
target_spiffe_id=config.spire_target_spiffe_id,
jwt_audiences=config.spire_jwt_audience,
)
local_app = service.create_app(local_name, provider_config, verifier_config)
elif auth_mode == AuthMode.JWT:
print("Using JWT + JWKS authentication.")
# These should always be set if auth_mode is JWT
if not config.jwt or not config.spire_trust_bundle:
raise ValueError(
"JWT and SPIRE trust bundle are required for JWT auth mode"
)
provider_config, verifier_config = jwt_identity(
config.jwt,
config.spire_trust_bundle,
str(local_name),
aud=config.audience,
)
local_app = service.create_app(local_name, provider_config, verifier_config)
else:
print("Using shared-secret authentication.")
local_app = service.create_app_with_secret(local_name, config.shared_secret)
# Provide feedback to user (instance numeric id).
format_message_print(f"{local_app.id()}", "Created app")
# Subscribe to the local name
await local_app.subscribe_async(local_name, conn_id)
return local_app, conn_id
Key Authentication Options
The following key authentication options are available:
SPIRE (Recommended for production)
Uses the SPIRE Workload API for dynamic identity. Requires a running SPIRE agent. See SLIM documentation for setup details.
JWT/JWKS (Production)
Uses static JWT files with JWKS for verification. Suitable for environments with an existing JWT infrastructure.
Shared Secret (Development only)
Simple symmetric key authentication.
Group Communication Using the Python Bindings
Now that you know how to set up a SLIM application, we can see how to create a group where multiple participants can exchange messages. We start by showing how to create a group session using the Python bindings.
In this setting, one participant acts as moderator: it creates the group session and invites participants by sending invitation control messages. A detailed description of group sessions and the invitation process is available in the SLIM documentation.
Creating the Group Session and Inviting Members
The creator of the group session invites other members to join the group. The session will be identified by a unique session ID, and the group communication will take place over a specific channel name. The session creator is responsible for managing the session lifecycle, including creating, updating, and terminating the session as needed.
As each participant is provided with an identity, setting up MLS for end-to-end
encryption is straightforward: the session is created with the
mls_enabled flag set to True, which will enable the MLS protocol for the
session. This ensures that all messages exchanged within the session are
end-to-end encrypted, providing confidentiality and integrity for the group
communication.
# Create group session configuration
session_config = slim_bindings.SessionConfig(
session_type=slim_bindings.SessionType.GROUP,
enable_mls=enable_mls, # Enable Messaging Layer Security for end-to-end encrypted & authenticated group communication.
max_retries=5, # Max per-message resend attempts upon missing ack before reporting a delivery failure.
interval=datetime.timedelta(seconds=5), # Ack / delivery wait window; after this duration a retry is triggered (until max_retries).
metadata={},
)
# Create session - returns a SessionContext
session = local_app.create_session(session_config, chat_channel)
# Wait for session to be established
await session.completion.wait_async()
created_session = session.session
# Invite each provided participant
for invite in invites:
invite_name = split_id(invite)
await local_app.set_route_async(invite_name, conn_id)
handle = await created_session.invite_async(invite_name)
await handle.wait_async()
print(f"{local} -> add {invite_name} to the group")
This code comes from the group.py example.
A new group session is created by calling local_app.create_session(...) which returns a SessionContext
object containing both the session and a completion handle. The completion handle must be awaited to ensure
the session is fully established.
Key configuration parameters for SessionConfig:
session_type: Set toSessionType.GROUPfor group/multicast sessions.enable_mls: Set toTrueto enable MLS for end-to-end encryption.max_retries: Maximum number of retransmission attempts (upon missing ack) before notifying the application of delivery failure.interval: Duration to wait for an acknowledgment; if the ack is not received in time, a retry is triggered. If omitted / None, the session is unreliable (no retry/ack flow).metadata: Optional key-value pairs for session metadata.
After session creation, the moderator invites participants via created_session.invite_async(invite_name).
Each invite call returns a completion handle that should be awaited to ensure the invitation completes.
Implement Participants and Receive Messages
The group participants are implemented in a similar way, but they do not create the session. They create the SLIM service instance and wait for invites. Once they receive the invite, they can read and write on the shared channel.
async def receive_loop(
local_app: slim_bindings.App,
created_session: slim_bindings.Session | None,
session_ready: asyncio.Event,
shared_session_container: list,
):
"""
Receive messages for the bound session.
Behavior:
* If not moderator: wait for a new group session (listen_for_session_async()).
* If moderator: reuse the created_session reference.
* Loop forever until cancellation or an error occurs.
"""
if created_session is None:
print_formatted_text("Waiting for session...", style=custom_style)
session = await local_app.listen_for_session_async(None)
else:
session = created_session
# Make session available to other tasks
shared_session_container[0] = session
session_ready.set()
# Get source and destination names for display
source_name = session.source()
while True:
try:
# Await next inbound message from the group session.
# Returns a ReceivedMessage object with context and payload.
received_msg = await session.get_message_async(
timeout=datetime.timedelta(seconds=30)
)
ctx = received_msg.context
payload = received_msg.payload
# Display sender name and message
sender = ctx.source_name if hasattr(ctx, "source_name") else source_name
print_formatted_text(
f"{sender} > {payload.decode()}",
style=custom_style,
)
# if the message metadata contains PUBLISH_TO this message is a reply
# to a previous one. In this case we do not reply to avoid loops
if "PUBLISH_TO" not in ctx.metadata:
reply = f"message received by {source_name}"
await session.publish_to_async(ctx, reply.encode(), None, ctx.metadata)
except asyncio.CancelledError:
# Graceful shutdown path (ctrl-c or program exit).
break
except Exception as e:
# Break if session is closed, otherwise continue listening
if "session closed" in str(e).lower():
break
continue
Each non-moderator participant listens for an incoming session using
local_app.listen_for_session_async(None). The None parameter means wait indefinitely for a session.
This returns a session object containing metadata such as session ID, type, source name, and destination name.
The moderator already holds this information and therefore reuses the existing
created_session (see session = created_session).
Once a session is established, the function retrieves the source name using session.source() for display purposes.
Participants (including the moderator) then call received_msg = await session.get_message_async(timeout=...) to receive
messages. The returned ReceivedMessage object has two attributes: context (with source, destination, message type, and metadata)
and payload (the raw message bytes).
The function displays each received message showing the sender's name and payload. Additionally, it automatically sends
an acknowledgment reply using session.publish_to_async(ctx, reply, None, metadata), unless the received message already
contains "PUBLISH_TO" in its metadata (indicating it's a reply), which prevents infinite reply loops.
Publish Messages to the Session
All participants can publish messages on the shared channel:
async def keyboard_loop(
created_session: slim_bindings.Session,
session_ready: asyncio.Event,
shared_session_container: list[slim_bindings.Session],
local_app: slim_bindings.App,
):
"""
Interactive loop allowing participants to publish messages.
Typing 'exit' or 'quit' (case-insensitive) terminates the loop.
Typing 'remove NAME' removes a participant from the group
Typing 'invite NAME' invites a participant to the group
Each line is published to the group channel as UTF-8 bytes.
"""
try:
# 1. Initialize an async session
prompt_session = PromptSession(style=custom_style)
# Wait for the session to be established
await session_ready.wait()
session = shared_session_container[0]
source_name = session.source()
dest_name = session.destination()
if created_session:
print_formatted_text(
f"Welcome to the group {dest_name}!\n"
"Commands:\n"
" - Type a message to send it to the group\n"
" - 'remove NAME' to remove a participant\n"
" - 'invite NAME' to invite a participant\n"
" - 'exit' or 'quit' to leave the group",
style=custom_style,
)
else:
print_formatted_text(
f"Welcome to the group {dest_name}!\n"
"Commands:\n"
" - Type a message to send it to the group\n"
" - 'exit' or 'quit' to leave the group",
style=custom_style,
)
while True:
# Run blocking input() in a worker thread so we do not block the event loop.
user_input = await prompt_session.prompt_async(f"{source_name} > ")
if user_input.lower() in ("exit", "quit") and created_session:
# Delete the session
handle = await local_app.delete_session_async(
shared_session_container[0]
)
await handle.wait_async()
break
if user_input.lower().startswith("invite ") and created_session:
invite_id = user_input[7:].strip() # Skip "invite " (7 chars)
await handle_invite(shared_session_container[0], invite_id)
continue
if user_input.lower().startswith("remove ") and created_session:
remove_id = user_input[7:].strip() # Skip "remove " (7 chars)
await handle_remove(shared_session_container[0], remove_id)
continue
# Send message to the channel_name specified when creating the session.
# As the session is group, all participants will receive it.
await shared_session_container[0].publish_async(
user_input.encode(), None, None
)
except KeyboardInterrupt:
# Handle Ctrl+C gracefully
pass
except asyncio.CancelledError:
# Handle task cancellation gracefully
pass
except Exception as e:
print_formatted_text(f"-> Error sending message: {e}")
Messages are sent using session.publish_async(payload, payload_type, metadata).
The payload is the message bytes, while payload_type and metadata are optional.
There is no explicit destination because the group channel was fixed at session creation and delivery
fans out to all participants.
The keyboard_loop function provides different interfaces depending on whether the application is a moderator or a regular participant:
-
Moderators (who created the session) have additional commands:
-
invite NAME- Dynamically invite a new participant to the group using thehandle_invite()helper remove NAME- Remove a participant from the group using thehandle_remove()helper-
exitorquit- Delete the session, which notifies all participants -
Regular participants can:
-
Type messages to broadcast to the group
exitorquit- Leave the group (local session only)
When a user types exit or quit as a moderator, the application calls local_app.delete_session_async()
which returns a completion handle that must be awaited to ensure proper session cleanup before
terminating the loop. When the moderator closes the session,
all other participants are automatically notified, causing their receive loops to terminate
and their sessions to close gracefully. If the session closure is initiated by a participant,
only its local session is closed.
Run the Group Communication Example
Now we will show how to run a new group session and how to enable group communication on top of SLIM. The full code can be found in group.py in the SLIM repo. To run the example, follow the steps listed here:
Run SLIM
As all members of the group are communicating via a SLIM network, we can set up a SLIM instance representing the SLIM network. We use the pre-built docker image for this purpose.
First execute this command to create the SLIM configuration file. Details about the configuration can be found in the SLIM repository documentation.
cat << EOF > ./config.yaml
tracing:
log_level: info
display_thread_names: true
display_thread_ids: true
runtime:
n_cores: 0
thread_name: "slim-data-plane"
drain_timeout: 10s
services:
slim/0:
dataplane:
servers:
- endpoint: "0.0.0.0:46357"
tls:
insecure: true
clients: []
controller:
servers: []
EOF
This configuration starts a SLIM instance with a server listening on port 46357, without TLS encryption for simplicity. Messages are still encrypted using the MLS protocol, but the connections between SLIM nodes do not use TLS. In a production environment, it is recommended to always use TLS and configure proper authentication and authorization mechanisms.
You can run the SLIM instance using Docker:
docker run -it \
-v ./config.yaml:/config.yaml -p 46357:46357 \
ghcr.io/agntcy/slim:1.0.0 /slim --config /config.yaml
If everything goes fine, you should see an output like this one:
2026-01-28T15:03:38.408128Z INFO main ThreadId(01) application_lifecycle: slim: 52: Runtime started
2026-01-28T15:03:38.414090Z INFO main ThreadId(01) application_lifecycle: slim_service::service: 338: dataplane server started endpoint=0.0.0.0:46357
2026-01-28T15:03:38.414813Z INFO main ThreadId(01) application_lifecycle: slim_service::service: 225: no controller configuration provided, skipping controller startup
2026-01-28T15:03:38.414823Z INFO main ThreadId(01) application_lifecycle: slim: 65: service started service=slim/0
...
Start the Participants
In this example, we use two participants: agntcy/ns/client-1 and agntcy/ns/client-2.
First, clone the SLIM repository and install the dependencies:
git clone --branch slim-v1.0.0 https://github.com/agntcy/slim.git
cd slim/data-plane/bindings/python
task python:bindings:build
task python:bindings:install-examples
Now run these commands in two different terminals from the slim/data-plane/bindings/python directory:
task python:example:group:client-1
task python:example:group:client-2
This starts two participants authenticated with a shared secret. The output of these commands should look like this:
2026-01-28T15:20:54.595284Z INFO slim slim_service::service: 402: client connected endpoint=http://127.0.0.1:46357 conn_id=0
Using shared-secret authentication.
12628940569466571367 Created app
Waiting for session...
Create the Group
Run the moderator application to create the session and invite the two
participants. In another terminal, from the slim/data-plane/bindings/python directory, run:
task python:example:group:moderator
The result should look like:
2026-01-28T15:22:08.907149Z INFO slim slim_service::service: 402: client connected endpoint=http://127.0.0.1:46357 conn_id=0
Using shared-secret authentication.
2704185522498177164 Created app
Creating new group session (moderator)... agntcy/ns/moderator/ffffffffffffffff
agntcy/ns/moderator -> add agntcy/ns/client-1/ffffffffffffffff to the group
agntcy/ns/moderator -> add agntcy/ns/client-2/ffffffffffffffff to the group
Welcome to the group agntcy/ns/chat/ffffffffffffffff!
Commands:
- Type a message to send it to the group
- 'remove NAME' to remove a participant
- 'invite NAME' to invite a participant
- 'exit' or 'quit' to leave the group
agntcy/ns/moderator/25873267c342088c >
Now client-1 and client-2 are invited to the group, so on both of their terminals you should
be able to see a welcome message such as:
Welcome to the group agntcy/ns/chat/ffffffffffffffff!
Commands:
- Type a message to send it to the group
- 'exit' or 'quit' to leave the group
agntcy/ns/client-1/af43028974930a67 >
At this point, you can write messages from any terminal and they will be received by all other group participants.
Writing 'exit' or 'quit' from the moderator will close all the applications.
You can also remove and add participants by using the remove and invite commands from the moderator.
Group Communication Using the SLIM Controller
Previously, we saw how to run group communication using the Python bindings with an in-application moderator. This participant creates the group session and invites all other participants. In this section, we describe how to create and orchestrate a group using the SLIM Controller, and we show how all these functions can be delegated to the controller. We reuse the same group example code in this section as well.
Application Differences
With the controller, you do not need to set up a moderator in your application. All participants can be run as we did for client-1 and client-2 in the previous examples. In code, this means you can avoid creating a new group session (using local_app.create_session) and the invitation loop. You only need to implement the receive_loop where the application waits for new sessions. This greatly simplifies your code.
Run the Group Communication Example
Now we will show how to set up a group using the SLIM Controller. The reference code for the application is still group.py. To run this example, follow the steps listed here.
Run the SLIM Controller
First, start the SLIM Controller. Full details are in the Controller documentation; here we reproduce the minimal setup. Create a configuration file:
cat << EOF > ./config-controller.yaml
northbound:
httpHost: 0.0.0.0
httpPort: 50051
logging:
level: INFO
southbound:
httpHost: 0.0.0.0
httpPort: 50052
logging:
level: INFO
reconciler:
maxRequeues: 15
maxNumOfParallelReconciles: 1000
logging:
level: INFO
database:
filePath: /db/controlplane.db
EOF
This config defines two APIs exposed by the controller:
- Northbound API: used by an operator (e.g. via slimctl) to configure channels and participants, as well as the SLIM network.
- Southbound API: used by SLIM nodes to synchronize with the controller.
Start the controller with Docker:
docker run -it \
-v ./config-controller.yaml:/config.yaml -v .:/db -p 50051:50051 -p 50052:50052 \
ghcr.io/agntcy/slim/control-plane:1.0.0 --config /config.yaml
If everything goes fine, you should see an output like this:
2026-01-28T15:26:53Z INF Starting Route Reconciler thread_name=reconciler
2026-01-28T15:26:53Z INF Northbound API Service is listening on [::]:50051
2026-01-28T15:26:53Z INF Southbound API Service is Listening on [::]:50052
Run the SLIM Node
With the controller running, start a SLIM node configured to talk to it over the Southbound API. This node config includes two additional settings compared to the file from the previous section:
- A controller client used to connect to the Southbound API running on port 50052.
- A shared secret token provider that will be used by the SLIM node to send messages over the SLIM network. As with the normal application, you can use a shared secret or a proper JWT.
Create the config-slim.yaml for the node using the command below. We use the host.docker.internal endpoint to reach the controller from inside the Docker container via the host.
cat << EOF > ./config-slim.yaml
tracing:
log_level: info
display_thread_names: true
display_thread_ids: true
runtime:
n_cores: 0
thread_name: "slim-data-plane"
drain_timeout: 10s
services:
slim/0:
dataplane:
servers:
- endpoint: "0.0.0.0:46357"
tls:
insecure: true
clients: []
controller:
servers: []
clients:
- endpoint: "http://host.docker.internal:50052"
tls:
insecure: true
token_provider:
type: shared_secret
id: "slim/0"
data: "very-long-shared-secret-value-0123456789abcdef"
EOF
This starts a SLIM node that connects to the controller:
docker run -it \
-v ./config-slim.yaml:/config.yaml -p 46357:46357 \
ghcr.io/agntcy/slim:1.0.0 /slim --config /config.yaml
If everything goes fine, you should see an output like this one:
2026-01-28T15:40:45.189063Z INFO main ThreadId(01) application_lifecycle: slim: 52: Runtime started
2026-01-28T15:40:45.189274Z INFO main ThreadId(01) application_lifecycle: slim_service::service: 338: dataplane server started endpoint=0.0.0.0:46357
2026-01-28T15:40:45.189348Z INFO main ThreadId(01) application_lifecycle: slim_controller::service: 1634: connecting to control plane config.endpoint=http://host.docker.internal:50052
2026-01-28T15:40:45.192773Z INFO slim-data-plane ThreadId(03) slim_controller::service: 1518: connected to control plane endpoint=http://host.docker.internal:50052
2026-01-28T15:40:45.192777Z INFO main ThreadId(01) application_lifecycle: slim: 65: service started service=slim/0
2026-01-28T15:40:45.196599Z INFO slim-data-plane ThreadId(04) slim_controller::service: 890: Processed ConfigurationCommand connections=0 subscriptions_to_set=0 subscriptions_to_del=0
...
On the Controller side, you can see that the new node registers with the controller. The output should be similar to this:
2026-01-28T15:40:45Z INF Registering node with ID: slim/0 svc=southbound
2026-01-28T15:40:45Z INF Connection details: [endpoint: 192.168.65.1:46357] nodeID=slim/0 svc=southbound
2026-01-28T15:40:45Z INF Create generic routes for node node_id=slim/0 service=RouteService
2026-01-28T15:40:45Z INF Sending routes to registered node slim/0 node_id=slim/0
2026-01-28T15:40:45Z INF Sending configuration command to registered node connections_count=0 message_id=95c75638-aa2d-4043-8f27-cb26f453716e node_id=slim/0 subscriptions_count=0 subscriptions_to_delete_count=0
2026-01-28T15:40:45Z INF Configuration command processing completed node_id=slim/0 original_message_id=95c75638-aa2d-4043-8f27-cb26f453716e
Run the Participants
Because the controller manages the group lifecycle, no participant needs to be designated as moderator in code. Every application instance just waits for a session invite. In three separate terminals, from the folder
data-plane/bindings/python/examples run:
uv run slim-bindings-group \
--local agntcy/ns/client-1 \
--shared-secret "very-long-shared-secret-value-0123456789abcdef"
uv run slim-bindings-group \
--local agntcy/ns/client-2 \
--shared-secret "very-long-shared-secret-value-0123456789abcdef"
uv run slim-bindings-group \
--local agntcy/ns/client-3 \
--shared-secret "very-long-shared-secret-value-0123456789abcdef"
Each terminal should show output similar to:
2026-01-28T15:44:59.125043Z INFO slim slim_service::service: 402: client connected endpoint=http://127.0.0.1:46357 conn_id=0
Using shared-secret authentication.
10494544672403736104 Created app
Waiting for session..
At this point all applications are waiting for a new session.
Manage the Group with slimctl
Use slimctl (see slim-controller) to send administrative commands to the controller.
First, you need to run slimctl. To install it see the related documentation
To verify that slimctl was downloaded successfully, run the following command:
slimctl version
Create the Group
Select any running participant to be the initial member of the group. This participant acts as the logical moderator of the channel, similar to the Python bindings example. However, you don't need to handle this explicitly in the code. Run the following command to create the channel:
slimctl controller channel create moderators=agntcy/ns/client-1/10494544672403736104
The full name of the application can be taken from the output in the console. The value
10494544672403736104 is the actual ID of the client-1 application returned by
SLIM and is visible in the logs. In your case, this value will be different.
The expected response from slimctl is:
Received response: agntcy/ns/hDxc8CKpElJUfTTief
The value agntcy/ns/hDxc8CKpElJUfTTief is the channel (or group) identifier (name) that must be used in subsequent commands.
On the application side, client-1 was added to the session, so you should see
something like this:
Welcome to the group agntcy/ns/hDxc8CKpElJUfTTief/ffffffffffffffff!
Commands:
- Type a message to send it to the group
- 'exit' or 'quit' to leave the group
agntcy/ns/client-1/91a41cc6ee17c628 >
Add Participants
Now that the new group is created, add the additional participants client-2 and client-3 using the following slimctl commands:
slimctl controller participant add -c agntcy/ns/xyIGhc2igNGmkeBDlZ agntcy/ns/client-2
slimctl controller participant add -c agntcy/ns/xyIGhc2igNGmkeBDlZ agntcy/ns/client-3
The expected slimctl output is:
Adding participant to channel ID agntcy/ns/hDxc8CKpElJUfTTief: agntcy/ns/client-2
Participant added successfully to channel ID agntcy/ns/hDxc8CKpElJUfTTief: agntcy/ns/client-2
Now all the participants are part of the same group, and so each client log should show that the join was successful:
Welcome to the group agntcy/ns/hDxc8CKpElJUfTTief/ffffffffffffffff!
Commands:
- Type a message to send it to the group
- 'exit' or 'quit' to leave the group
agntcy/ns/client-2/e9b95e5edaee3e2c >
At this point, every member can send messages, and they will be received by all the other participants.
Remove a Participant
To remove one of the participants from the channel, run the following command:
slimctl controller participant delete -c agntcy/ns/xyIGhc2igNGmkeBDlZ agntcy/ns/client-3
The slimctl expected output is this:
Deleting participant from channel ID agntcy/ns/hDxc8CKpElJUfTTief: agntcy/ns/client-3
Participant deleted successfully from channel ID agntcy/ns/hDxc8CKpElJUfTTief: agntcy/ns/client-3
The application on client-3 exits because the session related to the group was closed, which breaks the
receive loop in the Python code.
Delete channel
To delete the channel, run the following command:
slimctl controller channel delete agntcy/ns/xyIGhc2igNGmkeBDlZ
The slimctl output is this:
Channel deleted successfully with ID: agntcy/ns/hDxc8CKpElJUfTTief
All applications connected to the group stop as their receive loops terminate.