# Service Actor Platform

> Canonical path: `specs/service/service-actor-platform.md`
> Status: living-spec
> Scope: service actor definitions, installations, members, capabilities, target bindings, and service-authored messages.
>
> Related specs:
> - [`specs/architecture/service-actor-event-automation.md`](../architecture/service-actor-event-automation.md)
> - [`specs/identity/identity.md`](../identity/identity.md)
> - [`specs/app/app-system.md`](../app/app-system.md)
> - [`specs/app/app-notification.md`](../app/app-notification.md)
> - [`specs/service-account/service-account.md`](../service-account/service-account.md)
> - [`specs/service/service-event-delivery.md`](./service-event-delivery.md)

## 1. Purpose

The Service Actor Platform is the substrate for workspace-scoped service actors.
A registered service kind defines its installation cardinality, principal and
profile resolvers, capabilities, authorization bases, and target-binding
contracts. Runtime registrations include catalog-backed `app` definitions. The
Service Account target contract declares a platform-owned `service_account`
definition and named multi-instance installation mode.

## Terminology and product vocabulary

The Service Actor is the canonical substrate entity and model; storage and
runtime use `service_*` records. **App** is the only product model that maps to
`service_kind='app'`. Runtime support, provider ingress, and MCP exposure are
capabilities or ingress patterns of an App, not product subtypes. This glossary
is authoritative for the product/spec vocabulary and current implementation
names such as `app_*`, `/api/apps`, `capabilities.runtime`, and `app.*` scopes.

- **Service Actor** — the substrate entity. Storage and runtime use `service_*`
  records; a service actor's visible identity in a workspace is its
  `service_member`. The member/actor family is `human | agent | service`.
- **Service kind** — the kind of service actor. `app` is the kind for installed
  Apps; `service_account` is a target contract with
  `named_multi_instance` installation mode.
- **App Integrations / 应用集成** — a UI category, not an entity, service kind,
  protocol, or subtype. It groups user-manageable Apps:
  - Provider Apps (GitHub, Gitea) are first-party Apps that
    receive external events through signed provider webhooks and expose event
    subscription and MCP tooling. They do not declare `capabilities.runtime`.
  - External/custom Apps are workspace-onboarded Apps that declare
    `capabilities.runtime` and execute through the App Runtime API.
- **Service Accounts / 服务号** — a separate product family for broadcast /
  announcement authorship; see
  [`../service-account/service-account.md`](../service-account/service-account.md).
- **Capability** — what a service actor can do. Definition capability records
  include `message.write`, `events.receive`, `subscriptions.manage`, and
  `mcp.tools`; an App manifest declares runtime events, notification support,
  and `app.*` scopes under `capabilities.runtime`. Capability rows are
  descriptive; grant scopes, access decisions, and runtime tokens are the
  enforcement gate.
- **App Runtime API** — the protocol exposed at `/app-runtime` for external App
  actors (notifications, runner stream, delivery tokens).
- **Transport** (App runtime) — how an App runtime is reached:
  `internal_handler` (in-process, built-in Apps such as Approval and
  Workflow), `callback` (HTTPS HMAC push), and `runner_stream` (long-poll claim). These are App runtime
  transports, not protocols or entity types. Provider integrations receive
  events through a separate **provider-webhook ingress** path, which is a
  provider-specific ingress mechanism, not an App runtime transport.

Approval and Workflow are canonical built-in Apps
(`internal_handler`). GitHub and Gitea are provider Apps: they use
provider-specific ingress rather than `capabilities.runtime`. The Apps product
surface is organized into **App Integrations** and **Service Accounts**, with
the App Marketplace at the top.

## 2. Canonical model

Service actor storage and runtime code use service-oriented records:

```text
service_definitions
service_installations
service_members
service_connections
service_grants
service_principal_authorizations
service_capabilities
service_target_bindings
service_webhook_endpoints
```

Current App API and UI surfaces may still use the product word `App`, but the
cross-domain substrate is service actor data. A product facade may translate App
requests to service actor operations; the service actor store must not depend on
an app-only store as the source of truth.

## 3. Service definition and installation

A service definition describes a registered kind of installable service:

```text
service_definition_id
service_kind
slug
name
description
avatar/profile defaults
installation_mode    # singleton | named_multi_instance
manifest/config
status
```

A service installation is workspace-scoped:

```text
service_installation_id
workspace_id
service_definition_id
service_kind
instance_key
enabled/status
created_by_actor_ref
created_by_member_id?  # workspace-member attribution when the actor has one
installation metadata
```

Installation, grant, and target-binding creation records preserve a typed
authority actor ref. Workspace-authorized operations also persist the local
Human member id. Platform-owned provisioning persists the authenticated
platform operator or durable provisioning-operation ref and can leave the
workspace-member component absent. Audit projection retains the original Human
or control-plane authority together with the local service actor records.

