Principal-scoped storage (Slack and Discord)
For a Slack or Discord user on a team install, OpenSRE resolves one principal (the organization this deployment serves) and one actor (the messaging user). Team integrations and billing follow the principal. Conversation sessions follow the actor — the same private context a laptop CLI user keeps under~/.opensre.
Local CLI and Telegram stay on the flat host home when no org scope is bound.
Who owns the turn
With no organization configured, or a workspace outside the allowlist
(
OPENSRE_SILO_TEAM_IDS for Slack, DISCORD_SILO_GUILD_IDS for Discord), the
turn is refused rather than billed or attributed to the wrong owner.
Discord DMs use a sentinel guild id and skip the guild allowlist; they still
require ORGANIZATION_ID.
On-disk layout
A laptop user keeps everything in~/.opensre. A Slack or Discord user gets
the same private conversation context, filed under their organization.
Deployed, the organization root is the mounted volume named by
OPENSRE_CONTEXT_ROOT (for example /workspace/memories). The infrastructure
chroots that mount to one organization through a per-org access point, so
OpenSRE adds no org segment of its own:
OPENSRE_INTEGRATIONS_STORE_PATH (credentials re-hydrated at boot rather than
kept on the shared mount).
With no scope bound — a terminal run — every path resolves to the flat
~/.opensre layout, unchanged.
What happens on a scoped turn
- Resolve
StorageScope(principal+actor) at the start of the turn. - Bind the scope for the turn (
bound_storage_scope). - Resolve paths: org home for integrations, member home for sessions.
- Look up / create session bindings with
(platform, chat_id, principal_id, actor_id). - Consume credits against
principal.id(the org).
Other surfaces
All four chat transports — Slack, Discord, Telegram, Buzz — resolve a scope and meter the turn.principal and actor stay optional on the binding store
and resolver: the unbound CLI path omits them and keys bindings by empty
principal/actor ids.
Which organization a deployment serves
There is no install catalog. The workspace → organization mapping is control-plane data the webapp already owns, so OpenSRE does not keep a second copy that could drift. A deployment is told which organization it serves:Upgrading an existing deployment
Session bindings moved out of the host SQLite database into a per-organizationbindings.json beside that organization’s context. SQLite is gone from the
live path: the context root is an NFS-backed mount, where its advisory locking
is unreliable, while a write-temp-then-rename is atomic.
Opening the JSON file for the first time adopts rows from any old SQLite
index — including one written before scoping existed, which has neither
principal_id nor actor_id; those adopt as unscoped rows and keep working
for Telegram and the CLI. A deployment whose context moved onto a mounted
volume also adopts from the host database.
Scoped members still get a new session on their first turn after upgrade:
an adopted row carries an empty actor id, so it no longer matches a
member-scoped lookup. The transcript survives and remains readable.
Session transcripts and integrations.json are not copied automatically. To
keep continuity on a silo, copy them once:
Related env vars
Non-goals
- CLI emulation of a Slack or Discord member
- Per-user integration credentials or LLM auth
- Nesting
opensre.json, sessions, or REPL history under member homes - Changing local CLI individual home layout
- A database of any kind: bindings are a JSON file
- A workspace → organization catalog, so one process serves one organization