Collaboration, employee marketplace, and operator chat context

Organizations, teams, roles, sharing workflows, the employee marketplace, employee onboarding into chat, and how workspace context shapes the operator experience.

Overview

Supervity supports personal, organization, and team workspace scopes. This guide connects:

  • how org/team context is sent to the workflow service
  • Keycloak roles and how they map to actions on shared resources
  • Share (same workflow vs copies) and the APIs the UI uses
  • the employee marketplace — listing data, catalog rules, review, and moderation (from the app’s Prisma models and server modules)
  • employee onboarding from the marketplace into an installed operator (workflow plus operator chat routes the app implements)

Execution mechanics (steps, runs, retries) stay in Workflows Explained and Troubleshooting.


Organizations and teams

  • Organization — membership is backed by Keycloak; the app resolves org id/name and roles from the user’s token and admin API where needed.
  • Team — a team belongs to an org and has its own membership roles. The UI stores the current org/team selection (including the team’s prefix used as teamKey).

Why the selector matters

Browser calls to the workflow microservice include optional headers built in getWorkflowServiceContextHeaders (app/utils/workflow-svc.tsx):

  • x-active-org — current org name (when an org is selected)
  • x-active-team — current team name (when a team is selected)
  • x-teamKey — team prefix (when a team is selected)

If no org is selected, those headers are omitted (personal workspace). Server routes that proxy to the workflow service (for example /api/v1/workflows/:id/install-for-users) forward the same headers from getCurrentOrgTeam so installs run in the session’s active scope.


Roles and access (RBAC)

Org and team roles

Roles used in org/team checks include owner, admin, editor, and viewer (see getUserRoleInOrganization / getUserRoleInTeam in app/utils/resource-ownership.server.ts).

For organization- or team-level resources, canPerformAction defines:

Rolecreatereadupdatedelete
owner, adminyesyesyesyes
editoryesyesyesno
viewernoyesnono

Private resources (resourceOwnership.level === 'private') are readable/changeable only by the resource owner (ownerId === userId) in canUserAccessResource — org/team roles do not grant access to another user’s private workflows.

Effective role when switching context

getEffectiveOrgTeamRole (app/utils/permission-guard.server.ts) returns:

  1. getUserRoleInTeam for the current team when currentTeam.id is set (team context wins).
  2. Else getUserRoleInOrganization for the current org when currentOrg.id is set.
  3. Else 'admin' — documented in code as private context so create/update/delete on user-owned resources still passes org/team-based guards that call this helper.

It does not aggregate “highest role across all orgs” in this function body; only the active org or team selection matters here.

Employee marketplace publishing (which roles count)

requireMarketplacePublishContext (app/utils/marketplace/permissions.server.ts) only allows publishing a listing (an operator employees can discover and install) from a source context if the user has at least one of owner, admin, or editor on that org or team. Viewers cannot publish from that context. Personal workspace is always an allowed source context for creating listing flows that use the personal kind.

Workflow Share UI (workflow service baseRole)

The Share modal (app/components/workflow/workflow-share-modal.tsx) gates features with the workflow’s baseRole from the workflow service:

  • isOwner — baseRole === "owner" (required for org/team visibility changes and for Send a copy).
  • canPublish — baseRole is one of owner, admin, editor — required for Create draft listing for marketplace (draft employee-facing listing).
  • Non-owners with canPublish see a notice that only owners and organization admins can change sharing visibility.

Editors in team scope without owner/admin may be blocked from sharing or send-copy until they switch to organization scope (mustSwitchScopeForSharing).


Sharing: same workflow vs individual copies

Share the same workflow (org or team)

The modal calls updateWorkflowVisibility with:

  • targetScope: org or team
  • For team scope: shareWithTeamKeys (team prefix values), plus org/team names as needed

Copy in the UI states that sharing with the organization keeps one workflow and moves it into the selected org when promoting from private. Team sharing scopes the same graph to the chosen team keys.