Every installation has a non-empty `instance_key`. Its durable identity is
`(service_definition_id, workspace_id, instance_key)`. A `singleton` definition
uses the stable instance key `default`. A `named_multi_instance` definition uses
a stable product-domain root id as its instance key. Installation upsert,
locking, idempotency, and direct resolution use the full durable identity.
Definition-scoped list operations return every matching named installation in
the workspace.

Canonical uniqueness constraints are:

```text
UNIQUE(service_kind, slug)
UNIQUE(service_definition_id, workspace_id, instance_key)
```

| service kind | contract status | definition registration | installation mode | instance key | profile authority |
| --- | --- | --- | --- | --- | --- |
| `app` | runtime | one catalog definition per App | `singleton` | `default` | definition plus installed service-member projection |
| `service_account` | target-spec | one platform definition with slug `service-account` | `named_multi_instance` | `service_accounts.principal_id` | workspace-owned `service_accounts` or platform-owned `platform_service_accounts`, plus the workspace service-account and service-member projections |

## 4. Service members and author identity

A service installation owns a workspace member used for visible authorship:

```text
service_member_id
workspace_id
service_definition_id
service_installation_id
member_id
service_kind
handle
display_name
avatar/profile
status
```

Canonical service-member constraints are:

```text
UNIQUE(service_installation_id)
UNIQUE(member_id)
UNIQUE(workspace_id, service_kind, normalized_handle)
```

Service-authored messages use the service member as the visible author. Source
metadata explains why the message exists; it must not replace the author profile.

A named installation represents one visible product principal and owns exactly
one workspace service member. For product-instance profile authority,
`installation.instance_key` selects the product-domain record. The product
profile resolver supplies `principalId`, `displayName`, avatar, and typed target;
the service member supplies `memberId`, handle, and the synchronized workspace
display projection.

The target `service_account` profile resolver loads the workspace-local
`service_accounts` row by installation workspace and
`principal_id=installation.instance_key`. A workspace-owned account reads its
profile authority from that row. A platform-owned instance follows
`platform_service_account_id` to the global platform profile authority. Its
AuthorProfile uses:

```text
principalId = service_accounts.principal_id
memberId = service_members.member_id
targetId = service-account:{service_members.handle}
displayName and avatar = the management-kind profile authority
```

Historical author resolution uses retained active or disabled
installation/member records and the corresponding product profile.

For a profile-ready platform-owned instance, the workspace binding's applied
profile version, the local account projection version, and the global platform
profile version are equal. The local account projection, service-member handle
and display fields, AuthorProfile, and current target-binding ref all derive
from that same canonical profile. Platform service-account handles are claimed
through the service-account domain's deployment-serialized handle namespace
before any workspace installation is materialized.

## 5. Capabilities

Capabilities describe what the installed service can do:

```text
message.write
events.receive
subscriptions.manage
mcp.tools
```

GitHub/Gitea pull-request watch operations are App-hosted Extension
capabilities. Agent discovery and authorization use the flattened Extension
Catalog; execution resolves the host App Installation before it calls the
service-event subscription owner.

The target shared `service_account` definition registers `message.write` once. Its
capability config selects the `service_account_message` delivery contract.
Each named Service Account installation exercises that definition-level
capability through its own installation grant and service-account target binding.
Workspace-Human-authorized service-account writes use the `service_direct_post`
ContributionSource kind with the message id as the native ref. The service
account supplies durable author, service principal, and routing principal; the
service member supplies the visible author-member, notify-principal, and
message-idempotency-subject components. The authenticated Human manager
supplies the `service_account_manager` permission basis, caused-by actor,
execution actor, Audit actor, and optional byline.

Platform-owned service-account broadcasts use the same contribution source and
the local installation's capability, grant, binding, and ordinary MessagePlan
writer. Their typed permission basis is `platform_service_account_delivery`.
The platform account remains the durable principal, the local service member is
the visible author member and notify principal, the authenticated platform
operator is the caused-by Human principal, and the trusted platform delivery
service is the execution actor.

App proactive notification registrations add `app.notifications.write` and
`app.notification_actions.read` to the installed App's runtime scope set. A direct
notification uses the App principal/member as durable author, routing principal,
notify principal, and message-idempotency subject. Runtime credentials and
trusted business services are execution actors.
Human card actions use a registered service-event subscription and preserve the
Human member as the action actor.

## 6. Target bindings

A service target binding records that an installed service may deliver into or
operate against a AgentTeam target:

```text
workspace_id
service_definition_id
service_kind
service_installation_id
service_member_id
target_kind
target_ref
delivery_scope_id
permission_check_scope_id
authority_subject_member_id
delivery_contract
status
created_by
```

