# Elastic Request Link User-Active Warm Pool

> Canonical path: `specs/daemon/elastic-request-link-user-active-warm-pool.md`
> Status: design-spec
> Parent spec: [`specs/daemon/daemon-transport.md`](./daemon-transport.md)
> Scope: Elastic Request Link 的 user-active warm pool、request origin、warm endpoint profile、link grant/pool/assignment 边界、资源控制、观测测试和模块化落地路线。

## 1. Canonical relationship

`daemon-transport.md` 是 Daemon Transport 的入口规范，定义 Session Link、Elastic Request Link、Remote Request、Endpoint Registry、Command Registry、LinkPolicy、fallback 和兼容契约。

本规范是 Daemon Transport 下的 ERL 子规范，专门定义 **User-Active Warm Pool**：当 Web/API 明确由用户交互触发的请求持续发生时，Server 可以为同一 machine/session 预先保持少量 idle Elastic Request Link，使后续 Desktop-bound Remote Request 可复用已建好的 carrier；当只有后台/system/internal 请求时，warm pool 的目标 floor 回到 0，避免长期占用资源。

两份 Spec 的关系：

- transport 概念、carrier 路由、Session Generation、Remote Request envelope、Endpoint Registry、fallback matrix 以 `daemon-transport.md` 为准；
- user-active warm pool 的数据结构、状态机、算法、profile、cooldown、注释和验收 gate 以本规范为准；
- 后续修改任一规范时，必须检查另一份规范的入口摘要和链接是否仍一致，避免语义漂移。

## 2. Goals and non-goals

### 2.1 Goals

1. 在真实用户活跃窗口内，为后续 Desktop-bound Remote Request 预备可复用 ERL carrier。
2. 明确区分 request origin 与 endpoint 分类：用户触发的 demand-only endpoint 也能刷新 warm lease，但不会被加入 warm allowlist，也不会改变该请求自身的选路、retry 或 fallback 语义。
3. 用一等数据结构建模 origin、policy、lease、profile、grant、link state、cooldown，而不是把 warm side effect 隐藏在函数参数或 endpoint 名称推断里。
4. 保持安全边界：link token 只授权 carrier 建立；endpoint 执行仍由 per-request assignment envelope + Endpoint Registry + Desktop registry allowlist 授权。
5. 保持兼容性：旧 Desktop、capability/registry mismatch、emergency disable、capacity、connect timeout 等路径仍按现有 LinkPolicy/fallback/stable error 处理。
6. 在实现前先完成可审查的 Spec；后续代码必须用注释解释行为存在的原因和边界。

### 2.2 Non-goals

- 不把 ERL 变成任意本地 HTTP/path forward。
- 不把 `session_preferred` endpoint 强制改成 elastic。
- 不引入用户可见产品开关；只允许内部 config / emergency disable 用于灰度与回滚。
- 不在本轮实现 multi-node durable rendezvous、shared assignment store 或跨 node link pool。V1 仍依赖 same-node/sticky；wrong-node 必须稳定拒绝并脱敏观测。
- 不让后台/system-only 请求创建或续期 user-active warm lease。

## 3. Vocabulary

| Term | Meaning |
| --- | --- |
| `RemoteRequestOrigin` | 一个低基数来源标记，表示当前调用链是否来自明确用户交互。默认是 `background`。 |
| `user_interactive` | 只能在 Web/API 边界由明确用户请求设置；用于续期 warm lease。 |
| `WarmLease` | 一个 workspace/machine/sessionGeneration/node 维度的短 TTL 用户活跃窗口，不携带 userID、agentID、path 等高基数字段。 |
| `WarmPoolPolicy` | warm pool 的策略：profile、active TTL、target floor、max warm、dial/idle/max-age、cooldown/backoff。 |
| `WarmEndpointProfile` | 可由 warm link 覆盖的 endpoint set。必须从 Endpoint Registry descriptor 元数据生成并 guardrail 校验。 |
| `LinkGrantSpec` | 创建 ERL grant 的输入模型。它从单 endpoint descriptor 假设升级为 purpose/profile/allowedEndpoints/primaryEndpoint/timeout limits 的显式 spec。 |
| `WarmLinkState` | pool 内 link/grant 的状态投影，区分 demand/warm、grant/connecting/idle/assigned/draining/closed/cooldown。 |
| `Assignment authority` | 每次 Remote Request 的 endpoint/version/deadline/cancel/limits 授权。它只存在于 per-request assignment envelope，不存在于 link token。 |

