# AgentTeam Server Workspace Isolation And Deployment Profiles Spec

> Canonical path: `specs/deployment/agentteam-server-workspace-isolation.md`
> Status: living-spec
> Scope: AgentTeam Server workspace isolation, deployment profiles, managed workspace ownership, placement, and multi-environment deployment mapping.
> Related specs: [specs/architecture/unified-agentteam-runtime.md](../architecture/unified-agentteam-runtime.md), [specs/api/server-profile-routing.md](../api/server-profile-routing.md), [specs/daemon/daemon.md](../daemon/daemon.md), [specs/app/app-system.md](../app/app-system.md), [specs/trigger/trigger.md](../trigger/trigger.md), [specs/storage/storage.md](../storage/storage.md), [specs/observability/observability.md](../observability/observability.md)

This spec defines how one AgentTeam Server binary can run as a simple monolith or as workspace-isolated deployment profiles. The primary requirement is workspace isolation without breaking the current runtime-local realtime, daemon owner, and agent wake contracts.

Phase 2 multi-group planning is tracked in [AgentTeam Workspace Multi-Group Phase 2](./workspace-multigroup-phase2.md). Phase 2 keeps each workspace group single-owner and adds cross-group migration; same-workspace multi-pod execution remains a separate future project.

## 1. Core principles

1. AgentTeam Server remains one binary and one image unless a future packaging decision says otherwise.
2. `profile=all` is the default for development, single-machine deployment, docker compose, Kubernetes single-pod deployment, and private monobinary delivery.
3. Phase 1 deployment profiles are `all`, `workspace`, and `central`.
4. Workspace route keys and placement are routing hints, not authorization.
5. Runtime-local side effects, especially browser realtime, daemon websocket ownership, agent wake, `scopeFanoutRunner`, and trigger fanout, are not solved by workspace placement alone and must stay co-located with the managed workspace group in Phase 1.
6. Schema migration is a release / operations workflow and is not a AgentTeam Server profile in this spec.

## 2. Naming contract

Architecture docs must not use `ingress` for both the network entry layer and the future AgentTeam runtime role.

| Name | Meaning |
| --- | --- |
| Edge Gateway | External network entry layer such as Kubernetes Ingress, Envoy, Nginx, ALB, HAProxy, or a private-deployment reverse proxy. |
| Central profile | AgentTeam Server mode for global/control APIs and workspace placement management. |
| Workspace profile | AgentTeam Server mode for current-workspace data-plane plus runtime-local compatibility paths for its managed workspace set. |
| Runtime role | Future physical split target for connector/runtime entry points after owner-hop / durable fanout exists. |

Code must not keep standalone `control`, `runtime`, or `ingress` profiles for Phase 1. External interfaces — `AGENTTEAM_SERVER_PROFILE` env values, Kubernetes manifests, and operator-facing configuration — must use `central` and `workspace`; `control`, `ingress`, `runtime`, and `worker` must not be accepted as operator-facing profile values.

## 3. Profile contract

Use `profile`, not `api`, because not every mode is only an API surface.

Phase 1 deployment profiles:

| Profile | Meaning |
| --- | --- |
| `all` | Compatibility monolith. Runs current combined behavior. Default. |
| `workspace` | Workspace data-plane plus browser realtime, daemon owner, agent wake, current runtime compatibility paths, and current background loops for its managed workspace set. |
| `central` | Global and management APIs, including placement management. |

Process selection should be available through environment and, when implemented, CLI flag:

```text
AGENTTEAM_SERVER_PROFILE=all|workspace|central
agentteam-server --profile all|workspace|central
```

`all` must remain the default.

`runtime`, `ingress`, and `worker` are not Phase 1 server profiles. Runtime compatibility routes stay inside the `workspace` profile until a future physical split has owner-hop / durable fanout.

### 3.1 Startup config matrix

Server processes accept `AGENTTEAM_SERVER_PROFILE`, `AGENTTEAM_WORKSPACE_GROUP_ID`, and `AGENTTEAM_DEFAULT_WORKSPACE_GROUP_ID` as Phase 1 deployment knobs. Their routing semantics by profile:

| Profile | `AGENTTEAM_SERVER_PROFILE` | `AGENTTEAM_WORKSPACE_GROUP_ID` | Startup behavior on missing/invalid value |
| --- | --- | --- | --- |
| `all` | unset resolves to `all`; if set, must be one of `all|central|workspace` | ignored | `AGENTTEAM_WORKSPACE_GROUP_ID` is not consulted; setting it must not change behavior. |
| `central` | required, value `central` | ignored | If set, the value must not be used for ownership decisions and must not enable workspace data-plane handling. |
| `workspace` | required, value `workspace` | required; no implicit default | If the value is empty or whitespace, the process must fail-fast at startup. No silent fallback to `workspace-default`. |

Additional rules:

- `AGENTTEAM_SERVER_PROFILE` only accepts `all`, `central`, or `workspace`. Any other value, including `control`, `ingress`, `runtime`, or `worker`, must fail-fast at startup.
- Operators in Phase 1 must set `AGENTTEAM_WORKSPACE_GROUP_ID=workspace-default` explicitly on `workspace` deployments, even though only one group exists, so that the introduction of a second group does not silently re-home workspaces.
- Workspace creation flows use `AGENTTEAM_DEFAULT_WORKSPACE_GROUP_ID` (default `workspace-default`) as the explicit placement group for new workspaces. Store-layer placement writers must receive this group from the caller; they must not silently normalize an empty group to `workspace-default`.
- These knobs control routing and execution ownership only; they do not bypass session, membership, or object ownership authorization.

## 4. ManagedWorkspaceSet contract

The shared ownership abstraction is `ManagedWorkspaceSet`:

```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` controls execution ownership only. Backend authorization still validates session, membership, object ownership, machine key, agent token, app token, or another authenticated credential.

A workspace process must reject requests for workspaces not owned by its managed set. The response should be deterministic, for example `421 Misdirected Request` or `409 workspace owner mismatch`.

## 5. Role ownership

### 5.1 `central`

Owns global/control APIs:

- public config, auth, sessions, registration, OIDC callback;
- organization APIs;
- organization billing APIs and payment callbacks;
- workspace create/list/member/invite/usage/settings;
- admin APIs;
- developer app management;
- provider OAuth callback/setup;
- machine management and connect exchange;
- placement management.

`central` does not own workspace data-plane execution. `ManagedWorkspaceSet.Has(...)` is always false.

### 5.2 `workspace`

Owns current-workspace data-plane APIs and current runtime-local compatibility paths for its managed workspace set:

- workspace snapshot and channel scope snapshot;
- channels, threads, DMs, messages, reactions;
- tasks, task discussions, review surfaces;
- trigger management, run inspection, and Phase 1 trigger runner;
- workspace app install/config/subscription;
- workspace provider config;
- workspace agent, canonical Role, review context, and output target APIs;
- workspace media mutation;
- browser `/socket.io/*`;
- `/api/v1/workspaces/:workspaceId/daemon/*`;
- `/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/*`;
- `/api/v1/workspaces/:workspaceId/llm/*` (old `/api/llm/*` remains only as a Gateway compatibility rewrite);
- external app webhooks;
- GC, retention, machine offline reconcile, and other current background loops that are not yet split.

A workspace process should declare a group, for example:

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

The process refreshes its cached managed workspace set from `workspace_placements`.

### 5.3 Future runtime role

Runtime is a future physical split target, not a Phase 1 deployment profile.

It can own connector/runtime entry points only after the corresponding ownership path exists:

- browser realtime requires V1/SSE, socket.io-cluster, or durable fanout;
- daemon owner and agent wake require owner registry / owner-hop;
- trigger fanout requires durable handoff / outbox / owner-hop;
- webhook delivery requires durable queue ownership if it leaves `workspace`.

Until then, these paths stay co-located with the `workspace` profile that manages the workspace.

### 5.4 Future worker role

Worker is a future process role, not a Phase 1 deployment profile. Phase 1 keeps current background loops on the `workspace` profile and filters workspace-scoped work through `ManagedWorkspaceSet`.

A future worker may own background work after `system_leases` / DB claim / idempotency is in place. `scopeFanoutRunner` and trigger runner also require durable handoff / outbox / owner-hop before they can leave the workspace owner process.

