Tutorial: Creating a Session
This tutorial shows how to create point-to-point and group sessions to exchange messages between applications. Sessions provide reliable, optionally encrypted communication on top of the SLIM data plane.
Prerequisites
- Completed Creating an App
- Two running SLIM applications or two instances of the same service
For conceptual background on session types, see Sessions.
Point-to-Point Session
A point-to-point session connects your application to a single remote instance. SLIM performs a discovery phase to locate the remote, then binds all subsequent messages in the session to that endpoint.
Create the Session
use slim_session::{SessionConfig, Notification};
use slim_session::session_config::MlsSettings;
use slim_datapath::api::{ProtoName, ProtoSessionType};
use std::time::Duration;
use std::collections::HashMap;
let remote_name = ProtoName::from_strings(["myorg", "default", "other-service"]);
let session_config = SessionConfig {
session_type: ProtoSessionType::PointToPoint,
max_retries: Some(5),
interval: Some(Duration::from_secs(5)),
mls_settings: Some(MlsSettings::default()),
initiator: true,
metadata: HashMap::new(),
};
// Create the session — discovery happens automatically
let (ctx, completion) = app.create_session(session_config, remote_name.clone(), None).await?;
// Wait until the session is fully established
completion.await?;
let session = ctx.session_arc().unwrap();
println!("Point-to-point session established");
import asyncio
import datetime
import slim_bindings
async def run_client(app, remote_name):
session_config = slim_bindings.SessionConfig(
session_type=slim_bindings.SessionType.POINT_TO_POINT,
max_retries=5,
interval=datetime.timedelta(seconds=5),
metadata={},
mls_settings=slim_bindings.MlsSettings(
header_integrity_validation_percent=100,
max_seen_control_message_ids_size=None,
),
)
# Create the session — discovery happens automatically
session_context = await app.create_session_async(session_config, remote_name)
# Wait until the session is fully established
await session_context.completion.wait_async()
session = session_context.session
print("Point-to-point session established")
return session
import (
"fmt"
"log"
slim "github.com/agntcy/slim-bindings-go/v2"
)
func runClient(app *slim.App, remoteName *slim.Name) *slim.Session {
config := slim.SessionConfig{
SessionType: slim.SessionTypePointToPoint,
MlsSettings: &slim.MlsSettings{
HeaderIntegrityValidationPercent: 100,
},
}
// Create the session — discovery happens automatically
// Blocks until the session is fully established
session, err := app.CreateSessionAndWaitAsync(config, remoteName)
if err != nil {
log.Fatal(err)
}
fmt.Println("Point-to-point session established")
return session
}
import io.agntcy.slim.bindings.*;
import java.time.Duration;
import java.util.Map;
Session runClient(App app, Name remoteName) {
SessionConfig sessionConfig = new SessionConfig(
SessionType.POINT_TO_POINT,
5, // maxRetries
Duration.ofSeconds(5), // interval
Map.of(), // metadata
new MlsSettings(100, null) // Enable E2E encryption
);
// Create the session — discovery happens automatically
// Blocks until the session is fully established
Session session = app.createSessionAndWait(sessionConfig, remoteName);
System.out.println("Point-to-point session established");
return session;
}
import io.agntcy.slim.bindings.*
import java.time.Duration
suspend fun runClient(app: App, remoteName: Name): Session {
val sessionConfig = SessionConfig(
sessionType = SessionType.POINT_TO_POINT,
maxRetries = 5u,
interval = Duration.ofSeconds(5),
metadata = emptyMap(),
mlsSettings = MlsSettings(100u, null)
)
// Create the session — discovery happens automatically
val sessionContext = app.createSession(sessionConfig, remoteName)
// Wait until the session is fully established
sessionContext.completion.waitAsync()
val session = sessionContext.session
println("Point-to-point session established")
return session
}
import slimBindings from '@agntcy/slim-bindings';
async function runClient(app, remoteName) {
const sessionConfig = {
sessionType: slimBindings.SessionType.PointToPoint,
maxRetries: 5,
interval: 5000, // milliseconds
metadata: new Map(),
mlsSettings: { headerIntegrityValidationPercent: 100 },
};
// Create the session — discovery happens automatically
// Resolves when the session is fully established
const session = await app.createSessionAndWaitAsync(sessionConfig, remoteName);
console.log("Point-to-point session established");
return session;
}
using Agntcy.Slim;
var config = new SlimSessionConfig
{
SessionType = SlimSessionType.PointToPoint,
MlsSettings = new SlimMlsSettings(),
MaxRetries = 5,
RetryInterval = TimeSpan.FromSeconds(5),
Metadata = new Dictionary<string, string>()
};
// Create the session — discovery happens automatically
// Blocks until the session is fully established
using var session = await app.CreateSessionAsync(remoteName, config);
Console.WriteLine("Point-to-point session established");
const sessionConfig = {
sessionType: slimBindings.SessionType.PointToPoint,
maxRetries: 5,
interval: 5000, // milliseconds
metadata: new Map(),
mlsSettings: { headerIntegrityValidationPercent: 100 },
};
// Create the session — discovery happens automatically
const session = await app.createSessionAndWaitAsync(sessionConfig, remoteName);
console.log("Point-to-point session established");
Group Session
A group session enables many-to-many communication on a named channel. Every message sent to the channel is delivered to all current participants.
Create the Session
let channel_name = ProtoName::from_strings(["myorg", "default", "my-group"]);
let session_config = SessionConfig {
session_type: ProtoSessionType::Multicast,
max_retries: Some(5),
interval: Some(Duration::from_secs(5)),
mls_settings: Some(MlsSettings::default()),
initiator: true,
metadata: HashMap::new(),
};
// Create the group session on the given channel
let (ctx, completion) = app.create_session(session_config, channel_name.clone(), None).await?;
completion.await?;
let session = ctx.session_arc().unwrap();
println!("Group session created on channel: myorg/default/my-group");
async def create_group_session(app, channel_name):
session_config = slim_bindings.SessionConfig(
session_type=slim_bindings.SessionType.GROUP,
max_retries=5,
interval=datetime.timedelta(seconds=5),
metadata={},
mls_settings=slim_bindings.MlsSettings(
header_integrity_validation_percent=100,
max_seen_control_message_ids_size=None,
),
)
# Create the session on the given channel
session_context = await app.create_session_async(session_config, channel_name)
# Wait until the session is ready
await session_context.completion.wait_async()
session = session_context.session
print("Group session created on channel:", channel_name)
return session
func createGroupSession(app *slim.App, channelName *slim.Name) *slim.Session {
config := slim.SessionConfig{
SessionType: slim.SessionTypeGroup,
MlsSettings: &slim.MlsSettings{
HeaderIntegrityValidationPercent: 100,
},
}
// Create the session on the given channel
// Blocks until the session is ready
session, err := app.CreateSessionAndWaitAsync(config, channelName)
if err != nil {
log.Fatal(err)
}
fmt.Println("Group session created on channel:", channelName)
return session
}
Session createGroupSession(App app, Name channelName) {
SessionConfig sessionConfig = new SessionConfig(
SessionType.GROUP,
5, // maxRetries
Duration.ofSeconds(5), // interval
Map.of(), // metadata
new MlsSettings(100, null) // Enable E2E encryption
);
// Create the session on the given channel
// Blocks until the session is ready
Session session = app.createSessionAndWait(sessionConfig, channelName);
System.out.println("Group session created on channel: " + channelName);
return session;
}
suspend fun createGroupSession(app: App, channelName: Name): Session {
val sessionConfig = SessionConfig(
sessionType = SessionType.GROUP,
maxRetries = 5u,
interval = Duration.ofSeconds(5),
metadata = emptyMap(),
mlsSettings = MlsSettings(100u, null)
)
// Create the session on the given channel
val sessionContext = app.createSession(sessionConfig, channelName)
// Wait until the session is ready
sessionContext.completion.waitAsync()
val session = sessionContext.session
println("Group session created on channel: $channelName")
return session
}
const sessionConfig = {
sessionType: slimBindings.SessionType.Group,
maxRetries: 5,
interval: 5000, // milliseconds
metadata: new Map(),
mlsSettings: { headerIntegrityValidationPercent: 100 },
};
// Create the group session on the given channel
const session = await app.createSessionAndWaitAsync(sessionConfig, channelName);
console.log(`Group session created on channel: ${channelName}`);
using Agntcy.Slim;
var config = new SlimSessionConfig
{
SessionType = SlimSessionType.Group,
MlsSettings = new SlimMlsSettings(),
MaxRetries = 5,
RetryInterval = TimeSpan.FromSeconds(5),
Metadata = new Dictionary<string, string>()
};
// Create the group session on the given channel
using var session = await app.CreateSessionAsync(channelName, config);
Console.WriteLine($"Group session created on channel: {channelName}");
const sessionConfig = {
sessionType: slimBindings.SessionType.Group,
maxRetries: 5,
interval: 5000, // milliseconds
metadata: new Map(),
mlsSettings: { headerIntegrityValidationPercent: 100 },
};
// Create the group session on the given channel
const session = await app.createSessionAndWaitAsync(sessionConfig, channelName);
console.log(`Group session created on channel: ${channelName}`);
Invite a Participant
The session creator acts as a moderator and can invite other applications to join:
let participant_name = ProtoName::from_strings(["myorg", "default", "participant"]);
// Set the route to the participant first
app.set_route(&participant_name, conn_id).await?;
// Invite — performs discovery + MLS key exchange
session.invite_participant(&participant_name).await?;
println!("Invited participant to the group");
async def invite_participant(app, session, participant_name, conn_id):
# Set the route to the participant first
await app.set_route_async(participant_name, conn_id)
# Invite the participant — this performs discovery + MLS key exchange
handle = await session.invite_async(participant_name)
await handle.wait_async()
print(f"Invited {participant_name} to the group")
func inviteParticipant(app *slim.App, session *slim.Session, name *slim.Name, connID uint64) error {
// Set the route to the participant first
if err := app.SetRouteAsync(name, connID); err != nil {
return err
}
// Invite the participant — this performs discovery + MLS key exchange
if err := session.InviteAndWaitAsync(name); err != nil {
return err
}
return nil
}
void inviteParticipant(App app, Session session, Name participantName, Long connId) {
// Set the route to the participant first
app.setRoute(participantName, connId);
// Invite the participant — this performs discovery + MLS key exchange
session.inviteAndWait(participantName);
System.out.println("Invited " + participantName + " to the group");
}
suspend fun inviteParticipant(app: App, session: Session, participantName: Name, connId: ULong) {
// Set the route to the participant first
app.setRouteAsync(participantName, connId)
// Invite the participant — this performs discovery + MLS key exchange
val handle = session.inviteAsync(participantName)
handle.waitAsync()
println("Invited $participantName to the group")
}
// Set the route to the participant first
app.setRoute(inviteName, connId);
// Invite the participant
await session.inviteAndWaitAsync(inviteName);
console.log(`Invited ${inviteName} to the group`);
// Set the route to the participant first
using var inviteName = SlimName.Parse("myorg/default/participant");
app.SetRoute(inviteName, connId);
// Invite the participant (synchronous)
session.Invite(inviteName);
Console.WriteLine($"Invited {inviteName} to the group");
// Set the route to the participant first
await app.setRoute(inviteName, connId);
// Invite the participant
await session.inviteAndWaitAsync(inviteName);
console.log(`Invited ${inviteName} to the group`);
Send a Message
publish_and_wait_async / PublishAndWaitAsync delivers the message to all current session participants. For point-to-point sessions this is just the single remote peer; for group sessions every member receives it.
session.publish(&channel_name, b"hello".to_vec(), None, None).await?;
await session.publish_and_wait_async(
b"hello", # payload: bytes
None, # payload_type: str | None
None, # metadata: dict | None
)
if err := session.PublishAndWaitAsync([]byte("hello"), nil, nil); err != nil {
log.Fatal(err)
}
session.publishAndWait("hello".getBytes(), null, null);
val handle = session.publishAsync("hello".toByteArray(), null, null)
handle.waitAsync()
await session.publishAndWaitAsync(Buffer.from("hello"), undefined, undefined);
await session.PublishAsync("hello");
// Payload is Uint8Array in React Native
const payload = new Uint8Array("hello".split('').map(c => c.charCodeAt(0)));
await session.publishAndWaitAsync(payload, undefined, undefined);
Listen for a Reply
After sending, call get_message_async to wait for an inbound message on the same session.
// Messages arrive via spawn_receiver on the session context
ctx.spawn_receiver(|mut msg_rx, _| async move {
if let Some(Ok(msg)) = msg_rx.recv().await {
println!("Received: {}", String::from_utf8_lossy(msg.payload()));
}
});
import datetime
received = await session.get_message_async(
timeout=datetime.timedelta(seconds=30)
)
print("Received:", received.payload.decode())
import "time"
timeout := 30 * time.Second
msg, err := session.GetMessageAsync(&timeout)
if err != nil {
log.Fatal(err)
}
fmt.Println("Received:", string(msg.Payload))
import java.time.Duration;
ReceivedMessage msg = session.getMessage(Duration.ofSeconds(30));
System.out.println("Received: " + new String(msg.payload()));
import java.time.Duration
val msg = session.getMessageAsync(Duration.ofSeconds(30))
println("Received: " + String(msg.payload))
const msg = await session.getMessageAsync(30000); // timeout in milliseconds
console.log("Received:", Buffer.from(msg.payload).toString());
var msg = await session.GetMessageAsync(TimeSpan.FromSeconds(30));
Console.WriteLine($"Received: {msg.Text}");
const msg = await session.getMessageAsync(30000);
const text = String.fromCharCode(...new Uint8Array(msg.payload));
console.log("Received:", text);
Send to a Specific Participant
In a group session, publish_to_and_wait_async / PublishToAndWaitAsync sends to a single participant using the context from a previously received message. Other group members do not see the message.
// Use the source context from a received message to send directly to that participant
session.publish_to(msg.source(), b"private reply".to_vec(), None, None).await?;
# received is a ReceivedMessage obtained from session.get_message_async(...)
await session.publish_to_and_wait_async(
received.context,
b"private reply",
None, # payload_type
{}, # metadata
)
// msg is obtained from session.GetMessageAsync(...)
if err := session.PublishToAndWaitAsync(msg.Context, []byte("private reply"), nil, nil); err != nil {
log.Fatal(err)
}
// msg is obtained from session.getMessage(...)
session.publishToAndWait(msg.context(), "private reply".getBytes(), null, null);
// msg is obtained from session.getMessageAsync(...)
val handle = session.publishToAsync(msg.context, "private reply".toByteArray(), null, null)
handle.waitAsync()
// msg is obtained from session.getMessageAsync(...)
await session.publishToAndWaitAsync(
msg.context,
Buffer.from("private reply"),
undefined,
undefined
);
// msg is obtained from session.GetMessageAsync(...)
await session.ReplyAsync(msg, "private reply");
// msg is obtained from session.getMessageAsync(...)
const payload = new Uint8Array("private reply".split('').map(c => c.charCodeAt(0)));
await session.publishToAndWaitAsync(msg.context, payload, undefined, undefined);
Close the Session
Always call close when you are finished with a session. This notifies the remote peer (P2P) or all group members (group session) that you are leaving, flushes any in-flight messages, and releases local resources.
// Close the session and wait for the operation to complete
session.close().await?.await?;
await session.close_and_wait_async()
if err := session.CloseAndWaitAsync(); err != nil {
log.Fatal(err)
}
session.closeAndWait();
session.closeAndWaitAsync()
await session.closeAndWaitAsync();
await session.CloseAndWaitAsync();
await session.closeAndWaitAsync();
Group sessions: soft close vs hard close
close_with_mode(CloseMode::Soft) / closeWithModeAndWaitAsync(CloseMode.SOFT) goes offline temporarily without leaving the roster — the session can be restored later with rejoin. CloseMode::Hard (or the plain close() call) terminates the session permanently. When using persistence, prefer CloseMode::Soft so the session survives a restart. See Session Persistence.
Advanced Session Config
Reliability
By default, if max_retries and interval are omitted (or set to null/nil), the session layer sends messages fire-and-forget — no acknowledgement is requested and no retransmission occurs.
When max_retries and interval are set, the session layer requests an acknowledgement for each message. If no ack arrives within interval, the message is resent. This repeats up to max_retries times before the session reports a delivery failure to your application.
The code examples in this tutorial use max_retries=5 and a 5-second interval. Set both to null/nil/0 for unreliable (fire-and-forget) delivery.
End-to-End Encryption
Provide an MlsSettings object in the SessionConfig to enable MLS-based end-to-end encryption. The header_integrity_validation_percent field controls what percentage of message headers are validated (100 = all headers).
The session layer handles all key establishment automatically — your application code stays the same.
The examples in this tutorial already have MLS enabled. To disable it, set mls_settings=None / MlsSettings = null / mlsSettings = nil / omit the MlsSettings argument.
Runnable Examples
The slim-bindings repository contains complete, runnable examples:
- Python point-to-point example
- Python group example
- Go examples
- Java examples
- Kotlin examples
- Node.js examples
Next Steps
- Receiving a Session — Listen for incoming sessions, receive messages, and reply
- Sessions — Deep dive into session types, sequence diagrams, and the full API
- Groups — Group creation and membership management via the SLIM Controller