Skip to content

Organisation

Company structure facades: company, departments, teams, and role versions.

Enums

enums

Organization domain enumerations.

CompanyType

Bases: StrEnum

Pre-defined company template types.

DepartmentName

Bases: StrEnum

Standard department names within the organization.

Models

models

Organization-layer domain models.

Holds the input models for company, department, and agent mutations so the organization service layer can validate the same input shapes without importing from the API layer. The synthorg.api.dto_org module re-exports these for HTTP controllers.

UpdateCompanyRequest pydantic-model

Bases: BaseModel

Partial update for company-level settings.

Lives in the organization domain layer so the service can validate input without importing from synthorg.api.dto_org.

Config:

  • frozen: True
  • allow_inf_nan: False
  • extra: forbid

Fields:

company_name pydantic-field

company_name = None

Display name of the company.

autonomy_level pydantic-field

autonomy_level = None

Org-wide autonomy level (full, semi, supervised, locked).

budget_monthly pydantic-field

budget_monthly = None

Monthly budget cap for the company in the operator's configured currency; set to 0 to disable enforcement.

communication_pattern pydantic-field

communication_pattern = None

Communication strategy or pattern identifier.

CreateDepartmentRequest pydantic-model

Bases: BaseModel

Request body for creating a new department.

Config:

  • frozen: True
  • allow_inf_nan: False
  • extra: forbid

Fields:

UpdateDepartmentRequest pydantic-model

Bases: BaseModel

Partial update for an existing department.

Config:

  • frozen: True
  • allow_inf_nan: False
  • extra: forbid

Fields:

  • head (NotBlankStr | None)
  • budget_percent (float | None)
  • autonomy_level (AutonomyLevel | None)
  • teams (tuple[Team, ...] | None)
  • ceremony_policy (dict[str, object] | None)

Validators:

  • _validate_ceremony_policyceremony_policy

ReorderDepartmentsRequest pydantic-model

Bases: BaseModel

Reorder departments -- must be an exact permutation.

Config:

  • frozen: True
  • allow_inf_nan: False
  • extra: forbid

Fields:

CreateAgentOrgRequest pydantic-model

Bases: BaseModel

Request body for creating a new agent in the org config.

Config:

  • frozen: True
  • allow_inf_nan: False
  • extra: forbid

Fields:

Validators:

  • _validate_model_pair

UpdateAgentOrgRequest pydantic-model

Bases: BaseModel

Partial update for an existing agent in the org config.

Config:

  • frozen: True
  • allow_inf_nan: False
  • extra: forbid

Fields:

Validators:

  • _validate_model_pair

ReorderAgentsRequest pydantic-model

Bases: BaseModel

Reorder agents within a department -- must be an exact permutation.

Config:

  • frozen: True
  • allow_inf_nan: False
  • extra: forbid

Fields:

Services

services

Organization facades for the MCP handler layer.

Company read/versions, department CRUD + health, team CRUD, and role-version history. Writes route through org_mutation_service where it already owns the flow; other paths use in-memory stores until durable repositories land.

The file-level EM101 suppression is intentional: every capability gap in this module raises :class:CapabilityNotSupportedError from a short identifier and operator-readable reason, both string literals by design so capability telemetry (logged via :data:MCP_HANDLER_CAPABILITY_GAP) has a stable, grep-able message. Hoisting them to msg = ... locals would obscure the one-line intent with no runtime benefit.

UnsetType

Sentinel type for "field not provided" distinct from None.

Used by update operations where None is a legitimate value the caller may want to persist (e.g. clearing a department_id) and must be distinguished from "leave this field unchanged".

CompanyReadService

CompanyReadService(*, org_mutation)

Read + light mutation facade over the company/org surface.

Source code in src/synthorg/organization/services.py
def __init__(self, *, org_mutation: OrgMutationService) -> None:
    self._org = cast("Any", org_mutation)

get_company async

get_company()

Return the company snapshot or raise if the capability is missing.