## 4. Core data structures

以下结构是技术设计的语义模型；实际实现字段名可以微调，但职责边界不能弱化。

### 4.1 Remote request origin

```go
type RemoteRequestOrigin string

const (
    RemoteRequestOriginBackground      RemoteRequestOrigin = "background"
    RemoteRequestOriginUserInteractive RemoteRequestOrigin = "user_interactive"
    RemoteRequestOriginSystemInternal  RemoteRequestOrigin = "system_internal"
)

type RemoteRequestContext struct {
    Origin RemoteRequestOrigin
    Cause  string // low-cardinality only: web_api, task_scope, service_agentteam, unknown
}
```

Rules:

- `background` is the default when no marker exists.
- `user_interactive` can only be set by Web/API request entrypoints after authentication/workspace authorization has identified a real user request.
- Daemon/Hub/RemoteEndpoint layers must not infer user activity from endpoint id, payload, agentID, target path, or request size.
- Background workflows that call the same Hub methods, such as role materialization, runtime GC, scheduled jobs, automation, rewind, or service loops, must remain unmarked or explicitly `system_internal`.

### 4.2 Warm pool key

```go
type WarmPoolKey struct {
    WorkspaceID        string
    MachineID          string
    SessionGeneration  string
    NodeID             string
}
```

`WarmPoolKey` is an internal map key only. None of its fields may be used as metrics labels. Session Generation is part of the key so reconnect/replacement naturally invalidates old leases, grants, links, assignments, and cooldown state.

### 4.3 Warm pool policy and lease

```go
type WarmPoolPolicy struct {
    Profile          WarmEndpointProfile
    ActiveTTL        time.Duration // recommended 60-90s
    TargetIdleFloor  int           // default 1 while user-active, 0 otherwise
    MaxWarmLinks     int           // default 2, includes busy warm replacement
    DialTimeout      time.Duration
    IdleTimeout      time.Duration
    MaxAge           time.Duration
    CooldownBase     time.Duration // e.g. 5s
    CooldownMax      time.Duration // e.g. 60s
    JitterRatio      float64
}

type WarmLease struct {
    Key          WarmPoolKey
    Origin       RemoteRequestOrigin
    ProfileName  string
    ActivatedAt  time.Time
    ExpiresAt    time.Time
}
```

Policy invariants:

- no active lease => target idle floor is 0;
- active `user_interactive` lease => target idle floor is at least 1;
- all grants and connected links, demand or warm, count against the same machine/session capacity and hard cap;
- capacity skip is observable but does not by itself increase failure count;
- emergency disable forces target floor to 0 and drains/cleans according to the existing cleanup contract.

### 4.4 Registry-driven warm endpoint profile

Endpoint warm eligibility must be stored on descriptors, not duplicated as a hand-written list in the controller.

```go
type RemoteEndpointWarmSafety string

const (
    RemoteEndpointWarmSafetyReadOnly      RemoteEndpointWarmSafety = "read_only"
    RemoteEndpointWarmSafetyIdempotent    RemoteEndpointWarmSafety = "idempotent"
    RemoteEndpointWarmSafetyDemandOnly    RemoteEndpointWarmSafety = "demand_only"
    RemoteEndpointWarmSafetyNonIdempotent RemoteEndpointWarmSafety = "non_idempotent"
)

type RemoteEndpointWarmMetadata struct {
    Eligible     bool
    Profile      string
    Safety       RemoteEndpointWarmSafety
    Rationale    string // comment/spec text, not metrics label
}

type WarmEndpointProfile struct {
    Name            string
    EndpointScopes  []RemoteEndpointScope
    SafetyClasses   []RemoteEndpointWarmSafety
    MutatingAllowed bool
}
```

