Skip to main content

Ownership graph (Phase 2 of the multi-tenant rollout)

The ownership graph is the projection layer that lets the MCP catalog + UI ask "what can this user see?" / "who can read this?" without joining the canonical tenancy tables hop-by-hop.

Architecture

Hard rule

Hard rule 33 in AGENTS.md: "All ownership / membership queries that traverse more than one hop MUST go through alphaswarm.graph.OwnershipGraphStore."

Node + edge model

Node kindSource tableIdentity
OrganizationorganizationsUUID
TeamteamsUUID
UserusersUUID
WorkspaceworkspacesUUID
ProjectprojectsUUID
LablabsUUID
ExperimentexperimentsUUID
TesttestsUUID
ResourceresourcesUUID
Edge relationfrom -> to (kinds)Source
HAS_TEAMOrganization -> Teamteams.org_id
HAS_WORKSPACEOrganization -> Workspaceworkspaces.org_id
HAS_PROJECTWorkspace -> Projectprojects.workspace_id
HAS_LABWorkspace -> Lablabs.workspace_id
MEMBER_OFUser -> (Org|Team|Workspace|Project|Lab)memberships
OWNS(Org|Team|User|Workspace|Project) -> Resourceresources.owner_scope_*
IN_PROJECTExperiment / Resource -> Project*.project_id
IN_LABExperiment / Resource -> Lab*.lab_id
IN_WORKSPACEResource -> Workspaceresources.workspace_id
IN_EXPERIMENTTest -> Experimenttests.experiment_id
PARENT_OFExperiment -> Experimentexperiments.parent_experiment_id
DERIVED_FROM / CLONES / TRANSLATED_FROM / USES / REFERENCESResource -> Resourceresource_relations.relation

Sync semantics

  • Source of truth: Postgres. Every ownership write is a normal ORM commit.
  • Event bus: SQLAlchemy after_flush_postexec hooks translate each row insert/update/delete into an OwnershipEvent on the alphaswarm:ownership:events Redis stream (or an in-process fallback queue when Redis is unreachable).
  • Drain: the alphaswarm.tasks.ownership_tasks.drain_events Celery beat task runs every 5 s and applies up to ALPHASWARM_OWNERSHIP_SYNC_BATCH_SIZE (default 500) events through OwnershipGraphStore.apply_events.
  • Healer: the periodic full_resync task (default 30 min) walks the canonical tables + re-emits everything so any missed delivery is repaired.

Read paths

  • Python: from alphaswarm.graph import get_ownership_store; store = get_ownership_store(); store.traverse(...).
  • MCP tools:
    • data.ownership.tree — outward walk from a node.
    • data.ownership.list_resources — every Resource a user can see.
    • data.ownership.who_can_read — reverse — every user that can read a specific Resource.
  • HTTP (Phase 6 frontend): the ContextBar talks directly to the metadata cache (/cache/organizations etc.); deeper queries go through the MCP HTTP transport (/mcp/data/tools/<name>/invoke).

Stores

BackendUse case
PostgresOwnershipGraphStoreLocal dev, unit tests, bootstrap/recovery
Neo4jOwnershipGraphStoreProduction multi-hop queries

Pick via ALPHASWARM_OWNERSHIP_GRAPH_STORE (default postgres).

See also