# AgentTeam Server API Profile Routing Spec

> Canonical path: `specs/api/server-profile-routing.md`
> Status: living-spec
> Scope: AgentTeam Server HTTP/API route ownership, server profiles, workspace route-key contract, managed workspace ownership, and routing boundaries.
> Related specs: [`specs/architecture/unified-agentteam-runtime.md`](../architecture/unified-agentteam-runtime.md), [`specs/api/request-workspace-routing-matrix.md`](./request-workspace-routing-matrix.md), [`specs/api/handler-adapter.md`](./handler-adapter.md), [`specs/api/raw-gin-surface-classification.md`](./raw-gin-surface-classification.md), [`specs/deployment/agentteam-server-workspace-isolation.md`](../deployment/agentteam-server-workspace-isolation.md), [`specs/daemon/daemon.md`](../daemon/daemon.md), [`specs/product/machines.md`](../product/machines.md), [`specs/app/app-system.md`](../app/app-system.md), [`specs/trigger/trigger.md`](../trigger/trigger.md)

This spec defines how AgentTeam Server API routes are assigned to server profiles. The goal is workspace compute isolation, not database sharding.

## 1. Profile set

Phase 1 deployment profiles:

| Profile | Meaning |
| --- | --- |
| `all` | Default monobinary mode. Runs current combined behavior unless a split profile is selected. |
| `workspace` | Workspace data-plane plus browser realtime, daemon owner, agent wake compatibility routes, and current background loops for managed workspaces. |
| `central` | Global and management APIs, including placement management. |

`all` must remain the default.

```text
AGENTTEAM_SERVER_PROFILE=all|workspace|central
```

