# Server-side Message Freshness Gate 设计

> Canonical path: `specs/message/server-side-freshness-gate-design.md`
> Status: target design
> Related:
> - [`specs/message/server-side-freshness-gate.md`](./server-side-freshness-gate.md)
> - [`specs/message/scope-model.md`](./scope-model.md)
> - [`specs/message/scope-implementation-design.md`](./scope-implementation-design.md)
> - [`specs/daemon/codex-runtime-consolidation.md`](../daemon/codex-runtime-consolidation.md)
> - [`specs/storage/storage.md`](../storage/storage.md)

本文定义 server-side message freshness gate 的实现设计。设计聚焦 agent-authored visible message creation，并把 freshness 判断收敛到 target scope 的 message append transaction。

## 1. 架构

```mermaid
flowchart LR
    Runtime[Agent runtime]
    Daemon[Daemon session freshness state]
    API[Agent send API]
    Target[Target resolver]
    Tx[Message append transaction]
    Scope[scopes.latest_seq]
    Msg[scope_messages]
    Outbox[scope_fanout_outbox]

    Runtime -->|send_message target/content| Daemon
    Daemon -->|target + clientMessageId + observed freshness| API
    API --> Target
    Target -->|canonical targetKey| Tx
    Tx -->|conditional update + row count| Scope
    Tx -->|sent| Msg
    Tx -->|sent| Outbox
```

Design ownership:

| Layer | Responsibility |
| --- | --- |
| Agent runtime | Calls `send_message` with target and message content, then acts on sent / already_sent / held tool results. |
| `agentteam` MCP layer | Supplies `clientMessageId` and per-target observed freshness context; stores latest-known refresh hints separately. |
| Agent API DTO | Carries freshness request and structured send result. |
| Agent API handler | Authenticates agent, parses target, validates canonical targetKey, forwards send params to store port. |
| Message store | Resolves canonical target scope, applies CAS, persists accepted message rows and accepted-send outbox. |
| Taskboard discussion writer | Applies the same stateful CAS result when raw task scope target writes task discussion messages. |
| Realtime / wake fanout | Emits side effects for `sent` according to the delivery runtime contract. |

## 2. Data model

### 2.1 Canonical message state

Freshness uses message-domain state:

| Table / field | Role |
| --- | --- |
| `scopes.id` | Canonical target scope key. |
| `scopes.latest_seq` | Target scope authoritative message frontier. |
| `scope_messages.scope_id` + `scope_messages.seq` | Persisted target sequence position. |
| `scope_messages.idempotency_subject_member_id` | Direct send uses author member; a product-specific override is supplied only by its registered target contract. |
| `scope_messages.client_message_id` | Idempotent send key per target scope, author, and idempotency subject. |
| `scope_messages.idempotency_payload_digest` | Canonical create-payload digest used to detect same-key conflicts. |
| `scope_fanout_outbox` | Transactional fanout source for accepted sends. |

`scope_messages_client_message_id_key(scope_id, author_member_id, idempotency_subject_member_id, client_message_id)` stores the idempotency constraint. Agent direct send and workspace service-account direct send both set `idempotency_subject_member_id=author_member_id`; platform service-account delivery uses the local service author member.

### 2.2 Sequence vocabulary

Freshness uses explicit sequence names across server and daemon boundaries:

| Name | Source | Purpose |
| --- | --- | --- |
| `deliverySeq` | `agent:deliver` envelope | Daemon ack / dedup cursor for the delivery transport. |
| `targetSeq` | `scope_messages.seq` | Scope-local message sequence used by freshness. |
| `activitySeq` | `scope_activity_events` / activity projections | Non-message activity ordering. |
| `acceptedSeq` | CAS accepted target sequence | New message target sequence returned by `sent`. |
| `latestSeq` | `scopes.latest_seq` or author-visible miss frontier | Server authoritative target scope frontier for accepted sends; held responses for private visibility gaps use only a proved author-visible frontier. |

Daemon freshness logic reads `targetKey` + `targetSeq`. Transport ack logic reads `deliverySeq`.

### 2.3 Wire DTOs

`internal/api/dto/agent_messages.go` owns the request and response wire shape. `Freshness` is a pointer so the handler can distinguish compatibility send requests from freshness-capable send requests.

