Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ release tags and PEP 440 for the Python package.
one-time browser-local layout migration.
- Sanitized MCP, A2A, and Policy interaction cards and reduced-motion-aware data-packet projection
between Agents and governed Office stations.
- Versioned Company Pack SDK definition and Catalog boundary, with Music Studio moved into a
discoverable scenario package while retaining its existing API and compatibility import.

## 0.1.0-alpha.1 — 2026-07-27

Expand Down
1 change: 1 addition & 0 deletions docs/architecture/modules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ The currently implemented vertical slice is described here:
- [Task budget and admission control implementation](task-budget-admission-implementation.md)
- [Financial governance implementation](financial-governance-implementation.md)
- [Company Packs implementation](company-packs-implementation.md)
- [Company Pack SDK and scenario boundary](pack-sdk-boundary.md)
- [Human Task resolution implementation](human-task-resolution-implementation.md)
- [Identity, Principal, and RBAC baseline](identity-rbac-baseline-implementation.md)
- [Persistent Identity and OIDC baseline](persistent-identity-oidc-implementation.md)
Expand Down
5 changes: 5 additions & 0 deletions docs/architecture/modules/company-packs-implementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ A Pack is immutable declarative JSON with a semantic version, content digest, re
Gates, dependencies, and a bounded resource list. Arbitrary Pack-supplied executable code is
rejected by design.

Business scenarios depend on the stable `agentmesh.packs.sdk` boundary. The generic Pack service
accepts a `CompanyTemplateDefinition` and does not import Music Studio or validate music-specific
configuration. Built-in scenario discovery uses `PackCatalog`; see the
[Pack SDK and scenario boundary](pack-sdk-boundary.md).

The baseline supports Organization Units, Positions, published Business Object Types, Budget
Allocations, Operating Cycles, Objectives, Key Results, Initiatives, Memory Policies, and Company
Operations. A Template is a Pack composition marker, not a separate runtime. Installation follows:
Expand Down
51 changes: 51 additions & 0 deletions docs/architecture/modules/pack-sdk-boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Company Pack SDK and Scenario Boundary

Status: Implemented phase 1

AgentMesh keeps the operating runtime independent from business scenarios. The runtime owns Tasks,
Agents, governance, persistence, Pack installation, compatibility checks, upgrades, and audit.
A scenario owns its organization manifest, business vocabulary, configuration validation, required
capabilities, workflow adapters, and focused user experience.

The first stable in-process SDK surface is `agentmesh.packs.sdk`:

- `CompanyTemplateDefinition` declares identity, semantic version, mission, manifest factory,
Feature requirements, credentials, permissions, safety default, and configuration normalizer;
- `PackCatalog` provides deterministic discovery and rejects ambiguous slug or Pack-key
registration;
- `CompanyPackService` consumes the definition contract and contains no Music Studio imports or
music-specific validation;
- the API resolves Music Studio through the built-in Catalog and passes its definition into generic
preview, installation, upgrade-preview, and upgrade operations.

Music Studio now lives under `agentmesh.packs.music_studio`. The old
`agentmesh.templates.music_studio` module is a compatibility-only re-export so downstream imports
do not break during the transition.

## Dependency direction

```text
Music Studio definition -> Pack SDK -> Company Pack domain
Company Template API ----> Catalog ----> definition
Company Pack service ----> Pack SDK contract
```

The Company Pack service must not import a concrete scenario. Scenario configuration validation
must not be added to the generic service. External writes remain disabled by default at the SDK
boundary.

## Adding a scenario

An in-repository scenario implements a `CompanyTemplateDefinition`, registers it in a Catalog, and
uses the generic service operations. Its manifest remains declarative; executable code is not
loaded from Pack JSON. A future external repository will publish a Python package or signed bundle
that exposes the same definition contract after a separate trust and loading design is accepted.

## Remaining separation work

