User and inferlet (session)
Every running inferlet has a session: a bidirectional channel to the client that launched it. The inferlet sends progress events, partial outputs, and files; the client sends messages, files, and signals. This page covers the session API. Read this after I/O overview.
Send to the client
session.send ships text or structured data to the client. session.send_file ships binary blobs.
- Rust
- Python
- JavaScript
use inferlet::pie::core::session;
session::send("hello"); // text
session::send_file(&pdf_bytes); // binary
from inferlet import session
session.send("hello") # text
session.send({"event": "tick", "n": 3}) # dict / list → JSON
session.send_file(pdf_bytes) # binary
session.send accepts a string verbatim. Anything else is JSON-encoded.
import { session } from 'inferlet';
session.send('hello');
session.send({ event: 'tick', n: 3 }); // object → JSON
session.sendFile(pdfBytes); // Uint8Array
The client receives each send as a Stdout event and each send_file as a File event. See the client SDK reference for the receive side.
Receive from the client
session.receive waits for the next message; session.receive_file waits for the next file. Both are async.
- Rust
- Python
- JavaScript
use inferlet::pie::core::session;
use inferlet::FutureStringExt;
let next = session::receive();
let msg: Option<String> = next.wait_async().await; // None if the client disconnected
text: str = await session.receive()
data: bytes = await session.receive_file()
const text: string = await session.receive();
const data: Uint8Array = await session.receiveFile();
The receive futures resolve on the next inbound payload. If the client never sends, they wait forever; pair them with select! / asyncio.wait_for / Promise.race if you need a timeout.
Stream tokens as they decode
The most common pattern: forward each chat-decoder delta to the session.
- Rust
- Python
- JavaScript
use inferlet::{chat, sample::Sampler};
use inferlet::pie::core::session;
let mut g = ctx.generate(Sampler::TopP { temperature: 0.6, p: 0.95 })
.max_tokens(512);
let mut dec = chat::Decoder::new(&model);
while let Some(step) = g.next()? {
let out = step.execute().await?;
if let chat::Event::Delta(s) = dec.feed(&out.tokens)? {
session::send(&s);
}
}
from inferlet import Sampler, chat, session
g = ctx.generate(Sampler.top_p(0.6, 0.95), max_tokens=512)
dec = chat.Decoder(model)
async for step in g:
out = await step.execute()
match dec.feed(out.tokens):
case chat.Event.Delta(text=t):
session.send(t)
case _:
pass
import { Sampler, chat, session } from 'inferlet';
const g = ctx.generate(Sampler.topP(0.6, 0.95), { maxTokens: 512 });
const dec = new chat.Decoder(model);
for await (const step of g) {
const out = await step.execute();
const ev = dec.feed(out.tokens);
if (ev.type === 'delta') session.send(ev.text);
}
The client UI updates token by token. The inferlet's final return value still arrives as the Return event after the loop ends.
Structured events
Send a JSON object instead of a plain string when you want the client to dispatch on a tag:
- Rust
- Python
- JavaScript
use inferlet::pie::core::session;
let payload = serde_json::json!({
"channel": "progress",
"step": 3,
"of": 10,
}).to_string();
session::send(&payload);
session.send({"channel": "progress", "step": 3, "of": 10})
session.send({ channel: 'progress', step: 3, of: 10 });
The client receives the JSON-encoded string and parses on its end. Common channels: progress, think, answer, tool-result. The convention is yours.
Receive files
For inferlets that operate on user-uploaded data, the client uploads via the client SDK's transfer_file and the inferlet reads with session.receive_file.
- Rust
- Python
- JavaScript
use inferlet::pie::core::session;
use inferlet::FutureBlobExt;
let blob: Vec<u8> = session::receive_file()
.wait_async()
.await
.ok_or("client disconnected")?;
process_pdf(&blob)?;
blob: bytes = await session.receive_file()
process_pdf(blob)
const blob: Uint8Array = await session.receiveFile();
processPdf(blob);
The client side uses process.transfer_file(blob) (Python) or its equivalent. See the client SDK reference.
Signals from the client
A signal is a fire-and-forget string the client sends with process.signal(...). Inside the inferlet, signals arrive on the same channel as text messages, through session.receive. Treat them as out-of-band hints by tagging them on the client side:
- Rust
- Python
- JavaScript
// Client side: process.signal("stop").
// Inferlet side: pick the message up via session::receive.
use inferlet::pie::core::session;
use inferlet::FutureStringExt;
let next = session::receive();
if next.wait_async().await.as_deref() == Some("stop") {
return Ok("stopped".into());
}
# Client side: await process.signal("stop").
# Inferlet side: pick the message up via session.receive.
msg = await session.receive()
if msg == "stop":
return "stopped"
// Client side: await proc.signal('stop').
// Inferlet side: pick the message up via session.receive.
const msg = await session.receive();
if (msg === 'stop') return 'stopped';
Signals are not a separate channel from the inferlet's perspective. If you need them distinguishable, encode the discriminator yourself (e.g. "signal:stop" or a JSON payload).
Stderr and the engine
stderr writes from inside the inferlet (eprintln!, print(file=sys.stderr), console.error) reach the client as Stderr events. They are separate from session.send, so a UI can route them differently (typically to a log panel).
The pie run CLI mirrors Stderr events to the launcher's terminal. A long-running pie serve writes them to its own logs.
Next
- Inferlet and inferlet (messaging): pub/sub and queues.
- Filesystem: persistent storage that survives across runs.
- Client SDK: the receive side of the session.