Send a copy to individuals

Send a copy posts to POST /api/v1/workflows/:workflowId/install-for-users (app/routes/api+/v1+/workflows+/$workflowId.install-for-users.tsx), which calls the workflow service POST .../workflows/:workflowId/install-for-user once per recipient with recipientUserId. Recipients must have a Keycloak user id. The request uses the caller’s current x-active-org / x-active-team / x-teamKey headers so the workflow service resolves context consistently.


Employee marketplace

The marketplace is where employees (and other users) discover operators packaged as listings. Technical model names (MarketplaceListing, routes under /u/marketplace, etc.) stay as in code.

Public catalog (browse / discovery)

The catalog SQL in catalogListingBaseWhereSql (app/utils/marketplace/listing.server.ts) only surfaces listings employees will see in browse/discovery when all of the following hold:

  • MarketplaceListing.status = approved
  • visibility = public
  • discoverability = listed
  • delistedAt and suspendedAt are null
  • currentPublishedVersionId is set (a published version exists)

Search facets (categories, operator level, Employee Pack filter) further narrow that set.

Listing detail URL and employee onboarding eligibility

getMarketplaceListingBySlug loads a listing by slug when an employee (or admin) opens a detail page by slug and onboarding is allowed:

  • status = approved
  • currentPublishedVersionId is not null
  • delistedAt and suspendedAt are null

It does not re-check visibility / discoverability in that query — an approved listing that is public + listed appears in the catalog; other visibility combinations may still affect who finds it, but slug-based detail and employee onboarding use the approved + not moderated rule above.

Employee onboarding (onboardMarketplaceListing in app/utils/marketplace/onboarding.server.ts) installs the published operator for the employee’s chosen workspace; it requires currentPublishedVersion on that record — otherwise the action responds that the listing is not onboardable.

Creating and editing a listing (publisher, employee-facing)

  • New listings start as MarketplaceListing.status = draft with a first MarketplaceListingVersion whose submissionStatus is draft (createMarketplaceDraft in app/utils/marketplace/publisher.server.ts).
  • A version is editable in the publisher UI only when submissionStatus is draft or changes_requested (isEditableDraftStatus in publisher.server.ts).
  • Submitting for review sets the version to submissionStatus = pending_review. The listing’s status becomes pending_review only if there was no currentPublishedVersionId yet; if a live version already exists, the listing status is left unchanged so the storefront can keep serving the last approved version while the new version is reviewed.

Submit triggers emails to addresses in MARKETPLACE_REVIEWER_EMAILS, falling back to ADMIN_EMAILS, and a confirmation email to the publisher (submitMarketplaceDraft in publisher.server.ts) so the employee-facing listing can be reviewed before employees install it.

Version submission types

Each version has submissionType (MarketplaceSubmissionType in Prisma): new_listing, metadata_update, source_version_update, or republication. New drafts use new_listing; cloned drafts from an existing version use metadata_update when copying fields from the prior version.

Review decisions (admin)

reviewMarketplaceSubmission (app/utils/marketplace/admin.server.ts) only accepts versions in submissionStatus pending_review.

  • Approve — sets the version to approved, sets publishedAt, points the listing’s currentPublishedVersionId at this version, sets listing status to approved, clears delistedAt / suspendedAt, copies pack flags, and marks the previous published version’s submissionStatus as superseded when it was a different row.
  • Reject or Request changes — sets the version’s submissionStatus to rejected or changes_requested. The listing status becomes rejected or changes_requested only if there was no other live published version; if a previous version was already live, the listing status stays approved so the last good publish remains available.

Each decision writes a MarketplaceReview row and sends MarketplaceReviewDecisionPublisherEmail to the publisher when an email is present.

Listing-level statuses (reference)

MarketplaceListing.status enum in Prisma: draft, pending_review, approved, rejected, changes_requested, delisted, suspended, archived.