```go
type AgentSendMessageRequest struct {
    Target          string                          `json:"target"`
    Content         string                          `json:"content"`
    AttachmentIDs   []string                        `json:"attachmentIds"`
    ClientMessageID string                          `json:"clientMessageId,omitempty"`
    Freshness       *AgentSendMessageFreshnessInput `json:"freshness,omitempty"`
}

type AgentSendMessageFreshnessInput struct {
    TargetKey        string   `json:"targetKey"`
    SeenUpToSeq      int64    `json:"seenUpToSeq"`
    SourceMessageIDs []string `json:"sourceMessageIds,omitempty"`
}

type AgentSendMessageResponse struct {
    State                string `json:"state"`
    MessageID            string `json:"messageId,omitempty"`
    TargetKey            string `json:"targetKey"`
    ClientMessageID      string `json:"clientMessageId,omitempty"`
    SeenUpToSeq          int64  `json:"seenUpToSeq,omitempty"`
    AcceptedSeq          int64  `json:"acceptedSeq,omitempty"`
    LatestSeq            int64  `json:"latestSeq"`
    NeedsRefreshUntilSeq int64  `json:"needsRefreshUntilSeq,omitempty"`
}
```

Server-delivered message DTOs carry scope-local message sequence using target-specific names:

```go
type ReceiveMessage struct {
    MessageID string `json:"message_id,omitempty"`
    TargetKey string `json:"target_key"`
    TargetSeq int64  `json:"target_seq"`
    // channel, author, content, metadata, attachments...
}

type HistoryMessage struct {
    Seq       int64  `json:"seq"`       // scope-local message seq for this target
    TargetKey string `json:"targetKey"`
    TargetSeq int64  `json:"targetSeq"`
    // author, content, metadata, attachments...
}
```

### 2.4 Domain states and store params

Send state constants live in the message domain package so API, taskboard, daemon tests, and store code use one source of truth while preserving the store/API dependency boundary:

```go
package message

const (
    SendStateSent        = "sent"
    SendStateAlreadySent = "already_sent"
    SendStateHeld        = "held"
)
```

`internal/store/message/api_types.go` carries the domain shape:

```go
type SendMessageParams struct {
    TargetRef        string
    DMTo            string
    TaskScopeID     string
    Content         string
    AttachmentIDs   []string
    ClientMessageID string
    Freshness       SendMessageFreshness
}

type SendMessageFreshness struct {
    TargetKey        string
    SeenUpToSeq      int64
    SourceMessageIDs []string
}

type SendMessageResult struct {
    State                string
    MessageID            string
    TargetKey            string
    ClientMessageID      string
    SeenUpToSeq          int64
    AcceptedSeq          int64
    LatestSeq            int64
    NeedsRefreshUntilSeq int64
    Delivery             MessageDelivery
}
```

The agent API store port returns `SendMessageResult`, and the handler maps each store state directly into `AgentSendMessageResponse`.

CAS miss visibility decisions use a dedicated request shape for the allocator:

```go
type ScopeMessageSeqVisibleFreshnessRequest struct {
    ScopeID             string
    ViewerMemberID      string
    ViewerType          string
    SeenUpToSeq         int64
    UseVisibleMissCheck bool
    CandidateBatchLimit int
    MaxCandidateBatches int
}
```

`UseVisibleMissCheck` is enabled for author / target combinations that can have a private visibility gap. Default visible-miss bounds are a newest-first candidate batch of 16 messages and one batch. Budget/no-proof outcomes proceed through the permissive second raw CAS path rather than returning held.

### 2.5 Daemon session freshness state

The daemon process/session tracks observed frontiers and latest-known refresh hints separately:

```go
type MessageFreshnessFrontier struct {
    TargetKey             string
    ObservedSeq           int64
    LatestKnownSeq        int64
    NeedsRefreshUntilSeq  int64
    SourceMessageIDs      []string
    ObservedCoverageToken string
}

type SessionFreshnessState struct {
    ByTarget map[string]MessageFreshnessFrontier
}
```

Daemon helpers update the two fields through explicit methods:

```go
func (s *SessionFreshnessState) MarkSurfaced(targetKey string, targetSeq int64, sourceMessageID string)
func (s *SessionFreshnessState) MarkHeld(targetKey string, seenUpToSeq int64, latestSeq int64)
```

`MarkSurfaced` advances `ObservedSeq`. `MarkHeld` records `LatestKnownSeq` and `NeedsRefreshUntilSeq` for refresh guidance.

### 2.6 Task-scope message result shape

Task-scope message send uses a stateful result aligned with ordinary message send:

```go
type SendTaskScopeMessageResult struct {
    State                string
    MessageID            string
    TargetKey            string
    ClientMessageID      string
    SeenUpToSeq          int64
    AcceptedSeq          int64
    LatestSeq            int64
    NeedsRefreshUntilSeq int64
    Message              TaskDiscussionEntry
}
```

`sendAgentTaskScopeMessage` maps this result into the agent send response instead of assuming every task-scope message send creates a new visible entry.

## 3. Server write path

### 3.1 Request validation and target canonicalization

`MessageHandler.Create` routes by daemon capability / request shape:

1. Authenticate agent.
2. Bind `AgentSendMessageRequest`.
3. Parse `target` into canonical target scope.
4. Compute canonical `targetKey` from the resolved scope.
5. For freshness-capable requests, validate `clientMessageId`, `freshness`, `freshness.targetKey`, `freshness.seenUpToSeq`, bounded `sourceMessageIds`, and canonical targetKey match.
6. For compatibility requests, keep the compatible send path and response shape, and emit compatibility metric and structured log fields.
7. Convert freshness-capable requests to `message.SendMessageParams` using canonical target metadata.

Freshness validation errors use structured codes such as `freshness_required`, `client_message_id_required`, and `target_key_mismatch`. Compatibility requests keep wire compatibility through the compatibility path. The freshness guarantee applies to freshness-capable requests; server min daemon version elevation moves all sends onto the freshness-capable path.

### 3.2 Thread / DM / channel send

`SendAgentThreadMessage` receives a validated canonical target scope id, canonical targetKey, author, content, attachments, `clientMessageId`, and `seenUpToSeq`.

Transaction algorithm:

```text
input: author, idempotency subject, canonical target scope, canonical targetKey, content, attachment IDs, clientMessageId, payload digest, seenUpToSeq

1. Load scope_messages row by (scope_id, author.member_id, idempotency_subject.member_id, client_message_id).
2. When the row exists with the same payload digest, return already_sent with persisted message id and seq; a different digest returns idempotency conflict.
3. Allocate target seq with conditional scope update:
   UPDATE scopes
   SET latest_seq = latest_seq + 1
   WHERE id = $scopeID AND latest_seq = $seenUpToSeq
4. Interpret affected rows from the update result.
5. When affected rows is 1, set acceptedSeq = seenUpToSeq + 1 and insert scope_messages with seq, content, metadata, attachment facts, idempotency subject, client_message_id, and payload digest.
6. Insert search doc, source records, membership / notification mutations and scope_fanout_outbox records for the accepted send.
7. Return sent with message id, canonical targetKey and acceptedSeq.
8. When affected rows is 0, refresh the idempotency lookup; a persisted row with the same digest returns already_sent with message id and seq, while a different digest returns idempotency conflict.
9. Held allocation reads `scopes.latest_seq` only after raw CAS miss. Public/no-gap targets return held with raw latest. Private-gap targets run a bounded tail visibility check over `(seenUpToSeq, rawLatest]`; they return held only when a author-visible message is proved, otherwise they CAS against current raw latest and allow the send.
```

The initial conditional update is the zero-extra-read fast-path linearization point. Accepted sends on that path use `seenUpToSeq + 1` as `scope_messages.seq`. On private-gap CAS miss fallback, the bounded visibility check only decides whether to hold; allowed fallback sends still linearize through a second CAS against the current raw `scopes.latest_seq`, preserving global scope seq monotonicity.

TiDB retriable write-conflict errors rerun this bounded transaction with the same `clientMessageId`, idempotency subject, and payload digest. Initial lookup, CAS-miss lookup, duplicate-key lookup, and transaction retry all apply the same digest comparison.

### 3.3 Task discussion send