Default profile recommendation: `user_interactive_read_review_v1`.

Allowed by default:

- `workspace.list`
- `workspace.read`
- `review.summary`
- `review.files`
- `review.diff`
- `output_target.validate`
- `machine.workspace.scan`
- `runtime_access.trajectory_history`

Demand-only by default:

- `workspace.apply_seed_files`
- `workspace.delete`
- `machine.workspace.delete`
- `runtime_access.restore_recovery_point`

Guardrail requirements:

- profile generation walks `RemoteEndpointRegistry.Descriptors()` and selects descriptors with matching warm metadata;
- descriptor metadata and profile membership must be tested together;
- destructive or non-idempotent descriptors cannot enter the default warm profile unless the Spec, descriptor rationale, and tests are updated to prove no retry/authorization semantic changes;
- Desktop validates the Server-supplied `AllowedEndpoints` against its local registry before dialing, and each assignment validates endpoint/version against link allowlist again.

### 4.5 LinkGrantSpec

Current ERL grant creation is descriptor-coupled: one grant allows one endpoint. Warm pool needs a carrier grant that can cover a profile while preserving per-request assignment authority.

```go
type ElasticLinkPurpose string

const (
    ElasticLinkPurposeDemand ElasticLinkPurpose = "demand"
    ElasticLinkPurposeWarm   ElasticLinkPurpose = "warm"
)

type LinkGrantSpec struct {
    Purpose          ElasticLinkPurpose
    ProfileName      string
    AllowedEndpoints []RemoteEndpointScope
    PrimaryEndpoint  *RemoteEndpointScope
    DialTimeout      time.Duration
    IdleTimeout      time.Duration
    MaxAge           time.Duration
}
```

Rules:

- token claims bind workspace, machine, session generation, node, link id, protocol version, registry version, purpose and expiry;
- token remains single-use and authorizes only carrier establishment;
- `AllowedEndpoints` is an assignment allowlist for this link, not permission for Desktop to self-select work;
- `PrimaryEndpoint` is optional metadata for demand grants and route diagnostics; it is not the only authority boundary;
- assignment envelope remains the authority for endpoint/version/deadline/cancel/limits.

### 4.6 Warm link state

```go
type ElasticLinkPhase string

const (
    ElasticLinkPhaseGrantCreated ElasticLinkPhase = "grant_created"
    ElasticLinkPhaseConnecting   ElasticLinkPhase = "connecting"
    ElasticLinkPhaseIdleWarm     ElasticLinkPhase = "idle_warm"
    ElasticLinkPhaseAssigned     ElasticLinkPhase = "assigned"
    ElasticLinkPhaseDraining     ElasticLinkPhase = "draining"
    ElasticLinkPhaseClosed       ElasticLinkPhase = "closed"
    ElasticLinkPhaseCooldown     ElasticLinkPhase = "cooldown"
)

type WarmLinkState struct {
    LinkID              string
    Key                 WarmPoolKey
    Purpose             ElasticLinkPurpose
    ProfileName         string
    AllowedEndpoints    []RemoteEndpointScope
    Phase               ElasticLinkPhase
    CreatedAt           time.Time
    ExpiresAt           time.Time
    IdleSince           time.Time
    CooldownUntil       time.Time
    FailureCount        int
    CurrentAssignmentID string
}
```

Implementation may store demand and warm links in one `ElasticLinkPool`, with purpose/profile fields attached to grants/links. The pool must not maintain separate counters that allow warm links to bypass hard caps.

## 5. Request origin and endpoint decoupling

The user-active signal is about **who initiated the Server-side request path**, not about which endpoint is currently being invoked.

Rules:

1. A clear Web/API user action can set `OriginUserInteractive` for the current request context.
2. Any `OriginUserInteractive` request may refresh a `WarmLease`, even if the current endpoint is demand-only.
3. Demand-only endpoints do not enter the warm endpoint profile and do not become eligible for proactive assignment.
4. A demand-only user request that refreshes the warm lease must keep its own LinkPolicy/fallback/no-retry behavior unchanged.
5. Background/system-only requests may reuse an existing idle warm link if normal LinkPolicy chooses ERL, but they must not create or extend user-active windows.

This avoids two bad outcomes:

- treating `workspace.read` as user-triggered in all contexts, which would let background materialization keep warm links alive forever;
- treating destructive user actions as warm-profile endpoints, which could accidentally blur non-idempotent retry boundaries.

## 6. WarmPoolController algorithms

### 6.1 First user request semantics

The first `user_interactive` request updates the warm lease and triggers reconcile, but it does **not** have to wait for a warm link to become ready.

For the current request:

- LinkPolicy remains the final route decision;
- an existing matching idle warm link may be reused only when that endpoint's LinkPolicy and request options allow ERL for this request;
- warm pool presence must not implicitly promote `session_preferred` traffic to ERL unless a future descriptor explicitly defines that policy refinement and tests it;
- if no eligible idle warm link exists, the request follows the endpoint's current route behavior;
- proactive warm open failures are recorded as warm events only and do not change Web/API response semantics.

### 6.2 On request activity

Pseudo-code:

```go
func (c *WarmPoolController) OnRequest(ctx context.Context, client *clientState, descriptor RemoteEndpointDescriptor) {
    req := RemoteRequestContextFromContext(ctx) // default background
    if req.Origin != RemoteRequestOriginUserInteractive {
        c.RecordWarmEvent("skipped_background", req.Origin)
        return
    }
    if !c.TransportEligible(client) {
        c.RecordWarmEvent("skipped_ineligible", boundedReason)
        return
    }

    key := WarmPoolKeyFromClient(client)
    lease := c.UpsertLease(key, policy.Profile.Name, now.Add(policy.ActiveTTL))
    c.RecordWarmEvent("lease_refreshed", req.Cause)
    c.Reconcile(key, lease.ProfileName)
}
```

`TransportEligible` checks capability, protocol, registry version, session generation, and emergency disable. It must not look at high-cardinality user or path data.

### 6.3 Reconcile

Pseudo-code:

```go
func (c *WarmPoolController) Reconcile(key WarmPoolKey, profileName string) {
    lease := c.Lease(key, profileName)
    targetFloor := 0
    if lease.Active(now) && !policy.Disabled {
        targetFloor = policy.TargetIdleFloor
    }

    stats := pool.Stats(key, profileName) // grants + connecting + idle + assigned
    if targetFloor == 0 {
        pool.DrainSurplusWarmLinks(key, profileName, "lease_expired")
        return
    }

    if stats.Idle + stats.Connecting >= targetFloor {
        c.RecordWarmEvent("exists", "ok")
        return
    }
    if stats.WarmTotal >= policy.MaxWarmLinks {
        c.RecordWarmEvent("skipped_warm_cap", "capacity")
        return
    }
    if pool.ActiveCount(key.MachineID) >= global.MaxPerMachine {
        c.RecordWarmEvent("skipped_capacity", "capacity")
        return
    }
    if cooldown.Active(key, profileName, now) {
        c.RecordWarmEvent("cooldown", "backoff")
        return
    }

    spec := LinkGrantSpec{
        Purpose: ElasticLinkPurposeWarm,
        ProfileName: profileName,
        AllowedEndpoints: profile.EndpointScopes,
        DialTimeout: policy.DialTimeout,
        IdleTimeout: policy.IdleTimeout,
        MaxAge: policy.MaxAge,
    }
    pool.CreateGrantAndSendOpen(key, spec)
}
```

Reconcile is triggered by user requests, route decisions that notice stale state, and the existing cleanup agentteam. It must not add a per-request/per-client ticker. If a future implementation needs periodic reconciliation, it should use one bounded controller agentteam, not N loops per client/request.