Phase 1 separates the declarative Pack and configuration contract. Later phases can move the
Music Studio workflow service, HTTP routes, provider adapters, and static UI into a separately
versioned distribution. The older Market Intelligence template and Operations helpers must also
move onto the same definition contract before the whole application layer is scenario-neutral.
Physical repository separation should wait until the SDK compatibility policy and external Pack
loading/trust model are stable.
11 changes: 11 additions & 0 deletions docs/implementation-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,17 @@ Company Pack in-place upgrade verification on 2026-08-03 additionally:
- continues to reject resource additions/removals, non-Business-Object resource changes, stale
previews, incompatible schemas, and downgrade attempts.

Company Pack SDK separation phase 1 on 2026-08-03 additionally:

- passed 418 non-PostgreSQL tests at 83.09% line coverage (gate: 80%);
- introduced a stable `CompanyTemplateDefinition` and deterministic `PackCatalog` discovery API;
- moved the Music Studio manifest and configuration validation into
`agentmesh.packs.music_studio`, retaining a compatibility import for existing consumers;
- changed generic preview, installation, upgrade-preview, and upgrade operations to accept a Pack
definition instead of importing a concrete business scenario;
- kept workflow-service, route, provider, and UI distribution extraction as a later phase after
the SDK compatibility and external trust/loading contracts stabilize.

Market Intelligence Studio baseline verification on 2026-07-30 additionally:

- passed 390 non-PostgreSQL tests at 82.71% line coverage (gate: 80%);
Expand Down
31 changes: 24 additions & 7 deletions src/agentmesh/api/company_template_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,11 @@
)
from agentmesh.domain.identity import Permission
from agentmesh.features import Feature
from agentmesh.packs.catalog import BUILTIN_PACK_CATALOG
from agentmesh.templates.market_intelligence_studio import TEMPLATE_SLUG
from agentmesh.templates.music_studio import TEMPLATE_SLUG as MUSIC_STUDIO_TEMPLATE_SLUG

MUSIC_STUDIO_DEFINITION = BUILTIN_PACK_CATALOG.get("music-studio")
MUSIC_STUDIO_TEMPLATE_SLUG = MUSIC_STUDIO_DEFINITION.slug