Raw task scope target enters `sendAgentTaskScopeMessage` and passes freshness into the canonical `taskBoard.SendTaskScopeMessage`; taskboard owns task-scope resolution and write authorization for the canonical message send.

Taskboard request shape:

```go
type SendTaskScopeMessageRequest struct {
    Context         ServiceContext
    Board           BoardRef
    Task            TaskRef
    TaskScopeID     string
    Message         TaskScopeMessageInput
    Freshness       message.SendMessageFreshness
    ClientMessageID string
    IdempotencySubjectMemberID string
    IdempotencyPayloadDigest string
}
```

Task discussion transaction uses the same conditional sequence allocation helper. Branch behavior:

| State | Transaction effects |
| --- | --- |
| `sent` | Inserts `scope_messages` with `MessageTypeTaskDiscussion`, inserts the task-scope message, applies discussion count, task event, attachment claim, participant mutation, search doc and outbox records. |
| `already_sent` | Loads the persisted task-scope message by the four-part idempotency key and returns it when the payload digest matches; a different digest returns typed idempotency conflict. |
| `held` | Returns raw latest for public/no-gap misses or proved author-visible latest for private-gap misses through `SendTaskScopeMessageResult`. |

`sendAgentTaskScopeMessage` converts `SendTaskScopeMessageResult` into `SendMessageResult`, preserving state, targetKey, message id, acceptedSeq and latestSeq.

### 3.4 Conditional allocator

`internal/store/message/append.go` exposes a reusable allocator:

```go
type ScopeMessageSeqAllocation struct {
    State     string
    Seq       int64
    LatestSeq int64
}

func AllocateNextScopeMessageSeqWithVisibleFreshness(
    ctx context.Context,
    tx coredb.Tx,
    request ScopeMessageSeqVisibleFreshnessRequest,
) (ScopeMessageSeqAllocation, error)
```

SQL shape:

```sql
UPDATE scopes
SET latest_seq = latest_seq + 1,
    updated_at = $now
WHERE id = $scope_id
  AND latest_seq = $seen_up_to_seq
```

The allocator reads the TiDB row-count result. Affected rows 1 produces `State=sent`, `Seq=seenUpToSeq+1`, and `LatestSeq=Seq` without an additional read. Affected rows 0 enters the CAS miss branch:

1. If `UseVisibleMissCheck` is false, read raw `scopes.latest_seq` and return `State=held` with raw `LatestSeq`.
2. If visible miss check is enabled, read raw latest and current membership visibility.
3. Public membership returns `State=held` with raw latest.
4. Private membership uses `VisibilityFilter.ListVisibleMessages` over a bounded newest-first candidate window with `Limit=1`.
5. A proved author-visible seq greater than `seenUpToSeq` returns `State=held` with that visible `LatestSeq`.
6. No proved visible tail proceeds through `UPDATE scopes ... WHERE latest_seq = rawLatest`; success returns `State=sent` with the next raw seq.
7. A concurrent allocation miss repeats the visible-miss agentteam within a small retry bound.

### 3.5 Idempotency ordering

Idempotency lookup wraps the CAS:

```text
lookup persisted row -> CAS -> insert -> duplicate lookup fallback
```

Every lookup in this ordering compares `idempotency_payload_digest`. Matching
digests return `already_sent`; differing digests return typed
`idempotency_conflict`, including CAS-miss, duplicate-key race, task-discussion,
and transaction-retry branches.

This ordering gives stable retry semantics for:

- transaction result retry;
- daemon retry with same `clientMessageId`;
- simultaneous retry of the same logical send;
- long content split where each row has its own deterministic `clientMessageId` suffix.

### 3.6 Fanout and side effects

Handler fanout is state-aware:

| State | API handler behavior |
| --- | --- |
| `sent` | Emits accepted delivery, realtime fanout, wake side effects and outbox delivery marking from the accepted delivery payload. |
| `already_sent` | Returns persisted message id / acceptedSeq and performs local tool confirmation / daemon frontier update. |
| `held` | Returns latestSeq / needsRefreshUntilSeq and refresh action. |

External side effects originate from the original `sent` transaction.

## 4. Daemon frontier propagation

### 4.1 Delivery payloads

Server-delivered message payloads include both transport ack sequence and scope-local target sequence:

```json
{
  "delivery_seq": 1201,
  "message": {
    "message_id": "msg_...",
    "target_key": "scp_...",
    "target_seq": 42,
    "channel_type": "thread",
    "channel_name": "#agentteam:abcd1234",
    "content": "..."
  }
}
```

The daemon maps `message.target_key` and `message.target_seq` to `types.InboxMessage{TargetKey, TargetSeq}` and stores them in prompt metadata. The delivery envelope ack path uses `delivery_seq`.

### 4.2 Surface points

A daemon helper records observed frontiers:

```go
func (p *Process) RememberSurfacedFrontier(msg types.InboxMessage)
```

For confirmed runtime-visible surfaces it calls `SessionFreshnessState.MarkSurfaced(msg.TargetKey, msg.TargetSeq, msg.MessageID)`, which advances the observed frontier as a high-water mark. Daemon delivery state distinguishes received payloads, active-turn transport acceptance, pending runtime delivery, and surfaced frontier. Pending delivery becomes surfaced frontier only at the runtime-visible input boundary. The helper runs after successful delivery into these runtime-visible input paths:

- launch prompt assembly for initial wake, covering through the wake message `targetSeq`;
- protocol direct delivery that starts a new / idle turn, covering through the delivered message `targetSeq`;
- ack-aware active-turn delivery after the runtime emits a correlated surfaced ack for the pending runtime input id / client message id / uuid;
- idle stdin delivery success, covering through the delivered message `targetSeq`;
- deferred replay prompt assembly;
- automatic continuation delivery or history MCP results returned to runtime with target coverage.

Active-turn message bodies first try runtime-specific ack-aware direct delivery. The daemon records a pending runtime input outside the daemon-local inbox, sends the body through the runtime transport, and treats transport accept only as "pending accepted"; it is not a surfaced message. The pending input records latest-known / needs-refresh hints so a subsequent stale `send_message` still carries the last observed frontier and can be held locally or by the server. Only the correlated runtime surfaced ack advances observed frontier. Runtime unsupported / non-steerable state / transport failure / session mismatch requeues the message into the current live process's daemon-local continuation inbox, which the daemon retries automatically at a safe input boundary or idle transition. The inbox remains an in-memory owner of the current live Process; Stop, process exit, and daemon restart retain the existing best-effort semantics and do not add durable replay. Transport-accepted pending inputs do not use a fixed wall-clock ack timeout; they remain pending across long-running tool/command work until surfaced ack or an explicit failure path.

### 4.3 Send path

`send_message` receives tool-surface target/content/attachments and prepares an enriched HTTP send body as follows:

```text
1. Normalize target.
2. Resolve targetKey from in-memory target mapping or server target resolution cache.
3. Read observed frontier = SessionFreshnessState.ByTarget[targetKey].ObservedSeq.
4. Generate clientMessageId / restore pending partial-send id.
5. If local latest-known / needs-refresh for the same target is greater than the observed or partial-send frontier, return a held-like tool result without POSTing the send.
6. Attach freshness `{targetKey, seenUpToSeq, sourceMessageIds}` from the observed daemon session frontier.
7. POST `/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/send` with target, content, attachments, clientMessageId and freshness.
8. For sent / already_sent, call MarkSurfaced(targetKey, acceptedSeq, messageId).
9. For held, call MarkHeld(targetKey, seenUpToSeq, latestSeq) and prompt runtime to refresh messages for target.
```

When send races with pending delivery for the same target, the HTTP request carries the observed frontier available at send preparation time. The server CAS compares that frontier with `scopes.latest_seq` and returns the corresponding structured state.

### 4.4 History and search

`read_history` results and daemon automatic continuation deliveries carry `targetKey` and scope-local target sequence per message. Server-side agent `/receive` is not part of the current refresh surface. The daemon-local continuation inbox is not agent-readable and must remain separate from ack-aware accepted pending runtime input that has not emitted a surfaced ack. Responses that prove coverage through a target sequence include coverage metadata:

```json
{
  "targetKey": "scp_...",
  "messages": [
    {"messageId": "msg_...", "targetSeq": 43}
  ],
  "coversThroughSeq": 43
}
```

Tool formatting calls `MarkSurfaced(targetKey, coversThroughSeq, lastMessageID)` after the covered messages are included in the tool result.

