Skip to main content

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:
Without the mount, a bound org principal nests under the host home so several organizations can be exercised on one machine:
Integrations sit at the org root because credentials belong to the team: whoever connected Datadog, every member investigates with it. Sessions and memory sit under the user because conversation history is personal, the same way it is on a laptop. With no scope bound — a terminal run — every path resolves to the flat ~/.opensre layout, unchanged.

What changes for Slack

  1. Resolve StorageScope (principal + actor) at the start of each Slack turn.
  2. Bind the scope for the turn (bound_storage_scope).
  3. Resolve paths: org home for integrations, member home for sessions.
  4. Look up / create session bindings with (platform, chat_id, principal_id, actor_id).
  5. Consume credits against principal.id (the org).
Alice and Bob in the same Slack thread get different session files and different binding rows. They share the org’s integrations store.

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:
With 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-organization bindings.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:
Telegram and other surfaces keep matching their migrated empty-actor rows.

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