## 6. Placement contract

Placement stores stable groups, not pods or IPs.

Recommended fields:

| Field | Meaning |
| --- | --- |
| `workspace_id` | Workspace being placed. |
| `workspace_group_id` | Stable workspace group such as `workspace-default`. |
| `state` | `active`, `draining`, `moving`, or `disabled`. |
| `placement_epoch` | Monotonic fencing value for cache and migration safety. |
| `updated_at` | Last placement mutation time. |
| `heartbeat_at` | Optional group/controller heartbeat. |

Lifecycle rules:

1. Backfill existing workspaces to `workspace-default` or another explicit group. The `workspace_placements` table creation **and** the full backfill of every existing workspace must complete before the first `workspace` profile process is allowed to take traffic; otherwise every data-plane request will fail-closed at the owner check.
2. Write `workspace_placements` during workspace creation in the same transaction or creation flow.
3. Missing placement fails closed on the request path; use explicit repair/backfill instead of automatic default placement writes.
4. `central` / ops is the writer. Workspace processes and future gateway/router components are readers.

Workspace process cache rules:

- cache refresh may use polling or an explicit refresh signal;
- cache propagation delay is expected and must be observable;
- requests for newly created workspaces may need retry until the workspace process has refreshed;
- lower-latency routing can add notification later without changing the placement contract.

## 7. Runtime ownership contract

The daemon websocket owner is the process holding the machine connection. Any operation that starts, stops, notifies, inspects, or delivers to an agent must execute on that owner or delegate to it.

Phase 1 rule:

```text
workspace profile owns runtime-local effects for its managed workspace set
```

This keeps daemon-touching APIs, message/task agent wake, and local daemon hub delivery co-located with the workspace owner.

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

Long-term owner-hop requires:

1. register `machine_id -> owner_node_id` when daemon connects;
2. refresh ownership with TTL/lease while the websocket is alive;
3. expire or remove ownership on disconnect/process death;
4. include epoch/fencing so stale owners cannot accept new operations;
5. execute locally when the current process owns the machine;
6. call authenticated internal RPC when another process owns the machine;
7. return a final machine-offline style error when no live owner exists.

The registry may be Redis/Valkey, a database lease table, or peer discovery plus cache. Redis is an implementation option, not the architecture by itself.

### 7.1 Workspace identity propagation scope

Workspace identity propagation makes the current workspace explicit at process and edge-routing boundaries. It is in scope for Phase 1 only where it improves routing determinism without changing authorization or CLI compatibility.

In scope:

1. Browser / SDK workspace REST continues to send `X-AgentTeam-Route-Workspace-Id` as the edge-routing discriminator and `X-Workspace-Id` only where the business API needs workspace context.
2. Browser / deep-link machine connect receives `workspaceId` from the canonical machine connect exchange, persists it as `config.workspace_id` on the existing agentteam channel entry in `~/.agentteam/channels.yaml`, and uses it in workspace-scoped daemon and Agent Runtime paths.
3. Daemon websocket and Agent Runtime HTTP clients use workspace-scoped canonical paths. Missing `config.workspace_id` is a reconnect/login remediation error; there is no unscoped path fallback.
4. Server and delivery guardrails must treat the header as a routing hint only. The authenticated machine key, agent token, app token, session, or server-side row remains the authority for the workspace.

Out of scope for Phase 1:

1. Do not add parameters to `agentteam daemon --server-url ... --api-key ...`. Manual daemon connect resolves and persists the workspace before opening the daemon connection.
2. Do not persist `workspace_group_id` in desktop config or client state. Group ownership is movable operations state and must come from `workspace_placements` or a server-side resolver.
3. Do not make `workspace_id` propagation solve trigger / worker / control-plane side-effect ownership. Those paths are execution-owner problems and remain on the workspace profile with `ManagedWorkspaceSet` filtering until durable fanout, owner-hop, leases, and idempotency exist.
4. Do not route cross-workspace management, billing, org-admin, or global search through workspace route propagation unless a specific workspace data-plane operation is explicitly designed. These remain central or separately specified.

Compatibility and future gates:

- Existing `channels.yaml` entries without `config.workspace_id` must keep working in the single default workspace group.
- Multiple agentteam channels on one machine must keep separate `workspace_id` values bound to their own `api_key`; no process-global workspace ID is allowed.
- Before enabling multiple workspace groups, operators must have a documented reconnect, lazy backfill, or version gate for legacy daemon configs without `workspace_id`; otherwise those daemons stay in the default group.
- Placement changes make a previously stored `workspace_id` potentially stale with respect to group routing, not invalid as an identity. The server-side placement resolver and owner check decide the current group; clients must not cache or persist group IDs.
- External callbacks, webhook outputs, email links, and push/deep links that need to resume a workspace-scoped flow should carry or resolve workspace context through signed state, stored subscription rows, or server-side lookup. OAuth/provider callback routes may stay central, but the callback handler must recover the intended workspace before creating workspace side effects.
- Logs, metrics, and traces for split traffic should include bounded labels or fields for `server_profile`, route policy, and workspace identity where allowed by observability cardinality rules.

## 8. Schema migration boundary

Schema migration is part of release / operations workflow and is intentionally not expanded in this profile spec.

Server profiles must not execute implicit DDL or migration on startup. They should only check that the expected schema is current and fail fast when it is not. The release process is responsible for running schema migration before starting incompatible server code.

## 9. Deployment mapping

| Environment | Mapping |
| --- | --- |
| Local development | One process, `profile=all`. |
| Single-machine private deployment | One process, `profile=all`; schema migration handled by the release / operations workflow. |
| Docker compose | One server service using `profile=all`; schema handled by the compose/release flow. |
| Kubernetes single pod / single Deployment | `profile=all`. |
| Kubernetes SaaS Phase 1 | Separate `central` and `workspace` deployments. The default workspace group is `workspace-default`; no separate runtime or worker deployment is required. |
| Private monobinary delivery | One binary defaults to `profile=all`; advanced operators can run multiple profile processes. |

## 10. Rollout sequence

Phase 1 implementation must follow this dependency order; later steps assume earlier steps are deployed.

