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"
)
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.
When the service has one Edge connection and default_gateway is auto (the
default), SLIM automatically uses that connection for the discovery publish.
No route needs to be configured before the invite. If the service has multiple
Edge connections, automatic selection is ambiguous; use set_route (or pin a
default gateway) when the invite must use a specific uplink. The same explicit
routing is required when default_gateway is set to off.
let participant_name = ProtoName::from_strings(["myorg", "default", "participant"]);
// 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):
# 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(session *slim.Session, name *slim.Name) error {
// Invite the participant — this performs discovery + MLS key exchange
if err := session.InviteAndWaitAsync(name); err != nil {
return err
}
return nil
}
void inviteParticipant(Session session, Name participantName) {
// Invite the participant — this performs discovery + MLS key exchange
session.inviteAndWait(participantName);
System.out.println("Invited " + participantName + " to the group");
}
suspend fun inviteParticipant(session: Session, participantName: Name) {
// Invite the participant — this performs discovery + MLS key exchange
val handle = session.inviteAsync(participantName)
handle.waitAsync()
println("Invited $participantName to the group")
}
// Invite the participant
await session.inviteAndWaitAsync(inviteName);
console.log(`Invited ${inviteName} to the group`);
using var inviteName = SlimName.Parse("myorg/default/participant");
// Invite the participant (synchronous)
session.Invite(inviteName);
Console.WriteLine($"Invited {inviteName} to the group");
// 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