Search results are sparse source context. `search_messages` formatting does not call `MarkSurfaced` and does not update `LatestKnownSeq`; the target freshness refresh agentteam stays on current-message handoff, runtime surfaced ack, automatic continuation/history surfaces, and held send results.

### 4.5 Multi-row logical sends

`Channel.SendText` chunking assigns deterministic row ids:

```text
clientMessageId = logicalSendId + ":" + chunkIndex
```

After each accepted chunk, the next chunk uses the returned `acceptedSeq` as `seenUpToSeq`. The MCP tool accumulates accepted chunk facts:

```go
type SendMessageChunkResult struct {
    ChunkIndex int
    MessageID  string
    AcceptedSeq int64
}
```

When chunk `k` returns held, the tool stops additional chunk sends and returns text-only partial-held guidance without exposing accepted chunk ids / seqs, held chunk index, targetKey, seenUpToSeq or latestSeq. A subsequent retry with the same logicalSendId replays accepted chunks as `already_sent` and resumes from chunk `k` after the runtime refreshes target context. The first unaccepted chunk uses `seenUpToSeq = max(refreshedObservedSeq, lastAcceptedSeq)` so replayed accepted chunks preserve ordered send frontier progress after an intervening target message.

## 5. API handler flow

`internal/api/agent/api_messages.go` remains the entrypoint for the
workspace-scoped Agent Runtime `/send` route.

Handler flow:

```text
1. Authenticate agent.
2. Bind AgentSendMessageRequest.
3. Resolve target to canonical scope and canonical targetKey.
4. Route compatibility requests to the compatibility send response path and record compat observability.
5. Validate freshness-capable requests and canonical targetKey match.
6. Convert freshness-capable request to message.SendMessageParams, including clientMessageId and Freshness.
7. Call message store port.
8. For sent, fan out accepted delivery and mark outbox delivery.
9. Return AgentSendMessageResponse for sent / already_sent / held.
```

`held` responses return the store-provided canonical targetKey/latestSeq/needsRefreshUntilSeq payload.

## 6. Response handling in MCP

`send_message` tool renders:

- `sent`: destination, messageId, targetKey, acceptedSeq, thread reply target when applicable;
- `already_sent`: destination, existing messageId, targetKey, acceptedSeq;
- `held`: a concise text instruction to read already-surfaced new messages, wait for a pending runtime input to surface at the next input boundary, or call exact-target `read_history`;
- `partial_held`: the same text-only refresh instruction, without exposing chunk indexes, accepted chunk ids / seqs, targetKey or freshness frontiers.

The tool returns `held` as a successful tool result containing text guidance. Held / partial-held results intentionally do not return structured content or runtime-facing state/frontier fields; freshness internals stay in server state, logs, and tests.

Held result content includes a text-only refresh instruction for the target scope. It may best-effort include daemon-local fallback inbox messages for that target. If included messages already cover the held gap, the instruction tells the runtime to read those new messages and retry only if needed. If more same-target messages remain in the local inbox, the instruction directs the runtime to exact-target `read_history` before retrying. If the held gap is covered by an ack-aware pending runtime input that has not emitted surfaced ack yet, the instruction tells the runtime that the new message will be surfaced at the next input boundary and to retry after it only if needed. Otherwise, the instruction also directs the runtime to `read_history` for the exact target. Runtimes then issue a subsequent `send_message` with the refreshed observed frontier only if a visible reply is still needed. Ack-aware pending runtime input remains outside the continuation inbox until surfaced ack; fallback pending delivery remains available for automatic delivery when the active turn reaches a safe boundary.

## 7. Observability

Metrics:

| Metric | Labels | Description |
| --- | --- | --- |
| `agent_send_freshness_total` | `state`, `target_type` | Count of sent / already_sent / held freshness outcomes. |
| `agent_send_freshness_seq_delta` | `target_type` | Distribution of `latestSeq - seenUpToSeq` for held outcomes. |
| `agent_send_idempotency_total` | `target_type` | Count of idempotent send hits. |
| `agent_send_compat_total` | `target_type` | Count of compatibility sends that use the compatibility path. |
| `agent_send_partial_held_total` | `target_type` | Count of multi-row logical sends that stop at a held chunk. |

