Skip to main content

Principal-scoped storage (Slack team installs)

For a Slack user in a team workspace, OpenSRE resolves one principal (the Clerk organization that owns the install) 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

If the install catalog is unreadable, or there is neither an install row nor a silo org id, 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.

Registering a workspace

Nothing in this change writes slack_installs rows: there is no OAuth or control-plane hook yet. Until one exists, give a workspace an owner in one of two ways. Set the silo organization, which every workspace without a row falls back to:
Or insert the row directly, which is what a real multi-workspace deployment needs:
With neither, a Slack turn from that workspace is refused.

Upgrading an existing deployment

Session bindings moved out of the host state.db into a per-organization bindings.db beside that organization’s context. Opening the new database once adopts any rows from the old one — including databases written before scoping existed, which have neither principal_id nor actor_id; those adopt as unscoped rows and keep working for Telegram and the CLI. A silo 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
  • Postgres session bindings
  • OAuth writer for slack_installs