Raises:

Type Description
CapabilityNotSupportedError

When the wired OrgMutationService does not expose get_company.

Source code in src/synthorg/organization/services.py
async def get_company(self) -> object:
    """Return the company snapshot or raise if the capability is missing.

    Raises:
        CapabilityNotSupportedError: When the wired
            ``OrgMutationService`` does not expose ``get_company``.
    """
    fn = getattr(self._org, "get_company", None)
    if callable(fn):
        return await fn()
    raise CapabilityNotSupportedError(
        "company_get",
        "OrgMutationService does not expose get_company",
    )

update_company async

update_company(*, payload, actor_id)

Apply a partial company-settings update.

The payload is validated against :class:UpdateCompanyRequest here so unknown keys are rejected before reaching the mutation service. The call then matches the target signature update_company(data, *, saved_by=...) -- actor_id is recorded as saved_by in the persisted snapshot.

Returns:

Type Description
object

The updated company snapshot returned by the mutation service.

Raises:

Type Description
CapabilityNotSupportedError

When OrgMutationService does not expose update_company.

Source code in src/synthorg/organization/services.py
async def update_company(
    self,
    *,
    payload: Mapping[str, object],
    actor_id: NotBlankStr,
) -> object:
    """Apply a partial company-settings update.

    The payload is validated against :class:`UpdateCompanyRequest`
    here so unknown keys are rejected before reaching the mutation
    service.  The call then matches the target signature
    ``update_company(data, *, saved_by=...)`` -- ``actor_id`` is
    recorded as ``saved_by`` in the persisted snapshot.

    Returns:
        The updated company snapshot returned by the mutation service.

    Raises:
        CapabilityNotSupportedError: When ``OrgMutationService`` does
            not expose ``update_company``.
    """
    fn = getattr(self._org, "update_company", None)
    if not callable(fn):
        raise CapabilityNotSupportedError(
            "company_update",
            "OrgMutationService does not expose update_company",
        )
    data = UpdateCompanyRequest.model_validate(dict(payload))
    result = await fn(data, saved_by=actor_id)
    logger.info(COMPANY_UPDATED_VIA_MCP, actor_id=actor_id)
    return result

list_departments async

list_departments()

Return the company's departments.

Raises:

Type Description
CapabilityNotSupportedError

When OrgMutationService does not expose list_departments.

Source code in src/synthorg/organization/services.py
async def list_departments(self) -> Sequence[object]:
    """Return the company's departments.

    Raises:
        CapabilityNotSupportedError: When ``OrgMutationService`` does
            not expose ``list_departments``.
    """
    fn = getattr(self._org, "list_departments", None)
    if not callable(fn):
        raise CapabilityNotSupportedError(
            "company_list_departments",
            "OrgMutationService does not expose list_departments",
        )
    return tuple(await fn())

reorder_departments async

reorder_departments(*, department_ids, actor_id)

Apply a new department ordering, auditing the change.

Raises:

Type Description
CapabilityNotSupportedError

When OrgMutationService does not expose reorder_departments.

Source code in src/synthorg/organization/services.py
async def reorder_departments(
    self,
    *,
    department_ids: Sequence[str],
    actor_id: NotBlankStr,
) -> None:
    """Apply a new department ordering, auditing the change.

    Raises:
        CapabilityNotSupportedError: When ``OrgMutationService`` does
            not expose ``reorder_departments``.
    """
    fn = getattr(self._org, "reorder_departments", None)
    if not callable(fn):
        raise CapabilityNotSupportedError(
            "company_reorder_departments",
            "OrgMutationService does not expose reorder_departments",
        )
    await fn(department_ids=tuple(department_ids), actor=actor_id)
    logger.info(
        DEPARTMENTS_REORDERED_VIA_MCP,
        actor_id=actor_id,
        count=len(department_ids),
    )

list_versions async

list_versions()

Return all company-snapshot versions.

Raises:

Type Description
CapabilityNotSupportedError