Structured logs include workspace id, agent id, canonical targetKey, state, seenUpToSeq, latestSeq, acceptedSeq, needsRefreshUntilSeq, clientMessageId and targetKey validation result. High-cardinality fields stay in logs and traces, while metrics use low-cardinality labels.

## 8. Validation

Backend integration coverage:

- The workspace-scoped Agent Runtime `/send` route accepts fresh channel, DM and reply thread sends through the freshness-capable path.
- Freshness-capable requests with missing freshness, empty clientMessageId, negative seenUpToSeq, empty targetKey and targetKey mismatch return structured validation errors.
- Same target scope simultaneous sends from identical seenUpToSeq produce one `sent` result and held results carrying the advanced latestSeq.
- Public/no-gap CAS miss returns `held` with raw latest.
- Private-gap CAS miss runs the bounded visible-tail check, returns `held` only with a proved author-visible latest, and otherwise proceeds through a second raw CAS allocation.
- Private-gap held responses do not expose invisible raw latest as `latestSeq` or `needsRefreshUntilSeq`.
- TiDB affected-row conditional update drives `sent` / `held` allocation; retriable transaction conflicts converge through the same result states.
- Held response updates latest-known refresh hint while observed frontier remains at seenUpToSeq.
- Held followed by direct retry with a new clientMessageId and the same observed frontier returns held.
- Idempotent retry returns `already_sent` with the persisted message id and seq.
- Idempotent retry produces local response semantics and preserves external fanout side effects from the original sent transaction.
- Raw task scope send applies the same freshness CAS and returns stateful task discussion result.
- Task discussion held returns latestSeq and refresh hint through taskboard result mapping.
- Long content split chains accepted seq between chunks and returns partial_held when an intermediate chunk hits held.
- Partial-held retry uses `max(refreshedObservedSeq, lastAcceptedSeq)` for the first unaccepted chunk.

Daemon coverage:

- Initial delivery records canonical `targetKey` and scope-local `targetSeq` into observed session frontier.
- Delivery ack sequence stays in transport ack state and target sequence stays in freshness state.
- Active-turn delivery records ack-aware pending runtime inputs and latest-known / needs-refresh hints; new-turn / idle direct delivery records observed frontier. Ack-aware pending advances observed frontier only on correlated surfaced ack, and falls back to daemon-local queue on unsupported / failed delivery or session mismatch. It does not fall back merely because wall-clock time elapsed while the runtime was still busy.
- Busy fallback delivery records frontier when daemon automatic continuation surfaces target messages to runtime.
- `read_history` updates frontier for returned target messages when response carries coverage metadata.
- Search result provides sparse source context only and does not update observed or latest-known freshness frontier.
- Freshness-capable `send_message` sends `clientMessageId` and freshness from observed session frontier.
- Held tool result preserves the target, latest-known frontier and refresh instruction in runtime-readable text.
- Tool-result-only runtime recovery surfaces target messages through automatic fallback delivery, history tools, or ack-aware pending runtime input surfaced ack before the subsequent send; held text guidance selects already-surfaced new messages, waiting for the next input boundary, or exact-target `read_history` from the available coverage.

SQL-shape coverage:

- Fresh send uses one conditional `UPDATE scopes ... WHERE id = ? AND latest_seq = ?` plus affected-row interpretation in the append transaction.
- Accepted send is the sole producer of `scope_messages` insert, search doc insert and fanout outbox insert in this path.
- Idempotency lookup uses `(scope_id, author_member_id, idempotency_subject_member_id, client_message_id)` as the unique key; direct agent and workspace service-account sends set idempotency subject to author member.
- Concurrent requests with the same four-part key and different payload digests produce one accepted message and one typed idempotency conflict across initial, CAS-miss, duplicate-key, task-discussion, and transaction-retry branches.

## 9. Enforcement

This contract is protected by focused agent API tests, message store integration tests, task discussion integration tests, daemon delivery tests, and SQL-shape assertions for the conditional allocator. These tests are the executable guardrail for this message-domain freshness contract. The standalone guardrail decision is to keep executable protection in focused tests because this contract depends on transactional and runtime integration behavior that those tests exercise directly.