1. Keep `profile=all` as the default and document deployment mappings.
2. Land profile configuration parsing for `AGENTTEAM_SERVER_PROFILE` and `AGENTTEAM_WORKSPACE_GROUP_ID` per [§3.1](#31-startup-config-matrix), with `central` as the only control-plane profile name.
3. Add `workspace_placements` schema migration and an explicit backfill / repair command. Run schema migration and the full backfill of existing workspaces to `workspace-default` (or the operator-chosen group) **before** any `workspace` profile process is allowed to take traffic. Workspace creation must write placement in the same transaction or creation flow, using an explicit default group passed from server configuration rather than store-layer fallback.
4. Add a read-only `workspace-placement-verify` command and run it as the production cutover gate after schema/backfill and before Edge Gateway workspace routing takes traffic. This gate must pass before production split; see [§11.3](#113-phase-1-guardrail-scope-and-execution-plan).
5. Add `ManagedWorkspaceSet` with deterministic behavior for `all`, `central`, and `workspace`, sourced from the cached `workspace_placements` view on `workspace` profile.
6. Add the workspace data-plane owner-check middleware; reject owner mismatch with `421 Misdirected Request` or `409 workspace owner mismatch`.
7. Make router registration, dependency validation, and server startup profile-aware so each profile only activates the routes and loops it owns.
8. Filter background loops (`scopeFanoutRunner`, trigger runner, machine offline reconcile, GC, retention) by `ManagedWorkspaceSet` on the `workspace` profile and ensure they do not run on `central`.
9. Land route registration tests that assert `central`-only and `workspace`-only route groups, covering the path-family conflict rules in [`specs/api/server-profile-routing.md`](../api/server-profile-routing.md).
10. Keep current runtime-local paths (browser `/socket.io`, daemon owner, agent wake, trigger fanout) co-located with the workspace owner until durable handoff / owner-hop / lease prerequisites exist.
11. Add workspace identity propagation for browser/deep-link daemon connect without changing manual `agentteam daemon --server-url --api-key`; keep path routing fallback for old configs.
12. Add more workspace groups and placement-aware Edge Gateway / resolver only after the single-group deployment is stable and the daemon legacy-config gate is satisfied.
13. Add future Worker / Runtime deployments only when their ownership prerequisites (system_leases, owner-hop, durable fanout) exist.

The migration / backfill execution entrypoint in Kubernetes (pre-deploy Job, Helm hook, or operator-managed pipeline) is decided by the operator and is intentionally not pinned in this spec; the constraint is ordering, not mechanism.

## 11. Staging / production parity and production split gate

Before production traffic is split into `central` and `workspace` deployments, staging must be close enough to production that it validates the same behavior, while still using staging-only secrets and object prefixes.

### 11.1 Environment parity categories

Use this checklist when comparing staging and production.

| Category | Rule |
| --- | --- |
| Must match behavior | server profile env shape, route GitOps overlay, schema/backfill jobs, media object-storage driver behavior, app credential encryption key presence, auth/session behavior selected for the test, observability needed to diagnose split traffic. |
| May differ by value | domains, OIDC issuer/base URLs, database names, namespace names, image tags during branch testing, OSS bucket or prefix, secret values. |
| Must not be copied blindly | production OSS AK/SK, production object prefixes, production app credential master key, production database credentials. |

Media and credential parity rules:

1. If production stores avatars, chat attachments, and stickers in OSS, staging must also exercise OSS before production split. Staging may use either an independent bucket or the production bucket with a staging-only prefix, but the choice must be explicitly approved before any config is applied.
2. Staging OSS credentials must be least-privilege for the staging bucket/prefix. Do not copy production AK/SK into staging without an explicit security decision.
3. `AGENTTEAM_APP_CREDENTIALS_MASTER_KEY` or its configured secret source must exist in staging if app/provider credential flows are part of the review. The key value must be staging-only unless an operator explicitly decides otherwise.

For the current SaaS staging environment, the approved media target is the production OSS bucket with a staging-only prefix such as `staging/objects`. Secret values and secret file paths are operational inputs and must not be committed to specs or GitOps. The staging credential should eventually be scoped to the staging prefix. The staging app credential master key should be a separate fixed key unless staging must decrypt credentials copied from the production database.

### 11.2 Production split gate

Production central/workspace split can proceed only when all of these are true:

1. The routing spec, environment parity spec, and production split plan have been reviewed.
2. GitOps / delivery render is the source of truth for Ingress and env; live-only patches have either been removed or folded back into GitOps.
3. Staging render and live cluster match, including the Route SSoT guardrails in [`specs/api/server-profile-routing.md` §10.1](../api/server-profile-routing.md#101-phase-1-b-rest-route-slimming-and-gitops-source-of-truth).
4. Staging/prod env diff has been reviewed and every difference is classified as "must match behavior", "may differ by value", or "intentionally not aligned yet".
5. The Phase 1 entry audit checklist has been published, reviewed, and accepted. The checklist must cover browser workspace REST, central REST and central exceptions, browser realtime, daemon websocket, internal agent/app APIs, LLM/runtime routes, app webhooks, provider callbacks, machine connect exchange, workspace creation placement writes, background loops, and readiness endpoints. The first checklist snapshot is recorded in the workspace split planning thread message `59d65a94bd502a25507709cb91412a35`; if that message is superseded, the replacement must be linked from the production cutover runbook before cutover.
6. `workspace-placement-verify --workspace-group-id workspace-default` has passed as an automated deployment wave step after schema migration and placement backfill, and before Edge Gateway / Ingress header-rule cutover. A manual operator run can be useful for debugging, but it does not satisfy this production gate.
7. Staging smoke covers at least: auth, workspace list/bootstrap, channels/messages/tasks, browser realtime, daemon/agent wake, agent creation, attachments, avatars, stickers, provider/app credential flows when enabled, and route fallback behavior.
8. Rollback is documented: image/tag rollback, ArgoCD target revision rollback, Ingress rollback, and the expected behavior of `profile=all`.
9. Workspace Deployment remains `replicas=1` per workspace group. Central Deployment may scale horizontally.
10. New daemon connect flows with `workspace_id` and legacy daemon configs without `workspace_id` have both been tested: new configs send the route header, and old/manual configs keep using the default-group path fallback.

For the first production split, owner-hop, durable fanout, and worker/runtime physical split are not prerequisites if the production shape is `central` with one or more pods plus exactly one `workspace` pod for the single default workspace group. They are prerequisites before scaling any workspace group beyond one owner pod, adding HPA to workspace, or splitting runtime/worker from workspace.

### 11.3 Phase 1 guardrail scope and execution plan

This section records the Phase 1 guardrail decisions that are allowed to proceed after the spec is reviewed. It is intentionally narrow so the production split does not expand into multi-group or multi-replica runtime work.

#### 11.3.1 GA-blocking guardrail: `workspace-placement-verify`

`workspace-placement-verify` is the only new GA-blocking code guardrail added by this section. It validates that the placement state produced by schema migration and backfill is safe for the single default workspace group cutover.

Command shape:

```bash
agentteam-tidb-admin workspace-placement-verify --workspace-group-id workspace-default
```

Implementation requirements:

1. The command lives in `cmd/agentteam-tidb-admin` next to `workspace-placement-backfill`, but it is a separate read-only command. Backfill remains the repair/write path; verify must not repair, migrate, or insert data.
2. Database configuration reuses `AGENTTEAM_DATABASE_DSN` and `AGENTTEAM_DATABASE_CONNECT_TIMEOUT`; do not introduce a second connection configuration surface for this gate.
3. The target group is required through `--workspace-group-id` or `AGENTTEAM_WORKSPACE_GROUP_ID`. Production gate execution must not silently default to `workspace-default` when the group is absent.
4. The active workspace set for Phase 1 is every row in `workspaces`. The current schema has no archived/status lifecycle column; if a lifecycle state is introduced later, this spec must be amended before filtering workspaces.
5. The command may read only `workspaces` and `workspace_placements`. It must not read messages, tasks, channel data, credentials, or other business tables, and it must not call any write method. Prefer a read-only DB credential for production execution; if operations can only provide the shared DSN, the implementation and tests still must prove the command uses read-only store methods only.
6. Machine-readable JSON is the contract output. The output must include at least `workspace_group_id`, `total_workspace_count`, `placement_count`, `missing_placement_count`, `non_default_placement_count`, and `inactive_placement_count`. Human-readable summary output may be added, but automation must not parse prose.
7. Exit code is zero only when all three failure counts are zero. Missing placements, placements outside the target group, inactive placements (`draining`, `moving`, or `disabled`), invalid configuration, DB errors, or malformed data must exit non-zero with contextual error messages.
8. Production delivery must run the command automatically after schema migration (wave -5) and placement backfill (wave -4), but before the Ingress / Edge Gateway header-rule cutover. Failure blocks cutover. Manual runs are allowed for diagnostics and staging rehearsal only; they are not a substitute for the deployment wave step.

Non-goals for this command:

- It is not a post-GA placement consistency monitor, Prometheus collector, alerting job, or continuously running controller.
- It does not implement placement migration, multi workspace group routing, stale daemon re-homing, or legacy daemon config backfill.
- It does not validate business-row consistency outside the placement tables.

#### 11.3.2 Post-GA tracked guardrail: route contract PR-blocking CI

Route contract CI is important continuous governance, but it is not a Phase 1 production cutover blocker once existing route ownership tests, render review, and staging smoke pass.

The intended post-GA work is:

1. Keep `go run ./cmd/agentteam-route-contract` as the AgentTeam route ownership handoff artifact.
2. Add a AgentTeam-side PR check that fails when a public route is unclassified or ownership changes without updating the contract.
3. Add a delivery-side render validator that consumes the contract and checks header-rule priority, central exceptions, explicit `/socket.io` / daemon / internal fallbacks, and rule-count budget.
4. Make branch protection / required-check configuration an operations change, not something a code PR can claim by itself.

Before that post-GA work lands, route changes still must follow [`specs/api/server-profile-routing.md`](../api/server-profile-routing.md), and production split still requires reviewed GitOps/rendered routing plus staging smoke per [§11.2](#112-production-split-gate).

#### 11.3.3 Post-GA tracked guardrail: route observability

Route observability is also post-GA tracked work. It must not change routing semantics or block the first production split.

The field contract for that later work is:

- Structured logs may include `server_profile`, `workspace_group_id`, `route_workspace_id`, `resolved_workspace_id`, `route_decision`, `fallback_reason`, and `owner_mismatch` where those values are already available in the request path. Logs must redact authorization tokens, API keys, cookies, and provider credentials.
- Metrics labels may include only low-cardinality values: `server_profile`, `route_decision`, `fallback_reason`, and `owner_mismatch`.
- Metrics must not use `workspace_id`, `route_workspace_id`, `resolved_workspace_id`, API keys, tokens, or credential-derived values as labels.
- ServiceMonitor, Grafana dashboards, and post-GA alerts are follow-up operations/observability work, not prerequisites for the Phase 1 cutover.

## 12. Phase 2 multi-group handoff

Phase 2 planning is specified in [workspace-multigroup-phase2.md](./workspace-multigroup-phase2.md). The handoff from this Phase 1 spec is:

1. Phase 1 production split uses `central` plus `workspace-default`, with `workspace-default` at `replicas=1`.
2. Phase 2 may add more workspace groups, but every workspace group remains single-owner (`replicas=1`) until owner-hop and durable fanout are designed separately.
3. `workspace_placements` remains the routing source of truth; Phase 2 adds group metadata, migration metadata, and a workspace router/gateway.
4. Placement management moves to `agentteam-ops` through `agentteam` admin APIs. `agentteam-ops` must not directly mutate `agentteam` placement tables.
5. The Phase 1 `agentteam-tidb-admin workspace-placement-verify` command is acceptable for the first production split, but Phase 2 must replace it with a `agentteam-ops` verify/preflight path and then retire the CLI command.
6. Before production cutover, maintainers must publish a route/entry audit checklist for human review and delivery must include that checklist in the cutover gate.

## 13. Acceptance criteria

A change satisfies this spec when:

- `profile=all` remains the default and works for local, docker, single-machine, K8s single-pod, and monobinary deployments;
- Phase 1 deployment profiles are `all`, `workspace`, and `central`;
- `ManagedWorkspaceSet` has deterministic behavior for all three profiles;
- workspace data-plane routes have owner check and reject owner mismatch;
- `central` owns workspace entity management but does not process workspace data-plane requests;
- browser `/socket.io`, daemon owner, agent wake, `scopeFanoutRunner`, and trigger fanout stay co-located with the workspace owner in Phase 1;
- future Worker is blocked until `system_leases` / DB claim / idempotency exists;
- schema migration is documented as release / operations workflow, and server startup does not perform implicit DDL;
- runtime multi-replica deployment is blocked until owner-hop or equivalent ownership exists;
- Kubernetes SaaS Phase 1 workspace Deployments per workspace group must remain at `replicas=1` unless owner-hop, durable fanout, or equivalent ownership routing keeps each workspace's HTTP writes, `/socket.io`, workspace-scoped daemon traffic, and Agent Runtime traffic on the same owner process; the central Deployment is not subject to this constraint;
- `AGENTTEAM_SERVER_PROFILE` only accepts `all`, `central`, or `workspace`; legacy values such as `control` / `ingress` / `runtime` / `worker` fail-fast at startup;
- `workspace` profile fail-fasts when `AGENTTEAM_WORKSPACE_GROUP_ID` is missing or empty; no implicit fallback to `workspace-default`;
- workspace creation writes `workspace_placements` with the explicitly configured `AGENTTEAM_DEFAULT_WORKSPACE_GROUP_ID` default group; store code does not synthesize `workspace-default` from an empty argument;
- `workspace_placements` table creation and the full backfill of existing workspaces complete before the first `workspace` profile process takes traffic;
- `workspace-placement-verify` is specified as the single new GA-blocking code guardrail, is read-only against `workspaces` and `workspace_placements`, runs as an automated deployment wave step after schema/backfill and before Ingress cutover, and is not a continuous post-GA monitor;
- workspace identity propagation stores per-channel `config.workspace_id`, never stores `workspace_group_id`, and treats route headers as routing hints;
- route contract PR-blocking CI and route observability are tracked follow-up guardrails, not prerequisites for the first production split beyond the existing route tests, GitOps/render review, and staging smoke gates;
- production central/workspace split is blocked until the staging/prod parity gate in [§11](#11-staging--production-parity-and-production-split-gate) passes.