When OrgMutationService does not expose list_company_versions.

Source code in src/synthorg/organization/services.py
async def list_versions(self) -> Sequence[object]:
    """Return all company-snapshot versions.

    Raises:
        CapabilityNotSupportedError: When ``OrgMutationService`` does
            not expose ``list_company_versions``.
    """
    fn = getattr(self._org, "list_company_versions", None)
    if not callable(fn):
        raise CapabilityNotSupportedError(
            "company_list_versions",
            "OrgMutationService does not expose list_company_versions",
        )
    return tuple(await fn())

get_version async

get_version(version_id)

Fetch a single company-snapshot version by id.

Returns:

Type Description
object | None

The matching company-snapshot version, or None when no

object | None

version has that id.

Raises:

Type Description
CapabilityNotSupportedError

When OrgMutationService does not expose get_company_version.

Source code in src/synthorg/organization/services.py
async def get_version(self, version_id: NotBlankStr) -> object | None:
    """Fetch a single company-snapshot version by id.

    Returns:
        The matching company-snapshot version, or ``None`` when no
        version has that id.

    Raises:
        CapabilityNotSupportedError: When ``OrgMutationService`` does
            not expose ``get_company_version``.
    """
    fn = getattr(self._org, "get_company_version", None)
    if not callable(fn):
        raise CapabilityNotSupportedError(
            "company_get_version",
            "OrgMutationService does not expose get_company_version",
        )
    return cast("object | None", await fn(version_id))

DepartmentService

DepartmentService()

Department CRUD + health.

Mutations are serialised through a single :class:asyncio.Lock so concurrent MCP handler calls cannot race on the in-memory dict (check-then-act in :meth:update_department and :meth:delete_department).

Source code in src/synthorg/organization/services.py
def __init__(self) -> None:
    self._departments: dict[UUID, _DepartmentRecord] = {}
    self._lock = asyncio.Lock()

list_departments async

list_departments(*, offset=0, limit=None)

Return paginated departments newest-first plus unfiltered total.

Parameters:

Name Type Description Default
offset int

Non-negative page offset.

0
limit int | None

Optional positive page size; None returns every department from offset onwards.

None

Raises:

Type Description
ValueError

If offset is negative, or limit is provided and non-positive.

Source code in src/synthorg/organization/services.py
async def list_departments(
    self,
    *,
    offset: int = 0,
    limit: int | None = None,
) -> tuple[tuple[_DepartmentRecord, ...], int]:
    """Return paginated departments newest-first plus unfiltered total.

    Args:
        offset: Non-negative page offset.
        limit: Optional positive page size; ``None`` returns every
            department from ``offset`` onwards.

    Raises:
        ValueError: If ``offset`` is negative, or ``limit`` is
            provided and non-positive.
    """
    if offset < 0:
        msg = f"offset must be >= 0, got {offset}"
        raise ValueError(msg)
    if limit is not None and limit < 1:
        msg = f"limit must be >= 1 when provided, got {limit}"
        raise ValueError(msg)
    async with self._lock:
        snapshot = tuple(copy.deepcopy(d) for d in self._departments.values())
    ordered = tuple(
        sorted(snapshot, key=lambda d: d.created_at, reverse=True),
    )
    total = len(ordered)
    end = total if limit is None else offset + limit
    return ordered[offset:end], total

get_department async

get_department(department_id)

Fetch a single department by UUID or None if not found.

Returns:

Type Description
_DepartmentRecord | None

A deep copy of the stored department, or None when the id

_DepartmentRecord | None

is malformed or no such department exists.

Source code in src/synthorg/organization/services.py
async def get_department(
    self,
    department_id: NotBlankStr,
) -> _DepartmentRecord | None:
    """Fetch a single department by UUID or ``None`` if not found.

    Returns:
        A deep copy of the stored department, or ``None`` when the id
        is malformed or no such department exists.
    """
    try:
        key = UUID(department_id)
    except ValueError:
        return None
    async with self._lock:
        record = self._departments.get(key)
        return copy.deepcopy(record) if record is not None else None