`AGENTTEAM_SERVER_PROFILE` only accepts `all`, `central`, or `workspace`. Any other value, including `control`, `ingress`, `runtime`, or `worker`, must fail-fast at startup. The detailed startup config matrix lives in [`specs/deployment/agentteam-server-workspace-isolation.md` §3.1](../deployment/agentteam-server-workspace-isolation.md#31-startup-config-matrix).

Phase 1 code must not keep or introduce standalone `control`, `ingress`, `runtime`, or `worker` router profiles. Runtime-local compatibility routes are part of the `workspace` profile until owner-hop / durable fanout exists; `control` must not be accepted as an operator-facing profile value (env, manifests, deployment configuration).

## 2. Current implementation facts

1. `internal/router/router.go` contains the Phase 1 router profiles `ProfileAll`, `ProfileCentral`, and `ProfileWorkspace`.
2. Runtime/realtime responsibilities are registered under the `workspace` profile; Phase 1 deployment must keep browser realtime, daemon owner, and agent wake co-located with workspace writes.
3. Server startup wires profile-specific router/dependency validation and background agentteam gating.
4. Background loops still exist in server startup today. Phase 1 keeps them on the `workspace` profile and filters workspace-scoped work by `ManagedWorkspaceSet` instead of introducing an independent Worker.
5. Server startup must not run implicit schema migration; schema migration belongs to release / operations workflow.

## 3. Routing invariants

1. `workspaceId` and route keys are routing hints only. They are not authorization.
2. Backend authorization must continue to use session, membership, machine key, agent token, app token, and object ownership checks.
3. Edge Gateway / ALB Ingress does not write placement.
4. `workspace_placements` is the source of truth for workspace to group ownership.
5. `ManagedWorkspaceSet` determines whether the current process should execute workspace data-plane work for a workspace.
6. Workspace profile must still perform owner check after routing.
7. Phase 1 does not physically split browser realtime, daemon owner, agent wake, or schedule fanout from workspace writes; they stay with the workspace owner until V1/SSE, durable fanout, or owner-hop exists.
8. Runtime owner routes must not be horizontally scaled independently until daemon owner-hop or an equivalent owner registry exists.
9. Workspace entity management is not the same as workspace data-plane execution. Create/list/member/invite/settings routes belong to `central` unless explicitly classified otherwise.

## 4. ManagedWorkspaceSet

Route and background ownership use this shared abstraction:

```text
ManagedWorkspaceSet.Has(workspaceID) bool
```

Profile behavior:

| Profile | `Has(workspaceID)` |
| --- | --- |
| `all` | always true |
| `central` | always false |
| `workspace` | true only when the workspace is present in the local cached managed workspace set |

`ManagedWorkspaceSet` must not bypass authorization. It only prevents a process from handling a workspace it does not own.

## 5. Central profile

Central Server owns global and management APIs that are not current-workspace data-plane operations.

| Domain | Route examples |
| --- | --- |
| Public config | `GET /api/public/config` |
| Auth / identity | `/api/auth/*`, OIDC callback, session refresh |
| Admin | `/api/admin/*` |
| Organization | `/api/orgs/*` |
| Billing | `/api/billing/*` organization-management routes and public payment callbacks |
| Workspace management | `/api/v1/workspaces`, `/api/v1/workspaces/:workspaceId/*` entity-management routes |
| Machine management | machine create/list/update, connect exchange |
| Provider callbacks/setup | OAuth callback/setup routes |
| Placement management | workspace placement admin/control routes |
| Platform service accounts | platform-account profile/provisioning APIs, broadcast acceptance, target ledger, and leased delivery dispatcher |

Central routes do not use `workspace_placements` for execution ownership. `ManagedWorkspaceSet.Has(...)` is always false in `central` mode.

## 6. Workspace profile

Workspace profile owns current-workspace data-plane APIs. These routes require an explicit current workspace, currently mainly through `X-Workspace-Id`.

Phase 1 also keeps browser `/socket.io`, daemon owner, agent wake, `scopeFanoutRunner`, schedule fanout, GC, retention, and machine reconcile with the workspace write path. This preserves the current in-process message/task realtime fanout and daemon hub contract while V1 path, SSE migration, and owner-hop work are handled later.

| Domain | Route examples |
| --- | --- |
| Workspace snapshot | `/api/v1/workspaces/:workspaceId/snapshot` |
| Channel scope snapshot | `/api/v1/workspaces/:workspaceId/scopes/:scopeId/snapshot` |
| Channels / threads / DM | `/api/channels/*`, `/api/threads/*` |
| Messages / reactions | `/api/messages/*` |
| Tasks / reviews / discussions | `/api/tasks/*`, `/api/task-board/*` |
| Schedule / trigger management | `/api/v1/workspaces/:workspaceId/schedules`, `/api/v1/workspaces/:workspaceId/schedules:validate`, `/api/v1/workspaces/:workspaceId/schedules/:scheduleId:pause`, `/api/v1/workspaces/:workspaceId/triggers`, `/api/v1/workspaces/:workspaceId/triggers:validate`, `/api/v1/workspaces/:workspaceId/triggers/:triggerId:pause` |
| Workspace apps | workspace app install/config/subscription/service-event-subscription routes, with legacy provider-watch compatibility |
| Workspace provider config | workspace-scoped provider config/register/install routes |
| Workspace agents / role | workspace agent, review context, and canonical Role routes |
| Workspace media mutation | workspace-scoped attachment/sticker upload or mutation routes |
| Platform service-account delivery executor | authenticated `/internal/workspaces/:workspaceId/platform-service-account-deliveries/:deliveryId/execute` |
| Browser realtime compatibility | `/socket.io/*`, co-located with workspace writes in Phase 1 |
| Runtime owner routes | workspace-scoped `/api/v1/workspaces/:workspaceId/daemon/*` and `/agents/:agentId/runtime/*`, co-located in Phase 1 |
| LLM proxy / webhook ingress | `/api/v1/workspaces/:workspaceId/llm/*`, external app webhook entry routes, co-located until durable dispatch exists; old `/api/llm/*` is Gateway-only compatibility rewrite |

Workspace startup must declare its group:

```text
AGENTTEAM_WORKSPACE_GROUP_ID=workspace-default
AGENTTEAM_DEFAULT_WORKSPACE_GROUP_ID=workspace-default
```

Every workspace data-plane request must pass owner check:

```text
request workspace_id
  -> ManagedWorkspaceSet.Has(workspace_id)
  -> false: reject owner mismatch
  -> true: continue authorization and business handling
```

If the group does not match, the server must reject the request with owner mismatch instead of processing it.

Platform service-account broadcast uses a domain-owned durable handoff. The
central profile materializes per-workspace delivery rows, claims a versioned
lease, resolves current placement, and sends the authenticated internal execute
command with delivery version, lease token, placement epoch, and immutable
snapshot digest. The workspace handler requires
`ManagedWorkspaceSet.Has(workspace_id)` and the current placement epoch before it
can enter the local MessagePlan transaction. That transaction locks the delivery
and placement rows and revalidates delivery version, current lease token/expiry,
placement epoch, and unsettled state before committing the message, scope fanout
outbox, and delivered settlement atomically. The transaction persists a
recoverable stream projection. The app Store owns the unified
`autonomous_store_recoverable` committed-message lifecycle defined by
[`scope-model.md`](../message/scope-model.md): it captures the full fresh `MessageDelivery` inside the transaction
and, only after commit, submits that handoff to the same workspace-local
`DeliveryFanout` used by ordinary message APIs. `PlatformService` owns broadcast
leasing and invokes the Store writer, but it does not own a separate post-commit
message callback. The first attempt applies current Human unread and
`message.created`/browser projection before completing the outbox. Failed or
idempotent materialization does not repeat this first fanout; durable outbox
replay repairs the stable stream projection but deliberately does not
reconstruct an unfrozen ordinary-scope unread audience. `central` has no managed
workspace and therefore cannot run this local fanout; `workspace` and in-process
`all` bind their workspace-local shared fanout to the app Store. Message/outbox
idempotency converges repeated execution. An owner/epoch mismatch returns a typed misdirected
result; central re-resolves placement and retries the same delivery. `all`
profile preserves the same boundary through an in-process command dispatch.

## 7. Runtime compatibility routes stay with workspace

Runtime owner entry routes are not a standalone Phase 1 router profile or deployment target. Browser `/socket.io/*`, daemon owner, and agent wake stay with the workspace write path until the product moves browser realtime to a V1 workspace path, such as SSE, or introduces durable cross-service fanout / owner-hop.

| Domain | Route examples | Phase 1 rule |
| --- | --- | --- |
| Daemon websocket | `/api/v1/workspaces/:workspaceId/daemon/connect` | Co-located with the owning workspace process; a later split requires single owner or owner-hop. |
| Agent Runtime API | `/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/*` | Co-located with daemon owner until owner-hop exists. |
| LLM proxy | `/api/v1/workspaces/:workspaceId/llm/*` | Co-located in Phase 1 unless separately classified; old `/api/llm/*` is Gateway-only compatibility rewrite. |
| External app webhook | webhook entry routes | Co-located in Phase 1 unless/until durable queue ownership is introduced. |

A later physical runtime split must first introduce owner registry / owner-hop. Any daemon-owner operation must execute on the runtime owner or be delegated to it; message/task agent wake must also have a reliable path to the daemon owner.

## 8. Future Worker role

Worker is a future background process role. It is not part of the Phase 1 deployment set because the remaining background loops would otherwise introduce a new profile, a new Deployment, a larger startup matrix, and the `system_leases` decision without enough Phase 1 benefit.

Phase 1 keeps current background loops on the workspace profile, including:

- machine offline reconcile;
- attachment/media GC;
- service-event subscription / provider-watch compatibility retention;
- `scopeFanoutRunner`;
- schedule runner.

`scopeFanoutRunner` must remain with the workspace owner in Phase 1 because it can touch the local fanout / realtime path. Schedule runner must also remain there because schedule-created messages use message fanout, which calls local realtime and daemon hub. They can move to an independent Worker only after durable handoff / outbox / owner-hop exists.

Schedule scanning should filter by ownership:

```text
schedule_definitions.workspace_id
  JOIN workspace_placements.workspace_id
WHERE workspace_placements.workspace_group_id = AGENTTEAM_WORKSPACE_GROUP_ID
```

Schedule scanning must use the same routable placement semantics as `ManagedWorkspaceSet.Has`: active, draining, and moving placements are routable; disabled placements are not routable. This keeps due, retry, inactive-retry, and stale-run scanning consistent with HTTP owner checks during placement migration.

Future Worker deployment or multi-replica global worker requires `system_leases` or an equivalent lease mechanism. A single job/run still needs DB row lock, lease claim, or idempotency to avoid duplicate execution within the same group.

## 9. Placement tables

### 9.1 `workspace_placements`

Required for Phase 1.

```text
workspace_id        PK
workspace_group_id  stable group id, for example workspace-default
state               active / draining / moving / disabled
placement_epoch     fencing / cache invalidation
created_at
updated_at
```

This table stores stable workspace groups, not pods or IPs.

Phase 1 lifecycle:

- Backfill existing workspaces to `workspace-default` or another explicit group. New workspace creation must pass the default group explicitly from server configuration (`AGENTTEAM_DEFAULT_WORKSPACE_GROUP_ID`, default `workspace-default`) into the placement write path; store code must reject empty groups instead of silently defaulting.
- Write placement during workspace creation in the same transaction or creation flow.
- Missing placement fails closed on the request path; use an explicit repair/backfill command instead of auto-creating a default placement during request handling.
- Central / ops writes placement. Workspace processes and future router/gateway components read or cache it.

Phase 1 default:

```text
all workspaces -> workspace-default
```

### 9.2 `system_leases`

Required before a future Worker deployment or global Worker runs with more than one replica. Because Phase 1 does not introduce an independent Worker, this table does not block Phase 1.

```text
lease_name      PK
lease_owner
lease_epoch
expires_at
heartbeat_at
created_at
updated_at
```

### 9.3 `workspace_group_instances`

Not required in Phase 1. K8s Deployment / Service / Endpoint should own pod membership for the first implementation.

## 10. K8s routing contract

Phase 1 routing:

| Traffic | Target |
| --- | --- |
| `/` | agentteam-web |
| central API | Central Service |
| workspace data-plane API | Workspace Service for the request workspace |
| browser realtime `/socket.io/*` | Workspace Service for the request workspace |
| daemon / internal agent/app / LLM / webhook | Workspace Service for the request workspace; later may split to Runtime after owner-hop/fanout exists |
| schema migration | release / operations workflow, no ALB route |

Future multi Workspace group routing:

```text
ALB Ingress -> Workspace Router / Resolver -> target Workspace Service group
```

The Workspace Router / Resolver reads or caches `workspace_placements`. ALB itself does not write DB placement. Phase 2 requirements for that router/gateway are specified in [AgentTeam Workspace Multi-Group Phase 2](../deployment/workspace-multigroup-phase2.md).

If a workspace group has more than one pod before durable fanout / owner-hop exists, the Edge Gateway must keep that workspace's HTTP write, socket, and daemon-related traffic on the same owner process. Otherwise each workspace group remains single-owner.

### 10.1 Phase 1-B REST route slimming and GitOps source of truth

Phase 1-B may reduce Kubernetes Ingress / ALB rule count for browser and SDK REST traffic, but it must not change the ownership rules above.

The Phase 1-B edge routing contract is:

| Traffic | Edge discriminator | Target |
| --- | --- | --- |
| Workspace REST under `/api` | `X-AgentTeam-Route-Workspace-Id: ws_*` | Workspace Service for the matching workspace group. |
| `/api` without workspace route header | none | Central Service fallback. |
| Central REST exceptions | explicit higher-priority paths such as `/api/auth`, `/api/public`, `/api/github/callback`, `/api/github/setup` | Central Service even if a client accidentally sends the workspace route header. |
| Browser realtime | explicit `/socket.io` and `/w/.../socket.io` paths | Workspace Service; browser WebSocket cannot rely on custom route headers. |
| Daemon / Agent Runtime | workspace-scoped V1 paths carrying `workspaceId` | Workspace Service. |

`X-AgentTeam-Route-Workspace-Id` is only an edge-routing hint. Workspace servers must still validate that it matches the authenticated request workspace when present, and must still run owner check via `ManagedWorkspaceSet`.

Route ownership has one source of truth: the AgentTeam Server route registration set plus the path-family rules in this spec. Delivery / GitOps overlays must encode that source of truth; hand-written one-off Ingress hotfixes are acceptable only as temporary incident mitigation and must be folded back into GitOps before production split.

Required guardrails before production split:

1. API route tests must assert `(method, path)` ownership for `central` and `workspace`.
2. API route tests must also assert path-only intersections; every business path-only conflict must be listed in [§11](#11-route-conflict-rules) with an explicit method or header discriminator.
3. Delivery render validation must fail if:
   - the workspace REST header rule is missing or lower priority than `/api` central fallback;
   - central exception paths can be captured by the workspace header rule;
   - `/socket.io` or `/w/.../socket.io` no longer route to Workspace in Phase 1;
   - the rendered ALB / Ingress rule count exceeds the environment budget recorded in the delivery overlay.
4. The rendered delivery overlay, not the live cluster, is the reviewable artifact for staging and production routing.

### 10.2 Route SSoT implementation plan

The production-ready plan is to make code route ownership the source of truth and make delivery rules a generated or verified artifact. Live Ingress must not be the source of truth.

Historical routing shape:

1. Monolith production routed broad `/api` traffic to one `agentteam-server`, so broad prefixes were safe.
2. Initial split staging used explicit path and method Ingress rules to unblock central/workspace conflicts.
3. Phase 1-B reduced REST rules by using `/api + X-AgentTeam-Route-Workspace-Id` for workspace REST and `/api` fallback for Central.
4. The remaining explicit paths are exceptions or non-REST compatibility paths, not a pattern for future hand-written route maintenance.

Implementation should add a route contract generated from, or validated against, the AgentTeam Server router registration set. Each externally reachable route must be classified into one edge policy:

| Edge policy | Meaning |
| --- | --- |
| `central-rest` | Central REST handled by `/api` fallback; no workspace route header. |
| `workspace-rest` | Workspace REST handled by `/api + X-AgentTeam-Route-Workspace-Id`. |
| `central-exception` | Central path that must outrank the workspace header rule, for example auth, OAuth callback/setup, or central media download/upload routes that can be reached from workspace UI. |
| `workspace-explicit` | Workspace path that cannot rely on REST headers, for example socket, daemon, internal agent/app. |
| `health` | Health/readiness paths; may route to Central for external checks. |
| `internal-only` | Not exposed through the public Edge Gateway. |

The AgentTeam repository exposes the current contract with:

```bash
go run ./cmd/agentteam-route-contract
```

The JSON output is the handoff artifact for delivery-side render validation.

Developer workflow for route changes:

1. Adding a normal workspace REST route requires registering it under the workspace profile and using a workspace-aware client/API helper so `X-AgentTeam-Route-Workspace-Id` is sent by browser/SDK calls.
2. Adding a normal central REST route requires registering it under the central profile and omitting workspace route headers from clients.
3. Adding a central route that still needs business workspace context must send `X-Workspace-Id` without `X-AgentTeam-Route-Workspace-Id`. Media routes such as attachment download, sticker download, and avatar upload use this split because they are central-owned but commonly invoked from workspace screens.
4. Adding a central exception requires marking it as `central-exception`; delivery render must then include a higher-priority explicit path.
5. Socket, daemon, and Agent Runtime paths must be workspace-scoped and classified as Workspace-owned routes.
6. Daemon and Agent Runtime clients persist per-channel `config.workspace_id`; a missing workspace binding requires reconnect/login remediation rather than an unscoped route fallback.
7. Deleting a route removes it from the route contract. Once the post-GA route-contract CI is enabled, CI must fail if delivery still contains a stale generated explicit rule.
8. Any new path-only central/workspace conflict must be added to [§11](#11-route-conflict-rules) with an explicit discriminator before merge.

The target post-GA CI state is: CI fails when a public route is unclassified, when route ownership changes without updating the contract, or when the rendered delivery overlay does not match the contract. For the first Phase 1 production split, this PR-blocking CI is tracked follow-up work; the cutover relies on existing route ownership tests, reviewed GitOps/render artifacts, staging smoke, and the production split gate in [`specs/deployment/agentteam-server-workspace-isolation.md` §11](../deployment/agentteam-server-workspace-isolation.md#11-staging--production-parity-and-production-split-gate).

## 11. Route conflict rules

Some route families contain both central and workspace/runtime routes. They must be assigned by exact route ownership, not only by broad prefix.

The table below is the authoritative **path-family** contract that Edge Gateway / Kubernetes Ingress can encode for Phase 1 split deployments. It is intentionally not exhaustive at the handler level; handler-level precision is enforced by route registration tests in the API implementation rather than by this spec.

| Route family | Rule |
| --- | --- |
| `/api/apps/*` | Workspace app routes go to Workspace; external webhook/callback entry stays with the workspace runtime compatibility path until durable dispatch exists. |
| `/api/github/*` and other provider routes | OAuth callback/setup go to Central; workspace config/register/install routes go to Workspace. |
| `/api/billing/*` | Organization billing APIs and payment callbacks go to Central. `X-Workspace-Id` may select business context, but the edge route must not use workspace data-plane propagation. |
| media routes | Workspace mutations go to Workspace; bare object reads must be explicitly classified. |
| `/api/v1/workspaces/*` | Workspace entity-management routes go to Central; workspace data-plane subresources go to Workspace according to the explicit route contract. |
| `/api/agents/*` | Workspace agent management can be Workspace; daemon-owner actions stay co-located in Phase 1 and need Runtime owner delegation before any physical split. |
| `/socket.io/*` | Browser realtime stays co-located with Workspace writes in Phase 1; do not route it to a physically separate Runtime Service without durable fanout. |

Route registration tests are the required backstop for these ownership decisions: each profile's router must register only the routes it owns, and a regression test must catch any handler that crosses the boundary. The regression must check both `(method, path)` ownership and path-only ownership. Any path-only conflict between `central` and `workspace` must be listed here and must have an explicit ALB routing discriminator (method condition or route header), because broad path-prefix Ingress cannot represent it safely.

Allowed Phase 1 path-only conflicts:

| Path | Central methods | Workspace methods | Edge routing requirement |
| --- | --- | --- | --- |
| `/healthz` | `GET` | `GET` | External health checks may keep routing to Central; this is not a business route. |
| `/readyz` | `GET` | `GET` | External readiness checks may keep routing to Central; this is not a business route. |

## 12. Client routing contracts

Browser and desktop clients should keep control-plane and workspace data-plane intent explicit:

1. Control calls route to `central` and do not require current workspace ownership.
2. Workspace REST calls require an explicit workspace ID and route to the owning workspace group.
3. Runtime-compatible calls such as websocket, daemon, LLM, and internal agent/app traffic stay with the owning workspace group in Phase 1.

Phase 1-B uses `X-AgentTeam-Route-Workspace-Id` as the edge-routing discriminator for browser/API REST workspace calls. Browser workspace REST requests must set both `X-Workspace-Id` and `X-AgentTeam-Route-Workspace-Id` to the same workspace ID. Control-plane requests, including calls marked `workspace: false`, must omit both workspace headers. WebSocket, daemon, and internal agent/app traffic keep explicit path-based routing in Phase 1-B. New daemon/deep-link connect flows may also send `X-AgentTeam-Route-Workspace-Id` from per-channel `config.workspace_id`, but manual `agentteam daemon --server-url --api-key` and old configs must continue without it and route to the default workspace group. Backend validation must compare this route header with the workspace derived from authenticated credentials when the header is present.

## 13. Implementation requirements

1. Keep `all` as default.
2. Wire router registration by selected profile.
3. Wire dependency validation by selected profile.
4. Wire server startup by selected profile.
5. Add `ManagedWorkspaceSet` with deterministic behavior for `all`, `workspace`, and `central`.
6. Add `workspace_placements` and Workspace owner check.
7. Keep current runtime-local paths and background loops with the workspace owner in Phase 1 instead of introducing independent Runtime or Worker deployments/profiles.
8. Add `system_leases` or equivalent lease before introducing or scaling a future global Worker.
9. Keep schema migration as release / operations workflow; server startup may check schema version but must not perform implicit DDL / migration.
10. Keep runtime owner routes single-owner if they are physically split before owner-hop exists.

## 14. Non-goals for Phase 1

- DB shard;
- API URL redesign;
- V1 workspace path and SSE implementation;
- Public API / SDK / CLI;
- Workspace Router / Resolver implementation;
- physical browser realtime split from Workspace writes;
- physical daemon owner / agent wake split from Workspace writes;
- independent Runtime deployment / role;
- independent Worker deployment / role;
- Runtime multi-replica owner registry;
- Cluster RPC;
- socket.io-cluster;
- workspace auto migration;
- `workspace_group_instances`;
- schema migration workflow design;
- single workspace multi-writer.

## 15. Acceptance criteria

- `ProfileAll` remains compatible with existing local and single-binary deployments.
- `ManagedWorkspaceSet` returns true for all workspaces in `all`, false for all workspaces in `central`, and cached ownership in `workspace`.
- Workspace data-plane servers reject requests for unmanaged workspaces.
- Central servers can serve auth, workspace selection, org/admin, public config, developer, placement management, and external callback routes without `X-Workspace-Id`.
- Workspace entity management and workspace data-plane execution are not conflated.
- Runtime-local routes are not accidentally routed away from the workspace owner before durable fanout / owner-hop exists.
- AgentTeam Server Web uses explicit central/workspace request helpers or equivalent typed call-site patterns.
- Route contract and delivery render guardrails define the post-GA CI target: fail when a route is unclassified, changes ownership without a contract update, or loses required explicit daemon/internal fallback.
- Metrics and logs identify `server_profile` and route group for debugging split deployments when the post-GA route observability follow-up is implemented; metric labels must stay low-cardinality.