### 6.4 Cooldown and backoff

Backoff scope: `WarmPoolKey + ProfileName + SessionGeneration`.

Counts as failure and enters backoff:

- Desktop does not connect before dial/connect timeout;
- token reject: expired, replayed, wrong node, registry/protocol mismatch;
- Desktop dial failed as observed by no connect + timeout;
- send open failed on Session Link;
- malformed open ack / protocol rejection.

Does not increase failure count:

- normal idle timeout;
- max age close;
- drain after lease expiry;
- session generation replacement cleanup;
- capacity skip;
- emergency disable skip.

Backoff formula:

```text
delay = min(CooldownMax, CooldownBase * 2^FailureCount) + bounded jitter
```

Cooldown expires passively. The controller should not wake itself only to retry; the next user activity or bounded reconcile opportunity can open a new grant.

## 7. State machine

### 7.1 Normal warm lifecycle

```text
absent
  -> grant_created
  -> connecting
  -> idle_warm
  -> assigned
  -> idle_warm
  -> draining
  -> closed
```

`assigned -> idle_warm` happens only when the assignment result is received or cleaned with a stable result and the link remains healthy.

### 7.2 Failure lifecycle

```text
grant_created/connecting
  -> cooldown
  -> absent
```

The cooldown state is logical policy state, not an open carrier. It prevents rapid retry on repeated dial/token/connect failures.

### 7.3 Cleanup transitions

Any state may transition to cleanup on:

- Session Generation change;
- Session Link disconnect/replacement;
- emergency disable;
- wrong-node/token/registry/protocol mismatch;
- max age;
- idle timeout;
- request deadline/cancel;
- explicit drain.

Rules:

- cleanup is idempotent and can be called repeatedly;
- socket close happens outside pool locks;
- grants, links, assignments, waiters, lease state, cooldown state and metrics projection must converge;
- if max age fires while busy, mark draining and close after assignment finish unless deadline/cancel/session cleanup requires cancellation;
- single-flight remains mandatory: one ERL carries at most one assignment at a time.

## 8. LinkPolicy, fallback, and authority boundaries

Warm pool prepares carriers only. It does not change endpoint policy.

For actual requests:

1. Router evaluates the endpoint's `LinkPolicy` and request options.
2. If an idle link covers the requested endpoint/version, route may choose `elastic_request_link` according to policy.
3. If no idle link exists, route follows existing behavior: use Session Link, open demand ERL, fallback, or stable error according to policy.
4. Proactive warm failures do not affect the current request result.
5. If the endpoint is non-idempotent or `SafeRetryFallback=false`, accepted/in-flight ERL failure must not silently retry through legacy Session Link.

Authority boundaries:

- link token = carrier establishment only;
- `AllowedEndpoints` on grant = coarse allowlist for assignments on that carrier;
- assignment envelope = concrete endpoint execution authority;
- Desktop registry handler = local execution allowlist;
- cancel token = per-request cancellation authority.

Wrong patterns that must remain impossible:

- a warm link token that grants arbitrary local tunnel access;
- Desktop choosing endpoint/payload without a Server assignment;
- cancellation based only on link id/request id without matching cancel token;
- fallback that repeats a non-idempotent operation after partial execution.

## 9. Same-node and sticky routing

V1 remains same-node/sticky.

Server-local state includes:

- unconsumed grants and token consumed/replay state;
- connected link objects;
- assignment waiters/result channels;
- warm lease/cooldown state for the current Session Generation.

If an ERL connect arrives at the wrong Server node, the node must reject it with a redacted stable reason such as `node_mismatch` or `unknown_link`. The wrong node must not attempt cross-node lookup or tolerate the mismatch based only on token claims.

Future multi-node support would require a separate design with durable rendezvous, shared assignment store, replay protection across nodes, and explicit gateway semantics. It is out of scope for this Spec.

## 10. Metrics and logging

All metrics labels must be low-cardinality allowlists.

Suggested new events/reasons:

| Metric area | Labels |
| --- | --- |
| warm event | `warm_event=eligible|lease_refreshed|exists|grant_created|connected|cooldown|skipped_capacity|skipped_background|skipped_ineligible|drained|closed` |
| origin | `origin=background|user_interactive|system_internal|unknown` |
| profile | `profile=user_interactive_read_review_v1|unknown` |
| backoff reason | `connect_timeout|token_rejected|send_open_failed|dial_failed|protocol_mismatch|registry_mismatch|node_mismatch|unknown` |
| capacity reason | `skipped_capacity` is observable through warm events, but does not increment failure count or backoff state. |

Existing request/route/pool metrics remain valid. Warm pool adds only bounded dimensions and must reuse sanitizing helpers.

Forbidden in metric labels and ordinary errors:

- token, token hash, Authorization header;
- raw socket/URL/local path;
- link id, request id, machine id, user id, agent id, workspace id;
- raw handler error text;
- payload-derived strings.

Logs may include request/link ids only in debug/internal logs when already allowed by existing logging policy, but not in metrics labels or product-facing errors.

## 11. Module boundaries

### 11.1 Server modules

| Module | Owns | Depends on | Tests |
| --- | --- | --- | --- |
| `RemoteEndpointInvoker` | Typed invocation flow, route integration, fallback recording. | Router, WarmPoolController, AssignmentDispatcher. | route/fallback matrix, origin propagation. |
| `RemoteRequestContext` | Origin/context helpers. | none. | default background, Web marker, async leakage. |
| `GrantTokenAuthority` | Sign/verify/consume single-use link tokens. | config secret, clock. | replay, mismatch, expiry, wrong node. |
| `ElasticLinkPool` | Grant/link state, capacity, idle lookup, purpose/profile fields. | token authority, metrics recorder. | capacity, idle lookup, cleanup idempotency. |
| `AssignmentDispatcher` | Assignment create/send/wait/complete/cancel. | link pool, typed sender. | single-flight, deadline/cancel, non-idempotent no retry. |
| `WarmPoolController` | Warm lease, policy, profile, reconcile, backoff. | link pool, endpoint registry, metrics. | user/background, cooldown, busy replacement. |
| `ElasticCleanup` | Cleanup agentteam and synchronous cleanup routines. | link pool, dispatcher, warm controller. | session cleanup, idle/max-age/drain. |
| `TransportMetricsRecorder` | Low-cardinality label allowlists. | observability package. | redaction/allowlist guardrails. |

### 11.2 Desktop modules

| Module | Owns | Tests |
| --- | --- | --- |
| `DesktopEndpointRegistry` | Endpoint/version to typed handler mapping and supported endpoint summary. | unknown endpoint/version, profile allowlist validation. |
| `ElasticLinkDialer` | Build connect URL, attach bearer token, dial with timeout. | no machine key in URL, invalid URL, timeout. |
| `ElasticLinkRunner` | Read/write agentteam, idle/max-age timers, drain/close. | idle close, max-age busy drain, normal close. |
| `RemoteAssignmentExecutor` | Validate assignment envelope, enforce limits/deadline/cancel, call handler. | allowlist mismatch, cancel token, response limit. |
| `ElasticLinkState` | Single-flight busy/draining/current cancel state. | busy reject, finish/drain behavior. |

The module split is an architecture prerequisite. Warm behavior should not be inserted as additional side effects into a large `invokeRemoteEndpoint[T]` parameter list or a monolithic Desktop transport file.

## 12. Test and gate matrix

### 12.1 Spec-first and comment gate

- New Spec exists at this canonical path.
- `daemon-transport.md` links this child Spec and summarizes its boundary.
- Implementation PRs must include comments explaining why these behaviors exist and where boundaries are, not comments that merely restate code.

### 12.2 Origin tests

- `user_interactive` Web/API request refreshes lease.
- `background` default does not create or refresh lease.
- `system_internal` does not create or refresh lease.
- origin does not leak into asynchronous background goroutines unless explicitly re-marked by an entrypoint.
- `user_interactive` demand-only request refreshes lease but does not change current request route/fallback/no-retry semantics.