create_department async

create_department(*, name, description, actor_id)

Create a department, auditing the event on success.

Returns:

Type Description
_DepartmentRecord

A deep copy of the newly created department record.

Source code in src/synthorg/organization/services.py
async def create_department(
    self,
    *,
    name: NotBlankStr,
    description: NotBlankStr,
    actor_id: NotBlankStr,
) -> _DepartmentRecord:
    """Create a department, auditing the event on success.

    Returns:
        A deep copy of the newly created department record.
    """
    record = _DepartmentRecord(
        id=uuid4(),
        name=name,
        description=description,
        created_at=datetime.now(UTC),
    )
    async with self._lock:
        self._departments[record.id] = record
    logger.info(
        DEPARTMENT_CREATED_VIA_MCP,
        department_id=str(record.id),
        actor_id=actor_id,
    )
    return copy.deepcopy(record)

update_department async

update_department(*, department_id, actor_id, name=None, description=None)

Patch a department's name / description in place.

Returns:

Type Description
_DepartmentRecord | None

A deep copy of the updated department, or None when the id

_DepartmentRecord | None

is malformed or no such department exists.

Source code in src/synthorg/organization/services.py
async def update_department(
    self,
    *,
    department_id: NotBlankStr,
    actor_id: NotBlankStr,
    name: NotBlankStr | None = None,
    description: NotBlankStr | None = None,
) -> _DepartmentRecord | None:
    """Patch a department's ``name`` / ``description`` in place.

    Returns:
        A deep copy of the updated department, or ``None`` when the id
        is malformed or no such department exists.
    """
    try:
        key = UUID(department_id)
    except ValueError:
        return None
    async with self._lock:
        record = self._departments.get(key)
        if record is None:
            return None
        if name is not None:
            record.name = name
        if description is not None:
            record.description = description
        record.updated_at = datetime.now(UTC)
        returned = copy.deepcopy(record)
    logger.info(
        DEPARTMENT_UPDATED_VIA_MCP,
        department_id=department_id,
        actor_id=actor_id,
    )
    return returned

delete_department async

delete_department(*, department_id, actor_id, reason)

Remove a department; emit the audit event only on real removal.

Returns:

Type Description
bool

True when a department was removed, False when the id

bool

is malformed or no such department exists.

Source code in src/synthorg/organization/services.py
async def delete_department(
    self,
    *,
    department_id: NotBlankStr,
    actor_id: NotBlankStr,
    reason: NotBlankStr,
) -> bool:
    """Remove a department; emit the audit event only on real removal.

    Returns:
        ``True`` when a department was removed, ``False`` when the id
        is malformed or no such department exists.
    """
    try:
        key = UUID(department_id)
    except ValueError:
        return False
    async with self._lock:
        removed = self._departments.pop(key, None) is not None
    if removed:
        logger.info(
            DEPARTMENT_DELETED_VIA_MCP,
            department_id=department_id,
            actor_id=actor_id,
            reason=reason,
            removed=removed,
        )
    return removed

get_health async

get_health(department_id)

Return a summary health payload for the department.

Source code in src/synthorg/organization/services.py
async def get_health(
    self,
    department_id: NotBlankStr,
) -> Mapping[str, object]:
    """Return a summary health payload for the department."""
    record = await self.get_department(department_id)
    if record is None:
        return {"status": "unknown", "reason": "not_found"}
    return {
        "status": "healthy",
        "department_id": str(record.id),
        "last_update": record.updated_at.isoformat(),
    }

TeamService

TeamService()

Team CRUD.

Mutations are serialised through a single :class:asyncio.Lock so concurrent MCP handler calls cannot race on the in-memory dict.

Source code in src/synthorg/organization/services.py
def __init__(self) -> None:
    self._teams: dict[UUID, _TeamRecord] = {}
    self._lock = asyncio.Lock()

list_teams async