MarketplaceListingVersion.submissionStatus: draft, pending_review, approved, rejected, changes_requested, withdrawn, superseded.

Moderation after publish

updateMarketplaceListingLifecycle (admin) only runs when a currentPublishedVersion exists. Actions implemented in code:

ActionListing fields updatedMarketplaceReview.action when applicable
deliststatus → delisted, delistedAt set, suspendedAt cleareddelisted
suspendstatus → suspended, suspendedAt setsuspended
restorestatus → approved, delistedAt / suspendedAt clearedrestored
feature / unfeaturefeaturedRank set or cleared—
hide / showdiscoverability → hidden or listed—

Publisher delete rules

deleteMarketplaceDraftListing allows the publisher to delete their listing only when either there is no currentPublishedVersionId or listing status is rejected or suspended. Live approved listings cannot be deleted this way — the error message directs them to moderation/support.

Employee Pack onboarding guard

If an Employee Pack listing expects manually pinned bundled operator workflows, onboardMarketplaceListing throws before install when resolved member workflows do not match the expected count. The error string is: "This Employee Pack is missing pinned versions for one or more bundled workflows. Re-save and re-submit the listing before onboarding." (onboarding.server.ts).


Employee onboarding from the marketplace → operator workflow and chat

This is the path an employee (or anyone installing for their workspace) uses after choosing Onboard on an approved listing.

  1. The Onboard form posts to /u/marketplace/:workflowId/onboard (workflowId route param is the listing slug).

  2. onboardMarketplaceListing installs via onboardOperator into the target scope from resolveOnboardingTargetContext (personal vs org vs org+team), passing the published version’s sourceWorkflowId, optional versionPublicId, and Employee Pack memberWorkflows when applicable.

  3. On success, marketplace.$workflowId.onboard checks isDifferentFromCurrentContext from resolveOnboardingTargetContext (app/utils/marketplace/onboarding-context.ts):

    • true — e.g. employee onboarding into personal while the session still has an org/team selected, or org/team keys that do not match the active session — the app redirects to /u/marketplace/<slug> with a success toast (switch workspace before opening the installed operator).
    • false — the app redirects to /u/alpha/agent/workflow/<targetWorkflowId>?forked=1&marketplace_listing=<slug> (Remix route segment agent is the current URL) so the employee lands on the new operator workflow immediately.
  4. The employee onboarding modal (OnboardContextModal) tells users they can customize the operator later via Go to Chat on the workflow — handleGoToChat / createThreadFromWorkflow in alpha.agent.workflow.$id.tsx load or create an operator chat thread for that workflow id and navigate to /u/alpha/agent/<threadId>.


Context-based operator chat

  • Outbound operator chat and workflow API traffic from the browser attach getWorkflowServiceContextHeaders() so the workflow service sees the same org/team as the selector (see headers above).
  • GET /api/v1/community/workflow-context returns runs and chats for a workflow id using the same session org/team headers for scoping.
  • Loading a thread on /u/alpha/agent/:id (operator chat UI; route name unchanged) fetches messages and, when present, the workflow object on the thread payload, which hydrates the planner workflow in the client store (alpha.agent.index.tsx).

Tips: building and debugging workflows

These are operational practices aligned with how runs and steps are observed in the UI (logs, step I/O, retries); they are not separate product flags.

  • Narrow scope — fewer steps make failures easier to map to a single action.
  • Name steps and inputs — execution timelines and exports stay readable.
  • Validate early — failed fast steps reduce partial side effects.
  • Use run detail — inspect stdout/stderr, outputs, and retry rows before changing operator / step prompts.
  • Human review — add approval steps where policy is ambiguous; see Human Review & Approvals.

Where to go next


Workspace headers, Keycloak roles, employee marketplace Prisma state machines, and the employee onboard redirect are implemented as described above; if behavior changes, update this page alongside listing.server.ts, publisher.server.ts, admin.server.ts, and onboarding.server.ts.

guides/collaboration-marketplace-chat-context