10 unstable releases (3 breaking)
Uses new Rust 2024
| new 0.4.5 | Jan 18, 2026 |
|---|---|
| 0.4.4 | Dec 15, 2025 |
| 0.4.0 | Nov 15, 2025 |
| 0.3.2 | Nov 10, 2025 |
| 0.1.0 | Oct 31, 2025 |
#419 in Asynchronous
718 downloads per month
Used in 7 crates
(3 directly)
54KB
1K
SLoC
greentic-session
Greentic’s session manager persists flow execution state between user interactions. A session is
represented by greentic_types::SessionData, which bundles the tenant context, flow identifier,
cursor, and serialized execution snapshot so a runner can pause a Wasm flow, wait for input, and
resume exactly where it left off.
Highlights
- Shared schema – The crate reuses the
greentic_typesdefinitions forSessionKey,SessionData,TenantCtx, andUserId, ensuring every runtime speaks the same language when storing or resuming a flow. - Pluggable stores – The
SessionStoretrait exposes a compact CRUD surface (create_session,get_session,update_session,remove_session,find_by_user). There is a thread-safe in-memory implementation plus a Redis implementation for multi-process deployments, both selectable via a Redis-free factory. - User routing – Each store maintains a secondary map from
(env, tenant, team, user)to aSessionKeyso inbound activities can be routed to the correct paused flow without the caller needing to supply the opaque session identifier. - Deterministic key helpers – The
mappingmodule offers helpers for deriving stableSessionKeyvalues from connector payloads (e.g., Telegram update fields or webhook metadata).
SessionStore API
use greentic_session::{SessionData, SessionResult, SessionStore};
use greentic_types::{SessionCursor, TenantCtx};
pub trait SessionStore {
fn create_session(&self, ctx: &TenantCtx, data: SessionData) -> SessionResult<SessionKey>;
fn get_session(&self, key: &SessionKey) -> SessionResult<Option<SessionData>>;
fn update_session(&self, key: &SessionKey, data: SessionData) -> SessionResult<()>;
fn remove_session(&self, key: &SessionKey) -> SessionResult<()>;
fn find_by_user(
&self,
ctx: &TenantCtx,
user: &UserId,
) -> SessionResult<Option<(SessionKey, SessionData)>>;
}
All stores persist the full SessionData blob so the runner can deserialize the exact execution
context it previously saved. When find_by_user returns a result, the runner can call
update_session with the resumed snapshot (or remove_session once the flow completes).
Quickstart
use greentic_session::{create_session_store, SessionBackendConfig, SessionResult, SessionStore};
use greentic_types::{EnvId, FlowId, SessionCursor, SessionData, TenantCtx, TenantId, UserId};
fn demo() -> SessionResult<()> {
let store = create_session_store(SessionBackendConfig::InMemory)?;
let env = EnvId::try_from("dev")?;
let tenant = TenantId::try_from("tenant-42")?;
let user = UserId::try_from("user-7")?;
let ctx = TenantCtx::new(env, tenant).with_user(Some(user.clone()));
let snapshot = SessionData {
tenant_ctx: ctx.clone(),
flow_id: FlowId::try_from("support.flow")?,
cursor: SessionCursor::new("node.wait_input".to_string()),
context_json: "{\"ticket\":123}".into(),
};
let key = store.create_session(&ctx, snapshot.clone())?;
let hydrated = store.get_session(&key)?.expect("session present");
assert_eq!(hydrated.cursor.node_pointer, "node.wait_input");
let (found_key, _) = store.find_by_user(&ctx, &user)?.expect("user session");
assert_eq!(found_key, key);
store.remove_session(&key)?;
Ok(())
}
Run the example with cargo run --example quickstart to see the same flow end-to-end.
Choosing a backend
Construct a store with the Redis-free configuration enum (no Redis types in the public API and no
downstream redis dependency):
use greentic_session::{create_session_store, SessionBackendConfig};
let store = create_session_store(SessionBackendConfig::RedisUrl(
"redis://127.0.0.1/",
))?;
| Feature flag combo | Backend availability | Suggested usage |
|---|---|---|
default (no flags) |
In-memory only | Tests, single-node dev |
--features redis |
Redis + in-memory | Production runners |
--all-features |
Redis + schema docs | CI / documentation generation |
The Redis backend stores each SessionData blob as JSON under
greentic:session:session:{session_key} and maintains user lookup keys at
greentic:session:user:{env}:{tenant}:{team}:{user}. When a session is removed, the lookup entry is
cleared so new activities fall back to creating a fresh session.
Tenant context enforcement is strict: env, tenant, and team must always match between the caller’s
TenantCtx and the stored SessionData, and a stored user (when present) must match the caller. If
the stored user is absent, lookups may still be keyed by a caller-provided user without mutating the
stored context.
Deterministic Session Keys
mapping::telegram_update_to_session_key(bot_id, chat_id, user_id)mapping::webhook_to_session_key(source, subject, id_hint)
Both helpers derive a SHA-256 digest and hex-encode it, making it safe to hash stable identifiers
without leaking PII. Use them when the connector should resume the same session even if the runtime
doesn’t issue a SessionKey (e.g., webhooks that only provide conversation IDs).
Development
cargo fmt
cargo clippy --all-targets -- -D warnings
cargo test --all-features
Redis tests honor the REDIS_URL environment variable. If unset, the Redis-specific tests are
skipped automatically.
Toolchain: Rust 1.89.0 (tracked via rust-toolchain.toml and CI workflows).
Dependencies
~6–25MB
~347K SLoC