### 12.3 Warm state tests

- warm grant is created under active lease;
- Desktop accepts multi-endpoint warm allowlist after local registry validation;
- assignment for different allowed endpoints can reuse the same idle warm link over time;
- assignment for endpoint outside allowlist is rejected;
- busy warm link enforces single-flight;
- target floor may open a replacement warm link while one warm link is busy, subject to cap;
- idle/max-age/session-generation cleanup converges;
- connect timeout and token reject enter cooldown;
- capacity skip is observed but does not increase failure count.

### 12.4 Compatibility and fallback tests

- old Desktop / missing capability uses fallback or stable unavailable;
- registry/protocol mismatch uses fallback or stable unavailable;
- emergency disable prevents proactive warm and drains existing warm links;
- safe retry endpoint may fallback when allowed;
- non-idempotent endpoint does not retry after accepted/in-flight ERL failure;
- demand-only endpoint is not added to default warm profile.

### 12.5 Authorization tests

- token replay is rejected;
- link/workspace/machine/session/node/registry/protocol mismatch is rejected;
- carrier token alone cannot authorize endpoint execution;
- assignment endpoint/version/deadline/cancel/limits are enforced;
- cancel requires matching cancel token;
- wrong-node is rejected and redacted.

### 12.6 Observability tests

- metrics labels are allowlisted;
- token/raw socket/local path/link id/request id/machine id/agent id/user id/workspace path are not emitted as labels;
- route decision, warm reconcile, cleanup, cooldown, fallback and request result paths all record bounded outcomes.

## 13. Migration route

Implementation must proceed in phases, each with focused tests and rollback points.

1. **Spec phase**: add this child Spec and update `daemon-transport.md` entry summary. No implementation code changes.
2. **Behavior-preserving module split**: split Server and Desktop transport responsibilities into the modules listed above. Tests prove no behavior change.
3. **Origin marker phase**: add `RemoteRequestOrigin` helpers and mark only approved Web/API user entrypoints. Background remains default.
4. **Registry metadata phase**: add descriptor warm metadata, generate `WarmEndpointProfile` from registry, add guardrails.
5. **Grant/pool model phase**: introduce `LinkGrantSpec`, purpose/profile fields, and pool projections while preserving demand behavior.
6. **Warm controller phase**: add `WarmPoolController`, lease/cooldown state, request-triggered reconcile, and LinkPolicy route integration.
7. **Observability/test phase**: complete matrix, redaction tests, PR description, and gate review.
8. **Enablement/rollback phase**: keep emergency disable/internal config; if disabled, behavior returns to ordinary demand ERL + Session Link fallback.

## 14. Required implementation comments

Future code changes must include comments at the behavior boundaries below. Comments should explain the reason and boundary, not just repeat the code.

Required comment locations:

- origin marker helper: why default is background and why daemon layer must not infer user intent from endpoint names;
- warm profile builder: why profile is registry-driven and why destructive endpoints are demand-only by default;
- `LinkGrantSpec` / token authority: why token authorizes carrier only and remains single-use;
- assignment dispatcher: why per-request assignment is the endpoint execution authority;
- warm controller: why first user request does not wait on warm dial and why warm failure does not fail user request;
- cooldown: which failures count and why normal idle/max-age/drain do not;
- cleanup: same-node/session generation cleanup and busy max-age drain-after-finish semantics;
- fallback handling: why non-idempotent endpoints cannot silently retry;
- metrics recorder: why labels are low-cardinality and redacted.

Good comment shape:

```go
// User-active warm links are a carrier availability optimization only. The
// first user request must still follow LinkPolicy and must not fail just because
// proactive warm dialing failed; assignment envelopes remain the endpoint
// authorization boundary.
```

Bad comment shape:

```go
// Create a warm link if origin is user_interactive.
```

The bad comment repeats code but does not explain the behavior's existence or boundary.