list_teams(*, offset=0, limit=None)

Return paginated teams newest-first plus unfiltered total.

Parameters:

Name Type Description Default
offset int

Non-negative page offset.

0
limit int | None

Optional positive page size; None returns every team from offset onwards.

None

Raises:

Type Description
ValueError

If offset is negative, or limit is provided and non-positive.

Source code in src/synthorg/organization/services.py
async def list_teams(
    self,
    *,
    offset: int = 0,
    limit: int | None = None,
) -> tuple[tuple[_TeamRecord, ...], int]:
    """Return paginated teams newest-first plus unfiltered total.

    Args:
        offset: Non-negative page offset.
        limit: Optional positive page size; ``None`` returns every
            team from ``offset`` onwards.

    Raises:
        ValueError: If ``offset`` is negative, or ``limit`` is
            provided and non-positive.
    """
    if offset < 0:
        msg = f"offset must be >= 0, got {offset}"
        raise ValueError(msg)
    if limit is not None and limit < 1:
        msg = f"limit must be >= 1 when provided, got {limit}"
        raise ValueError(msg)
    async with self._lock:
        snapshot = tuple(copy.deepcopy(t) for t in self._teams.values())
    ordered = tuple(
        sorted(snapshot, key=lambda t: t.created_at, reverse=True),
    )
    total = len(ordered)
    end = total if limit is None else offset + limit
    return ordered[offset:end], total

get_team async

get_team(team_id)

Fetch a single team by UUID or None if not found.

Returns:

Type Description
_TeamRecord | None

A deep copy of the stored team, or None when the id is

_TeamRecord | None

malformed or no such team exists.

Source code in src/synthorg/organization/services.py
async def get_team(self, team_id: NotBlankStr) -> _TeamRecord | None:
    """Fetch a single team by UUID or ``None`` if not found.

    Returns:
        A deep copy of the stored team, or ``None`` when the id is
        malformed or no such team exists.
    """
    try:
        key = UUID(team_id)
    except ValueError:
        return None
    async with self._lock:
        record = self._teams.get(key)
        return copy.deepcopy(record) if record is not None else None

create_team async

create_team(*, name, actor_id, department_id=None)

Create a team, auditing the event on success.

Returns:

Type Description
_TeamRecord

A deep copy of the newly created team record.

Source code in src/synthorg/organization/services.py
async def create_team(
    self,
    *,
    name: NotBlankStr,
    actor_id: NotBlankStr,
    department_id: NotBlankStr | None = None,
) -> _TeamRecord:
    """Create a team, auditing the event on success.

    Returns:
        A deep copy of the newly created team record.
    """
    record = _TeamRecord(
        id=uuid4(),
        name=name,
        department_id=department_id,
        created_at=datetime.now(UTC),
    )
    async with self._lock:
        self._teams[record.id] = record
    logger.info(
        TEAM_CREATED_VIA_MCP,
        team_id=str(record.id),
        actor_id=actor_id,
    )
    return copy.deepcopy(record)

update_team async

update_team(*, team_id, actor_id, name=None, department_id=UNSET)

Update a team; department_id=None clears the field.

The default department_id=UNSET sentinel means "leave unchanged"; pass department_id=None explicitly to clear a team's department assignment.

Returns:

Type Description
_TeamRecord | None

A deep copy of the updated team, or None when the id is

_TeamRecord | None

malformed or no such team exists.

Source code in src/synthorg/organization/services.py
async def update_team(
    self,
    *,
    team_id: NotBlankStr,
    actor_id: NotBlankStr,
    name: NotBlankStr | None = None,
    department_id: NotBlankStr | None | UnsetType = UNSET,
) -> _TeamRecord | None:
    """Update a team; ``department_id=None`` clears the field.

    The default ``department_id=UNSET`` sentinel means "leave
    unchanged"; pass ``department_id=None`` explicitly to clear a
    team's department assignment.

    Returns:
        A deep copy of the updated team, or ``None`` when the id is
        malformed or no such team exists.
    """
    try:
        key = UUID(team_id)
    except ValueError:
        return None
    async with self._lock:
        record = self._teams.get(key)
        if record is None:
            return None
        if name is not None:
            record.name = name
        if not isinstance(department_id, UnsetType):
            record.department_id = department_id
        record.updated_at = datetime.now(UTC)
        returned = copy.deepcopy(record)
    logger.info(
        TEAM_UPDATED_VIA_MCP,
        team_id=team_id,
        actor_id=actor_id,
    )
    return returned

