> ## Documentation Index
> Fetch the complete documentation index at: https://docs.open.cx/llms.txt
> Use this file to discover all available pages before exploring further.

# 2026 08 23 sub session workflow team assignment design

# Sub-session Workflow and Team Assignment Design

**Linear:** [IMP-1119](https://linear.app/open-cx/issue/IMP-1119/create-sub-session-workflow-action-and-team-assignment), child of Snoonu IMP-700.

## Goal

Let workflows create a depth-one sub-session and let agents choose or change a sub-session's support team both during creation and directly from the parent session's Sub-sessions card.

## Existing behavior

* A sub-session is a normal `chat_sessions` row with `parent_session_id` and `parent_ticket_number`.
* The hierarchy is intentionally one level deep. Manual creation and retroactive linking lock the affected parent rows before writing so concurrent operations cannot create a depth-two tree.
* Agent-created sub-sessions are supported only where `InitiateSessionUtils.isSubSessionsEnabled` is true: OpenCX ticketing and Zendesk v2.
* Any opened sub-session can already use the normal header team switcher, and the existing `POST /backend/chat-session/update/:sessionId/team` route enforces `sessions:write` and the standard support-team assignment behavior.
* The parent card renders each child inside a navigation link. Its embedded team badge is therefore intentionally non-interactive.
* Workflows have no sub-session action. The closest action, Create Ticket, inserts a standalone row and does not enforce or emit sub-session behavior.

## Design

### Create Sub-session workflow action

Add `WorkflowActionEnum.CREATE_SUB_SESSION = 'create-sub-session'` and register a `Create Sub-session` non-terminal action in both eager and lazy registries.

Inputs:

* `parentSessionNumber: number` — required and resolved within `ctx.organization.id`.
* `team: string | undefined` — optional support-team lazy select.

Outputs:

* `sessionId: string`
* `sessionNumber: number`

The action creates an open child without sending a message. It inherits the parent's contact, channel, requester email, and source email address. It marks the session outbound, matching agent-created sub-sessions. If a team is supplied, the action validates that it belongs to the organization, is not deleted, and is support-enabled, then stores it as the child's initial team while leaving the child unassigned.

Before insertion, the action verifies that the organization's ticketing integration supports sub-sessions and that the parent is a top-level session. The parent is locked and rechecked inside the insertion transaction, preserving the current concurrency invariant. Cross-organization session or team identifiers never resolve.

After commit, the parent receives the existing `SUB_SESSION_CREATED` internal timeline event with the child identifiers plus `workflow_id` and `workflow_run_id`, and the parent is socket-refreshed. No new message enum or migration is needed.

### Team during dashboard creation

Extend `CreateAgentSessionDto` with optional `group_id`. The agent-initiated creation service validates an explicitly supplied team before any session row or outbound side effect is created and stores it as the initial `inbox_id`. Omitting the field preserves existing behavior.

The New Session dialog displays an optional Team select only when `parentSessionId` is present. It lists support-enabled teams, submits `group_id` for both existing- and new-contact branches, and resets the selection when the dialog closes. The current assignee selection remains authoritative; initial team selection does not invoke automatic distribution.

### Inline team assignment from the parent card

The parent Sub-sessions card renders the existing `SessionBadge__Team` control outside the child's navigation link, beside the unlink control. The embedded static team badge is hidden only when the external interactive control is shown, avoiding duplicate team labels.

The inline control is gated by `sessions:write`, independently of `sessions-sub-sessions:write`: changing ownership is a normal session mutation, while linking and unlinking remain sub-session-management mutations. It calls the existing team update route and assignment service, then refreshes the Sub-sessions SWR query so the card immediately reflects the new team.

## Error behavior

* Missing or foreign parent session number: workflow action returns a runtime error and writes nothing.
* Parent is already a sub-session: runtime error and no child.
* Unsupported ticketing integration: runtime error and no child.
* Missing, foreign, deleted, or non-support team: action/service error and no child.
* No team supplied: child is created with `inbox_id = null`.
* Inline team mutation without `sessions:write`: existing route returns 403.

## Testing strategy

* Real Postgres workflow-action tests cover the success shape, inherited fields, optional team, all validation failures, tenant isolation, and parent-link concurrency.
* A real workflow-runner test executes the registered action and asserts step output, child linkage, and workflow-attributed timeline data.
* Real agent-initiated service/controller tests cover initial team persistence, omission, invalid teams, and no-orphan guarantees.
* A full-stack Playwright test uses development login plus real backend routes to create a parent, create a team-owned child through the dialog, and change that team inline from the parent card. It also checks that the standalone New Session dialog does not expose the sub-session-only Team field.
* Focused typechecks, lint, formatting, SDK generation checks, and touched-area tests run before commit and PR creation.

## Non-goals

* No link/unlink workflow actions.
* No nested sub-sessions.
* No first-message composer inside the workflow action.
* No new shared abstraction, database migration, or public API endpoint.
* No change to automatic assignment strategy; workflows can follow the new action with the existing Assign Session to Support Team action when picker-based routing is desired.