router = APIRouter(
prefix="/api/v1/company-templates",
Expand Down Expand Up @@ -82,7 +85,9 @@ def list_templates(
service: ServiceDependency,
) -> list[CompanyTemplatePreviewResponse]:
return [
CompanyTemplatePreviewResponse.from_domain(service.preview_music_studio_template()),
CompanyTemplatePreviewResponse.from_domain(
service.preview_template(MUSIC_STUDIO_DEFINITION)
),
CompanyTemplatePreviewResponse.from_domain(service.preview_market_intelligence_template())
]

Expand All @@ -94,7 +99,9 @@ def list_templates(
def preview_music_studio_template(
service: ServiceDependency,
) -> CompanyTemplatePreviewResponse:
return CompanyTemplatePreviewResponse.from_domain(service.preview_music_studio_template())
return CompanyTemplatePreviewResponse.from_domain(
service.preview_template(MUSIC_STUDIO_DEFINITION)
)


@router.post(
Expand All @@ -108,8 +115,17 @@ def install_music_studio_template(
principal: PrincipalDependency,
) -> CompanyTemplateInstallationResponse:
return CompanyTemplateInstallationResponse.from_domain(
service.install_music_studio_template(
**payload.model_dump(),
service.install_template(
MUSIC_STUDIO_DEFINITION,
company_name=payload.company_name,
mission=payload.mission,
default_currency=payload.default_currency,
operating_timezone=payload.operating_timezone,
configuration={
"default_language": payload.default_language,
"default_genre": payload.default_genre,
"use_plan": payload.use_plan,
},
owner_principal_id=principal.principal_id,
)
)
Expand All @@ -123,7 +139,7 @@ def preview_music_studio_upgrade(
service: ServiceDependency,
) -> PackUpgradePreviewResponse:
return PackUpgradePreviewResponse.from_domain(
service.preview_music_studio_upgrade()
service.preview_template_upgrade(MUSIC_STUDIO_DEFINITION)
)


Expand All @@ -137,7 +153,8 @@ def upgrade_music_studio(
principal: PrincipalDependency,
) -> PackUpgradeResultResponse:
return PackUpgradeResultResponse.from_domain(
service.upgrade_music_studio(
service.upgrade_template(
MUSIC_STUDIO_DEFINITION,
**payload.model_dump(),
upgraded_by=principal.principal_id,
)
Expand Down
78 changes: 32 additions & 46 deletions src/agentmesh/application/company_pack_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
)
from agentmesh.domain.registry import AgentDefinitionLifecycle, AgentVersionStatus
from agentmesh.features import Feature, FeatureGateSet
from agentmesh.packs.sdk import CompanyTemplateDefinition
from agentmesh.templates.market_intelligence_operations import (
DEFAULT_BUDGET_LIMIT_MICROS,
DEFAULT_CYCLE_DAYS,
Expand All @@ -62,12 +63,6 @@
TEMPLATE_SLUG,
build_pack,
)
from agentmesh.templates.music_studio import (
DEFAULT_MISSION as MUSIC_STUDIO_DEFAULT_MISSION,
)
from agentmesh.templates.music_studio import TEMPLATE_SLUG as MUSIC_STUDIO_TEMPLATE_SLUG
from agentmesh.templates.music_studio import USE_PLANS as MUSIC_STUDIO_USE_PLANS
from agentmesh.templates.music_studio import build_pack as build_music_studio_pack


@dataclass(frozen=True)
Expand Down Expand Up @@ -199,9 +194,11 @@ def list_packs(self) -> list[CompanyPack]:
with self._uow_factory() as uow:
return uow.company_packs.list_packs()

def preview_music_studio_template(self) -> CompanyTemplatePreview:
def preview_template(
self, definition: CompanyTemplateDefinition
) -> CompanyTemplatePreview:
self._require_enabled()
pack = build_music_studio_pack()
pack = definition.build_pack()
missing_features = [
raw
for raw in pack.required_features
Expand All @@ -224,18 +221,18 @@ def preview_music_studio_template(self) -> CompanyTemplatePreview:
uow.company_packs.get_installation(active.id, pack.key) if active else None
)
return CompanyTemplatePreview(
slug=MUSIC_STUDIO_TEMPLATE_SLUG,
slug=definition.slug,
name=pack.name,
version=pack.version,
mission=MUSIC_STUDIO_DEFAULT_MISSION,
mission=definition.mission,
content_digest=pack.content_digest,
required_features=pack.required_features,
missing_features=missing_features,
resource_summary=summary,
resources=resources,
required_credentials=[],
permissions=["company:manage"],
external_writes_enabled=False,
required_credentials=list(definition.required_credentials),
permissions=list(definition.permissions),
external_writes_enabled=definition.external_writes_enabled,
active_company_id=active.id if active else None,
installed_version=installation.pack_version if installation else None,
upgrade_available=(
Expand All @@ -246,29 +243,36 @@ def preview_music_studio_template(self) -> CompanyTemplatePreview:
installable=not missing_features and active is None,
)

def preview_music_studio_upgrade(self) -> PackUpgradePreview:
def preview_template_upgrade(
self, definition: CompanyTemplateDefinition
) -> PackUpgradePreview:
self._require_enabled()
target = build_music_studio_pack()
target = definition.build_pack()
target.publish()
with self._uow_factory() as uow:
active = uow.company_model.get_active_company(self._tenant_id)
if active is None:
raise CompanyPackConflict("Install Music Studio before upgrading it")
raise CompanyPackConflict(
f"Install {definition.name} before upgrading it"
)
return self._build_upgrade_preview(uow, active.id, target)

def upgrade_music_studio(
def upgrade_template(
self,
definition: CompanyTemplateDefinition,
*,
expected_from_digest: str,
expected_target_digest: str,
upgraded_by: str,
) -> PackUpgradeResult:
self._require_enabled()
candidate = build_music_studio_pack()
candidate = definition.build_pack()
with self._uow_factory() as uow:
active = uow.company_model.get_active_company(self._tenant_id)
if active is None:
raise CompanyPackConflict("Install Music Studio before upgrading it")
raise CompanyPackConflict(
f"Install {definition.name} before upgrading it"
)
target = self._resolve_builtin_pack(uow, candidate)
uow.commit()
company_id = active.id
Expand All @@ -281,40 +285,22 @@ def upgrade_music_studio(
upgraded_by=upgraded_by,
)

def install_music_studio_template(
def install_template(
self,
definition: CompanyTemplateDefinition,
*,
company_name: str,
owner_principal_id: str,
default_language: str,
default_genre: str,
use_plan: str,
mission: str = MUSIC_STUDIO_DEFAULT_MISSION,
configuration: dict[str, Any],
mission: str,
default_currency: str = "USD",
operating_timezone: str = "UTC",
) -> CompanyTemplateInstallation:
self._require_enabled()
language = default_language.strip()
if not 2 <= len(language) <= 32:
raise InvalidCompanyPack("Default language must contain 2 to 32 characters")
genre = default_genre.strip()
if not 1 <= len(genre) <= 120:
raise InvalidCompanyPack("Default genre must contain 1 to 120 characters")
normalized_use_plan = use_plan.strip().lower()
if normalized_use_plan not in MUSIC_STUDIO_USE_PLANS:
raise InvalidCompanyPack(
"Use plan must be one of: " + ", ".join(MUSIC_STUDIO_USE_PLANS)
)
candidate = build_music_studio_pack()
normalized_configuration = definition.normalize_configuration(configuration)
candidate = definition.build_pack()
for raw in candidate.required_features:
self._feature_gates.require(Feature(raw))
configuration = {
"default_language": language,
"default_genre": genre,
"use_plan": normalized_use_plan,
"generation_provider": "deterministic-demo",
"external_writes_enabled": False,
}
company = Company.create(
tenant_id=self._tenant_id,
name=company_name,
Expand All @@ -333,23 +319,23 @@ def install_music_studio_template(
uow,
company.id,
pack,
configuration=configuration,
configuration=normalized_configuration,
installed_by=owner_principal_id,
)
installation = PackInstallation.create(
company_id=company.id,
pack=pack,
installed_by=owner_principal_id,
resource_refs=refs,
configuration=configuration,
configuration=normalized_configuration,
)
uow.company_packs.add_installation(installation)
uow.outbox.add(
MessageEnvelope.domain_event(
schema_name="agentmesh.company.created",
tenant_id=self._tenant_id,
aggregate_id=company.id,
payload={"name": company.name, "template": MUSIC_STUDIO_TEMPLATE_SLUG},
payload={"name": company.name, "template": definition.slug},
)
)
uow.outbox.add(self._installation_event(installation))
Expand Down
2 changes: 1 addition & 1 deletion src/agentmesh/application/music_studio_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
DeterministicAudioAnalyzer,
DeterministicMusicProvider,
)
from agentmesh.templates.music_studio import PACK_KEY
from agentmesh.packs.music_studio import PACK_KEY

WORKFLOW_KEY = "music-studio-demo"
AGENTS = {
Expand Down
9 changes: 9 additions & 0 deletions src/agentmesh/packs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""Stable SDK surface for installable AgentMesh business scenarios."""

from agentmesh.packs.sdk import (
PACK_SDK_API_VERSION,
CompanyTemplateDefinition,
PackCatalog,
)

__all__ = ["PACK_SDK_API_VERSION", "CompanyTemplateDefinition", "PackCatalog"]
11 changes: 11 additions & 0 deletions src/agentmesh/packs/catalog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""Built-in scenario discovery.

Third-party repositories can construct their own ``PackCatalog`` with the same SDK.
"""

from agentmesh.packs.music_studio import DEFINITION as MUSIC_STUDIO
from agentmesh.packs.sdk import PackCatalog

BUILTIN_PACK_CATALOG = PackCatalog([MUSIC_STUDIO])

__all__ = ["BUILTIN_PACK_CATALOG", "MUSIC_STUDIO"]
Loading