delete_team async

delete_team(*, team_id, actor_id, reason)

Remove a team; emit the audit event only on real removal.

Returns:

Type Description
bool

True when a team was removed, False when the id is

bool

malformed or no such team exists.

Source code in src/synthorg/organization/services.py
async def delete_team(
    self,
    *,
    team_id: NotBlankStr,
    actor_id: NotBlankStr,
    reason: NotBlankStr,
) -> bool:
    """Remove a team; emit the audit event only on real removal.

    Returns:
        ``True`` when a team was removed, ``False`` when the id is
        malformed or no such team exists.
    """
    try:
        key = UUID(team_id)
    except ValueError:
        return False
    async with self._lock:
        removed = self._teams.pop(key, None) is not None
    if removed:
        logger.info(
            TEAM_DELETED_VIA_MCP,
            team_id=team_id,
            actor_id=actor_id,
            reason=reason,
            removed=removed,
        )
    return removed

RoleVersionService

RoleVersionService(*, org_mutation=None)

Read facade for the role-version snapshot history.

Source code in src/synthorg/organization/services.py
def __init__(
    self,
    *,
    org_mutation: OrgMutationService | None = None,
) -> None:
    self._org = cast("Any", org_mutation) if org_mutation is not None else None

list_versions async

list_versions(*, role_name=None)

Return role-snapshot versions, optionally filtered by role name.

Raises:

Type Description
CapabilityNotSupportedError

When no OrgMutationService is wired, or the wired one does not expose list_role_versions.

Source code in src/synthorg/organization/services.py
async def list_versions(
    self,
    *,
    role_name: NotBlankStr | None = None,
) -> Sequence[object]:
    """Return role-snapshot versions, optionally filtered by role name.

    Raises:
        CapabilityNotSupportedError: When no ``OrgMutationService`` is
            wired, or the wired one does not expose
            ``list_role_versions``.
    """
    if self._org is None:
        raise CapabilityNotSupportedError(
            "role_versions_list",
            "OrgMutationService not wired on app_state",
        )
    fn = getattr(self._org, "list_role_versions", None)
    if not callable(fn):
        raise CapabilityNotSupportedError(
            "role_versions_list",
            "OrgMutationService does not expose list_role_versions",
        )
    return tuple(await fn(role_name=role_name))

get_version async

get_version(version_id)

Fetch a single role-snapshot version by id.

Returns:

Type Description
object | None

The matching role-snapshot version, or None when no

object | None

version has that id.

Raises:

Type Description
CapabilityNotSupportedError

When no OrgMutationService is wired, or the wired one does not expose get_role_version.

Source code in src/synthorg/organization/services.py
async def get_version(
    self,
    version_id: NotBlankStr,
) -> object | None:
    """Fetch a single role-snapshot version by id.

    Returns:
        The matching role-snapshot version, or ``None`` when no
        version has that id.

    Raises:
        CapabilityNotSupportedError: When no ``OrgMutationService`` is
            wired, or the wired one does not expose
            ``get_role_version``.
    """
    if self._org is None:
        raise CapabilityNotSupportedError(
            "role_versions_get",
            "OrgMutationService not wired on app_state",
        )
    fn = getattr(self._org, "get_role_version", None)
    if not callable(fn):
        raise CapabilityNotSupportedError(
            "role_versions_get",
            "OrgMutationService does not expose get_role_version",
        )
    return cast("object | None", await fn(version_id))