A binding belongs to one concrete installation instance. Its workspace,
definition, installation, service member, delivery scope, and permission-check
scope resolve to the same instance.

For a target Service Account installation, the binding stores
`target_kind=service_account`, `target_ref=service-account:{handle}`, and
`authority_subject_member_id=service_account.author_member_id`; it uses
`message_scope_id` for both delivery and permission-check scope and selects the
`service_account_message` delivery contract. A public message create names that
scope id; the scope-type resolver loads this binding and the Service Account
identity target. Workspace Human manager assignments or platform delivery authority
are resolved per command and remain outside the durable binding identity.
`service_grants` grants the installation `message.write`; the target binding
constrains that capability to the account's message scope; the registered
workspace-Human or platform-delivery basis completes message contribution
authorization.

A binding is not a human/agent scope membership. It does not grant full history
read by itself and it does not subscribe the service to every ordinary target
message.

App notification bindings support two target contracts. A Human DM binding is
created or reused with the canonical App-member/Human-member DM and a typed
recipient basis. A bound-scope notification requires the App member's active
scope membership plus a binding whose delivery and permission-check scope roles
point at that scope. Both contracts preserve the installation/member identity
used by the notification message and its later card-action subscription.

## 7. Authorization and visibility

A service actor can affect a target only through an explicit capability and
target binding. Target bindings record delivery and permission-check scopes; they
do not make the service a normal member and they do not grant passive access to
all future conversation traffic.

Every service action resolves:

```text
service definition
service installation and instance key
service member
capability
installation grant
product principal/authorizer basis
target binding or action target
```

Each product registration resolves authorization, routing, notification, and
execution subjects independently. The visible service author remains the
service member; an authorizer or owner becomes a routing/notification subject
only when that product contract explicitly selects it.

The target `service_account` registration declares two create bases. The
`service_account_manager` Human command basis resolves an active
`service_account_managers` row on a workspace-owned account. The
`platform_service_account_delivery` basis resolves an accepted immutable
platform broadcast, a current workspace-instance binding, and its trusted
delivery executor on a platform-owned account. The basis authorizes the local
MessagePlan create plus broadcast-asset materialization and attachment claim for
the local service-account durable author.

Both service-account create branches resolve an active definition capability,
the matching named installation and service member, an unrevoked
`message.write` grant, the active target binding, their registered command
basis, an active local account, and `audience_state=ready`. Workspace Human edit
and withdrawal also resolve their owning message precondition. The service
member supplies the visible author-member component of durable_author.
Workspace create uses the service-account principal/member as routing, notify,
and message-idempotency subject; the Human manager remains the permission,
caused-by, execution, Audit, and optional byline actor. Platform delivery uses
the platform service-account principal for routing, the platform operator as
caused-by Human principal, the trusted delivery service as execution actor, and
the local service author member as notify and message-idempotency subject.
The workspace-Human branch keeps its ordinary caller-owned post-commit fanout.
The autonomous platform branch instead uses the app Store's unified
`autonomous_store_recoverable` committed-message lifecycle defined by
[`scope-model.md`](../message/scope-model.md): the Store persists a recoverable
stream projection with the message and settlement, retains the complete fresh
`MessageDelivery`, and submits it to the workspace's shared fanout only after
commit.
`PlatformService` owns broadcast leasing/orchestration and invokes this Store
writer; it does not install or invoke a product-specific message callback.
Idempotent or failed materialization does not repeat first fanout. Ordinary
scope realtime delivery therefore continues for connected authorized viewers
in both branches without waking unrelated Agents.

Provider event delivery authorizes against canonical service installation
grants, and pull-request subscriptions use canonical service event subscription
records. AgentTeam does not retain a generic App callback-subscription facade.

### 7.1 Named-instance lifecycle

The shared definition and its capabilities remain active as platform
registration facts. Product lifecycle projects onto instance-scoped records:

| Product state | installation / member / binding | grant |
| --- | --- | --- |
| active | active | live |
| suspended | disabled with the same durable ids | retained; effective state derives from the full authorization chain |
| resumed | the same records return to active | the retained grant becomes effective |
| archived | retained as disabled history records; binding closes at archived_at | ends at archived_at |

Historical profile resolution uses the retained instance/member records together
with the product profile authority.

## 8. Configuration and secret boundaries

Provider connection credentials, callback signing secrets, service credential
master keys, and short-lived capability/session tokens are different secret
classes. Storage and rotation code must keep them separate. A webhook payload may
identify a provider resource only after AgentTeam has resolved a configured connection
and verified the callback secret for that connection.

## 9. Current App facade

The App product remains a facade over service actor data:

```text
/api/apps
App settings UI
GitHub/Gitea app profile and webhook routes
GitHub/Gitea Extension actions
```

The facade may use App names at the edge, but storage, authorization, message
identity, capability checks, and target binding resolution use service actor
records.
