Principal-scoped storage (Slack team installs)
For a Slack user in a team workspace, OpenSRE resolves one principal (the organization this deployment serves) and one actor (the Slack 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 team outside
OPENSRE_SILO_TEAM_IDS, the
Slack turn is refused rather than billed to the wrong owner.
On-disk layout
A laptop user keeps everything in~/.opensre. A Slack user gets the same
private conversation context, filed under their organization.
Deployed, the organization root is the mounted S3 Files volume named by
OPENSRE_CONTEXT_ROOT (/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 layout, unchanged.
What changes for Slack
- Resolve
StorageScope(principal+actor) at the start of each Slack 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
principal and actor are optional on the binding store and resolver. Callers
that omit them — Telegram, and anything else on main’s path — key bindings by
empty principal/actor ids, which is exactly the behavior they had before.
Which organization a deployment serves
There is no install catalog. The team → organization mapping is control-plane data the webapp already owns (organizations.workspace_provider and the Slack
team id on the organizations row), so OpenSRE does not keep a second copy that
could drift.
A deployment is told which organization it serves:
OPENSRE_SILO_TEAM_IDS set, only those teams are served and every other
workspace is refused. Unset, any workspace that installed the app is served from
the configured organization — convenient for dogfood, and logged as a warning
because it means an uninvited workspace would inherit that organization’s
credentials.
Consequence: one process serves one organization. A gateway fronting several
workspaces would need the team → organization lookup back, reading the webapp
rather than a local catalog.
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.
Slack 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 (this change)
- CLI emulation of a Slack member
- Per-user integration credentials or LLM auth
- Nesting
opensre.json, investigations, or REPL history under member homes - Changing local CLI individual home layout
- A database of any kind: bindings are a JSON file
- A team → organization catalog, so one process serves one organization