DOC11_OPENCLAW_GATEWAY_MODEL_CONTROLS_AND_EC_INTEGRATION_R14.md
Current Specs/DOC11/DOC11_OPENCLAW_GATEWAY_MODEL_CONTROLS_AND_EC_INTEGRATION_R14.md
# DOC11 - OpenClaw Gateway-First Interactive Chat, Protocol Evidence, Runtime Truth, Selector Truth, Protected Native Context, Streaming, Usage Telemetry, Multi-Agent Runtime Status, and EC/Q Integration
Version: v1.11.30 R14
Date: 2026-03-21
Status: R14 inline-compiled fully consolidated closure revision. This revision supersedes the R13 overlay model, compiles all accepted closure amendments into the operative body and canonical appendices, retires the former R13 Appendix T overlay as a normative layer, resolves the remaining R14 event-proof and route-canonicalization defects, and establishes a single authoritative source for schemas, routes, read-models, UI contracts, event payload minima, cross-document obligations, and anti-ghost implementation rules.
Companion docs: DOC4 OpenClaw Bridge current operative revision, DOC10 Unified Engagement Orchestration current operative revision and Integration Ledger current operative revision, DOC12 Inter-Agent Communication / Multi-Agent Rooms / ACP current operative revision, DOC13 Costs current operative revision, DOC15 CIL current operative revision and Cross-Document Integration Contract current operative revision, DOC16 Deferred Additions Punch List current operative revision, DOC17 Overlay Templates / Prompt Optimization current operative revision.
Companion ledger posture: All required-now cross-document obligations generated by DOC11 MUST be mirrored into the master DOC10 Orchestration Integration Ledger using stable XDI IDs. DOC11 does not create a second suite-wide obligation tracker.
## Why this document exists
ELNOR needs one build-safe seam between:
- **Q** as the operator-facing UI and control surface,
- **EC** as the sole durable writer and orchestration spine,
- **OpenClaw Gateway** as the authoritative runtime control plane,
- **OpenClaw sessions, plugins, channels, and agents** as native execution truth.
DOC11 exists to prevent:
1. **routing schism** - interactive Q chat bypasses OpenClaw-native runtime behavior,
2. **session fiction** - EC/Q pretend to know session state better than Gateway,
3. **selector gaslighting** - Q shows configured state as if it were effective or executed truth,
4. **ghost UI** - controls, buttons, or drawers appear real but do not map to real commands,
5. **room theater** - multi-agent turns are faked by prompt dressing instead of truthful participant/runtime execution,
6. **context sludge** - stale, duplicate, or monolithic context blobs are repeatedly injected,
7. **native-context mutilation** - EC/Q rewrite, summarize, compact, or otherwise modify OpenClaw-native context sources such as `SOUL.md`,
8. **fallback invisibility** - switches, rebinding, auth fallback, and model/provider fallback occur without explicit truth surfaces,
9. **cost blindness** - completion, failure, and abort usage facts never become machine-usable for DOC13,
10. **catalog drift** - deleted or unavailable models remain selectable or appear verified,
11. **auth fiction** - settings imply valid auth when runtime auth is missing, stale, limited, or different from what was actually used,
12. **audit drift** - the spec claims support that exists only in docs or source rather than in audited runtime evidence,
13. **greenfield ambiguity** - a coding agent building from scratch must guess how controls, routes, stores, and UI states work,
14. **local-runtime bypass** - local/self-hosted models are routed directly and escape Gateway-first execution, runtime truth, telemetry, and usage seams.
The core goals are:
1. keep interactive chat **Gateway-first**,
2. preserve OpenClaw ownership of native runtime behavior,
3. keep EC as the **sole durable writer**,
4. make every visible control truth-backed and testable,
5. distinguish **desired**, **effective**, **executed**, and **catalog/runtime evidence** truth,
6. keep EC/Q context bounded, observable, and separate from protected native context,
7. make multi-agent runtime truth renderable without flattening reality,
8. keep provider/model auth first-class and truthful,
9. allow defeatable architecture: advanced orchestration can be disabled without breaking the baseline,
10. make the document detailed enough that coding agents cannot ship decorative nonsense,
11. make local/self-hosted inference first-class without introducing special bypass paths around OpenClaw.
## Instruction
Implementers MUST treat this document as authoritative for:
- interactive Gateway-first chat and room-turn transport,
- runtime-truth resolution and selector governance,
- model/provider/catalog lifecycle and orphan repair,
- provider/model auth catalogs, challenges, verification, provenance, and local/self-hosted provider configuration truth,
- ACP / external harness configuration, agent runtime assignment, session truth, and operator-visible limitations,
- channel configuration seams for Discord and WhatsApp including login/pairing/account binding truth,
- capability checks, attachments, streaming, abort/STOP propagation, and usage producer seams,
- protected native context handling,
- Q-facing UI states, layouts, indicators, inspectors, and debug surfaces,
- required route contracts, store/read-model contracts, event contracts, migration steps, and anti-drift tests.
Where this document says:
- **MUST** - required to claim DOC11 compliance,
- **SHOULD** - strongly recommended; omitting requires explicit rationale and a logged variance,
- **MAY** - optional capability,
- **unsupported/settings-only** - visible in settings or configuration, but MUST NOT be rendered as verified runtime truth,
- **native/probed** - supported by runtime evidence for the audited build,
- **wrapper/composed** - implemented honestly by EC/Q/adapter rather than by native Gateway semantics.
This revision supersedes DOC11 V11 in full. V11 intent is retained unless explicitly changed here.
---
## 0) Spec pinning, runtime identity, evidence, and source-of-truth rule
### 0.1 Runtime identity block requirement
Every audited deployment MUST record:
- `openclaw_version`
- `gateway_build_ref`
- `platform`
- `adapter_build_ref`
- `runtime_audit_timestamp`
- `protocol_probe_artifact_ref`
- `control_support_matrix_ref`
- `context_engine_audit_ref`
- `release_alignment_basis_ref`
### 0.2 OpenClaw source-of-truth order
For runtime behavior, authority order is:
1. live runtime evidence and audited probes,
2. official OpenClaw runtime/docs for the audited build,
3. official release notes for the audited build,
4. bridge/adapter code that truthfully wraps the audited build,
5. this document.
If any higher source contradicts a lower source, the lower source MUST be corrected and not hand-waved.
### 0.3 Adapter contract vs Gateway protocol contract
- The **Gateway protocol contract** defines what OpenClaw natively does.
- The **DOC11 adapter contract** defines what EC/Q may honestly compose around that without pretending it is native.
- Wrapper behavior is allowed if typed, visible, and never presented as native support.
### 0.4 Protocol support evidence model
Each capability in Appendix A1 MUST be labeled:
- `native_probed`
- `native_documented_not_probed`
- `wrapper_composed`
- `unsupported`
- `advisory_only`
### 0.5 Unsupported-control honesty rule
A control may be visible even if unsupported natively, but it MUST:
- be labeled `settings-only` or `advisory-only`,
- never show `verified` runtime state,
- never affect effective/executed truth unless a verified native or explicitly wrapped path exists,
- render with tooltip/help text explaining the limitation.
### 0.6 Schema canonicality rule
Where a section contains prose plus a schema/code block, the canonical schema/code block is normative. Duplicate minimum-field lists are prohibited. This revision removes all duplicated normative definitions flagged in V11 red-team review.
### 0.6A Canonical naming table
The following names are canonical and win over any stale alternate names in historical notes, UI specs, or companion documents:
| Canonical name | Rejected / historical alternate names | Notes |
|---|---|---|
| `SwitchSegment` | `SwitchSegmentRecord` | Use the existing `SwitchSegment` schema at §7.4. |
| `ProviderAuthRemoveProfileRequest` | duplicate ad hoc remove-profile request variants | Canonical shape is defined in §4A.11 and Appendix O. |
| `ChannelRuntimeState` | split per-channel top-level runtime state types that duplicate shared fields | Channel-specific fields remain nested/optional inside the single canonical type. |
| `GatewayControlMutationRequest` | informal mutation payload prose variants | Canonical shape is defined at §3.11A. |
| `TruthResolverResult` | `RuntimeTruthDebugProjection` as a schema substitute | `TruthResolverResult` remains the canonical truth result; debug projections are derived views. |
| `ModelExecutionPolicy` | informal default-model/fallback policy blobs | Use the canonical type at §4.15B. |
| `AcpSessionRuntimeState` | ad hoc ACP session state payloads | Use the canonical type at §4C.7. |
| `SearchRuntimeState` | provider-specific search state views used as canonical runtime state | Search provider-specific details remain nested/optional inside the single canonical type. |
Naming sweep rule:
- any stale alternate name still present in body prose, appendices, wireframes, or companion examples MUST be replaced with the canonical name above,
- no new alternate name may be introduced for a canonical type without amending this table.
Additional canonical naming rows:
| Canonical name | Rejected / superseded variant | Rule |
|---|---|---|
| `AcpProjectRootEntry` | `AcpProjectRoot` when used as the final mutable root-entry type | Use `AcpProjectRootEntry` for the canonical root-entry schema. |
| `ChannelPairingChallenge` | earlier narrower challenge variants | Use the richer pairing challenge schema defined in §4D.6A. |
| `ProviderAuthRemoveProfileRequest` | all prior boolean/duplicate variants | Use the strategy-enum canonical request only. |
| `TruthResolverReadResult` | ad hoc debug-view bundles with no route contract | Use the canonical read result and route defined in §3.13 and Appendix E. |
### 0.6B Settings-Only Control Registry
The following controls are settings-only unless a specific runtime/provider path is explicitly marked `verified_runtime_supported` in the relevant provider capability/read model:
| Control | Why it is settings-only by default | Required UI treatment |
|---|---|---|
| `temperature` | not consistently verified/applied on the audited runtime paths | show `Settings only` badge; never show verified runtime truth unless capability proves support |
| `desired_max_output_tokens_user` | user preference is distinct from provider dispatch cap | show `Settings only` badge and separate from `dispatch_max_output_tokens_cap` |
| `provider_id` (when used as a preference on a native model binding) | may not map to a runtime-patchable field | show preference/config truth only |
| `base_url` | infrastructure/config seam, not ordinary runtime control | settings/config UI only |
| `api_name` | infrastructure/config seam, not ordinary runtime control | settings/config UI only |
| ACP `additional_project_roots` beyond the primary working directory | may be advisory only unless the harness/backend reports applied-path enforcement | show `Applied` / `Advisory` / `Rejected` per-root |
| local-provider metadata overrides marked `operator_asserted` | operator-supplied truth, not runtime-verified truth | show `Operator asserted` badge |
| search-provider compatibility-path-limited filters | may be unsupported when a provider runs on a compatibility path | disable or hide unsupported filters and show reason |
| provider-specific think/reasoning/verbose fields with unknown capability | do not claim runtime support without proof | show `Unknown support` or hide behind capability gating |
Registry rule:
- every settings-only control shown in Q MUST map to this registry or an updated successor row,
- every row in this registry MUST be reflected in the Q UI spec mirror required by §12.3A.
### 0.7 Companion-ledger rule
Any DOC11 change that requires another owner doc to change MUST also produce a master-ledger row in the DOC10 integration ledger with:
- stable XDI ID,
- source section,
- target doc,
- change type,
- required-now vs deferred classification,
- acceptance status.
### 0.8 R14 inline compilation rule
R14 is a compiled single-source document.
Normative rule:
- the operative body text and canonical appendices in this document are the only authoritative DOC11 source,
- no superseding overlay appendix may redefine or override a body section or canonical appendix,
- historical redline and amendment artifacts are provenance only and MUST NOT be used as implementation source unless their content is compiled into this document,
- if a compiled section and a historical artifact differ, the compiled R14 text wins.
Compilation requirement:
- every canonical DOC11 requirement in this document MUST be represented directly in the body or a canonical appendix,
- the former R13 Appendix T overlay is retired and retained only as historical provenance outside the operative body of this document.
### 0.8A Locked decision rule
The following decisions are locked in R14 and MUST NOT be deferred inside the body text:
- search fallback uses the existing `gateway.search.fallback.recorded` family extended with `stage = "triggered" | "completed" | "failed"`,
- `gateway.search.runtime_snapshot.refreshed` is retained and completed rather than removed,
- `gateway.health.changed` is the aggregate gateway-level health event and does not replace provider-scoped health events,
- `SwitchSegment` remains the canonical type name; `SwitchSegmentRecord` is not introduced,
- bootstrap gate defaults are surface-class-specific and are not left implementation-defined,
- DOC13 token naming uses either zero-alias alignment when revised in the same wave or a bounded compatibility window when not,
- context-engine health wiring is standard in R14 and not optional,
- this document MUST remain a single compiled normative spec rather than an overlay package.
## 1) Guiding principles
### 1.1 Gateway-first for interactive Q chat
Free-text chat initiated from Q MUST go through Gateway-backed runtime execution. Deterministic fast paths MUST NOT silently replace or bypass OpenClaw runtime behavior.
### 1.2 Gateway/session truth is canonical
Gateway session identity, runtime status, and executed behavior outrank UI assumptions and desired settings.
### 1.3 OpenClaw owns native runtime semantics
OpenClaw owns session compaction, native/system context, plugin system context, context-engine behavior, channel-native behavior, and native auth storage/flows where implemented.
### 1.4 EC owns bounded higher-order augmentation
EC may:
- build lean annotations,
- perform bounded route/orchestration decisions,
- track durable truth about Q/EC-owned state,
- aggregate telemetry and cost seams,
- compose wrapper behavior that is explicitly labeled as such.
EC may not:
- durably rewrite protected native context,
- claim native support without evidence,
- fabricate runtime truth.
### 1.5 Executed truth beats planned truth
Any time a choice was planned one way but executed another, Q MUST show that mismatch/fallback explicitly.
### 1.6 Desired state is not verification truth
Desired settings are user intent. Effective state is runtime-ready truth. Executed watermark is what the runtime actually used. They are separate.
### 1.7 Selector views are plural; selector truth is singular
There may be many selector widgets. There is exactly one selector truth model and one mutation queue per scope.
### 1.8 Fallback is never silent
Model, provider, auth, binding, stream-mode, or transport fallback MUST open a switch boundary or mismatch record.
### 1.9 Context injection must be relevant, non-duplicative, and observable
Any EC/Q context contribution MUST be inspectable, budgeted, de-duplicated, and degrade honestly.
### 1.10 Protected native context must remain intact
Protected native context MUST remain read-only from the perspective of EC/Q compaction, summarization, and rewriting logic.
### 1.11 DOC11 is runtime seam, not room policy owner
DOC11 owns runtime truth overlays and transport for rooms. DOC12 owns room policy semantics, participant identity, roster policies, and room-local workflow semantics.
### 1.12 Evidence beats assumption
If current runtime evidence is unavailable, DOC11 MUST surface `unknown` or `inferred`, not guessed `verified`.
### 1.13 Wrapper seams are legitimate if typed and honest
Wrapper functionality is allowed. Fake native claims are not.
### 1.14 Every visible control must map to real behavior
A visible control MUST have:
- defined semantics,
- a concrete operation/command/route,
- a validation owner,
- a durable owner or explicit non-durable effect,
- a read-model refresh path,
- required telemetry,
- explicit success/failure/pending/disabled/orphaned states,
- tests proving it is real.
---
## 2) Architectural ownership and layering
### 2.1 Ownership matrix
| Concern | Owner | Notes |
|---|---|---|
| Durable suite truth | EC | Sole durable writer |
| UI rendering and user actions | Q | May not write durable suite truth directly |
| Native runtime execution | OpenClaw | Gateway/session/plugin/channel/runtime behavior |
| Room policy and participant identity | DOC12 / room system | DOC11 consumes overlays |
| Cost interpretation and budget policy | DOC13 | DOC11 produces usage/correlation seams |
| CIL contracts | DOC15 | DOC11 consumes/injects CIL only via declared seam |
| Cross-doc obligation tracking | DOC10 master ledger | Single suite-wide active tracker |
### 2.2 Layering model
1. Q emits structured operations.
2. EC validates, queues, persists, routes, and projects read-models.
3. Gateway adapter translates DOC11 contracts to OpenClaw runtime-native behavior.
4. OpenClaw executes native runtime behavior and emits reverse telemetry.
5. EC updates read-models and Q renders truth.
### 2.3 No-shadow-truth rule
Q local component state MAY buffer user input while a control is being edited, but it MUST NOT become authoritative runtime truth. All persisted or cross-surface selector truth MUST come from EC read-models.
---
### 2.4 DOC11 logical service → EC Core ownership mapping
The following table is normative for implementation planning and coding-agent alignment.
| DOC11 logical service | EC Core write path | EC Core projection / read path | Notes |
|---|---|---|---|
| `SessionTruthService` | `CommandIngestionService` | `StateProjectionService` | Sole durable owner for session/binding mutation ingestion. |
| `SelectorTruthService` | `CommandIngestionService` | `StateProjectionService` | Writes desired-control and queue state; does not own truth display directly. |
| `RuntimeTruthService` | none (derived) except where explicitly mutating policy/catalog through command ingestion | `StateProjectionService` | Primarily a resolver/derived projection owner. |
| `RuntimeTruthDebugService` | none | `StateProjectionService` / debug projection path | Read-only bundle assembly. |
| `ProviderAuthStateService` | `CommandIngestionService` | `StateProjectionService` | Durable owner for profile/challenge/state mutation paths. |
| `LocalProviderConfigService` | `CommandIngestionService` | `StateProjectionService` | Durable owner for local/self-hosted provider config writes. |
| `SearchProviderConfigService` | `CommandIngestionService` | `StateProjectionService` | Durable owner for search config/fallback policy writes. |
| `AcpHarnessService` | `CommandIngestionService` | `StateProjectionService` | Durable owner for ACP profile/system-state config writes. |
| `AcpSessionService` | `CommandIngestionService` for durable session/binding-affecting changes; direct runtime call for live option changes when explicitly allowed | `StateProjectionService` | Live runtime changes MUST still round-trip through canonical projections. |
| `ChannelConfigService` | `CommandIngestionService` | `StateProjectionService` | Durable owner for Discord/WhatsApp config/account/binding writes. |
| `ChannelPairingService` | `CommandIngestionService` for approvals/rejections | `StateProjectionService` | Owns pairing queue decisions. |
| `ContextManifestService` | none for read-only explain; `CommandIngestionService` where durable budget/profile writes occur | `StateProjectionService` | Context manifests are projections; budget/profile changes are durable. |
| `AbortService` | `CommandIngestionService` for persistent abort state | `StateProjectionService` | Live abort propagation may fan out through Gateway adapter but must project state canonically. |
| `UsageSampleService` | append-only durable mutation path | `StateProjectionService` | Produces usage/cost handoff truth. |
| `GatewayProtocolAdapter` | no direct durable writes | direct runtime transport + event bridge | Runtime transport layer only; EC projections remain canonical. |
EC Core cross-reference rule:
- EC Core MUST reference this table in its own architecture document so coding agents can trace write/projection ownership in both directions,
- route matrix rows may reference these logical services without duplicating the entire ownership table.
## 3) Session lifecycle and canonical truth
### 3.1 Lifecycle states
Canonical session lifecycle states:
- `uninitialized`
- `creating`
- `live_active`
- `live_idle`
- `live_degraded`
- `reconnecting`
- `orphaned`
- `closing`
- `closed`
- `archived`
- `failed`
### 3.1A Session lifecycle transition table
Required transitions:
- `uninitialized -> creating` on accepted session create request.
- `creating -> live_active` on successful bind plus first verified effective state when the session immediately enters work.
- `creating -> live_idle` on successful bind plus first verified effective state when no dispatch or mutation is running.
- `creating -> failed` on bind failure, validation failure, or create timeout.
- `live_active -> live_idle` on inactivity timeout (default: 300s unless a stricter surface policy applies).
- `live_idle -> live_active` on new dispatch, accepted control mutation that enters active work, or successful reconnect restoring active work.
- `live_active -> live_degraded` on partial runtime failure (for example: auth expires, provider degrades, channel/account loses capability, ACP harness degrades).
- `live_idle -> live_degraded` on partial runtime failure discovered while idle.
- `live_degraded -> live_active` on successful repair or re-verification when active work is resumed immediately.
- `live_degraded -> live_idle` on successful repair or re-verification when no dispatch/mutation is running.
- `live_active | live_idle | live_degraded -> reconnecting` on connection loss or replay recovery start.
- `reconnecting -> live_active | live_idle | live_degraded` on successful reconnect, preserving the pre-reconnect semantic state where possible.
- `reconnecting -> orphaned` when automatic reconnect is exhausted per §3.5.
- `live_active | live_idle | live_degraded | orphaned -> closing` on explicit close, room/panel/task teardown, or forced cleanup.
- `closing -> closed` on close acknowledgment or terminal cleanup success.
- `closed -> archived` on explicit archive request.
- `any non-terminal -> failed` on unrecoverable system/runtime error.
Invalid transitions MUST be rejected with `reason_codes = ["invalid_lifecycle_transition"]`.
### 3.2 Session binding states
Canonical binding states (shared with DOC12 overlays):
- `unbound`
- `binding_pending`
- `bound_verified`
- `bound_stale`
- `bound_degraded`
- `binding_failed`
- `orphaned`
- `closed`
### 3.3 SessionBindingRecord (canonical)
```ts
type SessionBindingRecord = {
binding_id: string;
scope_kind: "session" | "participant" | "room_turn";
scope_ref: string;
gateway_session_key?: string;
participant_id?: string;
room_id?: string;
binding_state:
| "unbound"
| "binding_pending"
| "bound_verified"
| "bound_stale"
| "bound_degraded"
| "binding_failed"
| "orphaned"
| "closed";
catalog_generation?: string;
created_at: string;
updated_at: string;
last_verified_at?: string;
reason_codes?: string[];
schema_version: 1;
};
```
### 3.4 Lifecycle operations
Required operations:
- create session,
- verify session,
- reconnect session,
- close session,
- archive session,
- mark orphaned,
- repair orphan,
- rebind participant,
- replay from cursor.
Canonical request/result contracts:
```ts
type SessionCreateRequest = {
scope_kind: "chat" | "task" | "room_participant" | "panel" | "forum" | "agent";
scope_ref: string;
desired_control_state_ref?: string;
bootstrap_packet_ref?: string;
runtime_mode?: "native" | "acp";
schema_version: 1;
};
type SessionCreateResult = {
ok: true;
gateway_session_key: string;
binding_ref: string;
verification_state: "pending" | "verified" | "degraded";
schema_version: 1;
};
type SessionVerifyRequest = {
gateway_session_key: string;
scope_ref?: string;
schema_version: 1;
};
type SessionVerifyResult = {
ok: true;
gateway_session_key: string;
verification_state: "verified" | "degraded" | "stale";
effective_state_ref?: string;
schema_version: 1;
};
type SessionReconnectRequest = {
gateway_session_key: string;
reason?: string;
schema_version: 1;
};
type SessionReconnectResult = {
ok: true;
gateway_session_key: string;
reconnect_state: "reconnected" | "degraded" | "exhausted";
schema_version: 1;
};
type SessionCloseRequest = {
gateway_session_key: string;
schema_version: 1;
};
type SessionCloseResult = {
ok: true;
gateway_session_key: string;
closed_at: string;
schema_version: 1;
};
type SessionArchiveRequest = {
gateway_session_key: string;
schema_version: 1;
};
type SessionArchiveResult = {
ok: true;
gateway_session_key: string;
archived_at: string;
schema_version: 1;
};
```
### 3.5 Reconnect and replay defaults
Default reconnect backoff schedule:
- immediate,
- 250 ms,
- 1 s,
- 3 s,
- 10 s,
- then every 10 s capped,
- manual reconnect available at all times.
Maximum reconnect attempts: **30 consecutive failures**.
Maximum reconnect window: **5 minutes elapsed from first failure**.
On max exceeded:
- transition lifecycle state to `orphaned`,
- set `reason_codes = ["reconnect_exhausted"]`,
- surface `Reconnect exhausted` in Runtime & Connectivity and any relevant participant drawer,
- preserve manual reconnect as an explicit action.
### 3.6 ReplayCursorState (canonical)
```ts
type ReplayCursorState = {
scope_kind: "session" | "participant" | "room_turn";
scope_ref: string;
last_event_id?: string;
last_sequence?: number;
last_seen_at?: string;
replay_required: boolean;
reason_codes?: string[];
schema_version: 1;
};
```
### 3.7 Same-session concurrency rule
At most one live execution loop may be active per `gateway_session_key` unless the underlying runtime explicitly supports multiple concurrent runs on that session and the evidence matrix marks it `native_probed`. Otherwise:
- new dispatch MUST refuse or mint/rebind according to explicit policy,
- refusal must be visible,
- no hidden parallelization is allowed.
### 3.8 Orphan rule
A binding becomes `orphaned` when a referenced model/profile/provider/session no longer resolves truthfully. Orphaned scopes MUST remain visible until:
- repaired,
- closed,
- or explicitly archived.
### 3.9 Orphan repair contract
```ts
type OrphanRepairRequest = {
orphan_scope_kind: "session" | "participant";
orphan_scope_ref: string;
replacement_model_ref?: string;
replacement_provider_id?: string;
replacement_auth_profile_id?: string;
prefer_rebind_over_remint: boolean;
};
type OrphanRepairResult = {
repair_id: string;
accepted: boolean;
previous_binding_state: "orphaned";
next_binding_state:
| "binding_pending"
| "bound_verified"
| "binding_failed"
| "closed";
switch_segment_id?: string;
reason_codes?: string[];
};
```
### 3.10 Mutation ordering and conflict policy
Per-scope control mutations are ordered by `mutation_enqueued_at`. Conflict resolution uses:
1. `one_shot` scope beats all other scopes for the accepted dispatch only,
2. narrower scope beats broader scope (`participant > session > surface_default > global_default`),
3. later accepted mutation beats earlier accepted mutation within the same scope,
4. stale pending mutations may not overwrite verified newer truth,
5. incompatible in-flight mutations remain visible as `pending_conflict` until resolved or rejected.
### 3.11 ControlMutationQueueRecord (canonical)
```ts
type ControlMutationQueueRecord = {
mutation_id: string;
scope_kind: "global_default" | "surface_default" | "session" | "participant" | "one_shot";
scope_ref: string;
fields: Partial<Pick<DesiredControlState,
"model_ref" |
"provider_id" |
"thinking_level" |
"reasoning_mode" |
"reasoning_visibility" |
"verbose_level" |
"temperature" |
"desired_max_output_tokens_user" |
"auth_profile_id" |
"auth_method_kind"
>>;
status: "queued" | "sent" | "verified" | "rejected" | "timed_out" | "superseded";
operation_id: string;
correlation_id: string;
enqueued_at: string;
updated_at: string;
superseded_by_mutation_id?: string;
schema_version: 1;
};
```
### 3.11A GatewayControlMutationRequest (canonical)
```ts
type GatewayControlMutationRequest = {
scope_kind: "session" | "participant" | "surface_default" | "global_default";
scope_ref: string;
fields: ControlMutationQueueRecord["fields"];
idempotency_key: string;
source_surface_kind: string;
source_surface_id: string;
one_shot: boolean;
discard_on_cancel: boolean;
schema_version: 1;
};
type GatewayControlMutationResponse = {
mutation_id: string;
queue_status: ControlMutationQueueRecord["status"];
field_result_count: number;
effective_state_ref?: string;
reason_codes?: string[];
schema_version: 1;
};
```
Queue-status to selector-state mapping is canonical:
| queue status | selector UI state | required UI treatment |
|---|---|---|
| `pending` | `pending` | spinner / pending badge |
| `applied` | `verified` or `fallback` depending on resolved truth | verified badge or fallback warning |
| `rejected` | `rejected` | red reject state |
| `timed_out` | `stale` | stale warning; retry/reverify affordance |
| `superseded` | `pending_conflict` | muted superseded note; latest mutation remains active |
One-shot discard rule is canonical:
- if `one_shot = true` and the draft/surface action is cancelled before dispatch acceptance, the mutation MUST be discarded,
- if `one_shot = true` and dispatch is refused before `accepted = true`, the mutation MUST be discarded,
- discarded one-shot mutations MUST NOT remain in desired state,
- discard MUST emit `gateway.control.one_shot_discarded` with `reason = "cancelled" | "dispatch_refused"`.
### 3.11B Control-mutation queue state model
The queue state machine and the selector-truth state machine are related but not identical.
Canonical queue states:
- `pending`
- `applied`
- `rejected`
- `timed_out`
- `superseded`
Canonical selector-truth states remain those defined in §4.9.
Required relationship rule:
- queue state records mutation-processing progress,
- selector-truth state records user-visible runtime truth,
- one queue state may map to more than one selector-truth state depending on the resolved runtime truth,
- implementations MUST NOT collapse queue state and selector state into a single enum.
Canonical mapping:
| queue state | possible selector-truth outcomes | rule |
|---|---|---|
| `pending` | `pending` | mutation has not yet resolved into verified/fallback/rejected runtime truth |
| `applied` | `verified` or `fallback` or `mismatch` | resolver decides based on effective/executed truth |
| `rejected` | `rejected` | hard failure |
| `timed_out` | `stale` | verification window elapsed |
| `superseded` | `pending_conflict` or no longer primary visible mutation | latest surviving mutation remains user-primary |
### 3.12 One-shot lifecycle
`control_scope = "one_shot"` means:
- values attach to a single dispatch attempt,
- they are not persisted as durable selector defaults,
- they become consumed when the dispatch is **accepted** and a run/session execution is created,
- if dispatch is refused or fails before acceptance, the one-shot values are discarded and not silently promoted to session state,
- if the draft/input is canceled, the one-shot values are discarded.
### 3.13 Partial mutation acceptance rule
Partial acceptance MUST be representable per field. A mutation response may contain accepted, translated, unsupported, rejected, and rebind-required fields at once. Q MUST render field-level truth, not a single misleading banner.
---
## 4) Runtime truth, controls, selectors, catalogs, CRUD, and capability discovery
### 4.1 Controls are separate concerns
DOC11 separates:
- model/provider selection,
- reasoning/think controls,
- verbosity,
- auth/profile selection,
- unsupported/settings-only preferences,
- capability checks,
- catalog management,
- execution watermark truth.
### 4.2 Four runtime-truth layers
Required truth layers:
1. **Catalog truth** - what can be selected in the current environment,
2. **Desired control state** - what the operator or suite wants,
3. **Effective runtime state** - what the runtime is verified to be prepared to use now,
4. **Execution watermark** - what the last accepted execution actually used.
### 4.3 Truth confidence states
Allowed truth confidence states:
- `verified`
- `pending`
- `mismatch`
- `fallback`
- `rejected`
- `stale`
- `unknown`
- `orphaned`
- `settings_only`
### 4.3A RuntimeTruthResolver algorithm
All runtime truth surfaces MUST consume a single resolver result.
```text
resolveTruth(scope_ref):
1. load catalog_snapshot
2. load desired_control_state for scope_ref
3. load effective_runtime_state for scope_ref
4. load latest execution_watermark for scope_ref
5. load active switch_segment if any
6. load mismatch_fallback_log tail
7. load binding state and auth state
8. for each visible field:
a. desired = desired value
b. effective = effective runtime value
c. executed = last executed watermark value
d. verification precedence = executed > effective > desired > catalog/default
e. assign verification_state and ui_state
9. emit TruthResolverResult
```
### 4.3B TruthResolverResult (canonical)
```ts
type TruthResolverField = {
field_name: string;
desired_value?: unknown;
effective_value?: unknown;
executed_value?: unknown;
verification_state:
| "verified"
| "pending"
| "mismatch"
| "fallback"
| "rejected"
| "stale"
| "unknown"
| "orphaned"
| "settings_only";
reason_codes: string[];
};
type TruthResolverResult = {
scope_ref: string;
scope_kind: "global_default" | "surface_default" | "session" | "participant";
catalog_generation?: string;
desired_state_ref?: string;
effective_state_ref?: string;
execution_watermark_ref?: string;
switch_segment_id?: string;
binding_state?: SessionBindingRecord["binding_state"];
fields: TruthResolverField[];
auth_state_ref?: string;
auth_issue_count?: number;
schema_version: 1;
};
```
Fast Mode truth rules:
- `fast_mode_desired` follows the same desired/effective/executed precedence as `thinking_level`, `reasoning_mode`, and `verbose_level`,
- if fast mode is unsupported for the active provider/model/runtime, UI MUST render it as disabled with explicit unsupported copy,
- if support is unknown, only `inherit` may be selected until capability truth resolves.
### 4.3C Runtime truth query and aggregate indicator contracts
```ts
type RuntimeTruthQuery = {
scope_kind?: "session" | "participant" | "surface" | "room" | "global";
scope_ref?: string;
include_indicator_aggregate: boolean;
include_watermark_tail: boolean;
schema_version: 1;
};
type IndicatorAggregateProjection = {
active_scope_count: number;
verified_count: number;
degraded_count: number;
auth_issue_count: number;
orphaned_count: number;
source_store_ref: "indicator_aggregate_store";
generated_at: string;
schema_version: 1;
};
type RuntimeTruthResponse = {
query: RuntimeTruthQuery;
truth?: TruthResolverResult;
indicator_aggregate?: IndicatorAggregateProjection;
watermark_tail_refs?: string[];
schema_version: 1;
};
type TruthResolverReadResult = {
scope_kind: string;
scope_ref: string;
truth: TruthResolverResult;
desired_control_state_ref?: string;
effective_runtime_state_ref?: string;
execution_watermark_ref?: string;
mismatch_log_refs?: string[];
switch_segment_refs?: string[];
generated_at: string;
schema_version: 1;
};
```
Canonical read routes:
- `GET /api/openclaw/runtime/truth` returns `RuntimeTruthResponse`.
- `GET /api/openclaw/runtime/truth/debug` returns `TruthResolverReadResult`.
- `GET /api/openclaw/runtime/indicators` returns `IndicatorAggregateProjection`.
Route query semantics:
- if `scope_kind = "global"`, the response MUST include `indicator_aggregate`,
- if `scope_kind != "global"`, the response MUST include `truth`,
- if both are requested, both MAY be returned in the same envelope.
### 4.4 Control enums and lexicon
Canonical control enums:
```ts
type ThinkingLevel = "low" | "medium" | "high" | "provider_default";
type VerboseLevel = "off" | "low" | "medium" | "high" | "provider_default";
type ReasoningMode = "on" | "off" | "stream";
type ReasoningVisibility = "visible" | "hidden" | "provider_default";
```
Canonical enum dictionary for ambiguity cleanup:
- use `translated`, never `translate`,
- use `wrapper_composed`, never `wrapper`,
- use `settings_only`, never `config_only`,
- use `auth_profile_mismatch`, never `profile_conflict`,
- use `auth_challenge_pending`, never bare `connecting` for auth state.
### 4.5 DesiredControlState (canonical)
```ts
type FastModeDesired = "inherit" | "on" | "off";
type FastModeSupportState = "supported" | "unsupported" | "unknown";
type ServiceTier = "auto" | "standard" | "priority" | "unknown";
type DesiredControlState = {
desired_state_id: string;
scope_kind: "global_default" | "surface_default" | "session" | "participant" | "one_shot";
scope_ref: string;
model_ref?: string;
provider_id?: string;
thinking_level?: ThinkingLevel;
reasoning_mode?: ReasoningMode;
reasoning_visibility?: ReasoningVisibility;
verbose_level?: VerboseLevel;
temperature?: number;
desired_max_output_tokens_user?: number;
auth_profile_id?: string;
auth_method_kind?: ProviderAuthMethodKind;
fast_mode_desired?: FastModeDesired;
created_at: string;
updated_at: string;
schema_version: 1;
};
```
### 4.6 EffectiveRuntimeState (canonical)
```ts
type EffectiveRuntimeState = {
effective_state_id: string;
scope_kind: "session" | "participant";
scope_ref: string;
gateway_session_key?: string;
model_ref?: string;
provider_id?: string;
thinking_level?: ThinkingLevel;
reasoning_mode?: ReasoningMode;
verbose_level?: VerboseLevel;
auth_profile_id?: string;
auth_method_kind?: ProviderAuthMethodKind;
credential_source_kind?: CredentialSourceKind;
native_context_engine_id?: string;
native_context_engine_kind?: "legacy" | "plugin";
native_context_engine_owns_compaction?: boolean;
verified_at?: string;
verification_state: TruthResolverField["verification_state"];
reason_codes?: string[];
fast_mode_support_state?: FastModeSupportState;
fast_mode_desired?: FastModeDesired;
fast_mode_effective?: "on" | "off";
service_tier_effective?: ServiceTier;
schema_version: 1;
};
```
### 4.7 ExecutionWatermark (canonical)
```ts
type ExecutionWatermark = {
watermark_id: string;
scope_kind: "session" | "participant" | "room_turn";
scope_ref: string;
run_id?: string;
gateway_session_key?: string;
model_ref?: string;
provider_id?: string;
thinking_level?: ThinkingLevel;
reasoning_mode?: ReasoningMode;
verbose_level?: VerboseLevel;
attachment_transport_mode?: AttachmentTransportMode;
auth_profile_id?: string;
auth_method_kind?: ProviderAuthMethodKind;
credential_source_kind?: CredentialSourceKind;
native_context_engine_id?: string;
native_context_engine_kind?: "legacy" | "plugin";
dispatch_mode: "stream" | "final_only";
dispatch_max_output_tokens_cap?: number;
ttft_ms?: number;
latency_ms?: number;
stream_duration_ms?: number;
fast_mode_effective?: "on" | "off";
service_tier_effective?: ServiceTier;
fast_mode_unapplied_reason_code?:
| "unsupported_runtime"
| "unsupported_model"
| "provider_rejected"
| "policy_blocked"
| "unknown";
created_at: string;
schema_version: 1;
};
```
Measurement definitions:
- `ttft_ms` = `dispatch_accepted_at -> first assistant token/delta OR first final chunk in final_only mode`
- `latency_ms` = `dispatch_accepted_at -> terminal completed/failed/aborted`
- `stream_duration_ms` = `first assistant delta -> terminal completed`
### 4.8 ParticipantRuntimeStatus (canonical)
```ts
type ParticipantRuntimeStatus = {
participant_id: string;
room_id?: string;
binding_state: SessionBindingRecord["binding_state"];
activity_scope: "active" | "recent" | "background" | "idle";
is_critical?: boolean;
phase_index?: number;
phase_type?: string;
desired_state_ref?: string;
effective_state_ref?: string;
execution_watermark_ref?: string;
usage_subtotal_ref?: string;
mismatch_count: number;
auth_state_ref?: string;
native_context_pressure_state?: "normal" | "elevated" | "high" | "unknown";
native_context_estimate_tokens?: number;
native_context_estimate_confidence?: "verified" | "inferred" | "unknown";
schema_version: 1;
};
```
### 4.9 Selector truth state machine
Canonical selector states:
- `verified`
- `pending`
- `mismatch`
- `fallback`
- `rejected`
- `stale`
- `unknown`
- `orphaned`
- `settings_only`
- `pending_conflict`
Required transitions:
- `verified -> pending` on accepted mutation
- `pending -> verified` on matching effective state
- `pending -> rejected` on explicit rejection
- `pending -> mismatch` on execution != desired/effective
- `pending -> stale` on timeout without verification
- `fallback -> verified` if desired updates to executed value
- `fallback -> pending` on retry/repair
- `rejected -> pending` on new valid mutation
- `stale -> pending` on reverify/reconnect
- `unknown -> pending` on probe/dispatch
- `orphaned -> pending` on repair start
- `orphaned -> verified` on successful repair
### 4.10 Selector UI collapse map
Implementers MUST use this exact collapse map:
- `verified` -> green/normal verified state,
- `pending` / `pending_conflict` -> pending pill + disabled destructive actions until resolution,
- `mismatch` / `fallback` -> warning state + mismatch viewer link,
- `rejected` -> inline error with reason,
- `stale` -> neutral warning + reverify action,
- `unknown` -> neutral unknown badge,
- `orphaned` -> red/orange orphan badge + repair action,
- `settings_only` -> gray settings-only badge.
### 4.11 CatalogEntry (canonical)
```ts
type CatalogEntry = {
model_id: string;
provider_id: string;
model_ref: string; // fully qualified provider/model reference
display_name?: string;
ui_nickname?: string;
context_window_tokens?: number;
supports_streaming?: boolean;
supports_attachments?: boolean;
supports_thinking?: boolean;
supports_reasoning?: boolean;
supports_verbose?: boolean;
max_output_tokens?: number;
deleted_at?: string;
source_kind: "manual" | "runtime_catalog" | "imported";
ownership_kind?: "core" | "bundled_plugin" | "third_party_plugin" | "manual" | "unknown";
capability_source_kind?: "runtime_reported" | "plugin_reported" | "manual_operator_asserted";
plugin_id?: string;
plugin_version?: string;
plugin_state?: "available" | "missing" | "incompatible" | "disabled" | "unknown";
schema_version: 1;
};
```
### 4.12 Catalog snapshot and generation rule
Every dropdown/selectable list MUST be derived from a catalog snapshot tied to a `catalog_generation`. A deleted model MUST disappear from new selection lists on the next catalog generation update.
### 4.13 Model CRUD commands
```ts
type ModelCatalogCreateRequest = {
provider_id: string;
model_ref: string;
display_name?: string;
ui_nickname?: string;
source_kind: "manual" | "runtime_catalog" | "imported";
};
type ModelCatalogUpdateRequest = {
model_id: string;
display_name?: string;
ui_nickname?: string;
notes?: string;
};
type ModelCatalogDeleteRequest = {
model_id: string;
confirmation_phrase: string;
delete_mode: "soft_delete";
degrade_active_refs: boolean;
};
type ModelCatalogRestoreRequest = {
model_id: string;
};
```
Delete semantics:
- UI-facing delete is **soft delete only**.
- New dropdowns hide the model after catalog generation advances.
- Existing bindings become `orphaned`.
- Hard delete is maintenance-only and not a normal UI action.
### 4.13A Model catalog mutation result
```ts
type ModelCatalogMutationResult = {
model_id: string;
accepted: boolean;
catalog_generation: string;
orphaned_scope_refs?: string[];
reason_codes?: string[];
schema_version: 1;
};
```
Every create/edit/delete/restore route in the catalog family MUST return `ModelCatalogMutationResult`.
### 4.14 Delete Model UI contract
Settings > Models row -> trash icon -> confirmation modal.
Modal MUST show:
- display name,
- fully qualified model ref,
- active impacted session count,
- active impacted participant count,
- note that affected bindings become orphaned until repaired or closed.
Buttons:
- `Cancel`
- `Delete and mark bindings orphaned`
- optional `Delete and open repair flow`
### 4.15 FallbackPolicy (canonical)
```ts
type FallbackPolicy = {
fallback_policy_id: string;
allowed_provider_ids?: string[];
allowed_model_refs?: string[];
same_provider_first: boolean;
max_fallback_hops: number;
retry_on_rate_limit: boolean;
retry_on_auth_scope_failure: boolean;
retry_on_auth_invalid: boolean;
allow_cooldown_probe: boolean;
schema_version: 1;
};
```
### 4.15A OpenClaw-native model execution policy and fallback chain
OpenClaw-native default model selection is not just a picker convenience; it is runtime behavior. For the audited baseline, DOC11 MUST treat the following native configuration concepts as first-class truth:
- `agents.defaults.model.primary`
- `agents.defaults.model.fallbacks`
- `agents.defaults.models` as the model allowlist/catalog for picker visibility and allowed session overrides.
Q MUST surface the native default model execution policy explicitly. Q MUST NOT fabricate fallback candidates outside the current runtime allowlist/catalog.
Normative rules:
- default model policy is separate from per-session and per-participant overrides,
- changing default model policy affects new sessions and any scope still inheriting defaults,
- changing default model policy MUST NOT silently overwrite explicit session or participant overrides,
- provider auth rotation happens inside the current provider before runtime moves to the next fallback model,
- if a referenced model is soft-deleted or removed from the allowlist/catalog, the policy becomes degraded until repaired.
### 4.15B ModelExecutionPolicy (canonical)
```ts
type ModelExecutionPolicy = {
policy_id: string;
primary_model_ref: string;
fallback_model_refs: string[];
allowlist_catalog_generation?: string;
provider_auth_rotation_before_fallback: boolean;
verification_state: "verified" | "pending" | "degraded" | "unknown";
reason_codes?: string[];
schema_version: 1;
};
type ModelExecutionPolicyUpdateRequest = {
primary_model_ref: string;
fallback_model_refs: string[];
apply_to_existing_default_inheriting_scopes: boolean;
schema_version: 1;
};
```
Validation rules:
- `primary_model_ref` MUST exist in current catalog generation and allowlist.
- every `fallback_model_ref` MUST exist in current catalog generation and allowlist.
- duplicates are forbidden.
- `primary_model_ref` MUST NOT appear in `fallback_model_refs`.
- fallback ordering MUST be preserved exactly as submitted.
- if validation fails, no partial apply is allowed for the policy write.
```ts
type ModelExecutionPolicyUpdateResult = {
policy_id: string;
accepted: boolean;
verification_state: ModelExecutionPolicy["verification_state"];
impacted_default_inheriting_scope_count?: number;
reason_codes?: string[];
schema_version: 1;
};
```
### 4.15C Default model policy settings contract
Settings > Providers & Models MUST include a **Default Execution Policy** subsection showing:
- primary model dropdown,
- ordered fallback model list,
- add fallback action,
- remove fallback action,
- reorder controls,
- impacted-scope summary,
- last executed fallback summary,
- degraded/orphaned badges if any referenced model is no longer valid.
Required UI copy behavior:
- if a default policy change affects only future sessions: `Applies to new sessions and scopes that inherit defaults.`
- if default-inheriting live scopes will be updated: show exact impacted counts before save.
- if explicit overrides exist: show `Explicitly overridden scopes will not change.`
Required actions:
- `Save default policy`
- `Revert`
- `Open mismatch/fallback log`
Delete/remove interaction rules:
- if a model in the fallback chain is soft-deleted, it MUST be removed from new picker options immediately,
- existing policy rows that reference the deleted model MUST render `degraded` until repaired,
- a degraded policy row MUST offer `Replace deleted model` and `Remove from fallback chain` actions.
### 4.16 Meaningful switch rule
A `SwitchSegment` MUST open when executed truth changes for any of:
- `model_ref`
- `provider_id`
- `gateway_session_key`
- `auth_profile_id`
- `auth_method_kind`
- `thinking_level`
- `reasoning_mode`
- `verbose_level`
- `attachment_transport_mode`
- `dispatch_mode`
Unsupported/settings-only controls MUST NOT create a switch segment unless they actually changed execution.
### 4.17 GatewayControlMutationFieldResult (canonical)
```ts
type GatewayControlMutationFieldResult = {
field_name: string;
desired_value: unknown;
accepted: boolean;
translated: boolean;
unsupported: boolean;
rejected: boolean;
rebind_required: boolean;
result_code:
| "accepted"
| "translated"
| "unsupported"
| "rejected"
| "rebind_required"
| "stale_runtime_truth"
| "invalid_value"
| "capability_blocked";
reason?: string;
verification_state_after:
| "verified"
| "pending"
| "mismatch"
| "fallback"
| "rejected"
| "stale"
| "unknown"
| "orphaned"
| "settings_only";
ui_treatment:
| "show_verified"
| "show_pending"
| "show_warning"
| "show_error"
| "show_settings_only"
| "show_orphaned";
schema_version: 1;
};
type GatewayControlMutationResult = {
mutation_id: string;
accepted: boolean;
gateway_session_key?: string;
effective_state_ref?: string;
field_results: GatewayControlMutationFieldResult[];
accepted_fields: GatewayControlMutationFieldResult[];
rejected_fields: GatewayControlMutationFieldResult[];
unsupported_fields: GatewayControlMutationFieldResult[];
translated_fields: GatewayControlMutationFieldResult[];
rebind_required_fields: GatewayControlMutationFieldResult[];
reason_codes?: string[];
schema_version: 1;
};
```
### 4.18 Runtime capability check (canonical)
```ts
type AttachmentTransportMode = "inline_base64" | "staged_local_file_ref" | "remote_url";
type AttachmentDescriptor = {
attachment_id: string;
source_kind: "local_file" | "staged_blob" | "remote_url";
mime_type: string;
byte_length: number;
filename?: string;
secure_blob_ref?: SecureBlobRef;
remote_url?: string;
requested_transport_mode?: AttachmentTransportMode;
schema_version: 1;
};
type GatewayRuntimeCapabilityCheckRequest = {
scope_kind: "session" | "participant" | "room" | "surface";
gateway_session_key?: string;
participant_id?: string;
room_id?: string;
source_surface_kind?: string;
source_surface_id?: string;
intended_action:
| "chat_send"
| "room_turn_send"
| "attachment_send"
| "auth_probe"
| "context_build";
required_capabilities: string[];
attachment_descriptors?: AttachmentDescriptor[];
desired_control_state_ref?: string;
effective_state_ref?: string;
schema_version: 1;
};
type CapabilityCheckItem = {
capability_name: string;
status: "supported" | "unsupported" | "degraded" | "unknown";
fulfillment_kind: "native_probed" | "native_documented_not_probed" | "wrapper_composed" | "unsupported";
reason_codes?: string[];
translated?: boolean;
schema_version: 1;
};
type GatewayRuntimeCapabilityCheckResult = {
capability_check_id: string;
scope_kind: GatewayRuntimeCapabilityCheckRequest["scope_kind"];
scope_ref: string;
ttl_ms: number;
cached: boolean;
effective_state_hash: string;
items: CapabilityCheckItem[];
max_attachment_bytes?: number;
max_estimated_attachment_tokens?: number;
auth_scope_ok?: boolean;
schema_version: 1;
};
```
Cache policy:
- `ttl_ms = 60000`
- invalidate on effective control mutation, session rebind, auth change, catalog generation change, health degradation, attachment class change, participant binding change, or model switch.
Canonical capability names:
- `streaming`
- `thinking`
- `reasoning`
- `verbose`
- `attachments.inline_base64`
- `attachments.staged_local_file_ref`
- `attachments.remote_url`
- `model_selection`
- `auth_scope.model.request`
- `web_search`
- `tool_use`
- `acp_dispatch`
These names are normative. Implementations MUST NOT invent alternate capability-name spellings.
### 4.19 SecureBlobRef (canonical)
```ts
type SecureBlobRef = {
blob_id: string;
sha256: string;
mime_type: string;
byte_length: number;
storage_scope: "ephemeral" | "staged_local";
expires_at?: string;
schema_version: 1;
};
```
Raw base64 durability rule:
- raw base64 payloads MUST NOT be written to durable JSON state, append-only logs, or long-lived manifests,
- any persist path MUST strip raw content before write,
- logs/manifests may contain only `SecureBlobRef`, hashes, sizes, and metadata.
### 4.20 Attachment transport semantics
- `inline_base64` = supported verified baseline path for R14.
- `staged_local_file_ref` = wrapper/future path; may be surfaced only if clearly labeled and capability-verified.
- `remote_url` = unsupported unless runtime-probed in the current audited environment.
If a requested URL-only attachment is unsupported, Q MUST show an explicit reject state. Silent drop is forbidden.
### 4.21 Model strictness and fallback policy
Model references crossing the DOC11 seam MUST be fully qualified.
Rules:
- every `model_ref` crossing a mutation, dispatch, policy, fallback, or execution-watermark boundary MUST be in the canonical qualified form expected by the runtime/provider catalog,
- the earlier `qualified_model_required` flag is removed from behavioral logic and replaced with an unconditional requirement,
- if a caller supplies an unqualified or ambiguous model identifier, validation MUST fail with `reason_codes += ["model_ref_not_qualified"]`,
- UI selectors MAY display friendly names, but the submitted value MUST resolve to the qualified runtime form before the request leaves Q/EC.
### 4.22 sessions.patch wire example
Canonical wire example using actual Gateway field names:
```json
{
"type": "req",
"id": "uuid-1234",
"method": "sessions.patch",
"params": {
"key": "agent::elnor::main",
"model": "anthropic/claude-sonnet-4-5",
"thinkingLevel": "high",
"reasoningLevel": "on",
"verboseLevel": "off"
}
}
```
Success response:
```json
{
"type": "res",
"id": "uuid-1234",
"ok": true,
"payload": {
"key": "agent::elnor::main",
"entry": {
"key": "agent::elnor::main",
"model": "anthropic/claude-sonnet-4-5",
"thinkingLevel": "high",
"reasoningLevel": "on",
"verboseLevel": "off",
"updatedAt": "2026-03-17T12:34:56.000Z"
},
"resolved": {
"gateway_session_key": "agent::elnor::main",
"effective_model_ref": "anthropic/claude-sonnet-4-5",
"effective_provider_id": "anthropic",
"effective_thinking_level": "high",
"effective_reasoning_mode": "on",
"effective_verbose_level": "off"
}
}
}
```
Adapter-normalization note:
- `entry` reflects the runtime session entry returned by the Gateway.
- `resolved` is an adapter-normalized projection used by DOC11/Q for effective-state truth and MUST NOT be treated as a raw native Gateway shape.
Rejection response (bare model name):
```json
{
"type": "res",
"id": "uuid-1234",
"ok": false,
"error": "model not allowed"
}
```
Rejection response (unsupported field):
```json
{
"type": "res",
"id": "uuid-1234",
"ok": false,
"error": "validation error: unexpected property 'temperature'"
}
```
DOC11 → Gateway field name mapping table:
| DOC11 schema field | Gateway sessions.patch field | Accepted values |
|---|---|---|
| `model_ref` | `model` | Fully qualified `provider/model` only. Bare names rejected. |
| `thinking_level` | `thinkingLevel` | Provider/runtime-supported string values only. |
| `reasoning_mode` | `reasoningLevel` | `"on" | "off" | "stream"` only where supported. |
| `verbose_level` | `verboseLevel` | `"on" | "off"` only where supported. |
| `fast_mode_desired` | `fastMode` or provider-specific service-tier field only where audited and supported; otherwise settings-only | `"inherit" | "on" | "off"` where supported. |
| `temperature` | — | Not accepted. Settings-only. |
| `desired_max_output_tokens_user` | — | Not accepted. Settings-only. |
| `provider_id` | — | Not accepted. Settings-only or wrapper-rebind concern. |
| `base_url` | — | Not accepted. Settings-only. |
| `api_name` | — | Not accepted. Settings-only. |
Normative notes:
- bare model names are invalid and MUST be qualified by the adapter before emitting `sessions.patch`,
- unsupported/settings-only values MUST remain in desired state but MUST NOT be emitted to the runtime patch,
- the adapter MUST translate DOC11 field names to Gateway field names before emission,
- the adapter MUST preserve the raw Gateway response for diagnostics and derive `resolved` as the canonical effective-state projection for Q/EC truth surfaces.
## 4A) Provider/model authorization management, OAuth, setup-token flows, CLI-managed auth, plugin-managed auth, and runtime verification
### 4A.1 Authorization truth model
Auth truth has its own layers:
1. auth method catalog,
2. auth profile configuration,
3. effective runtime auth state,
4. execution watermark auth provenance.
### 4A.2 Supported auth method kinds
```ts
type ProviderAuthMethodKind =
| "oauth_browser"
| "oauth_device_code"
| "setup_token"
| "pasted_token"
| "cli_managed"
| "plugin_managed"
| "custom_header"
| "custom_proxy"
| "none_required";
type CredentialSourceKind =
| "gateway_store"
| "secret_ref"
| "env"
| "file"
| "exec"
| "cli_managed"
| "plugin_managed"
| "none";
```
### 4A.3 ProviderAuthMethodSchema
```ts
type ProviderAuthMethodSchema = {
auth_method_id: string;
provider_id: string;
auth_method_kind: ProviderAuthMethodKind;
display_name: string;
supports_reverify: boolean;
supports_probe: boolean;
supports_browser_redirect: boolean;
supports_device_code: boolean;
supports_token_entry: boolean;
supports_cli_verify: boolean;
managed_by_plugin?: boolean;
plugin_id?: string;
schema_version: 1;
};
```
### 4A.4 ProviderAuthProfileSchema
```ts
type ProviderAuthProfileSchema = {
profile_id: string;
provider_id: string;
auth_method_id: string;
auth_method_kind: ProviderAuthMethodKind;
display_name?: string;
account_hint?: string;
endpoint_override?: string;
base_url_override?: string;
custom_header_profile_ref?: string;
proxy_url?: string;
proxy_headers_ref?: string;
credential_source_kind?: CredentialSourceKind;
env_override_allowed?: boolean;
managed_by_plugin?: boolean;
plugin_id?: string;
created_at: string;
updated_at: string;
schema_version: 1;
};
```
### 4A.5 ProviderAuthStateSchema
```ts
type ProviderAuthStateSchema = {
provider_id: string;
profile_id?: string;
auth_method_kind?: ProviderAuthMethodKind;
credential_source_kind?: CredentialSourceKind;
auth_state:
| "missing"
| "challenge_pending"
| "verification_pending"
| "verified"
| "scope_limited"
| "invalid"
| "expired"
| "stale"
| "not_required";
granted_scopes?: string[];
missing_scopes?: string[];
last_verified_at?: string;
expires_at?: string;
reason_codes?: string[];
schema_version: 1;
};
```
### 4A.6 ProviderAuthChallengeSchema
```ts
type ProviderAuthChallengeSchema = {
challenge_id: string;
provider_id: string;
profile_id?: string;
auth_method_kind: ProviderAuthMethodKind;
challenge_kind:
| "browser_redirect"
| "device_code"
| "setup_token_entry"
| "paste_token_entry"
| "cli_verify"
| "plugin_delegate";
challenge_state:
| "pending"
| "browser_open"
| "awaiting_user_action"
| "verifying"
| "verified"
| "failed"
| "expired"
| "cancelled";
auth_url?: string;
verification_url?: string;
user_code?: string;
expires_at?: string;
pending_operation_ref?: string;
reason_codes?: string[];
schema_version: 1;
};
```
### 4A.6A Auth challenge state transition table
Required transitions:
- `pending -> browser_open` on browser redirect trigger,
- `pending -> awaiting_user_action` on device-code / token-entry presentation,
- `browser_open -> verifying` on callback receipt,
- `awaiting_user_action -> verifying` on user submission,
- `verifying -> verified` on successful verification,
- `verifying -> failed` on verification failure,
- any non-terminal -> `expired` on TTL expiry,
- any non-terminal -> `cancelled` on explicit cancel.
Expired and cancelled challenges MUST NOT be reused. A fresh `challenge_id` MUST be minted for any retry.
### 4A.7 ProviderAuthVerificationResultSchema
```ts
type ProviderAuthVerificationResultSchema = {
provider_id: string;
profile_id?: string;
verified: boolean;
auth_state: ProviderAuthStateSchema["auth_state"];
granted_scopes?: string[];
missing_scopes?: string[];
reason_codes?: string[];
schema_version: 1;
};
```
### 4A.8 GatewayAuthChallengeResponse (canonical)
```ts
type PostSuccessAction =
| "reverify"
| "resume_pending_operation"
| "retry_last_operation"
| "refresh_catalog"
| "none";
type GatewayAuthChallengeResponse = {
provider_id: string;
profile_id?: string;
challenge: ProviderAuthChallengeSchema;
post_success_action: PostSuccessAction;
schema_version: 1;
};
```
### 4A.9 Auth lifecycle transition table
Required transitions:
- `missing -> challenge_pending` on start,
- `challenge_pending -> verification_pending` on user response/callback,
- `verification_pending -> verified` on successful probe,
- `verification_pending -> scope_limited` on missing scopes,
- `verification_pending -> invalid` on bad token/credential,
- `verified -> stale` on TTL/runtime mismatch,
- `verified -> expired` on expiry,
- `expired -> challenge_pending` on relogin,
- `invalid -> challenge_pending` on retry,
- `scope_limited -> challenge_pending` on remediation,
- any non-terminal state -> `cancelled` on explicit cancel.
### 4A.10 Route ownership rule
If OpenClaw/provider/plugin already owns the auth exchange, Q MUST act only as challenge presenter and response relay. Q MUST NOT become a parallel OAuth client or durable credential vault.
### 4A.11 Auth route contracts
Canonical routes:
- `GET /api/openclaw/providers/auth/catalog`
- `GET /api/openclaw/providers/auth/profiles`
- `GET /api/openclaw/providers/auth/state`
- `GET /api/openclaw/providers/auth/challenges`
- `GET /api/openclaw/providers/auth/plugin-catalog`
- `POST /api/openclaw/providers/auth/start`
- `POST /api/openclaw/providers/auth/complete`
- `POST /api/openclaw/providers/auth/callback/complete`
- `POST /api/openclaw/providers/auth/cancel`
- `POST /api/openclaw/providers/auth/probe`
- `POST /api/openclaw/providers/auth/verify-cli`
- `POST /api/openclaw/providers/auth/cli-login`
- `POST /api/openclaw/providers/auth/setup-token`
- `POST /api/openclaw/providers/auth/paste-token`
- `POST /api/openclaw/providers/auth/remove`
- `POST /api/openclaw/providers/auth/set-default`
- `POST /api/openclaw/providers/auth/fix-scope`
- `POST /api/openclaw/providers/auth/relogin`
- `GET /api/openclaw/providers/auth/callback`
Canonical request and mutation shapes:
```ts
type ProviderAuthStartRequest = {
provider_id: string;
auth_method_id: string;
profile_id?: string;
desired_scopes?: string[];
pending_operation_ref?: string;
source_surface_kind?: string;
source_surface_id?: string;
schema_version: 1;
};
type ProviderAuthCompleteRequest = {
provider_id: string;
challenge_id: string;
auth_method_kind: ProviderAuthMethodKind;
oauth_code?: string;
code_verifier?: string;
redirect_uri?: string;
setup_token?: string;
pasted_token?: string;
pending_operation_ref?: string;
schema_version: 1;
};
type ProviderAuthCancelRequest = {
provider_id: string;
challenge_id: string;
schema_version: 1;
};
type ProviderAuthProbeRequest = {
provider_id: string;
profile_id?: string;
verify_scopes?: string[];
schema_version: 1;
};
type ProviderAuthRemoveProfileRequest = {
provider_id: string;
profile_id: string;
active_binding_strategy: "block" | "switch_to_profile" | "force_remove";
replacement_profile_id?: string;
confirmation_token: string;
source_surface_kind?: string;
source_surface_id?: string;
operation_id?: string;
schema_version: 1;
};
type ProviderAuthSetDefaultRequest = {
provider_id: string;
profile_id: string;
source_surface_kind?: string;
source_surface_id?: string;
operation_id?: string;
schema_version: 1;
};
type ProviderAuthVerifyCliRequest = {
provider_id: string;
profile_id?: string;
schema_version: 1;
};
type ProviderAuthFixScopeRequest = {
provider_id: string;
profile_id?: string;
required_scopes: string[];
source_surface_kind?: string;
source_surface_id?: string;
operation_id?: string;
schema_version: 1;
};
type ProviderAuthCallbackCompleteRequest = {
provider_id: string;
profile_id?: string;
challenge_id?: string;
code: string;
state: string;
redirect_uri: string;
source_surface_kind?: string;
source_surface_id?: string;
operation_id?: string;
schema_version: 1;
};
type ProviderAuthCallbackCompleteResult = {
ok: true;
provider_id: string;
profile_id?: string;
challenge_id?: string;
auth_state_ref?: string;
verification_result: "verified" | "failed" | "scope_limited";
reason_codes?: string[];
schema_version: 1;
};
type ProviderAuthMutationResult = {
ok: true;
provider_id: string;
profile_id?: string;
effective_default_profile_id?: string;
affected_scope_refs?: string[];
warning_codes?: string[];
auth_state_ref?: string;
schema_version: 1;
};
type ProviderAuthRemoveProfileError = {
error_code:
| "PROFILE_NOT_FOUND"
| "PROFILE_IN_USE"
| "REPLACEMENT_PROFILE_REQUIRED"
| "REPLACEMENT_PROFILE_NOT_FOUND"
| "REPLACEMENT_PROFILE_INVALID_PROVIDER"
| "INVALID_CONFIRMATION"
| "INTERNAL_ERROR";
message: string;
reason_codes?: string[];
schema_version: 1;
};
type GatewayAuthStateChangedEvent = {
event_id: string;
event_family: "gateway.auth.state_changed";
occurred_at: string;
correlation_id: string;
provider_id: string;
profile_id?: string;
state:
| "missing"
| "challenge_pending"
| "verifying"
| "verified"
| "failed"
| "invalid"
| "expired"
| "scope_limited"
| "stale";
reason_codes: string[];
challenge_id?: string;
schema_version: 1;
};
type GatewayAuthChallengeFailedEvent = {
event_id: string;
event_family: "gateway.auth.challenge_failed";
occurred_at: string;
correlation_id: string;
provider_id: string;
challenge_id: string;
profile_id?: string;
failure_stage: "challenge_start" | "callback" | "verification" | "scope_repair" | "unknown";
reason_codes: string[];
schema_version: 1;
};
```
Validation rules:
- if `active_binding_strategy = "switch_to_profile"`, `replacement_profile_id` is required,
- if `active_binding_strategy = "block"` and active bindings exist, mutation MUST fail with `PROFILE_IN_USE`,
- `replacement_profile_id` MUST belong to the same `provider_id`,
- callback completion MUST verify state/nonce integrity before writing auth/profile state.
Auth failure reporting rule:
- `gateway.auth.state_changed` is the canonical aggregate auth-state event,
- `gateway.auth.challenge_failed` is the canonical challenge-scoped failure event,
- challenge failures MUST emit both `gateway.auth.challenge_failed` and `gateway.auth.state_changed`,
- prose or legacy wording elsewhere in the document MUST NOT be interpreted as deleting, deprecating, or downgrading `gateway.auth.challenge_failed`.
### 4A.11A Auth read/list/result schemas
```ts
type ProviderAuthCatalogResponse = {
methods: ProviderAuthMethodSchema[];
schema_version: 1;
};
type ProviderAuthProfilesResponse = {
profiles: ProviderAuthProfileSchema[];
schema_version: 1;
};
type ProviderAuthStateResponse = {
states: ProviderAuthStateSchema[];
schema_version: 1;
};
type ProviderAuthChallengesResponse = {
challenges: ProviderAuthChallengeSchema[];
schema_version: 1;
};
type ProviderAuthProbeResult = {
provider_id: string;
profile_id?: string;
auth_state: ProviderAuthStateSchema["auth_state"];
granted_scopes?: string[];
missing_scopes?: string[];
probed_at: string;
reason_codes?: string[];
schema_version: 1;
};
type ProviderAuthSetDefaultResult = {
provider_id: string;
profile_id: string;
accepted: boolean;
reason_codes?: string[];
schema_version: 1;
};
type ProviderAuthRemoveProfileResult = {
provider_id: string;
profile_id: string;
accepted: boolean;
impacted_scope_refs?: string[];
replacement_profile_id?: string;
reason_codes?: string[];
schema_version: 1;
};
type ProviderAuthTokenEntryResult = {
provider_id: string;
profile_id?: string;
auth_state: ProviderAuthStateSchema["auth_state"];
reason_codes?: string[];
schema_version: 1;
};
type ProviderAuthIssueLogResponse = {
issues: ProviderAuthIssueLogEntry[];
schema_version: 1;
};
```
Required read routes:
- `GET /api/openclaw/providers/auth/catalog` → `ProviderAuthCatalogResponse`
- `GET /api/openclaw/providers/auth/profiles` → `ProviderAuthProfilesResponse`
- `GET /api/openclaw/providers/auth/state` → `ProviderAuthStateResponse`
- `GET /api/openclaw/providers/auth/challenges` → `ProviderAuthChallengesResponse`
- `GET /api/openclaw/providers/auth/issues` → `ProviderAuthIssueLogResponse`
The single canonical auth challenge read route is `GET /api/openclaw/providers/auth/challenges`.
Required callback terminal behavior:
- `GET /api/openclaw/providers/auth/callback` MUST terminate into either `verified`, `failed`, or `expired` challenge state,
- `POST /api/openclaw/providers/auth/callback/complete` MUST write the resulting auth/profile state before returning success to the UI,
- callback failure MUST emit `gateway.auth.challenge_failed` with reason codes.
### 4A.12 OAuth mechanics and proxying
Browser OAuth flow MUST work like this:
1. Q calls `auth/start`.
2. Adapter/runtime returns `GatewayAuthChallengeResponse` with `auth_url`.
3. Q opens the system browser or trusted app browser surface.
4. Callback lands on `GET /api/openclaw/providers/auth/callback`.
5. Q backend relays the callback payload to Gateway/plugin/native auth path.
6. Q probes auth state and only then renders `verified`.
Q MUST NOT store refresh tokens or long-lived provider tokens in EC durable state.
### 4A.13 Precedence rules
Default credential-source precedence:
1. `plugin_managed` / `cli_managed`
2. `gateway_store` / `secret_ref`
3. `env`
4. `file`
5. `exec`
6. `none`
If a profile explicitly sets `env_override_allowed = true`, env may outrank lower-priority persisted sources, but the effective auth state MUST show which source actually won.
### 4A.14 CLI-managed auth is not magic
Q may not fake CLI auth. Q may:
- detect it,
- instruct the operator,
- call `verify-cli` or `probe`,
- surface current state.
If the user must run a CLI command, the UI MUST say so explicitly.
### 4A.14.1 Plugin-managed auth discovery
If a provider/plugin manages auth internally, DOC11 MUST surface:
- `managed_by_plugin = true`
- `plugin_id`
- whether the profile is editable or read-only from Q
- whether Q is delegating to a plugin challenge UI or to a generic DOC11 challenge
Plugin-managed discovery MUST come from runtime/plugin truth, not a hardcoded provider allowlist.
### 4A.14.2 Custom proxy auth/profile support
If a provider is accessed through a proxy or gateway wrapper, the profile MAY use `auth_method_kind = "custom_proxy"`.
Q MUST surface:
- `proxy_url`
- `proxy_headers_ref` or a secure managed header profile reference
- `Test Connection`
- badge `Custom Proxy Active`
Custom proxy configuration MUST remain truthful: Q may not imply direct provider auth when a proxy is actually in path.
### 4A.15 Scope truth and model request truth
Missing scope such as `model.request` MUST be visible both in auth state and in capability check failures. A selected model is not requestable truth until required scopes are verified.
### 4A.16 Auth profile on dispatch
`GatewayChatDispatchRequest` and `GatewayRoomTurnDispatchRequest` MUST include optional:
- `auth_profile_id`
- `auth_method_kind`
If omitted, adapter may resolve defaults, but if operator selected a profile the dispatch MUST be able to carry it.
### 4A.17 Settings page requirements for provider/model auth
Settings > Providers & Models MUST include:
- provider list sidebar with auth badge,
- provider detail pane,
- auth method selector,
- profile list with badges,
- account hint / endpoint/base-url truth,
- action buttons,
- challenge pane,
- unsupported/settings-only controls explanation,
- verification timestamps,
- scope-limited reasons,
- profile source precedence note if relevant,
- custom proxy fields when `auth_method_kind = "custom_proxy"`,
- plugin-managed discovery badges when the runtime reports plugin ownership,
- explicit handoff to the Local / Self-Hosted Providers subsection for provider runtime configuration.
Required layout:
```text
Settings > Providers & Models
Left sidebar:
- Google / Gemini Verified
- Anthropic / Claude Verified
- OpenAI Missing
- Local / Ollama Reachable
Main pane for selected provider:
- Auth method dropdown
- Profile list with badges: Verified / Stale / Expired / Scope limited
- Account hint / endpoint / source / plugin-managed badges
- Action row: [Re-verify] [Re-login] [Add Profile] [Remove Profile] [Test Connection]
- Challenge pane when pending:
* OAuth: “Opening browser…” + cancel button
* Device code: user_code + verification_url + copy button
* Setup token: instruction text + secure entry field
* Paste token: masked secure token field
* CLI verify: instruction + [Verify Now]
- Available models table for the provider
- Unsupported controls note: “Saved as preference only; runtime does not verify/apply natively on this path.”
```
### 4A.17A Post-save runtime snapshot refresh rule
After any save, probe, relogin, remove-profile, set-default, or equivalent mutation on provider/model auth, web-search config, or local/self-hosted provider config surfaces, Q MUST immediately refetch the authoritative runtime snapshot/read-models rather than synthesizing post-save truth locally.
Normative rules:
- non-plaintext secret-backed values MUST preserve source/provenance truth after writes even when the raw secret cannot be round-tripped to the client,
- a preserved secret-backed or file-backed value MUST render as `present / preserved` with masked hint or source note rather than a fake empty field,
- if runtime reports that a loaded secret/token shape cannot be used directly by the current client surface, Q MUST show that incompatibility explicitly rather than silently clearing or rewriting the stored value,
- save success toasts MUST never imply verification; verification requires a fresh runtime snapshot or explicit probe result.
Auth challenge examples:
```json
{
"provider_id": "google",
"profile_id": "default",
"challenge": {
"challenge_id": "ch_123",
"provider_id": "google",
"auth_method_kind": "oauth_browser",
"challenge_kind": "browser_redirect",
"challenge_state": "pending",
"auth_url": "<runtime supplied>",
"pending_operation_ref": "op_456",
"schema_version": 1
},
"post_success_action": "resume_pending_operation",
"schema_version": 1
}
```
```json
{
"provider_id": "anthropic",
"challenge": {
"challenge_id": "ch_789",
"provider_id": "anthropic",
"auth_method_kind": "cli_managed",
"challenge_kind": "cli_verify",
"challenge_state": "awaiting_user_action",
"schema_version": 1
},
"post_success_action": "reverify",
"schema_version": 1
}
```
### 4A.18 Remove-profile destructive semantics
Default behavior: block removal if profile is active in any live scope.
Modal MUST show:
- impacted live sessions,
- impacted participants,
- current default profile status,
- replacement/default option.
Buttons:
- `Cancel`
- `Switch impacted scopes to another default profile and remove`
- optional engineering-only `Force remove and mark impacted scopes degraded`
### 4A.19 Auth-specific read-models
Required auth read-models:
- `provider_auth_catalog_store`
- `provider_auth_profile_store`
- `provider_auth_state_store`
- `provider_auth_challenge_store`
- `provider_auth_issue_log`
### 4A.20 ProviderAuthIssueLogEntry
```ts
type ProviderAuthIssueLogEntry = {
issue_id: string;
provider_id: string;
profile_id?: string;
issue_kind:
| "challenge_required"
| "scope_limited"
| "profile_mismatch"
| "expired"
| "invalid"
| "probe_failed"
| "callback_failed";
reason_codes: string[];
created_at: string;
resolved_at?: string;
impacted_scope_refs?: string[];
schema_version: 1;
};
```
### 4A.21 OpenClaw release alignment notes
For the current audited baseline:
- `plugins.slots.contextEngine` defaults to `legacy` unless another context engine is installed and selected,
- OpenClaw supports `gateway.auth.token` via SecretRef with auth-mode guardrails,
- plugin system context may be added via `prependSystemContext` / `appendSystemContext`,
- prompt-injection policy may disable prompt-mutating hooks per plugin,
- context engines own assembly/compaction when selected,
- `sessions.get` exists and may be used for runtime truth verification,
- 2026.3.8 is now a published baseline and includes backup commands, Brave web-search `llm-context`, config/runtime snapshot preservation after writes, context-engine registry/bootstrap hardening, remote gateway token UX additions, ACP ingress provenance, and model-switch context-token invalidation fixes.
DOC11 MUST surface runtime-discovered truth for active context engine and auth mode rather than assuming a particular engine or source. Published release features MAY be treated as supported baseline behavior, but any field shown as `verified` or native-effective in UI still requires runtime probe/read-model confirmation in the audited environment.
### 4A.21A Remote gateway token truth (optional surface)
If Q or any ELNOR-owned client surface displays remote-gateway connection state or exposes remote-gateway token replacement, it MUST preserve runtime truth for non-plaintext token values.
```ts
type RemoteGatewayTokenState = {
remote_mode_enabled: boolean;
remote_gateway_url?: string;
token_source_kind?: CredentialSourceKind | "preserved_nonplaintext" | "unknown";
token_present: boolean;
token_direct_use_state:
| "usable_directly"
| "preserved_placeholder"
| "unsupported_shape_for_client"
| "unknown";
last_validated_at?: string;
last_error?: string;
schema_version: 1;
};
```
Normative rules:
- editing a preserved non-plaintext token MUST require explicit replacement; save-without-replacement preserves the existing token,
- the UI MUST warn when the loaded token shape cannot be used directly from the current app/client surface,
- raw remote gateway tokens MUST NOT be written to EC durable state or Q client durable storage.
---
### 4A.22 Local and self-hosted provider configuration boundary
Local and self-hosted inference MUST remain Gateway-first. Q and EC MAY configure local runtimes through OpenClaw-native provider configuration (`models.providers.*`) or audited first-class provider surfaces, but live chat and room-turn dispatch MUST still flow through Gateway/OpenClaw runtime resolution.
Prohibited:
- direct Q -> local runtime inference calls,
- direct EC -> local runtime inference calls,
- any `preflightIsLocal` or equivalent that changes dispatch destination away from Gateway,
- bypass paths that skip execution watermark, usage extraction, capability checks, switch segments, or mismatch/fallback logging.
Allowed:
- non-dispatch probe and health checks,
- catalog sync against the configured provider endpoint,
- wrapper-composed provider config CRUD that writes OpenClaw-native provider config truth,
- advisory operator metadata overrides for context window, reasoning, tool support, and cost when runtime/proxy truth is incomplete.
If a local runtime or proxy response exposes usage fields, the adapter MUST normalize them into `UsageSample`. If usage fields are absent, `usage_state = "unknown"` MUST be explicit. Local/self-hosted responses MUST NOT bypass DOC11’s ordinary usage and execution watermark seams.
### 4A.23 Provider deployment and local runtime kinds
```ts
type ProviderDeploymentKind =
| "cloud"
| "local_native"
| "local_proxy"
| "self_hosted_remote";
type LocalRuntimeKind =
| "ollama"
| "vllm"
| "lmstudio"
| "litellm"
| "openai_compatible"
| "anthropic_compatible"
| "mlx_proxy"
| "custom";
type ModelDiscoveryMode =
| "implicit_runtime_discovery"
| "explicit_manual_catalog"
| "hybrid_manual_override";
type LocalAuthRequirement =
| "not_required"
| "placeholder_opt_in"
| "optional_real_key"
| "required";
```
### 4A.24 ProviderCapabilityCatalogEntry (canonical)
```ts
type ProviderCapabilityCatalogEntry = {
provider_id: string;
display_name: string;
provider_origin: "builtin" | "custom";
supported_deployment_kinds: ProviderDeploymentKind[];
supported_api_families: ("ollama" | "openai-completions" | "anthropic-messages" | "custom")[];
supports_implicit_discovery: boolean;
supports_manual_models: boolean;
supports_hybrid_manual_override: boolean;
supports_base_url: boolean;
supports_headers: boolean;
supports_auth_profiles: boolean;
supports_placeholder_api_key: boolean;
supports_health_probe: boolean;
supports_catalog_sync: boolean;
supports_context_window_override: boolean;
supports_max_tokens_override: boolean;
supports_reasoning_override: boolean;
supports_tool_support_filter: boolean;
supports_cost_override: boolean;
supports_openai_compat_num_ctx_injection: boolean;
recommended_local_runtime_kind?: LocalRuntimeKind;
ownership_kind?: "core" | "bundled_plugin" | "third_party_plugin" | "manual" | "unknown";
capability_source_kind?: "runtime_reported" | "plugin_reported" | "manual_operator_asserted";
plugin_id?: string;
plugin_version?: string;
plugin_state?: "available" | "missing" | "incompatible" | "disabled" | "unknown";
schema_version: 1;
};
```
Plugin-backed catalog rules:
- provider, search-provider, and local-provider catalogs are runtime/plugin-discovered capability catalogs and MUST NOT be hard-coded in Q,
- if plugin ownership is not reported by the runtime, `ownership_kind = "unknown"` and plugin badges MUST NOT be faked in UI,
- plugin-backed entries that become unavailable MUST remain visible but non-selectable with `plugin_state = "missing" | "incompatible" | "disabled"`,
- migration rule: pre-plugin entries default to `ownership_kind = "unknown"` until runtime truth is available.
Required events:
- `gateway.plugin.catalog.refreshed`
- `gateway.plugin.catalog.degraded`
### 4A.25 LocalProviderConfig (canonical)
```ts
type LocalProviderConfig = {
provider_id: string;
display_name?: string;
provider_origin: "builtin" | "custom";
deployment_kind: ProviderDeploymentKind;
local_runtime_kind?: LocalRuntimeKind;
api_family: "ollama" | "openai-completions" | "anthropic-messages" | "custom";
base_url: string;
enabled: boolean;
discovery_mode: ModelDiscoveryMode;
auto_sync_on_probe: boolean;
sync_interval_seconds?: number;
tool_support_filter_mode: "runtime_default" | "require_tools" | "allow_any";
auth_requirement: LocalAuthRequirement;
auth_profile_id?: string;
api_key_ref?: string;
headers_ref?: string;
healthcheck_path?: string;
billing_mode: "zero_cost" | "runtime_reported" | "operator_defined" | "unknown";
openai_compat_num_ctx_injection?: boolean;
schema_version: 1;
};
type LocalProviderProbeRequest = {
provider_id: string;
force: boolean;
include_catalog_preview: boolean;
schema_version: 1;
};
type LocalProviderCatalogSyncRequest = {
provider_id: string;
sync_mode: "adopt_runtime_discovery" | "hybrid_refresh" | "manual_noop";
remove_missing_models: boolean;
schema_version: 1;
};
type LocalProviderDeleteRequest = {
provider_id: string;
delete_mode: "disable_and_orphan" | "revert_to_implicit_discovery";
confirmation_phrase: string;
schema_version: 1;
};
```
Validation rules:
- `provider_id` MUST map either to an audited built-in provider id or to a valid `models.providers.<provider_id>` key.
- `local_runtime_kind = "ollama"` with `api_family = "ollama"` requires a base URL **without** `/v1`.
- `local_runtime_kind = "ollama"` with `api_family = "openai-completions"` is allowed only as an explicit compatibility mode with warning that tool-calling and streaming semantics may degrade.
- `local_runtime_kind = "vllm"` or any OpenAI-compatible local proxy SHOULD expose `/v1` endpoints.
- `auth_requirement = "placeholder_opt_in"` allows placeholder values such as `ollama-local` or `vllm-local`; Q MUST render these as opt-in activation tokens, not as real credential verification.
- `discovery_mode = "implicit_runtime_discovery"` forbids manual provider-side `models[]` entries in the same config.
- `discovery_mode = "hybrid_manual_override"` allows operator metadata overrides but MUST NOT invent synthetic runtime-discovered models unless operator explicitly switches to manual catalog mode.
- `openai_compat_num_ctx_injection` MAY be shown only when the capability catalog says the provider/runtime supports or benefits from that behavior.
### 4A.26 LocalProviderRuntimeState and probe truth
```ts
type LocalProviderRuntimeState = {
provider_id: string;
deployment_kind: ProviderDeploymentKind;
local_runtime_kind?: LocalRuntimeKind;
effective_base_url?: string;
api_family: LocalProviderConfig["api_family"];
discovery_mode: ModelDiscoveryMode;
reachability_state: "reachable" | "warming" | "offline" | "degraded" | "unknown";
verification_state: "verified" | "pending" | "degraded" | "unknown";
discovered_model_count?: number;
tool_support_discovery_mode: "runtime_reported" | "manual_override" | "not_reported" | "unknown";
usage_reporting_mode: "provider_usage_object" | "runtime_specific" | "none" | "unknown";
effective_auth_source_kind:
| "none"
| "placeholder_opt_in"
| "auth_profile"
| "secret_ref"
| "env"
| "headers_only"
| "unknown";
last_probed_at?: string;
last_probe_reason_codes?: string[];
schema_version: 1;
};
type LocalProviderProbeResult = {
provider_id: string;
reachability_state: LocalProviderRuntimeState["reachability_state"];
verification_state: LocalProviderRuntimeState["verification_state"];
inferred_api_family?: LocalProviderConfig["api_family"];
discovered_model_refs?: string[];
tool_support_discovery_mode: LocalProviderRuntimeState["tool_support_discovery_mode"];
usage_reporting_mode: LocalProviderRuntimeState["usage_reporting_mode"];
last_probed_at: string;
reason_codes?: string[];
schema_version: 1;
};
type LocalProviderCatalogSyncResult = {
provider_id: string;
catalog_generation: string;
added_model_refs: string[];
updated_model_refs: string[];
removed_model_refs: string[];
orphaned_scope_refs?: string[];
schema_version: 1;
};
```
`reachability_state = "warming"` MUST be available for local runtimes that are reachable but still loading a model or not yet ready to serve steady-state inference. Q MUST NOT collapse `warming` into either `reachable` or `offline`.
### 4A.27 LocalModelMetadataOverride (canonical)
```ts
type LocalModelMetadataOverride = {
model_ref: string;
context_window?: number;
max_tokens?: number;
reasoning?: boolean;
tool_support?: boolean;
input_modalities?: ("text" | "image" | "audio" | "video")[];
cost_mode: "zero_cost" | "operator_defined" | "unknown";
operator_cost_usd_per_1m_input?: number;
operator_cost_usd_per_1m_output?: number;
metadata_source: "runtime_reported" | "operator_asserted" | "proxy_reported" | "unknown";
verification_state: "verified" | "operator_asserted" | "unknown";
schema_version: 1;
};
```
Operational rules:
- local-model metadata overrides are stored separately from raw runtime discovery and layered on top of the catalog entry,
- `verified` runtime metadata always outranks operator-asserted metadata,
- if `context_window` or `max_tokens` is only operator-asserted, context budgeting and capability checks MUST downgrade confidence accordingly,
- `cost_mode = "zero_cost"` may produce `$0` cost truth for DOC13 while still preserving usage-token reporting if available,
- `cost_mode = "operator_defined"` MUST NOT be used unless per-token rates are present,
- editing local-model metadata uses the ordinary model edit flow plus the local metadata override store; Q MUST show override badges in the model table.
```ts
type LocalModelMetadataOverrideUpdateRequest = {
provider_id: string;
model_ref: string;
override: Omit<LocalModelMetadataOverride, "model_ref">;
schema_version: 1;
};
type LocalModelMetadataOverrideMutationResult = {
accepted: boolean;
provider_id: string;
model_ref: string;
override_present: boolean;
reason_codes?: string[];
schema_version: 1;
};
```
### 4A.28 Local/self-hosted discovery, catalog, and fallback rules
Local/self-hosted providers participate in the same model catalog lifecycle and default execution policy from §4.13-4.15C.
Normative rules:
- successful runtime discovery or sync advances `catalog_generation`,
- removed models disappear from new pickers on the next catalog generation update,
- removed local models orphan live bindings exactly like any other removed model,
- provider reachability failure alone MUST NOT delete models from the catalog,
- `Probe runtime` may update runtime state and preview discovered models without mutating the catalog,
- `Sync catalog` is the mutating action that adds/updates/removes models,
- `Add manual model` uses the ordinary model catalog create route with `provider_id` preselected,
- local models use the same primary-model + ordered-fallback chain as all other models,
- when a local provider becomes unreachable, fallback/mismatch logging MUST record whether failure reason was `local_provider_unreachable`, `model_missing`, `tool_support_missing`, `usage_unknown`, or another explicit reason code.
### 4A.29 Provider-specific audited guidance for local/self-hosted runtimes
For the current audited documentation baseline:
- **Ollama** is a local native runtime. Prefer `api_family = "ollama"` and a base URL without `/v1`. Implicit discovery is the preferred mode when runtime discovery is available and no explicit provider entry overrides it.
- **vLLM** is a local or self-hosted OpenAI-compatible server. `api_family = "openai-completions"` with `/v1` endpoints is the preferred mode. Implicit discovery is supported when runtime discovery is available and no explicit provider entry overrides it.
- **LM Studio**, **LiteLLM**, and other local proxies are treated as `local_proxy` or `self_hosted_remote` and typically use manual or hybrid discovery.
- **MLX-backed servers** are treated as `local_proxy` with `local_runtime_kind = "mlx_proxy"` until a future audited OpenClaw build documents a first-class MLX provider. Default discovery mode for `mlx_proxy` is `explicit_manual_catalog` unless a successful `/v1/models` probe proves a stable discovery surface and the operator explicitly enables implicit or hybrid discovery.
- Any MLX/Ollama/vLLM/local-proxy runtime MUST still dispatch through Gateway/OpenClaw; direct local execution bypass is forbidden.
### 4A.30 Settings > Providers & Models contract for local/self-hosted providers
Settings > Providers & Models MUST include a **Local / Self-Hosted Providers** subsection.
Minimum required subsections:
- provider capability-aware provider list,
- local/self-hosted provider config editor,
- runtime state card,
- discovered/manual models table,
- metadata override editor,
- primary/fallback actions for model rows,
- delete-provider-config destructive flow.
Required layout:
```text
Settings > Providers & Models > Local / Self-Hosted Providers
Left list:
- Ollama Reachable · Implicit discovery
- vLLM Offline · Manual catalog
- MLX Qwen 9B Warming · Manual catalog
- LM Studio Degraded · Hybrid overrides
Main pane for selected provider:
- Provider type dropdown
- Provider ID field
- Display name
- Base URL
- API family dropdown
- Discovery mode dropdown
- Auth requirement row + secret/profile selector
- Tool-support filter dropdown
- Billing mode dropdown
- Auto-sync toggle + interval
- OpenAI-compatible num_ctx injection toggle (only if supported)
- Runtime state card: reachability, verification, last probe, discovered model count, usage-reporting mode
- Models table with source badges: discovered / manual / overridden
- Per-model actions: [Edit Metadata Override] [Set as Primary] [Add to Fallback Chain]
- Action row: [Probe Runtime] [Sync Catalog] [Add Manual Model] [Save Provider] [Delete Provider]
- Post-save rule: after Save / Probe / Sync / Delete, refetch runtime snapshot/read-models; never fake success locally
```
Required UI copy behavior:
- for `ollama` in OpenAI-compatible mode: `Compatibility mode. Tool calling and streaming may degrade on the /v1 path.`
- for `mlx_proxy`: `Custom MLX-backed server. Gateway-first dispatch still applies; direct local bypass is forbidden.`
- for placeholder opt-in auth: `Presence of this placeholder activates discovery/probe logic. It does not prove real credentialed access.`
- for operator-asserted metadata: `Operator asserted. Runtime did not verify this field.`
Delete-provider modal MUST show:
- provider display name and `provider_id`,
- deployment kind / local runtime kind,
- impacted live sessions,
- impacted participants,
- impacted catalog model count,
- whether the provider supports reverting to implicit discovery.
Buttons:
- `Cancel`
- `Delete provider config and orphan affected bindings`
- optional `Remove explicit config and revert to implicit discovery`
Backup recommendation block (non-blocking but required):
- `openclaw backup create --verify`
- if the operator wants config-only backup: `openclaw backup create --only-config --verify`
### 4A.31 Local/self-hosted provider route contracts
Required local/self-hosted provider routes:
- `GET /api/openclaw/providers/capabilities`
- `GET /api/openclaw/providers/local-runtime-configs`
- `GET /api/openclaw/providers/local-runtime-state`
- `PUT /api/openclaw/providers/local-runtime-configs/:provider_id`
- `POST /api/openclaw/providers/local-runtime-configs/:provider_id/probe`
- `POST /api/openclaw/providers/local-runtime-configs/:provider_id/sync-catalog`
- `DELETE /api/openclaw/providers/local-runtime-configs/:provider_id`
- `POST /api/openclaw/providers/local-runtime-configs/:provider_id/revert-to-implicit-discovery`
- `PUT /api/openclaw/providers/local-runtime-configs/:provider_id/models/:model_ref/metadata`
- `DELETE /api/openclaw/providers/local-runtime-configs/:provider_id/models/:model_ref/metadata`
Canonical request/result/read contracts for those routes MUST be defined in Appendix G and Appendix E.
## 4B) Web search provider configuration, native tool truth, automatic usage, and search fallback management
### 4B.1 Native tool-configuration boundary
Web search provider configuration belongs to the OpenClaw-native `tools.web.search` surface. DOC11/Q/EC MUST treat it as a runtime-native tool configuration domain, not as a chat-model selector.
Normative rules:
- Q/EC MAY present and mutate web-search configuration through DOC11 routes, but the effective runtime state MUST mirror OpenClaw-native tool config truth.
- Q MUST NOT invent a global `search model` abstraction that is broader than the runtime supports.
- The canonical user-facing object is a **search provider config** with optional provider-scoped model override.
- When OpenClaw decides to invoke `web_search`, the effective native web-search configuration MUST be used automatically.
- Per-turn search provider override is out of scope unless future audited runtime evidence adds explicit support.
### 4B.2 Runtime-discovered search provider catalog
Q MUST render available search providers from runtime-advertised catalog truth, not from a hardcoded list.
For the current audited doc-alignment baseline, the runtime/docs advertise support for:
- `brave`
- `firecrawl`
- `gemini`
- `grok`
- `kimi`
- `perplexity`
These values MAY expand in later builds; Q MUST consume the runtime catalog rather than shipping a frozen provider list.
### 4B.2A Provider ordering neutrality and auto-detect disclosure
Provider picker order in Q MUST be neutral and alphabetical by display name unless the runtime explicitly returns a different operator-facing order.
Normative rules:
- picker/display order MUST NOT imply fallback order, auto-detect preference, or recommendation priority,
- if runtime auto-detect has a preferred provider resolution order, that order MUST be surfaced only as explicit advanced/help text or runtime-detail disclosure, not inferred from visible list order,
- if no runtime-provided display order exists, Q sorts provider rows alphabetically (e.g. Brave, Firecrawl, Gemini, Grok, Kimi, Perplexity).
### 4B.3 SearchProviderCatalogEntry (canonical)
```ts
type SearchProviderCatalogEntry = {
provider_id: string;
display_name: string;
runtime_supported: boolean;
supports_api_key: boolean;
supports_secret_ref: boolean;
supports_model_override: boolean;
supports_base_url_override: boolean;
supports_mode_override: boolean;
supported_modes?: string[];
supports_domain_filter: boolean;
supports_country_language_filters: boolean;
supports_citations: boolean;
supports_auto_detection: boolean;
runtime_advertised_model_options?: string[];
schema_version: 1;
};
```
UI rules:
- if `runtime_advertised_model_options` exists, Q MAY render a dropdown,
- if `supports_model_override = true` but no option list exists, Q MAY render an advanced validated text field,
- if `supports_model_override = false`, Q MUST NOT render a model-override control.
### 4B.4 One-config-per-provider baseline parity rule
Current OpenClaw web-search configuration is provider-centric. To stay native-first, this specification defines **one mutable provider config per provider** as the baseline parity model.
Normative rules:
- Q MUST NOT invent multi-profile-per-provider search auth/config accounts unless audited runtime support exists.
- `Add search provider` means enabling/configuring one provider entry.
- `Delete search provider` means removing that provider’s native config/credential path from the Q-managed surface.
- provider-scoped `model_override` is allowed only where runtime/provider supports it.
### 4B.5 SearchProviderConfig (canonical)
```ts
type SearchProviderConfig = {
provider_config_id: string;
provider_id: string;
enabled: boolean;
credential_source_kind: CredentialSourceKind;
credential_present: boolean;
credential_activity_state: "active" | "inactive" | "unknown";
masked_credential_hint?: string;
model_override?: string;
base_url_override?: string;
mode_override?: string;
compatibility_path_active?: boolean;
compatibility_path_kind?: "native" | "openrouter_sonar" | "other";
max_results?: number;
timeout_seconds?: number;
cache_ttl_minutes?: number;
country_override?: string;
language_override?: string;
ui_language_override?: string;
domain_filter_allowlist?: string[];
verification_state:
| "verified"
| "pending"
| "missing_credential"
| "stale"
| "error"
| "unknown";
last_verified_at?: string;
last_error?: string;
schema_version: 1;
};
```
Credential-storage rule:
- raw API keys MUST NOT be written to EC durable state, Q local durable storage, or suite telemetry.
- secure entry fields MUST relay secrets to Gateway-native config/secret storage or SecretRef-backed storage.
- EC/Q durable projections MAY store only metadata such as source kind, masked hint, verification status, and last error.
### 4B.6 SearchRuntimeState (canonical)
```ts
type SearchRuntimeState = {
search_enabled: boolean;
configured_provider_id?: string;
auto_detect_mode: boolean;
effective_provider_id?: string;
effective_provider_source: "configured" | "auto_detected" | "fallback" | "none";
effective_provider_config_id?: string;
effective_provider_mode?: string;
compatibility_path_active?: boolean;
compatibility_path_kind?: "native" | "openrouter_sonar" | "other";
effective_max_results?: number;
effective_timeout_seconds?: number;
effective_cache_ttl_minutes?: number;
fallback_policy_id?: string;
last_search_execution_ref?: string;
verification_state: "verified" | "pending" | "degraded" | "unknown";
reason_codes?: string[];
last_verified_at?: string;
last_error?: string;
schema_version: 1;
};
```
### 4B.7 SearchExecutionWatermark (canonical)
```ts
type SearchExecutionWatermark = {
search_execution_id: string;
run_id?: string;
tool_call_id?: string;
provider_id: string;
provider_config_id?: string;
provider_model?: string;
provider_mode?: string;
compatibility_path_active?: boolean;
compatibility_path_kind?: "native" | "openrouter_sonar" | "other";
effective_provider_source: SearchRuntimeState["effective_provider_source"];
used_auto_detection: boolean;
used_fallback: boolean;
fallback_from_provider_id?: string;
started_at: string;
completed_at?: string;
schema_version: 1;
};
```
### 4B.8 SearchProviderFallbackPolicy (canonical)
Search provider fallback is **wrapper-composed**, not assumed native.
Default posture:
- disabled unless operator explicitly enables it,
- never silent,
- always visible in runtime/debug surfaces,
- only used after the native configured provider or auto-detected provider fails with qualifying errors.
```ts
type SearchProviderFallbackPolicy = {
search_fallback_policy_id: string;
enabled: boolean;
provider_order: string[];
qualifying_error_codes: Array<
| "search_provider_unavailable"
| "search_credential_missing"
| "search_auth_invalid"
| "search_rate_limited"
| "search_timeout"
>;
max_fallback_hops: number;
allow_auto_detect_before_wrapper_fallback: boolean;
silent_fallback_forbidden: true;
schema_version: 1;
};
```
Normative rules:
- the fallback chain MUST reference configured provider entries only,
- disabled or credential-missing providers MUST NOT be inserted automatically into the chain,
- if `auto_detect_mode = true`, native auto-detection runs before wrapper fallback when `allow_auto_detect_before_wrapper_fallback = true`,
- every fallback hop MUST emit telemetry and record a `SearchExecutionWatermark` with `used_fallback = true`.
### 4B.9 Search provider configuration mutation requests (canonical)
```ts
type SearchProviderConfigUpsertRequest = {
provider_id: string;
enabled: boolean;
credential_source_kind: "gateway_store" | "secret_ref" | "env" | "none";
secure_secret_entry?: string;
secret_ref?: string;
model_override?: string;
base_url_override?: string;
mode_override?: string;
max_results?: number;
timeout_seconds?: number;
cache_ttl_minutes?: number;
country_override?: string;
language_override?: string;
ui_language_override?: string;
domain_filter_allowlist?: string[];
schema_version: 1;
};
type SearchProviderConfigDeleteRequest = {
provider_id: string;
confirmation_phrase: string;
schema_version: 1;
};
type SearchProviderSelectionRequest = {
provider_id?: string;
auto_detect_mode: boolean;
schema_version: 1;
};
type SearchFallbackPolicyUpdateRequest = {
enabled: boolean;
provider_order: string[];
qualifying_error_codes: SearchProviderFallbackPolicy["qualifying_error_codes"];
max_fallback_hops: number;
allow_auto_detect_before_wrapper_fallback: boolean;
schema_version: 1;
};
type SearchProviderProbeRequest = {
provider_id: string;
test_query?: string;
schema_version: 1;
};
```
Validation rules:
- `provider_id` MUST exist in `SearchProviderCatalogEntry`.
- `max_results`, when provided, MUST be `1..10`.
- `timeout_seconds`, when provided, MUST be positive.
- `cache_ttl_minutes`, when provided, MUST be positive.
- `model_override` MAY be set only when provider catalog says model override is supported.
- `base_url_override` MAY be set only when provider catalog says base-url override is supported.
- `mode_override` MAY be set only when provider catalog says mode override is supported.
- `domain_filter_allowlist` MAY be set only when provider catalog says domain filters are supported.
### 4B.9A Search provider read/result schemas
```ts
type SearchProviderCatalogResponse = {
providers: SearchProviderCatalogEntry[];
schema_version: 1;
};
type SearchProviderConfigsResponse = {
configs: SearchProviderConfig[];
schema_version: 1;
};
type SearchRuntimeStateResponse = {
state: SearchRuntimeState;
schema_version: 1;
};
type SearchExecutionWatermarkResponse = {
watermark?: SearchExecutionWatermark;
schema_version: 1;
};
type SearchProviderProbeResult = {
provider_id: string;
verification_state: SearchProviderConfig["verification_state"];
effective_provider_mode?: string;
compatibility_path_active: boolean;
compatibility_path_kind?: SearchProviderConfig["compatibility_path_kind"];
last_verified_at: string;
reason_codes?: string[];
schema_version: 1;
};
type SearchTestRequest = {
provider_id: string;
test_query: string;
schema_version: 1;
};
type SearchTestResult = {
provider_id: string;
search_execution_id: string;
result_count: number;
latency_ms: number;
verification_state: "verified" | "error";
error_message?: string;
reason_codes?: string[];
schema_version: 1;
};
type SearchProviderConfigDeleteResult = {
provider_id: string;
accepted: boolean;
search_state_degraded: boolean;
fallback_chain_affected: boolean;
reason_codes?: string[];
schema_version: 1;
};
type SearchProviderSelectionResult = {
effective_provider_id?: string;
effective_provider_source: SearchRuntimeState["effective_provider_source"];
auto_detect_mode: boolean;
reason_codes?: string[];
schema_version: 1;
};
```
Required route additions:
- `GET /api/openclaw/tools/web-search/catalog` → `SearchProviderCatalogResponse`
- `GET /api/openclaw/tools/web-search/configs` → `SearchProviderConfigsResponse`
- `GET /api/openclaw/tools/web-search/state` → `SearchRuntimeStateResponse`
- `GET /api/openclaw/tools/web-search/executions/latest` → `SearchExecutionWatermarkResponse`
- `POST /api/openclaw/tools/web-search/test` → `SearchTestResult`
### 4B.10 Native config application rule
Search configuration save behavior MUST follow this sequence:
1. validate against runtime provider catalog,
2. relay any secure credential entry directly to Gateway-native config/secret handling,
3. apply provider config mutation,
4. re-probe effective runtime state,
5. refresh `SearchRuntimeState`,
6. refresh all `credential_activity_state` projections,
7. emit `gateway.search.config.changed` or explicit failure telemetry.
Deletion behavior MUST follow this sequence:
1. confirm destructive action,
2. remove or clear native provider config,
3. if deleted provider was selected and `auto_detect_mode = false`, mark search runtime degraded until a new provider is selected,
4. if deleted provider participated in wrapper fallback chain, remove it from the fallback chain and emit fallback-policy change telemetry,
5. refresh runtime state and projections.
### 4B.11 Active/inactive credential truth
Q MUST render search credentials truthfully.
Rules:
- if a specific provider is selected, provider-specific credentials for non-selected providers render `inactive` unless the runtime says otherwise,
- if provider is unset and auto-detect mode is active, provider-specific credentials MAY render `active for auto-detection`,
- `credential_present` is not the same as `credential_activity_state = active`,
- a stored key/reference MUST NOT be shown as `verified` without a successful probe or runtime verification state.
### 4B.12 Automatic use rule (OpenClaw-native first)
DOC11 MUST preserve OpenClaw-native automatic search behavior.
Required behavior:
- chat and room dispatch requests MUST NOT include a baseline `search_provider_override` field,
- when a run invokes `web_search`, OpenClaw uses the effective native `tools.web.search` configuration,
- Q/EC may configure the native search provider and fallback policy, but MUST NOT micromanage provider choice on ordinary turns,
- if future runtime evidence adds explicit per-run search overrides, that must be added as a later audited extension rather than assumed in the current baseline.
### 4B.13 Provider-specific field rules
Required provider-specific handling for the current documented baseline:
- **Brave**: support API key / SecretRef plus optional `mode_override = "llm-context"`.
- **Perplexity**: support API key / SecretRef plus optional `base_url_override` and `model_override`.
- **Gemini**: support API key / SecretRef plus optional `model_override` when runtime/provider path supports it.
- **Grok** and **Kimi**: support API key / SecretRef and optional advanced fields only if runtime catalog advertises them.
UI honesty rules:
- if a provider-specific field is not supported for the selected provider, hide it or render it read-only with explanation,
- if `mode_override = "llm-context"` for Brave, the UI MUST explain that some filters become unavailable on that mode and that extracted grounding snippets/source metadata may be returned,
- if Perplexity uses `base_url_override`, `model_override`, `OPENROUTER_API_KEY`, or any runtime-detected Sonar compatibility path, Q MUST display `Compatibility path active`,
- when `compatibility_path_active = true` for Perplexity/OpenRouter Sonar, only `query` and `freshness`-compatible fields may remain enabled unless runtime later advertises broader support; Search-API-only filters MUST be hidden or disabled with explicit explanation rather than allowed to fail silently.
### 4B.14 Settings > Web Search contract
Q MUST expose a dedicated **Settings > Web Search** surface separate from chat model/provider settings.
Required layout:
```text
Settings > Web Search
Top summary row:
- Web Search: Enabled / Disabled
- Provider mode: Specific provider / Auto-detect
- Effective provider: Brave / Gemini / Grok / Kimi / Perplexity / None
- Effective provider mode: native / llm-context / compatibility path when applicable
- Last verified: timestamp
- Last search used: provider + time
Left sidebar:
- Global toggle
- Default provider selector or Auto-detect toggle
- Fallback chain badge (Off / 2 providers / Degraded)
- Provider rows (alphabetical by display name unless runtime returns explicit display order):
* Brave Verified / Missing key / Inactive / Auto-eligible
* Gemini Verified / Missing key
* Grok Missing key
* Kimi Missing key
* Perplexity Verified / Compatibility path / Missing key
Main pane for selected provider:
- Enabled toggle
- Credential source selector (Gateway Store / SecretRef / Env / None)
- Secure API key entry or SecretRef field
- Provider-specific advanced fields:
* Mode
* Model override
* Base URL override
* Max results
* Timeout seconds
* Cache TTL minutes
* Domain filter allowlist (if supported)
* Country / language / UI language (if supported)
- Compatibility path banner when applicable
- Action row: [Save] [Probe] [Test Search] [Delete Provider Config]
- Verification / error panel
- Active vs inactive secret explanation
- Post-save rule: after Save / Probe / Delete / Set Default / Fallback update, refetch runtime snapshot/read-models and preserve masked secret/source truth
Fallback policy pane:
- Enable fallback toggle
- Ordered provider chain
- Add provider to chain
- Remove provider from chain
- Move up / Move down
- Qualifying failure checkboxes
- Max fallback hops
- Note: “Wrapper-composed fallback. Native provider auto-detection runs first when enabled.”
```
Ghost-control prevention rules:
- `Probe` MUST hit a real probe route and update verification state.
- `Test Search` MUST execute a real `web_search` test call or a clearly labeled dry-run/probe path.
- `Delete Provider Config` MUST show confirm/cancel and dependency impact.
- no provider row may show `Verified` based only on stored key presence.
### 4B.15 Search provider destructive semantics
Delete Provider Config modal MUST show:
- provider display name,
- whether it is currently selected,
- whether auto-detect mode is on,
- whether it is referenced in wrapper fallback policy,
- last verified state,
- warning that the provider will disappear from active configuration until re-added.
Buttons:
- `Cancel`
- `Delete Provider Config`
- optional `Delete and switch default provider` when another verified provider exists
Backup recommendation block (non-blocking but required):
- `openclaw backup create --verify`
- if config-only backup is preferred: `openclaw backup create --only-config --verify`
### 4B.16 Search-specific telemetry and visibility
Required event families:
- `gateway.search.config.changed`
- `gateway.search.state.changed`
- `gateway.search.probe.completed`
- `gateway.search.probe.failed`
- `gateway.search.execution.recorded`
- `gateway.search.fallback.recorded`
- `gateway.search.provider_config.deleted`
- `gateway.acp.system_state.changed`
- `gateway.acp.profile.saved`
- `gateway.acp.profile.deleted`
- `gateway.acp.doctor.completed`
- `gateway.acp.agent_runtime_assignment.changed`
- `gateway.acp.session.state_changed`
- `gateway.acp.session.option_changed`
- `gateway.acp.session.option_reset`
- `gateway.acp.session.closed`
- `gateway.acp.session.cancelled`
- `gateway.acp.session.resumed`
- `gateway.acp.attachments.rejected`
- `gateway.acp.unavailable`
- `gateway.channel.config.changed`
- `gateway.channel.state.changed`
- `gateway.channel.probe.completed`
- `gateway.channel.probe.failed`
- `gateway.channel.login.started`
- `gateway.channel.login.cancelled`
- `gateway.channel.logout.completed`
- `gateway.channel.pairing.approved`
- `gateway.channel.pairing.rejected`
- `gateway.channel.binding.changed`
- `gateway.channel.binding.deleted`
- `gateway.search.runtime_snapshot.refreshed`
Every search fallback or provider mismatch MUST be visible in:
- Settings > Web Search status surfaces,
- Runtime & Connectivity search card,
- search execution watermark/debug view,
- any relevant mismatch/fallback log entry.
### 4B.17 OpenClaw release alignment notes for web search
R12.3 aligns with the current documented OpenClaw web-search behavior by treating these as runtime-native concepts:
- `tools.web.search.enabled`
- `tools.web.search.provider`
- provider-specific credential fields,
- provider-specific optional fields such as Brave `llm-context` mode and Perplexity `baseUrl` / `model`,
- `tools.web.search.maxResults`,
- `tools.web.search.timeoutSeconds`,
- `tools.web.search.cacheTtlMinutes`,
- onboarding and `openclaw configure --section web` parity,
- provider-auto-detection when provider is unset and eligible credentials exist,
- published 2026.3.8 provider ordering neutrality in picker/config metadata,
- published 2026.3.8 Grok-before-Kimi native auto-detect preference when multiple eligible keys exist,
- published 2026.3.8 Perplexity OpenRouter / Sonar compatibility behavior where only `query` and `freshness` are supported on the compatibility path and Search-API-only filters produce explicit errors.
DOC11 MUST treat search-provider fallback chains as wrapper-composed unless a future OpenClaw build documents and proves native multi-provider fallback semantics.
## 4C) ACP / external harness configuration, agent runtime assignment, session truth, and operator controls
### 4C.1 Boundary and user-level posture
ACP is the **external specialist runtime lane** for agents that should use a supported external harness instead of the ordinary OpenClaw runtime path.
DOC11 owns:
- ACP system settings as surfaced in Q,
- ACP harness/profile configuration,
- agent-level runtime assignment truth,
- ACP session/runtime/execution watermark truth,
- operator-visible ACP limitations and repair/health surfaces.
DOC12 owns:
- ACP-backed participants as visible room participants,
- room-specific ACP degradation behavior,
- room/channel projection truth when ACP-backed agents participate in rooms.
Baseline operator posture:
- ACP is an **agent-level runtime choice**.
- Baseline compliant UI MUST NOT show a visible per-surface runtime override on ordinary chat/task/room/panel/forum surfaces.
- If an agent is configured for ACP/Coding Mode, every ordinary surface that uses that agent inherits ACP automatically.
- If an agent is configured for ordinary OpenClaw runtime, it remains ordinary OpenClaw runtime everywhere unless a non-baseline admin override exists.
### 4C.2 Agent runtime mode and inheritance rule
Use one explicit runtime mode per agent:
```ts
type AgentRuntimeMode = "openclaw" | "acp";
type AgentRuntimeAssignmentRecord = {
agent_id: string;
runtime_mode: AgentRuntimeMode;
acp_profile_id?: string;
fallback_if_acp_unavailable: "fail_closed";
badge_label?: string; // ACP only; native has no badge
schema_version: 1;
};
```
Required rules:
- Native/OpenClaw agents do **not** show a `Standard` badge.
- ACP agents MUST show an `ACP` or `ACP: <harness>` badge in headers/cards/drawers where the active agent is visible.
- Model selectors do **not** switch runtime mode.
- If `runtime_mode = "acp"`, the model selector edits ACP runtime model state only when the chosen harness/backend supports model overrides.
- If ACP is unavailable, DOC11 MUST fail closed and surface `ACP unavailable` or a more specific degraded/error state. No silent fallback to native runtime is allowed.
### 4C.3 ACP system state (canonical)
```ts
type AcpSystemState = {
enabled: boolean;
dispatch_enabled: boolean;
backend_id?: string;
default_harness_id?: string;
allowed_harness_ids: string[];
max_concurrent_sessions?: number;
stream?: {
coalesce_idle_ms?: number;
max_chunk_chars?: number;
};
runtime?: {
ttl_minutes?: number;
};
health_state: "healthy" | "degraded" | "unavailable" | "disabled" | "unknown";
last_doctor_at?: string;
last_doctor_summary?: string[];
schema_version: 1;
};
```
This maps to current OpenClaw ACP settings such as `acp.enabled`, `acp.dispatch.enabled`, `acp.backend`, `acp.defaultAgent`, `acp.allowedAgents`, `acp.maxConcurrentSessions`, and ACP stream/runtime TTL settings.
### 4C.4 ACP harness catalog entry (canonical)
```ts
type AcpHarnessCatalogEntry = {
harness_id: string; // e.g. claude, codex, opencode, gemini, pi, kimi
display_name: string;
backend_id: string;
allowed_by_policy: boolean;
install_state: "installed" | "not_installed" | "unknown";
health_state: "healthy" | "degraded" | "unavailable" | "unknown";
supports_model_override: "verified" | "unsupported" | "unknown";
supports_permissions_profile: "verified" | "unsupported" | "unknown";
supports_timeout: "verified" | "unsupported" | "unknown";
supports_cwd: "verified" | "unsupported" | "unknown";
supports_resume_session: "verified" | "unsupported" | "unknown";
supports_persistent_sessions: "verified" | "unsupported" | "unknown";
supports_inline_attachments: "unsupported";
sandbox_state: "host_only";
supports_strict_path_boundary: "verified" | "unsupported" | "unknown";
notes?: string[];
schema_version: 1;
};
```
Current built-in `acpx` harness aliases are `pi`, `claude`, `codex`, `opencode`, `gemini`, and `kimi` unless custom aliases are added at the backend level.
### 4C.5 ACP project roots and file-access-root selection
```ts
type AcpProjectRootEntry = {
root_id: string;
absolute_path: string;
resolved_realpath?: string;
label?: string;
role: "working_directory" | "reference_root";
enforcement_state:
| "runtime_applied"
| "advisory_only"
| "runtime_enforced_boundary";
existence_state: "present" | "missing" | "unverified";
};
```
Required rules:
- Q MUST provide an OS-native directory picker when editing ACP/Coding Mode profiles.
- On macOS, that picker SHOULD use the native Finder directory-selection UI.
- Multiple folder selection MUST be supported.
- Exactly one selected folder MAY be marked `working_directory`.
- Additional selected folders MUST be stored as `reference_root` entries.
- If the active ACP backend/runtime does not advertise verified strict multi-root boundaries, additional roots MUST render as `advisory_only` and the UI MUST say they are convenience/context roots, not hard security boundaries.
- Saved paths MUST store the absolute path and SHOULD store the resolved realpath for duplicate/symlink detection.
- Missing or moved roots MUST degrade the ACP profile and surface a repair state.
### 4C.6 ACP harness profile (canonical)
```ts
type AcpHarnessProfile = {
profile_id: string;
label: string;
harness_id: string;
backend_id: string;
enabled: boolean;
session_style: "persistent" | "oneshot";
default_model_ref?: string;
approval_policy_profile?: string;
timeout_seconds?: number;
project_roots: AcpProjectRootEntry[];
resume_behavior: "never" | "resume_if_bound" | "prefer_resume";
notes?: string;
capability_expectations?: {
supports_model_override?: "verified" | "unsupported" | "unknown";
supports_resume_session?: "verified" | "unsupported" | "unknown";
supports_cwd?: "verified" | "unsupported" | "unknown";
supports_strict_path_boundary?: "verified" | "unsupported" | "unknown";
};
schema_version: 1;
};
```
Required rules:
- Coding-oriented ACP profiles SHOULD have one `working_directory` root.
- `approval_policy_profile` maps to ACP `approval_policy` / permissions profile.
- `timeout_seconds` maps to ACP runtime timeout.
- `default_model_ref` applies only when the chosen harness/backend exposes verified model override support.
- `resume_behavior = "resume_if_bound"` is the recommended baseline for persistent coding agents.
### 4C.7 ACP session runtime state (canonical)
```ts
type AcpSessionRuntimeState = {
gateway_session_key: string;
agent_id: string;
harness_id: string;
backend_id: string;
acp_profile_id?: string;
state:
| "not_started"
| "starting"
| "active"
| "idle"
| "streaming"
| "cancelling"
| "closed"
| "expired"
| "failed"
| "degraded"
| "resumed";
session_style: "persistent" | "oneshot";
upstream_session_id?: string;
resumed_from_session_id?: string;
effective_model_ref?: string;
effective_approval_policy?: string;
effective_timeout_seconds?: number;
working_directory?: string;
project_roots_ref?: string;
attachment_input_support: "rejected";
sandbox_state: "host_only";
last_status_checked_at?: string;
last_error_codes?: string[];
schema_version: 1;
};
```
### 4C.7A ACP resume recovery and state semantics
`resumed` is a **transient** state. After the harness acknowledges resume and the first delta/heartbeat/acknowledgment arrives, the session MUST transition to `active`.
If no acknowledgment arrives within 30 seconds, the UI MUST surface `still_resuming`.
If no acknowledgment arrives within the configured hard timeout (default 90 seconds, minimum 30, maximum 300), transition to `failed` with `reason_codes = ["resume_ack_timeout"]`.
Resume recovery rules:
1. If `resume_behavior = "resume_if_bound"` or `"prefer_resume"`, the adapter MUST first attempt to resume the bound ACP session when one exists.
2. If the session is disconnected but reported as recoverable by the harness/backend, set state to `degraded`, retain the existing `gateway_session_key`, and retry up to **3 attempts over the configured hard-timeout window**.
3. If retry succeeds, transition `resumed -> active` and emit `gateway.acp.session.resumed`.
4. If the session is confirmed terminated or retries fail, emit `gateway.acp.unavailable` and enforce fail-closed behavior. The system MUST NOT silently fall back to standard OpenClaw runtime.
### 4C.8 ACP execution watermark extension rule
When an execution uses ACP/Coding Mode, `ExecutionWatermark` MUST additionally include:
- `runtime_mode = "acp"`,
- `acp_harness_id`,
- `acp_backend_id`,
- `acp_profile_id`,
- `acp_session_style`,
- `acp_upstream_session_id` when available,
- `acp_resumed: boolean`,
- `acp_effective_model_ref` when supported,
- `acp_effective_approval_policy` when supported,
- `acp_working_directory`,
- `acp_project_roots_ref`,
- `acp_attachment_input_support = "rejected"`,
- `acp_sandbox_state = "host_only"`.
These fields MUST remain absent for ordinary OpenClaw runtime executions.
### 4C.9 Operator-visible ACP limitations and honesty rule
DOC11 MUST surface these current ACP truths honestly:
- ACP sessions run on the host runtime, not inside the OpenClaw sandbox.
- Sandboxed requester sessions cannot spawn ACP sessions.
- `runtime="acp"` does not support `sandbox="require"`.
- Inline `sessions_spawn` attachments are supported only for `runtime="subagent"`; ACP rejects them.
- ACP file read/write/exec capability depends on backend permission policy and non-interactive permission handling.
Required UI copy for ACP profiles and active ACP sessions:
- `ACP runs on the host runtime, not inside the OpenClaw sandbox.`
- `Inline OpenClaw attachments are not supported on ACP runtime.`
- `Additional project roots may be advisory only unless the selected ACP backend verifies strict path-boundary support.`
### 4C.10 ACP route contracts
Required routes:
| Route | Purpose | Triggering UI/control | Owner service | Durable effect | Telemetry |
|---|---|---|---|---|---|
| `GET /api/openclaw/acp/system-state` | read ACP global state | settings ACP/runtime & connectivity | `AcpHarnessService` | none | view telemetry only |
| `GET /api/openclaw/acp/harnesses` | read harness catalog | settings ACP/agent editor | `AcpHarnessService` | none | view telemetry only |
| `GET /api/openclaw/acp/profiles` | read ACP profiles | settings ACP/agent editor | `AcpHarnessService` | none | view telemetry only |
| `PUT /api/openclaw/acp/profiles/:profile_id` | create/update ACP profile | settings ACP | `AcpHarnessService` | `acp_profile_store` | `gateway.acp.profile.saved` |
| `DELETE /api/openclaw/acp/profiles/:profile_id` | delete ACP profile | settings ACP | `AcpHarnessService` | `acp_profile_store` tombstone + affected-agent degradation | `gateway.acp.profile.deleted` |
| `POST /api/openclaw/acp/doctor` | run backend/harness doctor | settings ACP/runtime & connectivity | `AcpHarnessService` | `acp_system_state_store` | `gateway.acp.doctor.completed` |
| `GET /api/openclaw/acp/sessions` | list recent/active ACP sessions | settings ACP/debug | `AcpSessionService` | none | view telemetry only |
| `POST /api/openclaw/acp/sessions/:session_key/model` | set ACP session model | ACP session panel | `AcpSessionService` | `acp_session_runtime_state_store` + watermark | `gateway.acp.session.option_changed` |
| `POST /api/openclaw/acp/sessions/:session_key/permissions` | set ACP permissions profile | ACP session panel | `AcpSessionService` | `acp_session_runtime_state_store` + watermark | `gateway.acp.session.option_changed` |
| `POST /api/openclaw/acp/sessions/:session_key/timeout` | set ACP timeout | ACP session panel | `AcpSessionService` | `acp_session_runtime_state_store` + watermark | `gateway.acp.session.option_changed` |
| `POST /api/openclaw/acp/sessions/:session_key/cwd` | set ACP working directory | ACP session panel | `AcpSessionService` | `acp_session_runtime_state_store` + watermark | `gateway.acp.session.option_changed` |
| `POST /api/openclaw/acp/sessions/:session_key/reset-options` | clear ACP runtime overrides | ACP session panel | `AcpSessionService` | `acp_session_runtime_state_store` | `gateway.acp.session.option_reset` |
| `POST /api/openclaw/acp/sessions/:session_key/cancel` | cancel active ACP turn | ACP session panel/STOP | `AcpSessionService` | `acp_session_runtime_state_store` | `gateway.acp.session.cancelled` |
| `POST /api/openclaw/acp/sessions/:session_key/close` | close ACP session | ACP session panel | `AcpSessionService` | `acp_session_runtime_state_store` + binding close | `gateway.acp.session.closed` |
| `PATCH /api/openclaw/agents/:agent_id/runtime-mode` | set agent runtime mode | agent editor | `AcpHarnessService` | `agent_runtime_assignment_store` | `gateway.acp.agent_runtime_assignment.changed` |
| `PATCH /api/openclaw/agents/:agent_id/acp-profile` | assign ACP profile to agent | agent editor | `AcpHarnessService` | `agent_runtime_assignment_store` | `gateway.acp.agent_runtime_assignment.changed` |
### 4C.10A ACP request/result and read schemas
```ts
type AcpDoctorResult = {
checks: Array<{
check_name: string;
status: "pass" | "warn" | "fail";
message: string;
fix_hint?: string;
}>;
overall_health_state: AcpSystemState["health_state"];
doctor_completed_at: string;
schema_version: 1;
};
type AcpProfilesResponse = {
profiles: AcpHarnessProfile[];
schema_version: 1;
};
type AcpProfileMutationRequest = {
profile: AcpHarnessProfile;
source_surface_kind?: string;
source_surface_id?: string;
operation_id?: string;
schema_version: 1;
};
type AcpProfileDeleteRequest = {
profile_id: string;
confirmation_token: string;
active_binding_strategy: "block" | "switch_to_profile" | "force_remove";
replacement_profile_id?: string;
schema_version: 1;
};
type AcpProfileMutationResult = {
ok: true;
profile_id: string;
warning_codes?: string[];
affected_scope_refs?: string[];
schema_version: 1;
};
type AgentRuntimeModePatchRequest = {
agent_id: string;
runtime_mode: "openclaw" | "acp";
acp_profile_id?: string;
schema_version: 1;
};
type AgentRuntimeModePatchResult = {
agent_id: string;
accepted: boolean;
runtime_mode: "openclaw" | "acp";
acp_profile_id?: string;
reason_codes?: string[];
schema_version: 1;
};
type AcpSessionOptionChangeResult = {
gateway_session_key: string;
accepted: boolean;
effective_value?: unknown;
requires_session_restart: boolean;
reason_codes?: string[];
schema_version: 1;
};
type AcpSessionResumeResult = {
gateway_session_key: string;
accepted: boolean;
resumed: boolean;
soft_threshold_ms: number;
hard_timeout_ms: number;
reason_codes?: string[];
schema_version: 1;
};
type AcpSessionListResponse = {
sessions: AcpSessionRuntimeState[];
schema_version: 1;
};
type AcpSessionDetailReadResult = {
session: AcpSessionRuntimeState;
execution_watermark_ref?: string;
recent_reason_codes?: string[];
schema_version: 1;
};
type AcpSessionHistoryReadResult = {
gateway_session_key: string;
entries: Array<{
entry_id: string;
role: "system" | "user" | "assistant" | "tool";
created_at: string;
text?: string;
truncated?: boolean;
}>;
schema_version: 1;
};
type AcpDurableBinding = {
binding_id: string;
binding_scope_kind: "chat" | "task" | "room_participant" | "panel" | "forum" | "channel_thread" | "channel_topic";
binding_scope_ref: string;
gateway_session_key?: string;
upstream_session_id?: string;
acp_profile_id?: string;
state: "bound" | "degraded" | "broken" | "closed";
schema_version: 1;
};
type AcpPathValidationRequest = {
absolute_path: string;
schema_version: 1;
};
type AcpPathValidationResult = {
absolute_path: string;
resolved_realpath?: string;
exists: boolean;
is_directory: boolean;
is_readable: boolean;
reason_codes?: string[];
schema_version: 1;
};
```
Atomic assignment rule:
- switching an agent to `runtime_mode = "acp"` and assigning `acp_profile_id` MUST be executed atomically through one mutation request,
- the backend MUST reject `runtime_mode = "acp"` when no valid `acp_profile_id` is provided.
Required route additions:
- `GET /api/openclaw/acp/profiles` → `AcpProfilesResponse`
- `PUT /api/openclaw/acp/profiles/:profile_id` → `AcpProfileMutationRequest` / `AcpProfileMutationResult`
- `DELETE /api/openclaw/acp/profiles/:profile_id` → `AcpProfileDeleteRequest` / `AcpProfileMutationResult`
- `GET /api/openclaw/acp/sessions` → `AcpSessionListResponse`
- `GET /api/openclaw/acp/sessions/:session_key` → `AcpSessionDetailReadResult`
- `GET /api/openclaw/acp/sessions/:acp_session_id/history` → `AcpSessionHistoryReadResult`
- `POST /api/openclaw/acp/sessions/:session_key/resume` → `AcpSessionResumeResult`
- `POST /api/openclaw/acp/validate-path` → `AcpPathValidationResult`
### 4C.11 Settings > ACP / External Harnesses contract
Q MUST expose a dedicated **Settings > ACP / External Harnesses** surface.
Required subsections:
- ACP global status card (`enabled`, `dispatch_enabled`, backend, allowed harnesses, max concurrent sessions, runtime TTL),
- harness catalog table with install/health/capability columns,
- ACP profile table/editor,
- active/recent ACP sessions table,
- doctor/install guidance pane,
- explicit limitations pane,
- affected-agent degradation list when a profile is invalid or removed.
### 4C.12 Agent editor ACP/Coding Mode contract
Agent editor MUST include a **Runtime Mode** control with exactly two baseline user-facing options:
- `OpenClaw Runtime`
- `ACP/Coding Mode`
If `ACP/Coding Mode` is selected, the editor MUST show:
- ACP profile picker,
- harness display,
- model field only when supported,
- permissions profile picker,
- timeout field,
- primary working directory picker,
- additional project roots list with add/remove/reorder controls,
- enforcement badges per root,
- session style (`Persistent` / `One-shot`),
- limitations note (`host-side`, `attachments unsupported`, `sandbox unsupported`).
There MUST NOT be a normal user-facing per-chat/per-task/per-room runtime-mode toggle elsewhere in baseline DOC11-compliant UI.
### 4C.13 ACP/Coding Mode badge and visibility rule
Required surface behavior:
- ACP-backed agents show an `ACP` or `ACP: <harness>` badge.
- Ordinary OpenClaw runtime agents show no runtime-mode badge.
- Headers/drawers/debug surfaces MUST still expose exact runtime truth when requested.
- Runtime badges MUST appear in chat headers, task headers, room participant chips/drawers, panel/forum headers, and agent cards when the active agent is visible.
### 4C.14 ACP-specific telemetry and read-model visibility
Required events:
- `gateway.acp.system_state.changed`
- `gateway.acp.profile.saved`
- `gateway.acp.profile.deleted`
- `gateway.acp.doctor.completed`
- `gateway.acp.agent_runtime_assignment.changed`
- `gateway.acp.session.state_changed`
- `gateway.acp.session.option_changed`
- `gateway.acp.session.option_reset`
- `gateway.acp.session.closed`
- `gateway.acp.session.cancelled`
- `gateway.acp.session.resumed`
- `gateway.acp.attachments.rejected`
- `gateway.acp.unavailable`
## 4D) Discord and WhatsApp channel configuration, account state, pairing/login truth, and channel-to-agent binding
### 4D.1 Channel-configuration boundary
DOC11 owns the operator-facing seam between Q settings and OpenClaw channel configuration/runtime truth for Discord and WhatsApp.
DOC11 owns:
- config mutation routes,
- runtime and account state read-models,
- login/pairing/probe truth,
- default-account truth,
- channel-account → agent binding truth,
- ACP-related channel binding visibility where supported by the runtime.
DOC12 owns:
- room projection into Discord/WhatsApp,
- room/channel projection capability limits,
- room narration/speaker-prefix truth.
### 4D.1A Room-to-channel projection flow and ownership rule
When a room, panel, or forum transcript is projected to Discord or WhatsApp, the data flow MUST be:
1. DOC12/room orchestration chooses the projection target and prepares the projection content.
2. DOC12 formats the outbound text according to the target channel capability profile (for example: narrated transcript, speaker-prefix mode, thread reply, or plain single-stream message).
3. DOC12 calls DOC11 channel dispatch through a normalized route-trace-bearing request.
4. DOC11 delivers the formatted payload through the OpenClaw channel runtime.
5. DOC11 records channel delivery success/failure telemetry and updates channel/account runtime truth.
Canonical transport contract:
```ts
type ChannelProjectionDispatchRequest = {
projection_id: string;
source_surface_kind: "room" | "panel" | "forum";
source_surface_id: string;
provider_id: "discord" | "whatsapp";
account_id: string;
target_ref: {
channel_id?: string;
thread_id?: string;
peer_id?: string;
group_id?: string;
};
projection_mode:
| "discord_thread"
| "discord_channel"
| "whatsapp_dm"
| "whatsapp_group"
| "single_stream_narrated"
| "speaker_prefixed";
formatted_text: string;
participant_attribution: Array<{
participant_id: string;
display_name: string;
}>;
route_trace_id: string;
correlation_id: string;
schema_version: 1;
};
type ChannelProjectionDispatchResult = {
projection_id: string;
accepted: boolean;
provider_id: "discord" | "whatsapp";
account_id: string;
delivery_ref?: string;
reason_codes?: string[];
schema_version: 1;
};
```
Normative ownership rule:
- DOC12 owns projection policy and formatted content.
- DOC11 owns transport, delivery telemetry, account truth, and channel runtime truth.
- DOC11 MUST NOT rewrite projection content other than provider-required transport normalization.
- DOC12 MUST NOT bypass DOC11 to talk directly to channel runtimes.
Required route addition:
- `POST /api/openclaw/channels/projected-dispatch` → `ChannelProjectionDispatchResult`
### 4D.2 Shared channel runtime, account, and rule truth (canonical)
```ts
type ChannelRuntimeState = {
channel_kind: "discord" | "whatsapp" | string;
enabled: boolean;
connection_state: "connected" | "connecting" | "disconnected" | "degraded" | "unknown";
health_state: "healthy" | "degraded" | "failed" | "unknown";
login_state?: "not_configured" | "token_required" | "qr_required" | "pairing_required" | "linking" | "linked" | "connected" | "degraded" | "disconnected" | "error";
pairing_state?: "none" | "pending" | "approved" | "rejected" | "expired";
challenge_state?: "none" | "issued" | "refreshed" | "awaiting_scan" | "awaiting_approval" | "viewed" | "cancelled" | "expired" | "error";
capability_state?: "full" | "limited" | "unsupported" | "missing_intents" | "unknown";
account_state?: "none" | "single" | "multiple" | "unknown";
default_account_id?: string;
active_account_id?: string;
last_connected_at?: string;
last_probe_at?: string;
missing_capabilities?: string[];
last_error_codes?: string[];
pairing_challenge_id?: string;
channel_specific?: Record<string, unknown>;
schema_version: 1;
};
type ChannelPairingChallenge = {
challenge_id: string;
provider_id: "discord" | "whatsapp";
account_id?: string;
challenge_state: ChannelRuntimeState["challenge_state"];
challenge_type: "qr_data" | "qr_image_base64" | "pair_code" | "oauth_url" | "link_url" | "none";
challenge_payload?: string;
expires_at?: string;
refresh_supported: boolean;
cancel_supported: boolean;
ack_expiry_supported: boolean;
reason_codes?: string[];
schema_version: 1;
};
type ChannelAccountEntry = {
account_id: string;
provider_id: "discord" | "whatsapp" | string;
display_name?: string;
is_default: boolean;
credential_source_kind?: "config" | "secret_ref" | "env" | "file" | "exec" | "qr_login" | "none" | "unknown";
connection_state: "connected" | "degraded" | "disconnected" | "unknown";
bound_agent_id?: string;
bound_agent_runtime_mode?: "openclaw" | "acp";
last_inbound_at?: string;
last_outbound_at?: string;
last_error_codes?: string[];
schema_version: 1;
};
type ChannelAccountsResponse = {
accounts: ChannelAccountEntry[];
schema_version: 1;
};
type ChannelRuleEntry = {
rule_id: string;
provider_id: "discord" | "whatsapp" | string;
scope_kind: "guild" | "group" | "dm";
scope_ref: string;
allow_from?: string[];
require_mention?: boolean;
dm_policy?: "pairing" | "allowlist" | "open" | "disabled";
group_policy?: "allowlist" | "open" | "disabled";
schema_version: 1;
};
type ChannelRuleReadResult = {
rule: ChannelRuleEntry;
schema_version: 1;
};
type ChannelRuleListResult = {
rules: ChannelRuleEntry[];
schema_version: 1;
};
```
Semantic rules:
- `connection_state` is transport connection truth,
- `health_state` is aggregate operational truth and may be degraded while transport remains connected,
- `pairing_state` is the canonical persistent runtime truth (`none` / `pending` / `approved` / `rejected` / `expired`),
- transient QR/login challenge progress MUST use `challenge_state`, not overload `pairing_state`,
- provider-specific login/logout details remain nested under the shared channel runtime model.
### 4D.3 Channel account binding truth (canonical)
```ts
type ChannelAccountBindingRecord = {
provider_id: "discord" | "whatsapp";
account_id: string;
default_agent_id: string;
binding_kind: "route";
notes?: string;
schema_version: 1;
};
```
Baseline DOC11 requires account-default routing/binding truth. More granular peer/channel/thread/topic bindings may exist later, but they are not required for baseline compliance unless separately specified.
### 4D.4 DiscordChannelConfig (canonical)
```ts
type DiscordGuildRule = {
guild_id: string;
require_mention?: boolean;
ignore_other_mentions?: boolean;
allowed_user_ids?: string[];
allowed_role_ids?: string[];
channel_rules?: Array<{
channel_id: string;
allow?: boolean;
require_mention?: boolean;
}>;
};
type DiscordChannelConfig = {
enabled: boolean;
token_source_kind?: "config" | "secret_ref" | "env" | "none" | "unknown";
default_account_id?: string;
dm_policy?: "pairing" | "allowlist" | "open" | "disabled";
allow_from?: string[];
group_policy?: "allowlist" | "open" | "disabled";
guild_rules?: DiscordGuildRule[];
thread_bindings?: {
enabled: boolean;
idle_hours?: number;
max_age_hours?: number;
spawn_subagent_sessions?: boolean;
spawn_acp_sessions?: boolean;
};
proxy?: string;
config_writes?: boolean;
schema_version: 1;
};
```
### 4D.5 WhatsAppChannelConfig (canonical)
```ts
type WhatsAppGroupRule = {
group_id: string; // may be "*"
require_mention?: boolean;
};
type WhatsAppChannelConfig = {
enabled: boolean;
default_account_id?: string;
dm_policy: "pairing" | "allowlist" | "open" | "disabled";
allow_from?: string[];
group_policy: "allowlist" | "open" | "disabled";
group_allow_from?: string[];
group_rules?: WhatsAppGroupRule[];
text_chunk_limit?: number;
chunk_mode?: "length" | "newline";
media_max_mb?: number;
send_read_receipts?: boolean;
web?: {
enabled?: boolean;
heartbeat_seconds?: number;
reconnect?: {
initial_ms?: number;
max_ms?: number;
factor?: number;
jitter?: number;
max_attempts?: number;
};
};
accounts?: Record<string, {
enabled?: boolean;
auth_dir?: string;
dm_policy?: "pairing" | "allowlist" | "open" | "disabled";
allow_from?: string[];
send_read_receipts?: boolean;
}>;
schema_version: 1;
};
```
### 4D.5A WhatsApp QR pairing data flow
When `login_state = "qr_required"`, the adapter MUST provide QR data through `ChannelRuntimeState` and the active `ChannelPairingChallenge` for inline Q rendering.
Required QR rendering rules:
- Q MUST render the QR code from `qr_data` using a client-side QR renderer, OR display `qr_image_base64` directly when supplied.
- Q MUST display a countdown using `qr_expires_at`.
- Q MUST refresh QR state through `qr_refresh_url` or `POST /api/openclaw/channels/whatsapp/qr/refresh` when expiry is reached.
- While a QR challenge is active, Q MUST refresh `ChannelRuntimeState` at 3-second intervals unless a push event updates the state sooner.
- A successful scan MUST transition challenge progress through `challenge_state = "awaiting_scan" -> "none"` and login truth through `login_state = "qr_required" -> "linking" -> "linked" -> "connected"`.
- Cancelling pairing MUST call the documented cancel route and transition to `challenge_state = "cancelled"`, while `pairing_state` remains the persistent pairing truth and `login_state` returns to `"not_configured"` or `"disconnected"` depending on previous link state.
Required UI copy:
- `Scan this QR code with WhatsApp on your phone.`
- `Expires in: <countdown>`
- Buttons: `[Refresh QR] [Cancel Pairing]`
Required layout:
```text
WhatsApp Pairing
Status: Awaiting QR scan
[ QR CODE BLOCK ]
Scan this QR code with WhatsApp on your phone.
Expires in: 42s
[Refresh QR] [Cancel Pairing]
```
### 4D.6 Login, pairing, and connected-state truth rule
DOC11 MUST distinguish these states visibly and semantically:
- configuration saved,
- account linked/logged in,
- DM pairing required/pending,
- connected and ready,
- degraded/disconnected.
Provider-specific truths:
- Discord requires bot token configuration and first-DM pairing approval for unknown senders when pairing mode is in effect.
- WhatsApp requires QR login/linking; DM pairing may still be required depending on `dmPolicy`.
Connected-state badges MUST NOT collapse these states into one fake `Connected` pill.
Discord vs WhatsApp semantics:
- Discord is token/bot connectivity. It uses `Clear Token`, `Disable Discord`, and optional `Disconnect Bot` semantics.
- WhatsApp is account/session connectivity. It uses `Start Login`, `Cancel Login`, `Refresh QR`, `Relink`, `Logout`, and `Refresh State` semantics.
- DOC11 MUST NOT use one generic `logout` abstraction across both channels.
### 4D.6A Interactive channel pairing challenge transport
```ts
type ChannelPairingChallengeResponse = {
challenge: ChannelPairingChallenge;
runtime_state_ref: string;
schema_version: 1;
};
type ChannelPairingChallengeRefreshRequest = {
provider_id: "discord" | "whatsapp";
account_id?: string;
challenge_id: string;
schema_version: 1;
};
type ChannelPairingChallengeCancelRequest = {
provider_id: "discord" | "whatsapp";
account_id?: string;
challenge_id: string;
cancel_reason: "user_cancelled" | "timeout" | "superseded";
schema_version: 1;
};
type ChannelPairingChallengeAckExpiryRequest = {
provider_id: "discord" | "whatsapp";
account_id?: string;
challenge_id: string;
schema_version: 1;
};
type ChannelPairingChallengeMutationResult = {
accepted: boolean;
challenge_id: string;
pairing_state: "none" | "pending" | "approved" | "rejected" | "expired";
challenge_state: "issued" | "refreshed" | "awaiting_scan" | "awaiting_approval" | "viewed" | "cancelled" | "expired" | "error";
login_state: ChannelRuntimeState["login_state"];
runtime_state_ref?: string;
reason_codes?: string[];
schema_version: 1;
};
type ChannelErrorResponse = {
error_code:
| "CHANNEL_NOT_ENABLED"
| "ACCOUNT_NOT_FOUND"
| "CHALLENGE_NOT_FOUND"
| "CHALLENGE_EXPIRED"
| "REFRESH_NOT_SUPPORTED"
| "CANCEL_NOT_SUPPORTED"
| "INVALID_REQUEST"
| "INTERNAL_ERROR";
message: string;
reason_codes?: string[];
schema_version: 1;
};
```
### 4D.7 Channel route contracts
Required routes:
| Route | Purpose | Triggering UI/control | Owner service | Durable effect | Telemetry |
|---|---|---|---|---|---|
| `GET /api/openclaw/channels/state` | read shared channel runtime/account state | settings channels/runtime & connectivity | `ChannelConfigService` | none | view telemetry only |
| `GET /api/openclaw/channels/accounts` | read channel account list | settings channels | `ChannelConfigService` | none | view telemetry only |
| `GET /api/openclaw/channels/accounts` | read channel account list | settings channels | `ChannelConfigService` | none | view telemetry only |
| `GET /api/openclaw/channels/bindings` | read channel account -> agent bindings | settings channels | `ChannelConfigService` | none | view telemetry only |
| `PUT /api/openclaw/channels/bindings/:provider_id/:account_id` | set default agent binding for account | settings channels | `ChannelConfigService` | `channel_binding_store` | `gateway.channel.binding.changed` |
| `DELETE /api/openclaw/channels/bindings/:provider_id/:account_id` | clear default agent binding | settings channels | `ChannelConfigService` | `channel_binding_store` | `gateway.channel.binding.deleted` |
| `GET /api/openclaw/channels/discord/config` | read Discord config | settings channels | `ChannelConfigService` | none | view telemetry only |
| `GET /api/openclaw/channels/discord/rules` | read Discord guild/group rules | settings channels | `ChannelConfigService` | none | view telemetry only |
| `PUT /api/openclaw/channels/discord/config` | save Discord config | settings channels | `ChannelConfigService` | `channel_provider_config_store` + runtime apply | `gateway.channel.config.changed` |
| `POST /api/openclaw/channels/discord/probe` | probe Discord runtime health | settings channels/runtime & connectivity | `ChannelConfigService` | `channel_runtime_state_store` | `gateway.channel.probe.completed` / `gateway.channel.probe.failed` |
| `GET /api/openclaw/channels/discord/rules` | read Discord guild/group rules | settings channels | `ChannelConfigService` | none | view telemetry only |
| `GET /api/openclaw/channels/discord/pairings` | read pending Discord pairings | settings channels | `ChannelPairingService` | none | view telemetry only |
| `POST /api/openclaw/channels/discord/pairings/:pair_code/approve` | approve Discord DM pairing | settings channels | `ChannelPairingService` | pairing store/runtime allow store | `gateway.channel.pairing.approved` |
| `POST /api/openclaw/channels/discord/pairings/:pair_code/reject` | reject Discord DM pairing | settings channels | `ChannelPairingService` | pairing store/runtime allow store | `gateway.channel.pairing.rejected` |
| `POST /api/openclaw/channels/discord/clear-token` | clear Discord token | settings channels | `ChannelConfigService` | provider config store + runtime apply | `gateway.channel.config.changed` |
| `POST /api/openclaw/channels/discord/disconnect` | disconnect Discord bot transport | settings channels | `ChannelConfigService` | channel runtime state | `gateway.channel.state.changed` |
| `GET /api/openclaw/channels/whatsapp/config` | read WhatsApp config | settings channels | `ChannelConfigService` | none | view telemetry only |
| `GET /api/openclaw/channels/whatsapp/rules` | read WhatsApp group rules | settings channels | `ChannelConfigService` | none | view telemetry only |
| `PUT /api/openclaw/channels/whatsapp/config` | save WhatsApp config | settings channels | `ChannelConfigService` | `channel_provider_config_store` + runtime apply | `gateway.channel.config.changed` |
| `GET /api/openclaw/channels/whatsapp/rules` | read WhatsApp group rules | settings channels | `ChannelConfigService` | none | view telemetry only |
| `POST /api/openclaw/channels/whatsapp/login/start` | start WhatsApp QR login/link flow | settings channels | `ChannelConfigService` | transient login session + runtime apply | `gateway.channel.login.started` / `gateway.channel.pairing_challenge_issued` |
| `POST /api/openclaw/channels/whatsapp/login/cancel` | cancel WhatsApp login flow | settings channels | `ChannelConfigService` | transient login session | `gateway.channel.login.cancelled` / `gateway.channel.pairing_challenge_cancelled` |
| `POST /api/openclaw/channels/whatsapp/qr/refresh` | refresh WhatsApp QR challenge | settings channels | `ChannelConfigService` | transient pairing challenge | `gateway.channel.pairing_challenge_refreshed` |
| `POST /api/openclaw/channels/whatsapp/relink` | relink WhatsApp account | settings channels | `ChannelConfigService` | runtime relink + account state change | `gateway.channel.state.changed` |
| `POST /api/openclaw/channels/whatsapp/logout` | logout linked WhatsApp account | settings channels | `ChannelConfigService` | runtime logout + account state change | `gateway.channel.logout.completed` / `gateway.channel.state.changed` |
| `GET /api/openclaw/channels/whatsapp/login-state` | read WhatsApp login/link state | settings channels | `ChannelConfigService` | none | view telemetry only |
| `GET /api/openclaw/channels/whatsapp/pairings` | read pending WhatsApp pairings | settings channels | `ChannelPairingService` | none | view telemetry only |
| `POST /api/openclaw/channels/whatsapp/pairings/:pair_code/approve` | approve WhatsApp pairing | settings channels | `ChannelPairingService` | pairing store/runtime allow store | `gateway.channel.pairing.approved` |
| `POST /api/openclaw/channels/whatsapp/pairings/:pair_code/reject` | reject WhatsApp pairing | settings channels | `ChannelPairingService` | pairing store/runtime allow store | `gateway.channel.pairing.rejected` |
| `GET /api/openclaw/channels/pairing-challenges/:challenge_id` | read current pairing challenge | settings channels | `ChannelPairingService` | none | `gateway.channel.pairing_challenge_viewed` |
Canonical mutation/result contracts:
```ts
type ChannelConfigMutationResult = {
ok: true;
provider_id: "discord" | "whatsapp" | string;
runtime_state_ref?: string;
reason_codes?: string[];
schema_version: 1;
};
type ChannelProbeResult = {
ok: true;
states: ChannelRuntimeState[];
schema_version: 1;
};
type ChannelBindingMutationResult = {
provider_id: "discord" | "whatsapp";
account_id: string;
accepted: boolean;
bound_agent_id?: string;
bound_agent_runtime_mode?: "openclaw" | "acp";
reason_codes?: string[];
schema_version: 1;
};
type ChannelRuleMutationRequest = {
provider_id: "discord" | "whatsapp" | string;
rule: ChannelRuleEntry;
schema_version: 1;
};
type ChannelRuleMutationResult = {
ok: true;
provider_id: "discord" | "whatsapp" | string;
rule_id: string;
reason_codes?: string[];
schema_version: 1;
};
type WhatsAppLoginStartResult = {
provider_id: "whatsapp";
accepted: boolean;
runtime_state_ref?: string;
pairing_challenge?: ChannelPairingChallenge;
reason_codes?: string[];
schema_version: 1;
};
type WhatsAppLoginStateResponse = {
runtime_state: ChannelRuntimeState;
schema_version: 1;
};
type ChannelLogoutResult = {
provider_id: "discord" | "whatsapp";
account_id?: string;
accepted: boolean;
runtime_state_ref?: string;
reason_codes?: string[];
schema_version: 1;
};
type ChannelPairingActionResult = {
provider_id: "discord" | "whatsapp";
pair_code: string;
accepted: boolean;
resulting_status: ChannelPairingQueueEntry["status"];
runtime_state_ref?: string;
reason_codes?: string[];
schema_version: 1;
};
type ChannelMutationRequest = {
channel_kind: string;
account_id?: string;
schema_version: 1;
};
type ChannelMutationResult = {
ok: true;
channel_kind: string;
state_ref?: string;
warning_codes?: string[];
schema_version: 1;
};
type ChannelPairingChallengeResult = {
ok: true;
challenge: ChannelPairingChallenge;
schema_version: 1;
};
type ChannelProbeRequest = {
channel_kind?: string;
account_id?: string;
schema_version: 1;
};
```
Required read/config route bindings:
- `GET /api/openclaw/channels/accounts` -> `ChannelAccountsResponse`
- `GET /api/openclaw/channels/discord/rules` -> `ChannelRuleListResult`
- `GET /api/openclaw/channels/whatsapp/rules` -> `ChannelRuleListResult`
- `PUT /api/openclaw/channels/discord/config` -> `ChannelConfigMutationResult`
- `PUT /api/openclaw/channels/whatsapp/config` -> `ChannelConfigMutationResult`
- `GET /api/openclaw/channels/pairing-challenges/:challenge_id` -> `ChannelPairingChallengeResult`
Set Default Account closure rule:
- changing a default-account selector MUST persist `default_account_id` through the provider config mutation route, not through local-only UI state,
- Discord default-account changes MUST round-trip through `PUT /api/openclaw/channels/discord/config`,
- WhatsApp default-account changes MUST round-trip through `PUT /api/openclaw/channels/whatsapp/config`,
- success UI MUST wait for refreshed authoritative runtime/account state before rendering the new default account.
Logout/destructive-flow backup rule:
- logout, token removal, and binding-clearing actions MUST show the destructive-action backup block from §10.14 when the action could sever an actively bound channel/account.
All failures MUST return `ChannelErrorResponse`.
### 4D.8 Settings > Channels contract
Q MUST expose a dedicated **Settings > Channels** surface.
Required structure:
- provider overview cards for Discord and WhatsApp,
- account list/status area,
- per-provider configuration editor,
- default-account selector when multiple accounts exist,
- default-agent binding editor for each account,
Default-account selector rule:
- changing the default-account selector MUST call the provider config mutation route for that provider and MUST NOT remain a local-only UI state.
- login/pairing queue subpanes,
- runtime health / last error banner,
- last inbound/outbound timestamps,
- destructive actions (logout/remove binding/clear token where applicable).
Discord-specific required controls:
- enabled toggle,
- token / SecretRef field,
- default account selector,
- DM/group policy controls,
- guild/channel rule table including `requireMention`,
- thread binding controls including `spawnAcpSessions` when runtime reports support,
- pairing queue,
- probe/test action.
WhatsApp-specific required controls:
- enabled toggle,
- QR login / relink / cancel / logout controls,
- default account selector,
- DM/group policy controls,
- allowlists,
- group rule table,
- chunk/media/read-receipt controls,
- pairing queue,
- connection status and reconnect diagnostics.
Required layout for Discord:
```text
Settings > Channels > Discord
Status card:
- Discord: Enabled / Disabled
- Health: Connected / Degraded / Disconnected / Not Configured
- Capability state: Full / Missing Intents / Pairing Only / Unknown
- Default account: BotName#1234
- Last connected: 2m ago
- [Test Connection] [Probe]
Token configuration:
- Token source: Gateway Store / SecretRef / Env
- Token: •••••••••• [Update] [Remove]
- Warning shown when capability_state = Missing Intents:
Connected, but missing Message Content Intent in Discord Developer Portal.
Account list:
| Account | Default | Bound Agent | Runtime | Status |
|--------------|---------|-------------|----------|-------------|
| BotName#1234 | Yes | Elnor | OpenClaw | Connected |
Guild rules:
| Guild ID | Require Mention | Ignore Others | Actions |
|----------|-----------------|---------------|---------|
| 123... | Yes | Yes | [Edit] |
Pairing queue:
| User | Requested | Status | Actions |
|-----------------|-----------|---------|---------------------|
| user@discord | 5m ago | Pending | [Approve] [Reject] |
Danger zone:
- [Disconnect Bot]
- [Clear Token]
- [Remove Binding]
```
Required layout for WhatsApp:
```text
Settings > Channels > WhatsApp
Status card:
- WhatsApp: Enabled / Disabled
- Health: Connected / Degraded / Disconnected / QR Required
- Default account: +1234567890
- Last connected: 10m ago
- [Relink] [Logout] [Refresh State]
QR login area (shown when qr_required):
[ QR CODE BLOCK ]
Scan this QR code with WhatsApp on your phone.
Expires in: 42s
[Refresh QR] [Cancel Pairing]
Account list:
| Account | Default | Bound Agent | Runtime | Status |
|--------------|---------|-------------|----------|-----------|
| +1234567890 | Yes | Elnor | OpenClaw | Connected |
Policies:
- DM policy: [pairing ▾]
- Group policy: [allowlist ▾]
Group rules:
| Group Name | Group ID | Policy | Actions |
|------------|----------|--------|---------|
| Dev Team | g_abc123 | Allow | [Edit] |
Pairing queue:
| Contact | Requested | Status | Actions |
|-------------|-----------|---------|---------------------|
| +9876543210 | 2m ago | Pending | [Approve] [Reject] |
```
Channel binding truth rule:
- The account list MUST show the currently bound agent and that agent’s runtime mode.
- If the bound agent is ACP-backed, the row MUST show `ACP:<harness>` or the ACP badge.
- The channel page MUST NOT imply that the channel itself is ACP-enabled; only the bound agent/runtime is ACP-backed.
### 4D.9 Channel settings truth and ACP interaction rule
If a Discord or WhatsApp account is bound to an agent whose `runtime_mode = "acp"`, channel-delivered sessions using that agent MUST show ACP runtime truth exactly like any other ACP-backed surface.
Required rules:
- binding a channel account to an ACP-backed agent does **not** change the channel settings UI into an ACP control panel; it changes the agent/runtime used when that channel routes work to that agent.
- channel settings MUST show the bound agent id and that agent’s runtime mode.
- no fake `ACP enabled for channel` state may be shown unless the bound agent is actually ACP-backed.
### 4D.9A Channel-safe outbound projection rule
When room/panel/forum/task/chat output is projected into a channel, the outbound payload MUST use `channel_safe_text`.
`channel_safe_text` rule:
- strip orchestration/debug wrappers,
- strip internal trace/debug markers,
- preserve ordinary human-readable text and citations/source references when allowed,
- never expose hidden runtime/debug scaffolding to channel recipients,
- channel-safe projection MUST be applied before dispatch to Discord or WhatsApp.
### 4D.10 Channel telemetry and visibility
Required events:
- `gateway.channel.config.changed`
- `gateway.channel.state.changed`
- `gateway.channel.probe.completed`
- `gateway.channel.probe.failed`
- `gateway.channel.login.started`
- `gateway.channel.login.cancelled`
- `gateway.channel.logout.completed`
- `gateway.channel.pairing.approved`
- `gateway.channel.pairing.rejected`
- `gateway.channel.binding.changed`
- `gateway.channel.binding.deleted`
```ts
type ChannelPairingQueueEntry = {
provider_id: "discord" | "whatsapp";
account_id?: string;
pair_code: string;
requester_display?: string;
requester_ref?: string;
requested_at: string;
expires_at?: string;
status: "pending" | "approved" | "rejected" | "expired";
challenge_id?: string;
schema_version: 1;
};
type ChannelPairingQueueResponse = {
entries: ChannelPairingQueueEntry[];
schema_version: 1;
};
```
Discord missing-intents truth rule:
- A successful token verification is not sufficient for `health_state = "healthy"`.
- If Discord authentication succeeds but required read capability is absent, the runtime MUST set:
- `health_state = "degraded"`
- `capability_state = "missing_intents"`
- `missing_capabilities` to the exact missing intent/capability names
- `last_error_codes` including `discord_missing_message_content_intent` when applicable.
- Q MUST render a visible warning: `Connected, but missing Message Content Intent in Discord Developer Portal.`
- Q MUST NOT show a green verified state when `capability_state = "missing_intents"`.
Pairing-challenge truth rule:
- Q MUST render pairing/login state from `ChannelRuntimeState.pairing_challenge` and `pairing_state` when present.
- Q MUST NOT invent a private polling or websocket payload format outside the schema above.
- Refresh, cancel, and expiry behavior MUST come from the challenge fields or the documented routes in §4D.7.
## 5) DOC11 adapter contracts (normative)
### 5.1 GatewayChatDispatchRequest
```ts
type GatewayChatDispatchRequest = {
operation_id: string;
correlation_id: string;
conversation_id?: string;
source_surface_kind: "chat" | "panel" | "forum" | "task" | "voice";
source_surface_id: string;
gateway_session_key?: string;
participant_id?: string;
user_message: string;
messages?: SharedConversationMessageSchema[];
attachments?: AttachmentDescriptor[];
model_ref?: string;
desired_control_state_ref?: string;
effective_state_ref?: string;
auth_profile_id?: string;
auth_method_kind?: ProviderAuthMethodKind;
fallback_policy_id?: string;
stream_preference: "stream" | "final_only";
dispatch_max_output_tokens_cap?: number;
context_manifest_ref?: string;
bootstrap_packet_ref?: string;
input_mode?: "typed" | "voice" | "programmatic";
schema_version: 1;
};
```
Rules:
- if `model_ref` is supplied on dispatch, it MUST already be fully qualified before the request leaves Q/EC,
- if the selected model/provider path requires explicit auth routing, `auth_profile_id` MUST be supplied or derivable from canonical runtime state,
- `dispatch_max_output_tokens_cap` is the orchestrator-imposed execution cap and is distinct from any user preference field,
- if `messages` is supplied, the adapter MUST either forward the audited native equivalent or normalize it into `user_message` plus context injection without fabricating extra runtime truth,
- if `stream_preference = "final_only"`, the UI MUST NOT simulate live streaming.
Baseline search rule: `GatewayChatDispatchRequest` does not carry a search-provider override in the current baseline. Native `web_search` uses effective `tools.web.search` runtime configuration automatically.
### 5.2 chat.send adapter→native mapping
| DOC11 field | Native field | Forwarded? | Notes |
|---|---|---:|---|
| `gateway_session_key` | `sessionKey` | yes | required for existing session |
| `user_message` | `message` | yes | model-visible text |
| `attachments` | `attachments` | yes | only verified descriptors |
| `stream_preference = stream` | `deliver=true` | yes | if runtime supports streaming |
| `stream_preference = final_only` | `deliver=false` | yes | explicit final-only mode |
| `desired_control_state_ref` | none | no | consumed by adapter before dispatch |
| `effective_state_ref` | none | no | consumed by adapter for verification/correlation |
| `context_manifest_ref` | none | no | debug/audit only |
| `bootstrap_packet_ref` | none | no | adapter resolves into context injection |
| `auth_profile_id` | runtime/provider-specific | maybe | forwarded only if runtime path supports it or adapter uses it to resolve auth |
### 5.3 Interactive chat dispatch result
```ts
type GatewayChatDispatchResult = {
accepted: boolean;
run_id?: string;
gateway_session_key?: string;
execution_watermark_ref?: string;
stream_mode: "stream" | "final_only";
route_trace_id?: string;
reason_codes?: string[];
schema_version: 1;
};
```
### 5.4 GatewayRoomTurnDispatchRequest
```ts
type GatewayRoomTurnDispatchRequest = {
operation_id: string;
correlation_id: string;
room_id: string;
room_turn_id: string;
participant_id: string;
gateway_session_key?: string;
room_prompt: string;
attachments?: AttachmentDescriptor[];
desired_control_state_ref?: string;
effective_state_ref?: string;
auth_profile_id?: string;
auth_method_kind?: ProviderAuthMethodKind;
fallback_policy_id?: string;
stream_preference: "stream" | "final_only";
dispatch_max_output_tokens_cap?: number;
context_manifest_ref?: string;
schema_version: 1;
};
```
### 5.5 GatewayRoomTurnDispatchResult
```ts
type GatewayRoomTurnStreamDelta = {
room_turn_id: string;
participant_id: string;
run_id: string;
sequence: number;
delta_text?: string;
event_kind: "delta" | "heartbeat" | "tool" | "metadata";
schema_version: 1;
};
type RoomTurnCompletedResult = {
room_turn_id: string;
participant_id: string;
run_id?: string;
status: "completed" | "failed" | "aborted";
execution_watermark_ref?: string;
usage_sample_ref?: string;
reason_codes?: string[];
schema_version: 1;
};
type GatewayRoomTurnDispatchResult = {
accepted: boolean;
stream_mode: "stream" | "final_only";
run_id?: string;
stream?: AsyncIterable<GatewayRoomTurnStreamDelta>;
onComplete?: Promise<RoomTurnCompletedResult>;
reason_codes?: string[];
schema_version: 1;
};
```
### 5.6 Room-turn no-fallback rule
Room-turn routing MUST NOT silently flatten per-participant runtime truth into a single room-level fake execution. If a participant cannot execute, the room turn must degrade, skip, or repair explicitly.
### 5.7 AbortRequest / AbortResult
```ts
type AbortRequest = {
operation_id: string;
correlation_id: string;
abort_scope_kind: "run" | "session" | "participant" | "room";
abort_scope_ref: string;
room_id?: string;
participant_id?: string;
run_id?: string;
schema_version: 1;
};
type AbortReceipt = {
abort_id: string;
accepted: boolean;
abort_state: "abort_pending" | "abort_verified" | "abort_unknown" | "abort_failed";
cleanup_state?: "cleanup_pending" | "cleanup_completed" | "cleanup_failed";
schema_version: 1;
};
```
### 5.8 Once-dispatched loop ownership rule
Once a dispatch is accepted and a `run_id` exists, OpenClaw owns the live execution loop for that run. EC/Q MUST NOT:
- inject additional context into that run,
- issue a second `chat.send` for the same run,
- retroactively patch execution truth for that run,
- fabricate replacement output while the run remains live.
Violation MUST emit `gateway.loop_hamstringing_attempted`.
---
## 6) Context classes, protected native context, budgets, manifests, and inspector
### 6.1 Two distinct upstream context classes
1. **OpenClaw-native context**: runtime-owned system prompt, session history, native/plugin system context, context-engine output, protected files.
2. **EC/Q-contributed context**: lean annotations, bootstrap packets, room bootstrap additions, selected memory refs, declared attachment metadata.
### 6.2 Protected native context sources
Protected native context includes, at minimum:
- `SOUL.md`
- OpenClaw-native session/system prompt state
- plugin `prependSystemContext` / `appendSystemContext`
- context-engine assembled native summaries/indices
- native memory/plugin-managed context not owned by EC/Q
- any runtime-owned prompt fragments generated during native compaction
EC/Q MUST treat these as read-only.
### 6.3 ContextSourceRef
```ts
type ContextSourceRef = {
source_id: string;
source_kind:
| "bootstrap_packet"
| "cil"
| "room_document"
| "search_result"
| "local_file"
| "attachment_ref"
| "native_memory"
| "plugin_system_context"
| "acp_ingress"
| "manual_note"
| "unknown";
title?: string;
path_or_ref?: string;
token_estimate?: number;
byte_length?: number;
provenance_mode?: "verified" | "inferred" | "unknown" | "interim_synthesized";
stale?: boolean;
duplicate_of?: string;
protected_native?: boolean;
created_at?: string;
schema_version: 1;
};
```
### 6.4 Lean annotation packet
Lean EC annotation packets MUST stay lean, structured, and source-referenced. They MUST NOT attempt to replicate full history or protected native context.
### 6.5 Bootstrap packet
Bootstrap packets MAY be larger than lean annotations but remain EC/Q-owned context. They are still subject to budgeting and manifesting.
### 6.5A Context injection transport mechanism
For the audited Gateway runtime, EC/Q context reaches the agent through these paths:
| Context class | Transport mechanism | Native method | Notes |
|---|---|---|---|
| Lean annotation packet | Prepend to `message` field in `chat.send` | `chat.send` | Text is prepended before the user's message with a clear delimiter. |
| Bootstrap packet (fresh surface) | `chat.inject` before first `chat.send` on a new/reset session | `chat.inject` | Inject creates a system-context entry before the first user turn. |
| Bootstrap packet (room/panel/forum) | `chat.inject` before the first participant turn | `chat.inject` | Same mechanism, scoped to the participant's bound session. |
| Attachment context | Inline in `chat.send.attachments[]` | `chat.send` | Verified attachment descriptors only. |
| Protected native context | NOT injected by EC/Q | N/A | OpenClaw injects its own native/plugin/context-engine context. |
Lean annotation prepend format:
```text
[ELNOR_CONTEXT_V2]
{lean annotation JSON or structured text}
[/ELNOR_CONTEXT_V2]
{user's actual message}
```
Normative rules:
- The manifest MUST record the transport mechanism used: `transport_mode = "message_prepend" | "chat_inject" | "attachment" | "native_only"`.
- If `chat.inject` is used, the inject call MUST complete before `chat.send` begins.
- The adapter MUST NOT fire `chat.inject` and `chat.send` concurrently for the same bootstrap packet.
- Bootstrap injections SHOULD include a `label` parameter such as `room_bootstrap`, `panel_bootstrap`, or `forum_bootstrap`.
- Protected native context MUST NEVER be re-expressed through `message_prepend` or `chat_inject` by EC/Q.
### 6.6 Attachment transport modes
Attachment modes are defined in §4.18 and Appendix D. Unsupported modes MUST be shown honestly.
### 6.7 Dynamic context budget model
Budgeting applies only to EC/Q-contributed context. Protected native context is not clipped by EC/Q budget policy.
### 6.8 Initial budget profiles
```ts
const CONTEXT_BUDGET_PROFILES = {
default: { soft_cap_tokens: 4000, hard_cap_tokens: 5500 },
lean: { soft_cap_tokens: 2500, hard_cap_tokens: 3200 },
constrained_local: { soft_cap_tokens: 1200, hard_cap_tokens: 1800 },
room_bootstrap: { soft_cap_tokens: 3500, hard_cap_tokens: 5000 },
testing_unbounded: { soft_cap_tokens: null, hard_cap_tokens: null }
};
```
### 6.9 Budget computation algorithm
```text
ecq_soft_budget =
min(profile.soft_cap_tokens, model_context_window
- native_session_reserve
- expected_output_reserve
- safety_margin
- protected_native_context_estimate
- attachment_estimate_tokens
- room_bootstrap_reserve)
ecq_hard_budget =
min(profile.hard_cap_tokens, ecq_soft_budget + spillover_allowance)
If any required factor is unknown:
- set budget_confidence = inferred or unknown
- record the missing factor explicitly
- never pretend verified precision
```
### 6.10 ContextBudgetResolutionRecord (canonical)
```ts
type ContextBudgetResolutionRecord = {
resolution_id: string;
scope_kind: "session" | "participant" | "room_turn";
scope_ref: string;
budget_profile_id: keyof typeof CONTEXT_BUDGET_PROFILES;
model_context_window?: number;
native_context_estimate_tokens?: number;
native_context_estimate_confidence?: "verified" | "inferred" | "unknown";
estimation_method?: "runtime_reported" | "context_endpoint" | "file_size_heuristic" | "engine_metadata" | "unknown";
ecq_soft_budget_tokens?: number | null;
ecq_hard_budget_tokens?: number | null;
dropped_source_ids?: string[];
reason_codes?: string[];
budget_confidence: "verified" | "inferred" | "unknown";
created_at: string;
schema_version: 1;
};
```
### 6.11 Stale / duplicate / over-injection prevention
Manifest builder MUST:
- detect duplicate source payloads,
- detect superseded/stale route hints,
- reject repeated full-history injection,
- prefer source refs over repeated content,
- record drops and degradation reasons explicitly.
### 6.12 ContextInjectionManifest (canonical)
```ts
type AcpIngressProvenance = {
provenance_mode: "verified" | "inferred" | "unknown";
source_session_trace_id?: string;
source_runtime_kind?: "native" | "acp" | "unknown";
receipt_ref?: string;
schema_version: 1;
};
type ContextInjectionManifest = {
manifest_id: string;
scope_kind: string;
scope_ref: string;
included_source_ids: string[];
protected_native_source_ids: string[];
context_budget_resolution_id?: string;
created_at: string;
acp_ingress_provenance?: AcpIngressProvenance;
included_sources: ContextSourceRef[];
dropped_sources: Array<{ source_ref: ContextSourceRef; drop_order: number; reason_codes: string[] }>;
stale_sources: Array<{ source_ref: ContextSourceRef; reason_codes: string[] }>;
duplicate_sources: Array<{ source_ref: ContextSourceRef; duplicate_of: string; reason_codes: string[] }>;
protected_native_sources: Array<{ source_ref: ContextSourceRef; protection_reason: string; reason_codes: string[] }>;
overlap_sources: Array<{ source_ref: ContextSourceRef; overlap_with: string[]; reason_codes: string[] }>;
truncated_sources: Array<{ source_ref: ContextSourceRef; reason_codes: string[] }>;
degraded_sources: Array<{ source_ref: ContextSourceRef; reason_codes: string[] }>;
transport_rejected_sources: Array<{ source_ref: ContextSourceRef; reason_codes: string[] }>;
transport_mode: "message_prepend" | "chat_inject" | "attachment" | "native_only";
budget_resolution_ref?: string;
native_context_estimate_mode: "verified" | "inferred" | "unknown";
native_context_engine_id?: string;
schema_version: 1;
};
```
Canonical manifest rule:
- the legacy top-level fields (`scope_kind`, `scope_ref`, `included_source_ids`, `protected_native_source_ids`, `context_budget_resolution_id`, `created_at`, `acp_ingress_provenance`) remain required and MUST NOT be dropped,
- the structured arrays are additive and are the UI/debug-friendly expansion of the same manifest truth,
- `drop_order` is required for `dropped_sources` so the Context Inspector can render the actual budget-drop sequence.
Canonical wrapper rule:
- wrapper arrays (`stale_sources`, `duplicate_sources`, `protected_native_sources`) are authoritative for manifest rendering,
- the compatibility fields on `ContextSourceRef` exist for standalone and legacy-compatible uses and MUST NOT contradict wrapper-level classification.
Canonicalization note:
- `transport_mode` is not a new concept in R14; it is being promoted from prior normative prose into the canonical type definition.
### 6.13 Context dry-run / explain endpoint
`POST /api/openclaw/context/explain` MUST exist. In developer/debug mode it is mandatory. It must return:
- proposed manifest,
- budget resolution,
- dropped/degraded reasons,
- native-context estimate confidence,
- overlap summary.
### 6.14 Native context estimation order
Use this order:
1. latest runtime-built system prompt/context report,
2. `/context` estimate from OpenClaw,
3. selected context-engine estimate metadata,
4. `unknown`.
Concrete estimation heuristic when runtime/engine facts are not available:
```text
estimateNativeContext(workspace_path, session_key):
1. if runtime reports native context size:
return { tokens: runtime_value, confidence: "verified", method: "runtime_reported" }
2. if /context or equivalent runtime estimate exists:
return { tokens: context_value, confidence: "inferred" or "verified", method: "context_endpoint" }
3. if workspace directory is accessible:
read file sizes for protected/native prompt files such as:
- SOUL.md
- AGENTS.md
- TOOLS.md
- IDENTITY.md
- USER.md
- HEARTBEAT.md
- BOOTSTRAP.md
total_chars = summed file size in characters
estimated_tokens = ceil(total_chars / 3.2)
safety_buffer = ceil(estimated_tokens * 0.20)
return { tokens: estimated_tokens + safety_buffer, confidence: "inferred", method: "file_size_heuristic" }
4. if selected context engine exposes estimate metadata:
return { tokens: engine_estimate, confidence: "inferred", method: "engine_metadata" }
5. otherwise:
return { tokens: null, confidence: "unknown", method: "unknown" }
```
Remote/inaccessible workspace rule:
- if the runtime is remote or the workspace directory is not directly accessible, the heuristic MUST return `confidence = "unknown"` unless the runtime itself supplies an estimate.
- Q MUST display: `Native context pressure unknown — runtime estimate unavailable.`
### 6.15 Protected native context interception points
Hard guard points MUST exist in:
- annotation builder,
- bootstrap packet builder,
- summary generator,
- compaction helper,
- repair tooling,
- upload preprocessing,
- dedupe/truncation passes.
If a protected source is targeted for rewrite/summarize/compact:
- throw `ProtectedNativeContextViolationError`,
- emit `gateway.protected_native_context.violation`,
- mark manifest degraded/error,
- surface violation in Q diagnostics.
### 6.16 Native context engine truth
If OpenClaw reports a non-legacy context engine, DOC11 MUST surface:
- `native_context_engine_id`
- `native_context_engine_kind`
- `native_context_engine_plugin_id?`
- `native_context_engine_owns_compaction`
- `native_context_engine_estimate_mode`
- `native_context_engine_last_bootstrapped_at?`
DOC11 MUST treat this as runtime-discovered truth, not a baseline assumption.
### 6.16A Context-engine health and visibility
Because Runtime & Connectivity and Context Inspector surface native context engine truth in R14, the following are standard rather than optional:
- `gateway.context_engine.health.changed` event family,
- `context_engine_runtime_state_store`,
- visible context-engine health card or row in Runtime & Connectivity,
- visible context-engine status in Context Inspector when a non-legacy engine is active.
Canonical payload minimum:
- `event_id`
- `occurred_at`
- `native_context_engine_id`
- `health_state = "healthy" | "degraded" | "unavailable" | "unknown"`
- `reason_codes?`
### 6.17 Context revalidation on switch/rebind
Context and capability caches MUST be invalidated when:
- model changes,
- provider changes,
- auth profile changes,
- session rebind/remint occurs,
- context engine changes,
- `contextTokens` become stale after model switch,
- attachment set changes.
### 6.18 Context inspector requirement
Q MUST provide a context inspector showing:
- native vs EC/Q contribution split,
- native pressure state,
- included/dropped/deduped/truncated/degraded sources,
- dry-run/explain entry point,
- protected native context warnings,
- ACP ingress provenance / visible receipt metadata when runtime reports it,
- engine identity and estimate confidence.
Required card layout:
```text
Context Budget
- Native (OpenClaw): ~8,200 tokens (67% window) [verified|inferred|unknown]
- Engine: legacy | plugin:<id>
- Pressure: Normal | Elevated | High | Unknown
- EC/Q contributed: 2,100 / 4,000 cap
- Dropped: forum_summary (stale)
- Deduped: route_hint_17
Buttons: [Dry Run] [Show Dropped] [Show Overlap] [View Protected Files]
```
---
## 7) Usage and cost producer seams (DOC13 handoff)
### 7.1 UsageSample (canonical)
```ts
type UsageSample = {
usage_sample_id: string;
scope_kind: "session" | "participant" | "room_turn";
scope_ref: string;
room_id?: string;
room_turn_id?: string;
participant_id?: string;
run_id?: string;
dispatch_mode?: "gateway" | "acp";
acp_harness_id?: string;
acp_profile_id?: string;
acp_session_id?: string;
search_execution_id?: string;
channel_provider_id?: "discord" | "whatsapp";
channel_account_id?: string;
input_tokens?: number;
output_tokens?: number;
total_tokens?: number;
usage_state: "known" | "partial" | "unknown";
cost_truth_mode: "verified" | "estimated" | "unknown";
total_cost_usd?: number;
switch_segment_id?: string;
created_at: string;
schema_version: 1;
};
```
ACP usage attribution rule:
- When `dispatch_mode = "acp"`, DOC13/Q cost surfaces MUST still attribute execution frequency, latency, and harness/profile identity even when `usage_state = "unknown"`.
- Q MUST distinguish:
- `Unknown (ACP harness does not report usage)`
- `Unknown (Gateway/provider did not return usage)`
- `acp_harness_id` and `acp_profile_id` MUST remain available on the execution watermark path and the usage path.
Channel/search attribution rule:
- `search_execution_id` links search-provider execution truth to any later cost/usage or debug view.
- `channel_provider_id` and `channel_account_id` identify the originating route/account when a usage sample came from a channel-delivered interaction.
- channel mutations/login/pairing events themselves MUST NOT create fake usage samples.
### 7.2 Producer rules
DOC11 MUST emit usage on:
- `completed`,
- `failed`,
- `aborted`
when provider-native usage exists. If no usage exists, emit `usage_state = unknown` explicitly.
### 7.3 max_output split
Two different concepts MUST remain separate:
- `desired_max_output_tokens_user` - settings/desire only, unsupported natively unless audited,
- `dispatch_max_output_tokens_cap` - orchestration-imposed provider-call cap for budget enforcement.
`dispatch_max_output_tokens_cap` MUST be forwarded to provider calls when present.
### 7.4 SwitchSegment (canonical)
```ts
type SwitchSegment = {
switch_segment_id: string;
scope_kind: "session" | "participant" | "room_turn";
scope_ref: string;
previous_execution_watermark_id?: string;
next_execution_watermark_id?: string;
switch_reason:
| "fallback"
| "auth_change"
| "model_change"
| "provider_change"
| "rebind"
| "abort_recovery"
| "transport_change";
created_at: string;
schema_version: 1;
};
```
### 7.5 Usage producer event for DOC13
```ts
type UsageSampleRecordedEvent = {
usage_sample_id: string;
room_id?: string;
participant_id?: string;
switch_segment_id?: string;
surface_kind?: string;
surface_id?: string;
input_tokens?: number;
output_tokens?: number;
total_tokens?: number;
usage_state: UsageSample["usage_state"];
cost_truth_mode: UsageSample["cost_truth_mode"];
schema_version: 1;
};
```
DOC13 MUST consume either `usage_sample_store` or the normalized internal `usage.sample.recorded` event. Implementers MUST pick one and not support both half-heartedly.
### 7.6 Room and participant subtotal stores
Required stores:
- `room_cost_subtotal_store`
- `participant_cost_subtotal_store`
Required UI surfaces:
- room header cost chip,
- participant drawer subtotal row,
- running jobs / activity list cost column,
- budget warning modal.
---
ACP subtotal visibility rule:
- ACP-backed executions MUST still contribute to room/participant execution counts and latency totals even when token/cost truth is `unknown`.
- subtotal cards MUST show `Cost unknown` rather than `Cost $0.00` when harness usage is unavailable.
## 8) Streaming and reverse telemetry / execution-truth bridge
### 8.1 Streaming contract
Streaming support MUST be explicit per surface. A surface MAY ship initially in `final_only` mode if:
- runtime truth badge says `final_only`,
- no fake typing indicator is shown,
- no simulated delta stream is shown,
- terminal completion/failure/abort truth still arrives normally.
### 8.2 Streaming delta payload minimum
```ts
type StreamingDeltaPayload = {
run_id: string;
sequence: number;
delta_text?: string;
event_kind: "delta" | "heartbeat" | "tool" | "metadata";
truncated?: boolean;
hidden_line_count?: number;
raw_output_ref?: string;
occurred_at: string;
schema_version: 1;
};
```
Tool-output truncation rule:
- When tool output is truncated for UI-performance reasons, the payload MUST set:
- `truncated = true`
- `hidden_line_count`
- `raw_output_ref` when a fuller raw/log view is available.
- Q MUST render a visible block such as:
- `[… 49,900 lines hidden for performance. View raw output.]`
- Q MUST NOT silently collapse output without surfacing the truncation state.
### 8.3 Gap detection and replay
A stream gap exists if:
- a required sequence number is skipped, or
- no delta or heartbeat arrives for 1500 ms while run remains active.
On gap:
1. mark stream as degraded,
2. attempt replay from `replay_cursor`,
3. if replay succeeds, emit `gateway.stream.recovered`,
4. otherwise keep degraded state and reconcile on final event.
### 8.4 Required event families (canonical list)
The combined canonical event-family set for DOC11 is defined by:
- this section,
- the mapping table in §8.5A,
- and Appendix C payload minima.
At minimum, DOC11 MUST support these family names:
Core control/chat/room families:
- `gateway.control.mutated`
- `gateway.control.rejected`
- `gateway.chat.accepted`
- `gateway.chat.stream.delta`
- `gateway.chat.completed`
- `gateway.chat.failed`
- `gateway.chat.aborted`
- `gateway.tool.started`
- `gateway.tool.stdout_stream`
- `gateway.tool.completed`
- `gateway.tool.failed`
- `gateway.tool.unclassified`
- `room.turn.accepted`
- `room.turn.stream.delta`
- `room.turn.completed`
- `room.turn.failed`
- `room.turn.aborted`
Abort/runtime-truth families:
- `gateway.abort.requested`
- `gateway.abort.acknowledged`
- `gateway.abort.timeout`
- `gateway.abort.cleanup_failed`
- `runtime_truth.resolved`
- `runtime_truth.resolve_conflict_detected`
Auth/search/local/ACP/channel families:
- `gateway.auth.challenge_required`
- `gateway.auth.state_changed`
- `gateway.auth.profile_removed`
- `gateway.local_provider.config.changed`
- `gateway.local_provider.state.changed`
- `gateway.local_provider.probe.completed`
- `gateway.local_provider.probe.failed`
- `gateway.local_provider.catalog.synced`
- `gateway.local_provider.config.deleted`
- `gateway.local_provider.reverted_to_implicit_discovery`
- `gateway.local_provider.model_metadata.updated`
- `gateway.local_provider.direct_bypass.blocked`
- `gateway.search.config.changed`
- `gateway.search.state.changed`
- `gateway.search.probe.completed`
- `gateway.search.probe.failed`
- `gateway.search.execution.recorded`
- `gateway.search.fallback.recorded`
- `gateway.search.provider_config.deleted`
- `gateway.acp.system_state.changed`
- `gateway.acp.profile.saved`
- `gateway.acp.profile.deleted`
- `gateway.acp.doctor.completed`
- `gateway.acp.agent_runtime_assignment.changed`
- `gateway.acp.session.state_changed`
- `gateway.acp.session.option_changed`
- `gateway.acp.session.option_reset`
- `gateway.acp.session.closed`
- `gateway.acp.session.cancelled`
- `gateway.acp.session.resumed`
- `gateway.acp.attachments.rejected`
- `gateway.acp.unavailable`
- `gateway.channel.config.changed`
- `gateway.channel.state.changed`
- `gateway.channel.probe.completed`
- `gateway.channel.probe.failed`
- `gateway.channel.login.started`
- `gateway.channel.login.cancelled`
- `gateway.channel.logout.completed`
- `gateway.channel.pairing.approved`
- `gateway.channel.pairing.rejected`
- `gateway.channel.binding.changed`
- `gateway.channel.binding.deleted`
- `gateway.channel.projected_dispatch.accepted`
- `gateway.channel.projected_dispatch.failed`
Context/capability/mismatch/usage families:
- `gateway.capability.check.completed`
- `gateway.capability_cache.invalidated`
- `gateway.context.applied`
- `gateway.context.degraded`
- `gateway.context.diagnostics.viewed`
- `gateway.context.budget_settings.changed`
- `gateway.mismatch.recorded`
- `gateway.fallback.recorded`
- `gateway.switch_segment.opened`
- `gateway.switch_segment.closed`
- `gateway.protected_native_context.violation`
- `gateway.loop_hamstringing_attempted`
- `usage.sample.recorded`
```ts
Canonical auth-event rule:
- `gateway.auth.challenge_failed` is the **challenge-scoped** failure event and MUST remain canonical.
- `gateway.auth.state_changed` is the **aggregate auth-state** event and MUST also be emitted when a challenge failure changes effective auth state.
- On challenge failure, emit both events when both scopes are materially true.
### 8.4A Health event delineation rule
`gateway.health.changed` is the aggregate gateway-level event. It is used for Runtime & Connectivity aggregate health and high-level reconnect/degraded visibility.
Provider/channel/local subsystem events remain distinct, including but not limited to:
- `gateway.provider.health.degraded`
- `gateway.provider.health.recovered`
- `gateway.local_provider.state.changed`
- `gateway.channel.state.changed`
Implementations MUST NOT treat `gateway.health.changed` as a replacement for subsystem events. It is an aggregate signal.
### 8.4B Master SSE event-family enumeration requirement
R14 MUST contain one compiled master enumeration of all `gateway.*`, `room.turn.accepted`, `room.turn.stream.delta`, `room.turn.completed`, `room.turn.failed`, `room.turn.aborted`, and other DOC11-consumed SSE event families.
Minimum required families include:
- control mutation,
- session lifecycle,
- runtime truth,
- provider auth,
- local/self-hosted providers,
- web search,
- ACP,
- channels,
- context/budget,
- capability/attachments,
- streaming,
- abort,
- usage/cost,
- gateway/provider health,
- context-engine health.
The master enumeration MUST cross-reference Appendix C payload minima and any read-model refresh expectations.
### 8.5 Reverse telemetry envelope
```ts
type GatewayReverseTelemetryEnvelope = {
event_id: string;
event_family: string;
occurred_at: string;
operation_id?: string;
correlation_id: string;
route_trace_id?: string;
gateway_session_key?: string;
run_id?: string;
tool_call_id?: string;
search_execution_id?: string;
participant_id?: string;
room_id?: string;
room_turn_id?: string;
payload: Record<string, unknown>;
schema_version: 1;
};
```
### 8.5A Native event → DOC11 event family mapping table
| Native/runtime signal | DOC11 event family | Required projection target | Notes |
|---|---|---|---|
| `chat.delta` / incremental token event | `gateway.chat.stream.delta` | `stream_state_store`, `execution_watermark_store` | Preserve sequence/order. |
| `chat.completed` | `gateway.chat.completed` | `execution_watermark_store`, `usage_sample_store` | Terminal success. |
| `chat.failed` | `gateway.chat.failed` | `execution_watermark_store`, `usage_sample_store` | Terminal failure. |
| `chat.aborted` | `gateway.chat.aborted` | `abort_state_store`, `execution_watermark_store` | Terminal abort. |
| room turn delta | `room.turn.stream.delta` | `stream_state_store`, participant/room projections | Room/participant scoped. |
| room turn completed | `room.turn.completed` | room/participant execution truth | Terminal success. |
| `sessions.patch` success | `gateway.control.mutated` | `effective_runtime_state_store`, `control_mutation_queue_store` | Control mutation verified/pending truth. |
| `sessions.patch` reject | `gateway.control.rejected` | `control_mutation_queue_store` | Include rejected field list. |
| auth challenge/start/finish | `gateway.auth.challenge_required` / `gateway.auth.state_changed` | auth stores + indicators | Preserve challenge ids. |
| capability probe result | `gateway.capability.check.completed` | `capability_cache_store` | Include TTL + reason codes. |
| context apply result | `gateway.context.applied` | `context_manifest_store` | Include manifest id. |
| context degradation/violation | `gateway.context.degraded` / `gateway.protected_native_context.violation` | context stores + diagnostics | Preserve degradation reasons. |
| provider disconnect/recover | `gateway.provider.health.degraded` / `gateway.provider.health.recovered` | `local_provider_runtime_state_store`, indicators | Overrides stale probe cache immediately. |
| search execution | `gateway.search.execution.recorded` | `search_execution_watermark_store` | Include provider/mode/fallback truth. |
| search fallback | `gateway.search.fallback.recorded` | `search_execution_watermark_store`, mismatch/fallback log | No silent fallback. |
| ACP session state change | `gateway.acp.session.state_changed` | `acp_session_runtime_state_store`, indicators | Preserve session id/state. |
| channel login/pairing state change | `gateway.channel.state.changed` | `channel_runtime_state_store`, `channel_pairing_queue_store` | Includes QR/pairing transitions. |
| usage sample recorded | `usage.sample.recorded` | `usage_sample_store`, subtotal stores | Canonical DOC13 seam. |
Immediate health-override rule for local/self-hosted providers:
- UI MUST NOT rely solely on probe polling for local/self-hosted provider health.
- On daemon disconnect, transport loss, or explicit runtime degradation, the runtime MUST emit:
- `gateway.provider.health.degraded`
- and later `gateway.provider.health.recovered` when restored.
- These push events MUST override any cached `healthy` probe result immediately.
- Selector rows and local-provider cards MUST disable dispatch affordances when a push degradation event is active.
### 8.6 Event payload minima rule
Each event family MUST define payload minima in Appendix C. `payload: object` is not sufficient.
### 8.7 Read-model rule
EC MUST project reverse telemetry into read-models; Q MUST render from read-models, not directly from transient event streams alone.
### 8.8 V1 streaming posture
Q chat and room surfaces MAY use streaming or final-only per surface, but the current mode MUST be visible and truthful.
---
## 9) Abort and STOP propagation
### 9.1 Abort scopes
Supported scopes:
- `run`
- `session`
- `participant`
- `room`
### 9.2 Abort state machine
Required states and transitions:
- `abort_pending -> abort_verified` on runtime ack or terminal aborted event,
- `abort_pending -> abort_unknown` after 10 s without ack,
- `abort_unknown -> abort_verified` if aborted terminal arrives later,
- `abort_unknown -> abort_failed` on explicit failure/cleanup timeout,
- `abort_verified -> cleanup_pending` only if post-abort cleanup is required,
- `cleanup_pending -> cleanup_completed | cleanup_failed`.
ACP session lifecycle additions:
- `closing` is a real transitional state.
- `cancelling` is a real transitional state.
- `cancelling -> failed` and `closing -> failed` are valid transitions.
- `live_idle -> live_degraded` is valid when runtime truth or upstream harness truth becomes uncertain while no dispatch is active.
- `live_degraded -> live_idle` is valid after successful verification when no dispatch/mutation is active.
- `live_degraded -> live_active` is valid only when a new dispatch or resume completes and the session is actively processing.
```ts
ACP resume rule:
- At `soft_resume_threshold_ms`, UI MUST surface `Still resuming…` and keep the session in `resuming`.
- At `hard_resume_timeout_ms`, emit `gateway.acp.session.resume_failed` unless a terminal success/failure already arrived.
- Retry/backoff behavior is internal unless a later UI spec explicitly exposes it.
### 9.3 Global STOP interop
Global STOP surfaces may project room/session/participant abort affordances, but the underlying abort must still resolve to a real scope and real abort receipt.
### 9.4 Abort honesty rule
Q MUST render:
- `abort_pending`
- `abort_verified`
- `abort_unknown`
- `abort_failed`
- optional cleanup state.
A STOP button may never optimistically show “stopped” before abort truth arrives.
---
## 10) Q-facing requirements, indicators, and debug surfaces
### 10.1 Required surfaces
Q MUST include:
- global runtime indicator,
- page-level runtime indicators,
- settings > providers & models,
- settings > local / self-hosted providers,
- settings > web search,
- settings > ACP / external harnesses,
- settings > channels,
- settings > context & budget,
- settings > runtime & connectivity,
- participant runtime drawer,
- context inspector,
- mismatch/fallback log viewer,
- capability viewer,
- auth challenge presentation,
- truth resolver debug view,
- streaming/abort state displays.
### 10.2 Global indicator rule
Global indicator MUST be aggregate, not singular fake truth.
Required render states:
- `No active agents`
- `1 agent · verified`
- `3 agents · 2 verified · 1 auth expired`
- `2 agents · 1 degraded`
- `1 agent · final-only`
Color/threshold guidance:
- green only when all visible active scopes are `verified`,
- amber when any visible active scope is `pending`, `stale`, `mismatch`, or `fallback`,
- red when any visible active scope is `orphaned`, `auth_invalid`, `auth_expired`, or `binding_failed`,
- gray when there are no active scopes or truth is `unknown`.
Required global indicator state examples:
```text
Single active native agent:
"Elnor · claude-sonnet-4-5 ✅"
Single active ACP agent:
"Scout · ACP:claude ✅"
Single active agent, pending mutation:
"Elnor · switching… ⏳"
Multi-agent, all verified:
"3 agents ✅"
Multi-agent, one degraded:
"2 agents ✅ · 1 degraded ⚠️"
Multi-agent, one auth issue:
"2 agents ✅ · 1 auth expired ❌"
No active agents:
"No active agents"
Gateway offline:
"Gateway offline ❌"
```
System-agent visibility rule:
- system/internal agents that are not user-visible on the current surface MUST NOT inflate the top-level agent count.
- their issues MUST still appear on the Runtime & Connectivity page.
### 10.3 Page-level indicator rule
Chat/panel/forum/task surfaces MUST show runtime truth for the currently relevant scope. Room surfaces MUST show both aggregate room truth and participant-specific truth via drawer/accessory chips.
ACP-aware participant-chip rule:
- In rooms, panels, and forums, participant chips MUST show ACP badge truth when the participant runtime mode is ACP.
- If the participant is not ACP-backed, show no runtime-mode badge.
- ACP chip presence MUST derive from `ParticipantRuntimeStatus.runtime_mode` or the ACP execution/runtime watermark path, never from agent naming conventions.
### 10.4 Participant Runtime Drawer requirement
Drawer MUST show:
- agent/participant identity,
- runtime status badge,
- desired/effective/executed control lines,
- auth provenance,
- usage subtotal,
- native context pressure,
- mismatch/fallback summary,
- room action affordances only when DOC12 grants them,
- repair actions for orphan/degraded state.
Required drawer layout:
```text
Header: Agent Name + status badge
Model line: anthropic/claude-sonnet-4-5 Verified
Controls line: Think High · Reasoning On · Verbose Off
Settings-only line: Temperature 0.7 · Max tokens 4096 (greyed, settings-only)
Auth line: CLI-managed / OAuth / Scope limited
Usage line: Messages 42 · Cost $0.23 · Last run 30s ago · TTFT 1.2s
Native context line: ~8K tokens (inferred) · Pressure Normal
Buttons: [Change Model] [Context Inspector] [Mismatch Log] [Repair]
Room policy action row: [Mute] [Pause] [Remove] only if DOC12 capability says true
```
ACP-variant drawer layout (when `runtime_mode = "acp"`):
```text
Header: Agent Name + ACP:claude badge
Harness line: claude via acpx backend
Session line: ses_abc123 · Persistent · Active (12m)
Model line: harness default or explicit override
Working dir: ~/Projects/elnor
Additional roots: ~/Projects/shared-libs (advisory), ~/Documents/specs (advisory)
Permissions line: approve-reads
Usage line: Cost unknown (ACP harness does not report usage)
Buttons: [View ACP Session] [Resume Session] [Cancel Session] [Context Inspector] [Mismatch Log]
⚠️ Inline attachments are not supported through ACP dispatch.
```
ACP room-visibility rule:
- Room participant chips MUST show the ACP badge when a participant is ACP-backed.
- Room participant chips MUST NOT show a `Standard` badge for non-ACP participants.
- If ACP roots are configured, the drawer MAY show a tooltip or secondary line explaining that file boundaries are `runtime_applied` or `advisory_only`, but the chip itself MUST remain compact.
### 10.5 Drawer action contract table
- `Change Model` -> control mutation route; allowed in session/participant scopes with permission.
- `Repair` -> orphan repair route.
- `Mute/Pause/Remove` -> DOC12-owned room policy actions; if unavailable, render read-only explanation instead of clickable no-op buttons.
- `Context Inspector` -> open inspector scoped to participant/session.
- `Mismatch Log` -> open filtered mismatch viewer.
Additional ACP drawer actions:
- `View ACP Session` → opens ACP session detail surface with session id, harness, state, uptime, cwd, model, permissions, timeout, and last error.
- `Resume Session` → only visible when the selected harness/backend reports resume support and there is a resumable inactive/disconnected ACP session.
- `Cancel Session` → confirmation modal: `Cancel ACP session? In-flight work may be lost.` Buttons: `[Cancel Session] [Keep Running]`
### 10.6 Settings > Providers & Models contract
Providers tab and models tab may be one combined surface or two sub-tabs, but all required content from §4A.17, §4.13-4.15C, and the native default execution policy requirements MUST be present.
Minimum required subsections:
- provider auth and verification,
- local/self-hosted provider configuration and runtime health,
- model catalog CRUD,
- default execution policy with primary model and ordered fallback chain,
- orphan/degraded model repair,
- unsupported/settings-only control explanations,
- last executed fallback summary.
Required layout:
```text
Settings > Providers & Models
Top summary row:
- Effective execution policy: Primary = anthropic/claude-sonnet-4-5
- Fallback chain: openai/gpt-5.4 → openai-codex/gpt-5.4
- Last executed model: anthropic/claude-sonnet-4-5
- Last fallback summary: 3 fallbacks in last 7 days [Time range ▾]
- [View Mismatch/Fallback Log]
Provider/Auth pane:
| Provider | Status | Default Profile | Last Verified | Actions |
|----------|---------------|-----------------|---------------|----------------------------|
| Anthropic| Verified | Work OAuth | 2m ago | [Reverify] [Profiles] |
| OpenAI | Scope Limited | CLI | 10m ago | [Fix Scope] [Profiles] |
Model catalog table:
| Model | Source | Tools | Context | State | Actions |
|----------------------------------|-------------|-------|---------|------------|-----------------------------|
| anthropic/claude-sonnet-4-5 | runtime | Yes | 200K | Active | [Set Primary] [Edit] [Del] |
| openai/gpt-5.4 | runtime | Yes | 1.05M | Fallback 1 | [Move] [Edit] [Del] |
| custom/spec-helper | manual | ? | 128K | Orphaned | [Repair] [Edit] [Del] |
Execution defaults card:
- Primary model: [anthropic/claude-sonnet-4-5 ▾]
- Ordered fallbacks:
1. [openai/gpt-5.4 ▾] [Remove]
2. [openai-codex/gpt-5.4 ▾] [Remove]
- [Add Fallback] [Save Policy]
Default selector behavior card:
- Think: [High ▾]
- Reasoning: [On ▾]
- Verbose: [Off ▾]
- Temperature: 0.7 (settings-only)
- Max output tokens: 4096 (settings-only)
- Note: settings-only controls are stored preferences unless a verified native path exists.
Repair/orphan pane:
| Scope | Missing Model | Status | Actions |
|--------------------|----------------------|-----------|--------------------|
| Agent: SpecHelper | custom/spec-helper | Orphaned | [Repair] [Close] |
```
Fallback-summary truth rule:
- `Last fallback summary` MUST default to the previous 7 days.
- The time range selector MUST offer at least: `24 hours`, `7 days`, `30 days`, `All time`.
- The summary source MUST be the mismatch/fallback log projection, not a hand-maintained UI counter.
UI placement requirement:
- Fast Mode MUST appear on every surface where `thinking_level`, `reasoning_mode`, and `verbose_level` appear:
- chat header control row,
- participant runtime drawer,
- Settings > Providers & Models,
- agent editor defaults,
- Truth Resolver debug view,
- Appendix U control matrix.
- Only ACP/Coding Mode surfaces that actually support Fast Mode may expose the control.
Fast Mode event rule:
- Fast Mode MUST use the canonical `gateway.control.mutated` / `gateway.control.rejected` path plus runtime-truth and execution-watermark refresh.
- R14 defines the standalone `gateway.control.fast_mode.changed` family for direct fast-mode state visibility, while canonical control mutation events still apply to the underlying control write path.
Credential source badge rule:
- every verified auth profile MUST display a non-editable credential source badge using the canonical `credential_source_kind` value,
- minimum badge labels:
- `Env Variable`
- `CLI Managed`
- `Secret Vault`
- `Plugin Managed`
- `Gateway Store`
- credential source truth MUST come from runtime/auth verification results, not from local form assumptions.
### 10.6A Settings > Local / Self-Hosted Providers contract
All required content from §4A.22-4A.33 MUST be present.
Minimum required subsections:
- provider capability list,
- local/self-hosted provider config editor,
- runtime state card,
- sync/probe action row,
- discovered/manual model table,
- metadata override editor,
- local-provider delete/revert destructive flows with backup recommendation block,
- post-save snapshot refresh behavior,
- explicit notes that local/self-hosted dispatch remains Gateway-first.
Required layout:
```text
Settings > Local / Self-Hosted Providers
Provider list:
| Provider | Runtime Kind | Reachable | Discovery | Models | Last Probe | Usage Mode |
|----------|------------------|-----------|-----------|--------|------------|----------------|
| Ollama | local_native | Yes | implicit | 4 | 30s ago | runtime_reported |
| vLLM | self_hosted_remote| No | manual | 0 | 5m ago | unknown |
| MLX Lab | mlx_proxy | Yes | manual | 2 | 1m ago | operator_defined |
Selected provider editor:
- Provider name: [MLX Lab]
- Runtime kind: [mlx_proxy ▾]
- Deployment kind: [local_proxy ▾]
- API family: [openai-completions ▾]
- Base URL: [http://localhost:8081/v1]
- Discovery mode: [explicit_manual_catalog ▾]
- Auth requirement: Placeholder opt-in
- [Probe Runtime] [Sync Catalog] [Save]
Discovered/manual model table:
| Model | Provenance | Tools | Context | Cost Mode | Actions |
|---------------------------|--------------|-------|---------|-------------------|----------------------|
| qwen3.5-9b-mlx | manual | Unknown | 262K | operator_defined | [Overrides] [Delete] |
Metadata overrides:
- Context window: [262000]
- Max tokens: [2048]
- Reasoning: [Unknown ▾]
- Tool support: [Unknown ▾]
- Input modalities: [text]
- Billing mode: [operator_defined ▾]
- [Save Overrides]
Warnings panel:
- Ollama must use native API, not /v1, unless explicit compatibility mode is enabled.
- Direct local bypass around Gateway is prohibited.
```
### 10.6B Settings > Web Search contract
All required content from §4B.14-4B.15 MUST be present.
Minimum required subsections:
- global search enable and provider-selection mode,
- provider catalog list from runtime truth in neutral alphabetical order unless runtime provides explicit display order,
- per-provider config editor,
- provider-specific advanced fields,
- active/inactive credential truth,
- compatibility-path banner / provider-mode truth when applicable,
- default-provider and auto-detect semantics,
- wrapper fallback-chain editor,
- last executed search provider summary,
- explicit probe/test actions,
- delete-provider-config destructive flow with backup recommendation block,
- post-save snapshot refresh behavior.
### 10.6C Settings > ACP / External Harnesses contract
All required content from §4C.10-§4C.14 MUST be present.
Minimum required subsections:
- ACP global status card,
- harness catalog table,
- ACP profile list/editor,
- active/recent ACP sessions table,
- doctor/install/help panel,
- explicit limitations panel,
- agent runtime assignment preview,
- profile delete/reassignment safeguards,
- file-access/project-root picker with enforcement badges.
ACP badge derivation rule:
- show `ACP` (or `ACP: <harness>` when space permits) when `runtime_mode = "acp"`,
- do not show a `Standard` badge for native runtime,
- if ACP session status is `resuming`, `live_degraded`, `failed`, or `expired`, the ACP badge MUST include a degraded/error visual treatment derived from `AcpSessionStatus`.
ACP full-log rule:
- `[View Full Log]` MUST map to `GET /api/openclaw/acp/sessions/:acp_session_id/history` and MUST NOT remain a phantom control.
### 10.6D Settings > Channels contract
All required content from §4D.7-§4D.10 MUST be present.
Minimum required subsections:
- Discord provider card/editor,
- WhatsApp provider card/editor,
- account list and default-account selectors,
- channel-account -> agent binding editor,
- login/pairing queue surfaces,
- runtime health / last error banner,
- provider-specific advanced options (Discord thread bindings, WhatsApp reconnect/media options),
- logout / clear token / binding removal destructive flows.
### 10.7 Context & Budget settings contract
Must show:
- budget profile selector,
- manual override controls,
- protected native context note,
- native pressure estimate,
- dry-run button,
- engine identity.
Required layout:
```text
Settings > Context & Budget
Budget profile section:
- Active profile: [Default ▾]
- Description: Dynamic budget based on model window, native context, and output reserve.
Manual override section:
- Override mode: [None ▾]
- Override value: [____] tokens / [____]%
- Physical clamp: Always active
- Note: Testing unbounded removes soft caps but not the physical model-window ceiling.
Native context section:
- Engine: legacy / plugin:<id>
- Native estimate: ~8,200 tokens (inferred)
- Pressure: Normal / Elevated / High / Unknown
- Protected files summary
- [View Protected Files]
Dry-run section:
- [Run Context Dry-Run]
- Expandable result panel showing included/dropped/deduped/degraded sources
Engine identity section:
- Context engine: legacy | plugin:<id>
- Engine version: from runtime
- Slot mode: legacy_prepend | plugin_slot | unknown
```
Ghost-control prevention rule:
- if manual override is not currently writable in the runtime/config path, the controls MUST render disabled with a reason banner instead of appearing editable and silently failing.
Budget settings mutation route:
- `PUT /api/openclaw/context/budget-settings`
```ts
type SpilloverAllowanceConfig = {
mode: "fixed" | "percentage";
fixed_tokens?: number;
percentage_of_budget?: number;
};
type BudgetSettingsUpdateRequest = {
budget_profile_id: string;
spillover_allowance?: SpilloverAllowanceConfig;
soft_cap_tokens?: number;
hard_cap_tokens?: number;
correlation_id: string;
schema_version: 1;
};
type BudgetSettingsUpdateResult = {
accepted: boolean;
context_budget_settings_ref: string;
spillover_clamped: boolean;
effective_hard_budget_tokens?: number;
reason_codes?: string[];
schema_version: 1;
};
```
Budget profile validation rule:
- `budget_profile_id` MUST match a built-in profile or a registered custom profile,
- unknown profile IDs MUST fail with `reason_codes += ["unknown_budget_profile"]`.
Spillover safety clamp:
- if spillover allowance would push the effective hard budget beyond the safe budget ceiling derived from the active model window and reserves, the backend MUST clamp it,
- result payloads MUST expose `spillover_clamped` and `effective_hard_budget_tokens`,
- when clamped, `reason_codes += ["spillover_clamped"]`.
### 10.8 Runtime & Connectivity surface contract
Must show:
- Gateway connection state,
- auth issue log,
- remote gateway mode/token truth card when remote mode is relevant to the current client surface,
- local/self-hosted provider runtime cards (reachability, discovery mode, last probe, discovered model count, usage-reporting mode),
- search runtime card (enabled, configured provider, effective provider, provider mode / compatibility path when applicable, auto-detect/fallback state, last probe result),
- ACP system card (enabled, dispatch enabled, backend, health, allowed harnesses, max concurrent sessions, runtime TTL),
- active ACP sessions summary (count, degraded count, recent failures),
- channel runtime cards for Discord and WhatsApp (configured, linked, pairing pending, connected, degraded/disconnected),
- mismatch/fallback log summary,
- stream mode/degraded state,
- active sessions / participants,
- dual-connection ownership diagnostics.
Required layout:
```text
Settings > Runtime & Connectivity
Gateway section:
- Gateway URL
- Status: Connected / Degraded / Offline
- Protocol version
- Auth scopes
- Uptime
- Last event
- Write leader / dual-connection diagnostics
- [Reconnect] [View Connection Log]
Local/Self-Hosted Providers section:
| Provider | Reachable | Discovery | Models | Last Probe | Usage Mode |
|----------|-----------|-----------|--------|------------|------------|
| Ollama | Yes | Implicit | 4 | 30s ago | Runtime |
| vLLM | Offline | Manual | 0 | 5m ago | Unknown |
Web Search section:
- Search enabled
- Configured provider
- Effective provider
- Provider mode / compatibility path
- Fallback chain
- Last probe
- Last search execution
- [Probe] [Test Search]
ACP System section:
- ACP enabled
- Dispatch enabled
- Backend
- Health
- Allowed harnesses
- Active sessions
- Degraded sessions
- Max concurrent sessions
- Runtime TTL
- [Run Doctor] [View Sessions]
Channel Runtime section:
| Channel | Status | Account | Agent | Last Activity |
|----------|------------|--------------|--------|---------------|
| Discord | Connected | BotName#1234 | Elnor | 3m ago |
| WhatsApp | QR needed | — | — | Never |
Mismatch/Fallback section:
- Recent issues: 0 in last 24h
- [View Full Log]
Stream State section:
- Mode: stream / final_only
- Gap count
- Last delta / heartbeat
Active Sessions section:
| Session Key | Agent | Runtime | State | Last Activity |
```
Unified health-card rule:
- Runtime & Connectivity MUST render one health-card family across Gateway, local providers, search, ACP, and channels.
- Each card MUST show: state, last probe or event time, active warnings, and entry-point actions.
- The card family MUST use the same color/status lexicon as global/page-level indicators.
```ts
type ContextEngineDiagnostics = {
native_context_engine_id?: string;
native_context_engine_kind?: "legacy" | "plugin";
native_context_engine_plugin_id?: string;
owns_compaction?: boolean;
compaction_delegation_mode?: "engine_owned" | "runtime_delegated" | "legacy" | "unknown";
root_memory_bootstrap_source?: "MEMORY.md" | "memory.md" | "none" | "unknown";
ignored_duplicate_root_memory_source?: "MEMORY.md" | "memory.md" | "none";
root_memory_bootstrap_conflict_detected: boolean;
last_bootstrapped_at?: string;
health_state?: "healthy" | "degraded" | "unknown";
reason_codes?: string[];
schema_version: 1;
};
type ContextDiagnosticsResponse = {
diagnostics: ContextEngineDiagnostics;
schema_version: 1;
};
```
Required route:
- `GET /api/openclaw/context/diagnostics` -> `ContextDiagnosticsResponse`
Rules:
- only one root memory bootstrap source may be active,
- if both files exist, the winner and ignored source MUST be explicit,
- no UI surface may imply both were injected,
- context-engine health MUST surface in Runtime & Connectivity and Context Inspector.
Runtime reconnect route:
```ts
POST /api/openclaw/runtime/reconnect
```
```ts
type RuntimeReconnectRequest = {
scope_refs?: string[];
include_channel_transports?: boolean;
schema_version: 1;
};
type RuntimeReconnectResult = {
ok: true;
attempted_scope_refs: string[];
reconnect_state: "completed" | "partial" | "failed";
warning_codes?: string[];
schema_version: 1;
};
```
Connection log route:
- `GET /api/openclaw/runtime/connection-log`
```ts
type RuntimeConnectionLogEntry = {
occurred_at: string;
source: "gateway" | "channel" | "provider" | "browser" | "runtime";
level: "info" | "warn" | "error";
message: string;
reason_codes?: string[];
schema_version: 1;
};
type ConnectionLogReadResult = {
entries: RuntimeConnectionLogEntry[];
next_cursor?: string;
schema_version: 1;
};
```
### 10.8A Runtime & Connectivity action semantics
If Runtime & Connectivity surfaces any of the following actions, they MUST map to canonical routes:
- `Reconnect` → `POST /api/openclaw/runtime/reconnect`
- `View Connection Log` → `GET /api/openclaw/runtime/connection-log`
- `View Protected Files` → route defined at §4D/§6 as applicable; metadata-only
`Reconnect` success rule:
- on success, the surface MUST refresh gateway health, session verification state, channel/search/local-provider runtime cards, and any affected participant/session drawers.
`View Connection Log` rule:
- the route returns metadata and messages only; it MUST NOT stream arbitrary raw logs from unrelated subsystems,
- retention default is the latest 500 entries or 7 days, whichever is smaller, unless a stricter operator setting applies.
### 10.9 Context inspector requirement
See §6.18. Inspector MUST prominently distinguish native vs EC/Q context, show protected native context warnings, and surface ACP ingress provenance / receipt truth when runtime reports it.
### 10.10 Mismatch/fallback log requirement
Q MUST provide a viewer showing:
- intended value,
- actual executed value,
- participant/session/room scope,
- switch segment link,
- timestamps,
- reason codes,
- usage/cost implication if any,
- time-window selector,
- filter by runtime mode (`openclaw` / `acp`), provider, model, and search provider.
Required layout:
```text
Mismatch / Fallback Log
Filters:
- Time range: [7 days ▾]
- Scope: [All ▾]
- Runtime: [All ▾]
- Provider: [All ▾]
- Search provider: [All ▾]
Table:
| Time | Scope | Runtime | Intended | Executed | Reason | Switch Segment | Cost Impact |
|------|-------|---------|----------|----------|--------|----------------|-------------|
| 2m | Chat | ACP | Codex | Codex | timeout reset | seg_123 | Unknown |
| 5m | Room | OpenClaw| gpt-5.4 | claude-sonnet-4-5 | fallback | seg_124 | +$0.03 |
```
Ghost-control prevention rule:
- summaries and badges derived from this viewer MUST name the active time window.
- Q MUST NOT show bare counts like `Fallback used 3 times` without a time-window label.
### 10.10A MismatchFallbackLogEntrySchema and read contract
```ts
type MismatchFallbackLogEntry = {
entry_id: string;
scope_kind: "session" | "participant" | "room_turn";
scope_ref: string;
switch_segment_id?: string;
mismatch_kind: "desired_vs_effective" | "effective_vs_executed" | "fallback_executed";
field_name?: string;
desired_value?: unknown;
effective_value?: unknown;
executed_value?: unknown;
reason_codes: string[];
occurred_at: string;
cost_impact_note?: string;
schema_version: 1;
};
type MismatchFallbackLogQueryRequest = {
scope_ref?: string;
participant_id?: string;
room_id?: string;
provider_id?: string;
model_ref?: string;
fallback_kind?: string;
mismatch_kind?: "desired_vs_effective" | "effective_vs_executed" | "fallback_executed";
limit?: number;
cursor?: string;
schema_version: 1;
};
type MismatchFallbackLogQueryResult = {
entries: MismatchFallbackLogEntry[];
next_cursor?: string;
schema_version: 1;
};
```
Canonical route:
- `GET /api/openclaw/mismatch-log` returns `MismatchFallbackLogQueryResult`.
- Supported query parameters: `scope_ref`, `participant_id`, `room_id`, `provider_id`, `model_ref`, `fallback_kind`, `mismatch_kind`, `limit`, `cursor`.
Viewer requirements:
- sort newest first,
- allow filter by `mismatch_kind`,
- show switch segment linkage when present,
- show cost impact note when present.
### 10.11 Capability viewer requirement
For any attachment send or auth/capability-sensitive action, Q MUST be able to show:
- capability status,
- TTL/staleness,
- max attachment bytes,
- token ceiling,
- failure reason,
- auth-scope linkage.
### 10.12 Truth Resolver debug view
A dedicated debug surface MUST show:
- catalog generation,
- desired/effective/executed values field-by-field,
- mismatch/fallback tail,
- active switch segment,
- auth provenance,
- local/self-hosted provider runtime state and metadata-override provenance when relevant,
- capability result snapshot,
- current search runtime state and latest search execution watermark,
- ACP ingress provenance / receipt metadata when runtime reports it,
- ACP session/runtime state and effective ACP options when the active scope uses ACP,
- bound channel account/runtime state when the active scope was entered via Discord or WhatsApp routing,
- usage sample and cost truth mode.
### 10.13 Unsupported-control UI contract
Unsupported/settings-only controls MUST:
- render gray/neutral,
- be editable only in clearly labeled settings surfaces,
- include tooltip: `Stored as preference only. The audited runtime does not verify or apply this natively on this path.`
- never render as green `verified` runtime truth.
### 10.14 Destructive action UI contract
Any destructive action MUST define:
- modal contents,
- confirmation/cancel buttons,
- dependency warnings,
- whether undo exists,
- audit event emitted,
- visible success/failure states,
- a non-blocking backup recommendation block for stateful/high-risk actions.
Minimum backup copy for high-risk config/state mutations:
- `openclaw backup create --verify`
- `openclaw backup create --only-config --verify` when config-only backup is sufficient
### 10.15 Voice boundary note
DOC11 does not define end-user STT/TTS/voice UX. If a future Q voice surface exists, voice input MUST normalize to ordinary DOC11 dispatch, and voice output MUST consume a presentation-safe `speakable_text` projection rather than raw debug/runtime text.
---
## 11) Cross-spec integration contracts
### 11.1 DOC10 integration contract
DOC10 owns the orchestration spine and the suite-wide master integration ledger.
DOC11 owns the runtime/control seam.
Any DOC11 requirement that causes another owner document or implementation area to change MUST be mirrored into the DOC10 integration ledger before implementation is considered complete.
Minimum ledger mirror fields:
- `source_doc = "DOC11"`
- `source_revision`
- `source_section`
- `target_doc`
- `change_class`
- `blocking_state`
- `acceptance_state`
- `absorbed_in_target_revision?`
- `notes`
Minimum required DOC11-driven mirror classes for this revision:
- runtime-truth / selector-truth / execution-watermark changes,
- auth profile removal and affected-scope degradation/repair,
- model soft-delete / orphan repair / fallback chain effects,
- search-provider runtime truth / fallback truth / compatibility-path truth,
- local/self-hosted provider runtime truth / probe / discovery / direct-bypass blocking,
- ACP/Coding Mode runtime assignment / session truth / execution watermark truth,
- Discord/WhatsApp login / pairing / account binding / channel runtime truth,
- DOC12 prompt-plan truth deferral or companion-doc split,
- UI-spec synchronization obligations for every DOC11-owned Q surface.
DOC10 ledger mirror rule:
- DOC11 MUST NOT rely on conversational memory or reviewer prose as the only record of a cross-doc obligation.
- If DOC11 changes a cross-doc seam and the ledger is not updated, that seam is not considered closed.
- The latest Q Dashboard UI spec revision MUST be referenced in the ledger whenever DOC11 changes a Q-facing surface contract.
Auth-profile-removal ledger rule:
- removing an auth profile is a cross-doc event, not a purely local mutation.
- the ledger MUST contain a row for auth profile removal because it affects:
- provider auth state,
- degraded/orphaned scopes,
- execution truth,
- destructive-action UI,
- and recovery requirements.
### 11.1A Ledger mirroring obligations
R14 creates ledger obligations for at minimum the following areas:
- prompt-plan interim ownership closure,
- DOC13 token/cost seam decision,
- ACP/channel projection/runtime-mode alignment in DOC12,
- EC Core service-mapping cross-reference,
- Q UI spec control-matrix mirror and shared runtime primitive alignment.
For each required-now obligation, DOC10 Integration Ledger MUST receive:
- stable XDI ID,
- title,
- owner docs,
- status,
- closure criteria,
- and explicit reference to the controlling DOC11 section.
### 11.2 DOC12 integration contract
DOC12 owns room policy, participant identity, room composition, projection semantics, and ACP/channel posture inside rooms.
DOC11 owns runtime overlays, participant runtime truth, session/binding truth, execution watermark truth, capability truth, and runtime-mode truth.
Prompt-plan interim rule:
- until DOC17 overlay consumption is wired into DOC11 prompt-plan assembly, DOC12 MAY expose only an interim prompt-plan projection route,
- that interim projection MUST be marked `truth_state = "partial"`,
- the interim response MUST include `source_refs[]` identifying the upstream inputs used to synthesize the projection,
- DOC12 MUST NOT claim canonical prompt-plan ownership before DOC11 defines the canonical schema.
Trigger milestone:
- once DOC17 overlay consumption is wired into DOC11 prompt-plan assembly, the interim rule expires and DOC11 MUST define canonical prompt-plan truth, with DOC12 consuming it rather than synthesizing it independently.
### 11.3 DOC4 integration contract
DOC4/OpenClaw bridge owns actual runtime, plugin, harness, channel, and tool realities.
DOC11 MUST NOT claim behavior that is not supported by current bridge/runtime evidence.
Evidence-backed truth rule:
- if a behavior is audited and runtime-probed, DOC11 may classify it as `native_probed`,
- if it is documented by current OpenClaw docs but not locally re-probed, DOC11 MUST classify it as `native_documented_not_probed`,
- if it is composed by DOC11/Q/adapter logic, it MUST remain `wrapper_composed`,
- if it is policy-only, it MUST remain `wrapper_policy_required`.
Stale-bridge-truth rule:
If DOC4 or older bridge audits lag current OpenClaw channel/plugin/runtime behavior:
- DOC11 MUST prefer the freshest evidence source actually available,
- MUST downgrade stale bridge claims to `unknown` or `native_documented_not_probed` when appropriate,
- and MUST NOT show verified runtime truth based solely on stale bridge assumptions.
This is especially required for:
- channel pairing/login/probe behavior,
- ACP harness/session capabilities,
- search provider config/runtime truth,
- local/self-hosted provider discovery and cost/usage truth,
- and plugin/channel runtime behavior that changed after DOC4 was written.
### 11.4 DOC13 integration contract
DOC11 produces usage and execution truth. DOC13 consumes that truth for costs, budgets, and display.
Canonical naming rule:
- if DOC13 is revised in the same implementation wave as R14, both DOC11 and DOC13 MUST use `input_tokens` / `output_tokens` / `total_tokens` only,
- if DOC13 is not revised in the same wave, the consumer layer MAY accept legacy `prompt_tokens` / `completion_tokens` for a bounded compatibility window only,
- that compatibility window closes at the earlier of: DOC13 R2 adoption or two subsequent operative DOC revisions,
- after closure, legacy names MUST be rejected in new code and removed from the compatibility shim.
No indefinite dual naming is permitted.
### 11.5 DOC15 integration contract
DOC15/CIL may provide additional context sources, but they remain EC/Q-contributed context unless explicitly runtime-native.
DOC11 MUST expose the following capture points or references for DOC15/CIL and related diagnostics:
- `context_manifest_ref`
- `context_budget_resolution_ref`
- `execution_watermark_ref`
- `switch_segment_id`
- mismatch/fallback log refs,
- search execution watermark refs,
- local/self-hosted provider runtime refs,
- ACP harness/profile/session refs,
- channel binding/runtime refs,
- auth profile/state refs where relevant to context access.
DOC15 mismatch-analysis rule:
CIL mismatch analysis MUST consume DOC11 manifests/diagnostics rather than recreating a second context-truth system.
Required companion-document bundle for DOC11 R14:
- DOC10 ledger row additions with stable `XDI-D11-R14-###` IDs,
- DOC12 ACP durable binding and channel capability truth alignment,
- DOC13 fast-mode/service-tier usage ingestion extension,
- current canonical Q UI master spec additions for fast mode, plugin ownership, channel pairing challenge controls, context diagnostics, and channel/ACP settings parity,
- DOC4 plugin ownership, singular memory bootstrap, and compaction delegation assumptions.
Companion-doc obligations listed here MUST be mirrored into their owner documents and the integration ledger before implementation is treated as complete.
Required companion-document bundle for DOC11 R14:
- current DOC10 orchestration integration ledger rows with stable `XDI-D11-R14-###` IDs,
- current DOC12 ACP durable binding, channel capability truth, and interim prompt-plan compatibility updates,
- current DOC13 fast-mode/service-tier usage ingestion and token-field transition updates,
- current canonical Q UI master spec additions for fast mode, plugin ownership, ACP/channels/settings parity, and context diagnostics,
- current DOC4 bridge/plugin ownership, singular memory bootstrap, and compaction delegation assumptions.
Companion-doc obligations listed here MUST be mirrored into their owner documents and the integration ledger before implementation is treated as complete.
### 11.5A Companion reference replacement rule
Any speculative or stale references to non-operative placeholder companion documents MUST be replaced with the actual current canonical Q UI specification / inventory artifact names in force for the operative revision wave.
DOC11 MUST use `current canonical Q UI spec` / `current canonical Q UI master spec` only as references to the real operative artifact, never as placeholders for nonexistent documents.
## 12) Implementation modules, route contracts, read-model contracts, and no-drift requirements
### 12.1 Required backend modules
- `GatewayProtocolAdapter`
- `SessionTruthService`
- `RuntimeTruthService`
- `SelectorTruthService`
- `ExecutionWatermarkService`
- `ProviderAuthStateService`
- `LocalProviderConfigService`
- `SearchProviderConfigService`
- `SearchExecutionWatermarkService`
- `AcpHarnessService`
- `AcpSessionService`
- `ChannelConfigService`
- `ChannelPairingService`
- `ChannelProjectionService`
- `RuntimeCapabilityCheckService`
- `ContextManifestService`
- `MismatchFallbackLogService`
- `UsageSampleService`
- `SwitchSegmentService`
- `AbortService`
- `IndicatorAggregateService`
- `RuntimeTruthDebugService`
### 12.1A HTTP success/error envelope rule
Unless a route is explicitly streaming/SSE-only, HTTP responses MUST use one of the following canonical envelopes:
```ts
type HttpOkEnvelope<T> = {
ok: true;
data: T;
reason_codes?: string[];
schema_version: 1;
};
type HttpErrorEnvelope = {
ok: false;
error_code: string;
message: string;
reason_codes?: string[];
retryable?: boolean;
schema_version: 1;
};
```
Exceptions:
- SSE/streaming routes may use streaming event frames instead of the envelope,
- file/image/QR payload endpoints may return the typed payload directly only if the route contract says so explicitly,
- callback-relay routes may return minimal HTML/redirect responses when required by the provider, but MUST still project canonical auth state to read models.
### 12.2 Fully enumerated route matrix requirement
Appendix E is normative.
Rules:
- every visible control in section 10 MUST appear in Appendix E,
- every route referenced in sections 4A, 4B, 4C, 4D, 5, 6, 8, 9, or 10 MUST appear in Appendix E,
- wildcard catch-all rows are forbidden in Appendix E,
- route rows MUST name:
- purpose,
- triggering UI/control,
- owner service,
- durable effect or explicit non-durable effect,
- emitted telemetry,
- and, where applicable, whether the route is a read, mutation, or terminal-action route.
If a control is not in Appendix E, it is not specified enough to build.
Legacy drift normalization pass:
- canonical search-config read route: `GET /api/openclaw/tools/web-search/configs`
- deprecated singular search-config read aliases MAY remain runtime-compatible for one revision only,
- canonical `ControlMutationQueueRecord.status` values are:
- `pending`
- `applied`
- `rejected`
- `timed_out`
- `superseded`
- exactly one canonical `ProviderAuthRemoveProfileRequest` definition may remain,
- exactly one Appendix C, one Appendix E, and one Appendix F may remain in the final compiled text.
```ts
type DeprecatedAliasMap = {
route_aliases: Array<{
deprecated_route: string;
canonical_route: string;
removal_revision: string;
}>;
enum_aliases: Array<{
field: string;
deprecated_value: string;
canonical_value: string;
removal_revision: string;
}>;
schema_version: 1;
};
```
### 12.3 Fully enumerated read-model matrix requirement
Appendix F is normative. Every surface in section 10 MUST have a declared read-model dependency.
### 12.3A Elnor Control Surface Exposure Contract
Every mutable Elnor-facing control in the DOC4 “Elnor drives Q/EC via commands” catalog MUST appear in Appendix U and have:
- a route or operation,
- a validation owner,
- a durable owner or explicit non-durable effect owner,
- a refreshed read-model,
- emitted event proof,
- and disabled-state rules.
Rules:
- mutable Elnor-facing controls MUST route through the canonical control-mutation path,
- read-only views, telemetry streams, pairing challenges, and passive projections MUST NOT be forced through `ControlMutationQueueRecord` unless they are actually mutations,
- if a mutable control exists in UI but not in Appendix U, it MUST be removed or rendered disabled with explicit `Not wired to runtime truth` copy.
### 12.3A UI-spec synchronization rule
Canonical UI spec reference lock:
- DOC11 UI obligations are pinned to the current canonical Q UI master spec:
- `Q_DASHBOARD_MASTER_UI_SPECIFICATION_R4_3_CONSOLIDATED.md`
- If a separate canonical UI inventory artifact exists at compile time, it MUST be named explicitly in the parity checkpoint; otherwise `ui_inventory_revision` remains omitted.
```ts
type Doc11UiParityCheckpoint = {
doc11_revision: string;
ui_master_revision: string;
ui_inventory_revision?: string;
checkpoint_results: Array<{
checkpoint_kind:
| "control_parity"
| "wireframe_field_parity"
| "badge_state_parity"
| "settings_page_parity"
| "drawer_modal_parity";
status: "pass" | "fail";
failing_items?: string[];
}>;
completed_at: string;
owner: string;
schema_version: 1;
};
```
### 12.4 Dual-connection ownership rule
If Q backend and EC both subscribe to Gateway telemetry:
- exactly one process is the write leader per read-model,
- non-leaders may mirror transiently but may not write authoritative read-model files,
- duplicate writes MUST emit `gateway.dual_connection_write_conflict_detected`.
Suggested dedupe pseudocode:
```text
if incoming.operation_id + incoming.correlation_id already applied to read-model version:
discard duplicate and log mirror event
else apply and persist
```
### 12.5 Required stores/read-models
Minimum required durable stores:
- `desired_control_state_store`
- `effective_runtime_state_store`
- `execution_watermark_store`
- `session_binding_store`
- `control_mutation_queue_store`
- `replay_cursor_store`
- `provider_auth_catalog_store`
- `provider_auth_profile_store`
- `provider_auth_state_store`
- `provider_auth_challenge_store`
- `provider_auth_issue_log`
- `model_execution_policy_store`
- `provider_capability_catalog_store`
- `local_provider_config_store`
- `local_provider_runtime_state_store`
- `local_model_metadata_override_store`
- `search_provider_catalog_store`
- `search_provider_config_store`
- `search_runtime_state_store`
- `search_execution_watermark_store`
- `search_fallback_policy_store`
- `acp_system_state_store`
- `acp_harness_catalog_store`
- `acp_profile_store`
- `agent_runtime_assignment_store`
- `acp_session_runtime_state_store`
- `channel_provider_config_store`
- `channel_runtime_state_store`
- `channel_account_state_store`
- `channel_binding_store`
- `channel_pairing_queue_store`
- `capability_cache_store`
- `context_budget_resolution_store`
- `context_manifest_store`
- `mismatch_fallback_log`
- `switch_segment_store`
- `usage_sample_store`
- `room_cost_subtotal_store`
- `participant_cost_subtotal_store`
- `stream_state_store`
- `abort_state_store`
- `indicator_aggregate_store`
### 12.6 Docs-consistency CI rule
CI MUST fail if:
- a route exists in prose but not Appendix E,
- a read-model exists in prose but not Appendix F,
- a schema name is referenced but not defined/exported,
- an unsupported control is shown as native/verified in any matrix,
- companion doc pins are stale or contradictory,
- runtime evidence refs are stale for the audited build,
- duplicate schema definitions exist with conflicting fields,
- a DOC11-owned Q surface changes but the latest Q UI spec does not mirror the change,
- Appendix E contains wildcard catch-all rows instead of explicit route rows,
- Appendix F omits any store required by §12.5,
- DOC11 introduces a cross-doc obligation but the DOC10 integration ledger is not updated,
- or prompt-plan truth remains unresolved in DOC12 without the explicit `unavailable / proposal_only` deferral note required by §11.2.
### 12.7 Migration runbook requirement
Migration appendix MUST include:
- introduction order for stores,
- cutover order for services,
- backfill order,
- rollback triggers,
- smoke gates,
- orphan/backfill rules,
- auth profile migration,
- deleted model migration,
- room binding reconciliation,
- cost/usage backfill strategy.
### 12.8 Current-codebase compatibility note
This document is written for a greenfield rebuild. Existing code may be used as reference only if it is shown to comply with this spec. Legacy implementation does not override the spec.
---
EC Core service-mapping table:
| DOC11 logical service | EC Core write path | EC Core projection path |
|---|---|---|
| `SelectorTruthService` | `CommandIngestionService` | `StateProjectionService` |
| `SessionTruthService` | `CommandIngestionService` | `StateProjectionService` |
| `RuntimeTruthService` | `CommandIngestionService` | `StateProjectionService` |
| `ProviderAuthStateService` | `CommandIngestionService` | `StateProjectionService` |
| `SearchConfigService` | `CommandIngestionService` | `StateProjectionService` |
| `LocalProviderService` | `CommandIngestionService` | `StateProjectionService` |
| `AcpRuntimeService` | `CommandIngestionService` | `StateProjectionService` |
| `ChannelConfigService` | `CommandIngestionService` | `StateProjectionService` |
| `GatewayProtocolAdapter` | direct gateway I/O | direct event consumption |
| `RuntimeTruthDebugService` | none (read-only) | `StateProjectionService` |
## 13) Acceptance criteria and validation tests
### 13.1 Runtime truth correctness
Tests MUST verify:
- desired/effective/executed divergence is visible,
- fallback creates mismatch or switch segment,
- orphaned model/profile remains visible until repaired or closed,
- settings-only controls never show verified runtime truth.
### 13.2 Auth correctness
Tests MUST verify:
- OAuth browser flow,
- device-code flow,
- setup-token flow,
- pasted-token flow,
- CLI verify flow,
- missing-scope visibility,
- execution watermark auth provenance,
- auth-profile removal destructive semantics,
- auth mode truth,
- post-save runtime snapshot refresh preserves masked secret/source truth rather than rendering fake-empty fields.
### 13.2A Web search configuration correctness
Tests MUST verify:
- runtime catalog drives provider visibility,
- provider lists render in neutral alphabetical order unless runtime supplies explicit display order,
- specific-provider vs auto-detect mode renders correctly,
- active vs inactive credential truth updates after provider selection changes,
- provider config save/probe/delete trigger the correct routes and telemetry,
- unsupported provider-specific fields never appear as live controls,
- Brave `llm-context` mode surfaces its mode truth and filter limitations honestly,
- Perplexity compatibility-path truth is visible and disables unsupported Search-API-only filters,
- last executed search provider, provider mode, and fallback state are visible,
- wrapper search fallback is never silent,
- deleting the selected provider degrades search state honestly until repaired,
- post-save runtime snapshot refresh preserves secret/source truth for provider configs.
### 13.2B Local/self-hosted provider correctness
Tests MUST verify:
- implicit discovery vs explicit manual catalog vs hybrid manual override,
- `ollama` native API validation forbids accidental `/v1` without explicit compatibility mode,
- `vllm` and other OpenAI-compatible local providers probe the expected `/v1` surface,
- `mlx_proxy` defaults to manual catalog unless runtime discovery is explicitly proven and enabled,
- placeholder opt-in auth is rendered honestly and never as real credential verification,
- provider probe updates runtime state without silently mutating catalog unless sync was requested,
- post-save runtime snapshot refresh preserves masked secret/source truth and discovered/manual catalog provenance,
- sync catalog mutates picker visibility and orphans affected bindings honestly,
- direct local bypass is blocked and emits telemetry,
- usage extraction from local responses works when usage fields are present and stays explicitly `unknown` when absent,
- operator-asserted metadata affects budget/capability confidence visibly.
### 13.2C Remote gateway and snapshot truth correctness
Tests MUST verify:
- preserved non-plaintext remote gateway tokens remain preserved until explicit replacement,
- the UI surfaces `unsupported_shape_for_client` warnings when runtime reports them,
- saving unrelated config does not erase remote token/source truth in runtime snapshots.
### 13.2D ACP/Coding Mode correctness
Tests MUST verify:
- agent-level `runtime_mode` inheritance across chat/task/room/panel/forum/red-team surfaces,
- only ACP-backed agents show an ACP badge,
- model selectors do not change runtime mode,
- deleting or invalidating an ACP profile degrades affected agents honestly,
- working-directory selection uses the saved primary root and additional roots render truthful enforcement badges,
- additional project roots render `advisory_only` unless runtime-verified strict boundary support exists,
- ACP availability/doctor state surfaces route to the correct ACP health/probe controls,
- ACP `attachments unsupported` and `sandbox unsupported` limitations are visible and truthful,
- ACP attachment rejection happens before dispatch and does not produce fake partial execution state,
- `resumeSessionId` / resume affordances only appear when the chosen harness/backend supports them,
- resume failure for a persistent ACP session first surfaces `session_disconnected_recoverable` when recovery is still plausible,
- only after recovery attempts are exhausted may the system emit a fail-closed terminal state,
- ACP session cancel / close / resume controls call the correct routes and update runtime truth,
- path validation failures surface `acp_boundary_violation` or equivalent explicit error truth,
- and no silent fallback to native runtime occurs when ACP is unavailable.
ACP session lifecycle additions:
- `closing` is a real transitional state.
- `cancelling` is a real transitional state.
- `cancelling -> failed` and `closing -> failed` are valid transitions.
- `live_idle -> live_degraded` is valid when runtime truth or upstream harness truth becomes uncertain while no dispatch is active.
- `live_degraded -> live_idle` is valid after successful verification when no dispatch/mutation is active.
- `live_degraded -> live_active` is valid only when a new dispatch or resume completes and the session is actively processing.
```ts
ACP resume rule:
- At `soft_resume_threshold_ms`, UI MUST surface `Still resuming…` and keep the session in `resuming`.
- At `hard_resume_timeout_ms`, emit `gateway.acp.session.resume_failed` unless a terminal success/failure already arrived.
- Retry/backoff behavior is internal unless a later UI spec explicitly exposes it.
### 13.2E Discord/WhatsApp channel correctness
Tests MUST verify:
- Discord token save/probe/pairing flows work and preserve secret/source truth after save,
- Discord missing-intents truth surfaces `missing_intents` rather than fake healthy/verified state,
- WhatsApp QR login, relink, cancel, expiry refresh, and logout flows work and render distinct `qr_required`, `awaiting_scan`, `linked`, `connected`, `expired`, and `error` states,
- QR/pairing challenge payloads render through the canonical challenge schema without ad hoc polling inventions,
- DM/group policies and allowlists round-trip correctly,
- default account selection and account-level overrides are visible and truthful,
- channel account -> agent binding updates routing truth and renders the bound agent runtime mode,
- binding a channel account to an ACP-backed agent results in ACP badge/watermark truth on resulting sessions,
- Discord thread-binding and `spawnAcpSessions` controls only appear when supported,
- room/panel/forum projected dispatch to channels goes through the normalized DOC11 route and does not bypass channel truth,
- and channel status never collapses `configured`, `pairing_required`, `linked`, `connected`, and `missing_intents` into one fake state.
### 13.3 Selector correctness
Tests MUST verify:
- all selectors read from unified stores,
- concurrent mutations follow deterministic precedence,
- pending/rejected/orphaned/fallback states render consistently,
- deleted model disappears from dropdowns on catalog update.
### 13.4 Context correctness
Tests MUST verify:
- dry-run/explain output,
- stale/duplicate prevention,
- protected native context violation handling,
- ACP ingress provenance / receipt metadata appear when runtime reports them and remain absent when not reported,
- native context estimate confidence,
- engine identity rendering,
- non-legacy context-engine truth survives embedded-run, compaction, and subagent boundaries,
- no raw base64 durability.
### 13.5 Attachment/capability correctness
Tests MUST verify:
- capability TTL and invalidation,
- byte/token ceiling rendering,
- unsupported URL-only attachments reject explicitly,
- attachment transport mode is visible.
### 13.6 Streaming and abort correctness
Tests MUST verify:
- stream delta ordering,
- gap detection and replay,
- final-only truthful rendering,
- abort pending/verified/unknown/failed/cleanup states,
- room-turn abort correlation,
- room-turn stream semantics remain participant-specific and terminal-event-driven,
- DOC12 room surfaces consume the same room-turn completion/failure/abort contract rather than inventing synchronous room completion,
- native event → DOC11 event-family mapping works for chat, tool, abort, ACP, and channel-runtime families.
### 13.8 Anti-ghost-button rule
Every visible control MUST have a test proving one of:
- it triggers the correct route/command and telemetry,
- or it renders explicit unsupported/read-only state.
### 13.8A Anti-ghost acceptance gate
R14 is not acceptable unless all three are true:
1. every mutable control appears in Appendix U,
2. every Appendix U row has a matching route and read-model row,
3. every visible runtime badge/status chip/current-model display resolves from canonical truth projections rather than local component state.
### 13.9 Action-to-test mapping minima
At minimum there MUST be action tests for:
- model create/edit/delete/restore,
- model default/fallback policy update,
- model repair,
- provider/profile change,
- web-search provider config create/update/delete,
- web-search provider probe/test,
- web-search fallback policy update,
- auth start/complete/cancel/reverify/remove,
- selector mutation,
- capability check,
- attachment send,
- STOP/abort,
- context dry-run,
- mismatch/fallback viewer open,
- drawer actions,
- settings-only unsupported control behavior.
---
### 13.7 Usage/cost correctness
Tests MUST verify:
- completion usage,
- partial usage on fail/abort,
- verified vs estimated vs unknown cost truth,
- room and participant subtotals,
- switch/fallback attribution,
- `dispatch_max_output_tokens_cap` propagation.
## 14) Summary of DOC11 R14
R14 compiles the previously overlaid closure material into one operative document and strengthens DOC11 by:
- eliminating duplicate normative schema definitions and replacing the overlay model with one authoritative body plus canonical appendices,
- fully specifying session lifecycle, control-mutation, runtime-truth, model CRUD, auth, search, local/self-hosted provider, ACP, and channel seams,
- closing route/request/response/event/read-model gaps that previously left settings controls and diagnostics at risk of becoming phantom UI,
- making configured, effective, executed, watermark, mismatch, fallback, and health truth explicit across chat, settings, rooms, and debug surfaces,
- completing model execution policy, primary/fallback routing, search fallback, and Fast Mode / service-tier truth,
- formalizing ACP/Coding Mode as an agent-level runtime with durable binding truth, project-root semantics, resume behavior, and honest limitations,
- completing Discord and WhatsApp channel setup, pairing/login/account-binding, and channel-safe projection semantics,
- tightening context budgets, manifests, protected native context, context-engine diagnostics, and the singular root-memory bootstrap rule,
- aligning provider/search/local catalogs with plugin-backed runtime discovery and post–March 3 OpenClaw release behavior,
- strengthening cross-doc obligations to DOC10, DOC12, DOC13, DOC4, DOC15, and the canonical Q UI spec,
- replacing exemplar-only anti-ghost guidance with an exhaustive control-to-route/read-model matrix,
- and removing placeholders, duplicate definitions, contradictory route families, and raw amendment markers.
## Appendix A1 - Protocol support and evidence matrix for DOC11 R14
| Capability | Classification | Evidence requirement | Notes |
|---|---|---|---|
| `chat.send` | `native_probed` | live probe + runtime artifact | adapter may still add correlation fields |
| `sessions.patch` | `native_probed` | live probe | requires fully qualified model refs |
| `sessions.get` | `native_probed` | live probe | used for verification truth |
| context engine slot | `native_probed` for 2026.3.7+ | release + config docs + runtime evidence | defaults to `legacy` |
| auth start/complete/probe | `wrapper_composed` / runtime-relay | adapter contract + runtime/plugin relay evidence | Q is presenter/relay, not credential vault |
| provider/model auth state | `wrapper_composed` | auth stores + runtime probe | execution watermark carries provenance |
| native default model primary/fallback policy | `native_documented_not_probed` until local probe refreshed | model docs + configuration docs | Q settings surface mirrors runtime policy |
| local/self-hosted provider config via `models.providers` | `native_documented_not_probed` until local probe refreshed | provider docs + configuration docs | Q settings surface mirrors provider config truth |
| implicit local discovery (`ollama`, `vllm`) | `native_documented_not_probed` until local probe refreshed | provider docs + runtime probe | do not assume without audit refresh |
| local metadata overrides | `wrapper_composed` | DOC11 stores + UI badges | operator asserted unless runtime verified |
| local Gateway-bypass prevention | `wrapper_policy_required` | route/service tests + telemetry | no direct local inference bypass |
| web-search provider config (`tools.web.search`) | `native_documented_not_probed` until local probe refreshed | web tools + configuration docs | Q settings surface mirrors runtime tool config |
| search provider auto-detection | `native_documented_not_probed` until local probe refreshed | web tools + secrets docs | provider unset may still activate provider-specific credentials |
| search fallback chain | `wrapper_composed` | DOC11 routes + watermark/fallback telemetry | never silent |
| temperature patch | `unsupported` unless re-audited | control support matrix | settings-only |
| desired max output | `advisory_only` | DOC13 seam only | not native control without audited support |
| dispatch max output cap | `wrapper_composed` | provider-call enforcement evidence | physical request cap |
| remote_url attachment transport | `unsupported` unless runtime-probed | capability check evidence | no silent drop |
## Appendix B - Control field support matrix for audited Gateway build
| Field | Desired state | Effective state | Executed watermark | Native patch support | UI treatment |
|---|---|---|---|---|---|
| `model_ref` | yes | yes | yes | yes | fully editable |
| `primary_model_ref` | yes | yes | indirect via executed watermark + fallback log | native config path | settings default policy |
| `fallback_model_refs` | yes | yes | indirect via fallback log and switch segments | native config path | ordered settings UI |
| `provider_id` | yes | yes | yes | runtime-dependent | editable only where truth-backed |
| `thinking_level` | yes | yes | yes | yes if audited | fully editable |
| `reasoning_mode` | yes | yes | yes | yes if audited | fully editable |
| `verbose_level` | yes | yes | yes | yes if audited | fully editable |
| `temperature` | yes | no verified runtime claim | watermark only if actually executed | unsupported in audited baseline | settings-only gray |
| `desired_max_output_tokens_user` | yes | no verified runtime claim | no unless executed via audited path | unsupported in audited baseline | settings-only gray |
| `dispatch_max_output_tokens_cap` | no user selector | adapter-only | yes if applied | wrapper-composed | debug/cost only |
| `auth_profile_id` | yes | yes | yes | wrapper/runtime relay | editable where auth supported |
| `local_runtime_kind` | yes | yes | indirect via provider_id + local provider state | provider-config path | settings local providers |
| `local_provider_base_url` | yes | yes | indirect via provider_id + debug/runtime state | provider-config path | settings local providers |
| `local_provider_discovery_mode` | yes | yes | indirect via debug/runtime state | provider-config path | settings local providers |
| `local_model_metadata_override` | yes | yes when applied | indirect via capability/budget/debug | wrapper-composed | model row override editor |
| `search_provider_id` | yes | yes | via search execution watermark | native tool-config path | settings web search |
| `search_provider_model_override` | yes | yes where supported | via search execution watermark | provider-specific | advanced search settings |
## Appendix C - Event payload minima by family
Every event payload MUST include:
- `event_id`
- `event_family`
- `occurred_at`
- `correlation_id`
Additional envelope-level refs such as `operation_id`, `route_trace_id`, `gateway_session_key`, `run_id`, `participant_id`, `room_id`, `room_turn_id`, `search_execution_id`, `tool_call_id`, `channel_kind`, `account_id`, and `session_key` MUST be present whenever that family logically requires them.
| Event family | Required payload minima | Read-model / UI impact |
|---|---|---|
| `runtime_truth.resolved` | `scope_kind`, `scope_ref`, `truth_resolver_result_ref`, `catalog_generation?`, `effective_state_ref?`, `execution_watermark_ref?` | truth resolver, selector truth, runtime chips |
| `runtime_truth.resolve_conflict_detected` | `scope_kind`, `scope_ref`, `field_names[]`, `reason_codes[]` | conflict badges, debug surfaces |
| `gateway.runtime.truth_resolver.viewed` | `scope_kind`, `scope_ref`, `include_history?` | debug/audit telemetry only |
| `gateway.health.changed` | `gateway_status`, `auth_mode`, `active_context_engines[]`, `reason_codes[]`, `scope_ref?` | Runtime & Connectivity aggregate cards |
| `gateway.provider.health.degraded` | `provider_id`, `reason_codes[]` | provider cards / warnings |
| `gateway.provider.health.recovered` | `provider_id`, `reason_codes[]` | provider cards / warnings |
| `gateway.session.created` | `session_key`, `scope_kind`, `scope_ref`, `binding_state`, `gateway_session_key?`, `reason_codes[]?` | session cards, participant/runtime binding state |
| `gateway.session.verified` | `session_key`, `scope_kind`, `scope_ref`, `binding_state`, `effective_state_ref?`, `auth_mode`, `native_context_engine_id?`, `native_context_engine_active`, `prompt_injection_policy_active`, `reason_codes[]?` | session/runtime verification surfaces |
| `gateway.session.reconnected` | `session_key`, `scope_kind`, `scope_ref`, `binding_state`, `reason_codes[]?` | reconnect banners, Runtime & Connectivity |
| `gateway.session.reconnect_exhausted` | `session_key`, `scope_kind`, `scope_ref`, `reason_codes[]` | reconnect failure surfaces |
| `gateway.session.repaired` | `session_key`, `scope_kind`, `scope_ref`, `repair_kind`, `reason_codes[]?` | orphan repair UI |
| `gateway.session.closed` | `session_key`, `scope_kind`, `scope_ref`, `close_reason`, `reason_codes[]?` | runtime/session panels |
| `gateway.session.archived` | `session_key`, `scope_kind`, `scope_ref`, `archive_reason?`, `reason_codes[]?` | archival/admin views |
| `gateway.runtime.connection_log.viewed` | `scope_ref?`, `limit?`, `before?` | connection-log telemetry only |
| `gateway.control.mutated` | `mutation_id`, `scope_kind`, `scope_ref`, `field_results[]` | selector states, settings forms, drawers |
| `gateway.control.rejected` | `mutation_id`, `scope_kind`, `scope_ref`, `field_results[]`, `reason_codes[]` | selector errors, settings forms |
| `gateway.control.fast_mode.changed` | `scope_kind`, `scope_ref`, `fast_mode`, `effective_fast_mode`, `reason_codes[]?` | fast-mode badges, selector rows, execution telemetry |
| `gateway.control.one_shot_discarded` | `scope_kind`, `scope_ref`, `field_name`, `reason_codes[]` | one-shot control diagnostics |
| `gateway.chat.accepted` | `run_id`, `gateway_session_key`, `stream_mode`, `execution_watermark_ref` | chat transcript / streaming state |
| `gateway.chat.stream.delta` | `run_id`, `sequence`, `event_kind`, `delta_text?` | live chat stream |
| `gateway.chat.completed` | `run_id`, `usage_sample_ref?`, `execution_watermark_ref` | transcript finalization |
| `gateway.chat.failed` | `run_id`, `reason_codes[]`, `usage_sample_ref?` | failure surfaces |
| `gateway.chat.aborted` | `run_id`, `abort_id`, `usage_sample_ref?` | abort surfaces |
| `gateway.stream.recovered` | `run_id`, `replay_cursor`, `recovered_sequence`, `reason_codes[]?` | streaming recovery diagnostics |
| `gateway.dual_connection_write_conflict_detected` | `read_model_id`, `owner_process`, `duplicate_operation_id`, `duplicate_correlation_id`, `reason_codes[]?` | diagnostics / write-conflict surfaces |
| `gateway.tool.started` | `run_id`, `tool_call_id`, `tool_name`, `tool_kind?` | tool activity views |
| `gateway.tool.stdout_stream` | `run_id`, `tool_call_id`, `sequence`, `stream_kind`, `chunk_text?`, `truncated?` | tool stream views |
| `gateway.tool.completed` | `run_id`, `tool_call_id`, `result_kind`, `truncated?` | tool result views |
| `gateway.tool.failed` | `run_id`, `tool_call_id`, `reason_codes[]`, `error_message?` | tool error views |
| `gateway.tool.unclassified` | `run_id`, `raw_native_event`, `reason_codes[]` | debug/telemetry only |
| `room.turn.accepted` | `room_id`, `room_turn_id`, `participant_id`, `run_id` | room transcript / participant chips |
| `room.turn.stream.delta` | `room_id`, `room_turn_id`, `participant_id`, `run_id`, `sequence` | room live stream |
| `room.turn.completed` | `room_id`, `room_turn_id`, `participant_id`, `usage_sample_ref?`, `execution_watermark_ref` | room transcript finalization |
| `room.turn.failed` | `room_id`, `room_turn_id`, `participant_id`, `reason_codes[]` | room error surfaces |
| `room.turn.aborted` | `room_id`, `room_turn_id`, `participant_id`, `abort_id` | room abort surfaces |
| `gateway.abort.requested` | `abort_id`, `abort_scope_kind`, `abort_scope_ref`, `run_id?`, `participant_id?`, `room_id?` | STOP pending state |
| `gateway.abort.acknowledged` | `abort_id`, `abort_scope_kind`, `abort_scope_ref`, `abort_state` | STOP acknowledged state |
| `gateway.abort.timeout` | `abort_id`, `abort_scope_kind`, `abort_scope_ref`, `abort_state`, `reason_codes[]` | STOP timeout state |
| `gateway.abort.cleanup_failed` | `abort_id`, `abort_scope_kind`, `abort_scope_ref`, `cleanup_state`, `reason_codes[]` | cleanup failure surfaces |
| `gateway.auth.challenge_required` | `provider_id`, `profile_id?`, `challenge_id`, `challenge_kind`, `post_success_action` | auth challenge modal/banner |
| `gateway.auth.challenge_completed` | `provider_id`, `profile_id?`, `challenge_id`, `challenge_kind`, `reason_codes[]?` | auth challenge closure |
| `gateway.auth.challenge_failed` | `provider_id`, `profile_id?`, `challenge_id`, `challenge_kind`, `failure_stage`, `reason_codes[]` | auth challenge failure UI, auth issue log |
| `gateway.auth.state_changed` | `provider_id`, `profile_id?`, `auth_state`, `reason_codes[]` | auth badges, settings, execution truth |
| `gateway.auth.default_changed` | `provider_id`, `profile_id`, `reason_codes[]?` | auth settings |
| `gateway.auth.profile_removed` | `provider_id`, `profile_id`, `affected_scope_refs[]?`, `reason_codes[]` | auth settings / degraded scopes |
| `catalog.model.created` | `model_id`, `provider_id`, `model_ref`, `catalog_generation`, `source_kind`, `reason_codes[]?` | model catalog tables / selectors |
| `catalog.model.updated` | `model_id`, `provider_id`, `model_ref`, `catalog_generation`, `changed_fields[]`, `reason_codes[]?` | model catalog tables / selectors |
| `catalog.model.deleted` | `model_id`, `provider_id`, `model_ref`, `catalog_generation`, `delete_mode`, `orphaned_scope_refs[]?`, `reason_codes[]?` | model catalog tables / orphan diagnostics |
| `catalog.model.restored` | `model_id`, `provider_id`, `model_ref`, `catalog_generation`, `reason_codes[]?` | model catalog tables / orphan diagnostics |
| `gateway.plugin.catalog.refreshed` | `catalog_kind`, `ownership_kind`, `warning_codes[]?` | catalog surfaces, Runtime & Connectivity |
| `gateway.plugin.catalog.degraded` | `catalog_kind`, `reason_codes[]` | catalog warning surfaces |
| `gateway.local_provider.config.changed` | `provider_id`, `deployment_kind`, `local_runtime_kind?`, `discovery_mode`, `base_url?` | local provider settings |
| `gateway.local_provider.state.changed` | `provider_id`, `reachability_state`, `verification_state`, `reason_codes[]` | local provider runtime cards |
| `gateway.local_provider.probe.completed` | `provider_id`, `reachability_state`, `discovered_model_refs[]?`, `usage_reporting_mode` | local provider cards |
| `gateway.local_provider.probe.failed` | `provider_id`, `reason_codes[]` | local provider error surfaces |
| `gateway.local_provider.catalog.synced` | `provider_id`, `catalog_generation`, `added_model_refs[]`, `removed_model_refs[]` | model catalog / local provider surfaces |
| `gateway.local_provider.config.deleted` | `provider_id`, `delete_mode`, `orphaned_scope_refs[]?` | local provider settings |
| `gateway.local_provider.reverted_to_implicit_discovery` | `provider_id`, `catalog_generation`, `reason_codes[]` | local provider settings |
| `gateway.local_provider.model_metadata.updated` | `provider_id`, `model_ref`, `verification_state` | local provider model rows |
| `gateway.local_provider.model_metadata.deleted` | `provider_id`, `model_ref`, `reason_codes[]?` | local provider model rows |
| `gateway.local_provider.direct_bypass.blocked` | `provider_id`, `attempted_path`, `reason_codes[]` | diagnostics / audit |
| `gateway.search.config.changed` | `provider_id?`, `search_enabled`, `auto_detect_mode`, `reason_codes[]` | search settings |
| `gateway.search.state.changed` | `effective_provider_id?`, `effective_provider_source`, `verification_state`, `compatibility_path_active?`, `reason_codes[]` | search runtime cards |
| `gateway.search.runtime_snapshot.refreshed` | `provider_mode`, `effective_provider_id?`, `effective_profile_id?`, `auto_detect_mode`, `compatibility_path_active?`, `reason_codes[]?` | search runtime state |
| `gateway.search.probe.completed` | `provider_id`, `provider_config_id`, `verification_state`, `last_verified_at` | search settings |
| `gateway.search.probe.failed` | `provider_id`, `provider_config_id?`, `reason_codes[]` | search settings |
| `gateway.search.execution.recorded` | `search_execution_id`, `provider_id`, `provider_mode?`, `compatibility_path_active?`, `run_id?`, `tool_call_id?`, `used_fallback` | latest search execution watermark |
| `gateway.search.fallback.recorded` | `search_execution_id`, `stage`, `trigger_reason`, `candidate_index`, `provider_id`, `to_provider_id?`, `compatibility_path_active?`, `zero_results_final`, `reason_codes[]?` | search fallback panels / runtime truth |
| `gateway.search.provider_config.deleted` | `provider_id`, `delete_mode`, `reason_codes[]` | search settings |
| `gateway.acp.system_state.changed` | `acp_enabled`, `dispatch_enabled`, `backend`, `health_state`, `allowed_harnesses[]` | ACP settings/runtime cards |
| `gateway.acp.profile.saved` | `acp_profile_id`, `harness_id`, `mode`, `session_style`, `primary_workdir_uri?` | ACP settings |
| `gateway.acp.profile.deleted` | `acp_profile_id`, `affected_agent_ids[]?`, `reason_codes[]` | ACP settings |
| `gateway.acp.doctor.completed` | `backend`, `health_state`, `harness_results[]`, `reason_codes[]` | ACP system diagnostics |
| `gateway.acp.agent_runtime_assignment.changed` | `agent_id`, `runtime_mode`, `acp_profile_id?`, `reason_codes[]` | agent editor, ACP badges |
| `gateway.acp.project_roots.changed` | `acp_profile_id`, `root_count`, `apply_results[]`, `reason_codes[]?` | ACP root tables |
| `gateway.acp.project_roots.validated` | `acp_profile_id?`, `root_results[]`, `reason_codes[]?` | ACP root validation UI |
| `gateway.acp.binding.changed` | `binding_id`, `binding_scope_kind`, `binding_scope_ref`, `session_key?`, `reason_codes[]?` | ACP binding surfaces |
| `gateway.acp.binding.broken` | `binding_id`, `binding_scope_kind`, `binding_scope_ref`, `reason_codes[]` | ACP degraded/broken surfaces |
| `gateway.acp.session.state_changed` | `session_key`, `session_id?`, `harness_id`, `session_state`, `resume_supported`, `working_directory_uri?` | ACP session cards / badges |
| `gateway.acp.session.option_changed` | `session_key`, `option_name`, `old_value?`, `new_value?`, `execution_watermark_ref?` | ACP session panel |
| `gateway.acp.session.option_reset` | `session_key`, `reset_fields[]` | ACP session panel |
| `gateway.acp.session.resuming` | `session_key`, `session_id?`, `soft_threshold_ms`, `hard_timeout_ms` | ACP session pending UI |
| `gateway.acp.session.resume_degraded` | `session_key`, `session_id?`, `reason_codes[]?` | ACP resume degraded UI |
| `gateway.acp.session.resumed` | `session_key`, `session_id`, `resumed_from_session_id?`, `reason_codes[]?` | ACP session state |
| `gateway.acp.session.resume_failed` | `session_key`, `session_id?`, `reason_codes[]` | ACP session error UI |
| `gateway.acp.session.cancelled` | `session_key`, `session_id?`, `reason_codes[]` | ACP session UI |
| `gateway.acp.session.closed` | `session_key`, `session_id?`, `close_reason`, `reason_codes[]` | ACP session UI |
| view telemetry only | `cursor?`, `limit?`, `state_filter[]?`, `harness_filter[]?` | ACP session list telemetry only |
| `gateway.acp.session.detail.viewed` | `session_key`, `session_id?` | audit telemetry only |
| `gateway.acp.session.history.viewed` | `session_key`, `session_id?`, `cursor?` | audit telemetry only |
| `gateway.acp.attachments.rejected` | `scope_kind`, `scope_ref`, `attachment_ids[]?`, `reason_codes[]` | ACP attachment-disabled UI |
| `gateway.acp.unavailable` | `scope_kind`, `scope_ref`, `harness_id?`, `reason_codes[]` | ACP unavailable UI |
| `gateway.channel.config.changed` | `provider_id`, `account_id?`, `enabled`, `reason_codes[]` | channel settings |
| `gateway.channel.state.changed` | `provider_id`, `account_id?`, `health_state`, `login_state`, `pairing_state`, `challenge_state?`, `capability_state`, `pairing_challenge_id?` | channel runtime cards |
| `gateway.channel.probe.completed` | `provider_id`, `account_id?`, `health_state`, `capability_state`, `last_probe_at` | channel settings/runtime cards |
| `gateway.channel.probe.failed` | `provider_id`, `account_id?`, `reason_codes[]` | channel settings/runtime cards |
| `gateway.channel.login.started` | `provider_id`, `account_id?`, `challenge_id?`, `challenge_type?`, `expires_at?` | channel login UI |
| `gateway.channel.login.cancelled` | `provider_id`, `account_id?`, `reason_codes[]` | channel login UI |
| `gateway.channel.logout.completed` | `provider_id`, `account_id`, `reason_codes[]` | channel login/runtime UI |
| `gateway.channel.pairing_challenge_issued` | `provider_id`, `account_id?`, `challenge_id`, `challenge_type`, `expires_at?` | interactive pairing / QR UI |
| `gateway.channel.pairing_challenge_refreshed` | `provider_id`, `account_id?`, `challenge_id`, `expires_at?` | QR refresh UI |
| `gateway.channel.pairing_challenge_cancelled` | `provider_id`, `account_id?`, `challenge_id` | pairing teardown UI |
| `gateway.channel.pairing_challenge_viewed` | `provider_id`, `account_id?`, `challenge_id` | pairing view telemetry |
| `gateway.channel.pairing_challenge_expired` | `provider_id`, `account_id?`, `challenge_id`, `reason_codes[]?` | expired challenge UI |
| `gateway.channel.pairing_queue.updated` | `provider_id`, `queue_size`, `account_id?`, `reason_codes[]?` | pairing queue views |
Canonical event-name rewrite rules:
- legacy plugin-catalog per-entry event aliases are retired; compiled DOC11 MUST use only the canonical plugin-catalog families `gateway.plugin.catalog.refreshed` and `gateway.plugin.catalog.degraded`.
- legacy dotted channel pairing event-family aliases are retired; compiled DOC11 MUST use only the underscore pairing families listed in Appendix C.
- legacy local-provider health alias names are retired; compiled DOC11 MUST use `gateway.local_provider.state.changed`.
- legacy generic channel-health alias names are retired; compiled DOC11 MUST use only the canonical channel families declared in Appendix C, Appendix E, and Appendix U.
| `gateway.channel.pairing.approved` | `provider_id`, `account_id?`, `pair_code`, `reason_codes[]?` | pairing queue / account state |
| `gateway.channel.pairing.rejected` | `provider_id`, `account_id?`, `pair_code`, `reason_codes[]?` | pairing queue / account state |
| `gateway.channel.rules.changed` | `provider_id`, `account_id?`, `rule_count?`, `reason_codes[]?` | Discord/WhatsApp rules editors |
| `gateway.channel.binding.changed` | `provider_id`, `account_id`, `agent_id`, `runtime_mode?`, `reason_codes[]` | channel binding editor |
| `gateway.channel.binding.deleted` | `provider_id`, `account_id`, `reason_codes[]` | channel binding editor |
| `gateway.channel.projected_dispatch.accepted` | `projection_id`, `provider_id`, `account_id`, `source_surface_kind`, `source_surface_id`, `delivery_ref?` | channel projection telemetry |
| `gateway.channel.projected_dispatch.failed` | `projection_id`, `provider_id`, `account_id`, `source_surface_kind`, `source_surface_id`, `reason_codes[]` | channel projection error UI |
| `gateway.capability.check.completed` | `capability_check_id`, `scope_kind`, `scope_ref`, `ttl_ms`, `items[]` | Capability Viewer |
| `gateway.capability_cache.invalidated` | `scope_kind`, `scope_ref`, `reason_codes[]` | capability viewer freshness |
| `gateway.context.applied` | `manifest_id`, `scope_kind`, `scope_ref`, `included_source_ids[]` | Context Inspector |
| `gateway.context.degraded` | `manifest_id`, `scope_kind`, `scope_ref`, `reason_codes[]` | Context Inspector warnings |
| `gateway.context.explain` | `scope_kind`, `scope_ref`, `manifest_id?`, `reason_codes[]?` | Context Inspector / dry-run |
| `gateway.context.diagnostics.viewed` | `scope_kind`, `scope_ref`, `diagnostics_state?`, `reason_codes[]?` | Context diagnostics viewer telemetry |
| `gateway.context.budget_settings.changed` | `scope_kind`, `scope_ref?`, `budget_profile_id?`, `reason_codes[]?` | budget settings mutation telemetry |
| `gateway.context.protected_files.viewed` | `scope_kind`, `scope_ref`, `file_count?` | protected-files viewer telemetry |
| `gateway.context_engine.health.changed` | `engine_id`, `engine_kind`, `health_state`, `reason_codes[]?` | Runtime & Connectivity / Context Inspector |
| `gateway.mismatch.recorded` | `scope_kind`, `scope_ref`, `field_name`, `desired_value`, `executed_value`, `switch_segment_id?` | mismatch log |
| `gateway.mismatch_log.viewed` | `scope_ref?`, `participant_id?`, `room_id?`, `cursor?` | mismatch/fallback log telemetry |
| `gateway.fallback.recorded` | `scope_kind`, `scope_ref`, `field_name`, `from_value`, `to_value`, `switch_segment_id` | fallback log |
| `gateway.switch_segment.opened` | `switch_segment_id`, `scope_kind`, `scope_ref`, `meaningful_change_fields[]` | switch segment views |
| `gateway.switch_segment.closed` | `switch_segment_id`, `scope_kind`, `scope_ref`, `close_reason` | switch segment views |
| `gateway.protected_native_context.violation` | `scope_kind`, `scope_ref`, `source_id`, `blocked_mutator`, `reason_codes[]` | protected native context violations |
| `gateway.loop_hamstringing_attempted` | `run_id`, `scope_ref`, `attempted_action`, `reason_codes[]` | diagnostics |
| `usage.sample.recorded` | `usage_sample_id`, `scope_kind`, `scope_ref`, `usage_state`, `cost_truth_mode`, `switch_segment_id?`, `participant_id?`, `room_id?`, `room_turn_id?` | usage/cost surfaces |
Canonical event rules:
- `gateway.auth.challenge_failed` remains canonical and challenge-scoped; `gateway.auth.state_changed` remains canonical and aggregate.
- `gateway.search.runtime_snapshot.refreshed` is canonical and MUST NOT be deleted as phantom.
- Fast Mode uses the canonical control-mutation and truth-resolution flow. `gateway.control.fast_mode.changed` is the canonical direct fast-mode visibility family, while `gateway.control.mutated` / `gateway.control.rejected` remain the underlying mutation path.
- `gateway.health.changed` is aggregate gateway health and does not replace subsystem events such as provider, local-provider, channel, or context-engine health families.
## Appendix D - Attachment transport and switch/compaction matrix
| Attachment mode | Baseline status | Persist raw bytes? | User-visible state | Failure behavior |
|---|---|---:|---|---|
| `inline_base64` | supported baseline | no | normal | explicit failure if too large |
| `staged_local_file_ref` | wrapper/future | no | only when capability-verified | explicit unsupported/degraded if unavailable |
| `remote_url` | unsupported unless probed | no | explicit unsupported badge | explicit reject; no silent drop |
## Appendix E - Route contract table (normative)
HTTP response-mode compatibility rule:
- the default response mode is `typed_json`,
- callback relay routes MAY use `redirect_html`,
- explicitly destructive routes MAY use `no_content` only when the route row says so,
- no route may silently switch response mode without declaring it in Appendix E.
| Route | Purpose | Triggering UI/control | Owner service | Durable effect | Telemetry |
|---|---|---|---|---|---|
| `GET /api/openclaw/runtime/catalog` | read current catalog | selectors/settings | `RuntimeTruthService` | none | view telemetry only |
| `GET /api/openclaw/runtime/truth` | truth resolver read | indicators/drawer | `RuntimeTruthService` | none | `runtime_truth.resolved` |
| `GET /api/openclaw/runtime/truth/debug` | debug truth bundle read | truth resolver debug view | `RuntimeTruthDebugService` | `truth_resolver_debug_projection` refresh | `gateway.runtime.truth_resolver.viewed` |
| `GET /api/openclaw/runtime/indicators` | aggregated runtime/status read | top bar/page badges/runtime & connectivity | `IndicatorAggregateService` | none | view telemetry only |
| `POST /api/openclaw/runtime/control-mutations` | mutate controls | selectors/drawer/settings | `SelectorTruthService` | desired state + queue + projections | `gateway.control.mutated` / `gateway.control.rejected` |
| `POST /api/openclaw/catalog/models` | create model | settings models | `RuntimeTruthService` | catalog store | `catalog.model.created` |
| `PATCH /api/openclaw/catalog/models/:id` | edit model | settings models | `RuntimeTruthService` | catalog store / override stores | `catalog.model.updated` |
| `DELETE /api/openclaw/catalog/models/:id` | soft delete model | settings models | `RuntimeTruthService` | catalog tombstone + orphaning | `catalog.model.deleted` |
| `POST /api/openclaw/catalog/models/:id/restore` | restore model | settings models | `RuntimeTruthService` | catalog restore | `catalog.model.restored` |
| `GET /api/openclaw/model-execution-policy` | read default model primary/fallback policy | settings providers/models | `RuntimeTruthService` | none | view telemetry only |
| `POST /api/openclaw/model-execution-policy` | update default model primary/fallback policy | settings providers/models | `RuntimeTruthService` | `model_execution_policy_store` + runtime apply | `gateway.control.mutated` |
| `POST /api/openclaw/models/orphan-repair` | repair orphaned binding | drawer/settings | `SessionTruthService` | binding/store + switch segment | `gateway.session.repaired` |
| `GET /api/openclaw/providers/auth/catalog` | read auth method catalog | settings auth | `ProviderAuthStateService` | none | view telemetry only |
| `GET /api/openclaw/providers/auth/profiles` | read auth profiles | settings auth | `ProviderAuthStateService` | none | view telemetry only |
| `GET /api/openclaw/providers/auth/state` | read effective auth state | settings auth/runtime cards/drawer | `ProviderAuthStateService` | none | view telemetry only |
| `GET /api/openclaw/providers/auth/challenges` | read active/pending challenges | settings auth/auth modal/banner | `ProviderAuthStateService` | none | view telemetry only |
| `GET /api/openclaw/providers/auth/plugin-catalog` | read plugin-managed auth catalog | settings auth | `ProviderAuthStateService` | none | view telemetry only |
| `GET /api/openclaw/providers/auth/issues` | read auth issue log | settings auth/debug | `ProviderAuthStateService` | none | view telemetry only |
| `POST /api/openclaw/providers/auth/start` | start auth flow | settings auth / auth banner | `ProviderAuthStateService` | challenge store | `gateway.auth.challenge_required` |
| `POST /api/openclaw/providers/auth/complete` | complete auth flow | auth modal/callback relay | `ProviderAuthStateService` | auth state/challenge/profile stores | `gateway.auth.state_changed` |
| `POST /api/openclaw/providers/auth/callback/complete` | complete OAuth callback relay | auth callback close/success/failure handling | `ProviderAuthStateService` | auth state/challenge/profile stores | `gateway.auth.state_changed` / `gateway.auth.challenge_failed` |
| `POST /api/openclaw/providers/auth/cancel` | cancel auth flow | auth modal | `ProviderAuthStateService` | auth state/challenge stores | `gateway.auth.state_changed` |
| `POST /api/openclaw/providers/auth/probe` | verify auth/profile/scopes | settings auth | `ProviderAuthStateService` | auth state store | `gateway.auth.state_changed` |
| `POST /api/openclaw/providers/auth/verify-cli` | verify CLI-managed auth | settings auth | `ProviderAuthStateService` | auth state store | `gateway.auth.state_changed` |
| `POST /api/openclaw/providers/auth/cli-login` | initiate CLI login | settings auth | `ProviderAuthStateService` | auth state/challenge stores | `gateway.auth.state_changed` |
| `POST /api/openclaw/providers/auth/setup-token` | store/setup token path | settings auth | `ProviderAuthStateService` | auth/profile stores | `gateway.auth.state_changed` |
| `POST /api/openclaw/providers/auth/paste-token` | pasted-token path | settings auth | `ProviderAuthStateService` | auth/profile stores | `gateway.auth.state_changed` |
| `POST /api/openclaw/providers/auth/remove` | remove auth profile | settings auth | `ProviderAuthStateService` | auth/profile stores + degraded scopes | `gateway.auth.profile_removed` / `gateway.auth.state_changed` |
| `POST /api/openclaw/providers/auth/set-default` | set default auth profile | settings auth | `ProviderAuthStateService` | auth/profile stores | `gateway.auth.default_changed` / `gateway.auth.state_changed` |
| `POST /api/openclaw/providers/auth/relogin` | restart login for existing profile | settings auth | `ProviderAuthStateService` | auth/challenge stores | `gateway.auth.challenge_required` |
| `GET /api/openclaw/providers/auth/callback` | provider callback relay / completion | browser auth return | `ProviderAuthStateService` | auth/challenge stores | `gateway.auth.challenge_completed` / `gateway.auth.challenge_failed` / `gateway.auth.state_changed` |
| `GET /api/openclaw/providers/capabilities` | read provider capability catalog | settings local providers | `LocalProviderConfigService` | none | view telemetry only |
| `GET /api/openclaw/providers/local-runtime-configs` | read local/self-hosted provider configs | settings local providers | `LocalProviderConfigService` | none | view telemetry only |
| `GET /api/openclaw/providers/local-runtime-state` | read local/self-hosted runtime state | settings local providers/runtime & connectivity/debug | `LocalProviderConfigService` | none | view telemetry only |
| `PUT /api/openclaw/providers/local-runtime-configs/:provider_id` | create/update local/self-hosted provider config | settings local providers | `LocalProviderConfigService` | local provider config store + runtime apply | `gateway.local_provider.config.changed` |
| `POST /api/openclaw/providers/local-runtime-configs/:provider_id/probe` | probe local/self-hosted provider runtime | settings local providers | `LocalProviderConfigService` | runtime state store | `gateway.local_provider.probe.completed` / `gateway.local_provider.probe.failed` |
| `POST /api/openclaw/providers/local-runtime-configs/:provider_id/sync-catalog` | sync local/self-hosted provider models into catalog | settings local providers | `LocalProviderConfigService` | catalog store + runtime state store | `gateway.local_provider.catalog.synced` |
| `DELETE /api/openclaw/providers/local-runtime-configs/:provider_id` | delete local/self-hosted provider config | settings local providers | `LocalProviderConfigService` | config tombstone + orphaning | `gateway.local_provider.config.deleted` |
| `POST /api/openclaw/providers/local-runtime-configs/:provider_id/revert-to-implicit-discovery` | remove explicit config and revert to implicit discovery | settings local providers | `LocalProviderConfigService` | config removal + catalog refresh | `gateway.local_provider.reverted_to_implicit_discovery` |
| `GET /api/openclaw/tools/web-search/catalog` | read search provider catalog | settings web search | `SearchProviderConfigService` | none | view telemetry only |
| `GET /api/openclaw/tools/web-search/configs` | read provider configs | settings web search | `SearchProviderConfigService` | none | view telemetry only |
| `GET /api/openclaw/tools/web-search/state` | read effective search runtime state | settings web search/runtime & connectivity | `SearchProviderConfigService` | none | view telemetry only |
| `GET /api/openclaw/tools/web-search/executions/latest` | read latest search execution watermark | settings web search/debug/runtime & connectivity | `SearchExecutionWatermarkService` | none | view telemetry only |
| `PUT /api/openclaw/tools/web-search/provider-configs/:provider_id` | create/update provider config | settings web search | `SearchProviderConfigService` | config store + runtime apply | `gateway.search.config.changed` |
| `DELETE /api/openclaw/tools/web-search/provider-configs/:provider_id` | delete provider config | settings web search | `SearchProviderConfigService` | config store + runtime apply | `gateway.search.provider_config.deleted` |
| `POST /api/openclaw/tools/web-search/default-provider` | set explicit provider or auto mode | settings web search | `SearchProviderConfigService` | runtime state + runtime apply | `gateway.search.state.changed` |
| `POST /api/openclaw/tools/web-search/fallback-policy` | update wrapper search fallback policy | settings web search | `SearchProviderConfigService` | fallback policy store | `gateway.search.config.changed` |
| `POST /api/openclaw/tools/web-search/probe` | probe selected provider config | settings web search | `SearchProviderConfigService` | runtime/config verification fields | `gateway.search.probe.completed` / `gateway.search.probe.failed` |
| `POST /api/openclaw/tools/web-search/test` | execute test search | settings web search | `SearchProviderConfigService` | search execution watermark | `gateway.search.execution.recorded` |
| `GET /api/openclaw/acp/system-state` | read ACP global state | settings ACP/runtime & connectivity | `AcpHarnessService` | none | view telemetry only |
| `GET /api/openclaw/acp/harnesses` | read ACP harness catalog | settings ACP/agent editor | `AcpHarnessService` | none | view telemetry only |
| `GET /api/openclaw/acp/profiles` | read ACP profiles | settings ACP/agent editor | `AcpHarnessService` | none | view telemetry only |
| `PUT /api/openclaw/acp/profiles/:profile_id` | create/update ACP profile | settings ACP | `AcpHarnessService` | `acp_profile_store` | `gateway.acp.profile.saved` |
| `DELETE /api/openclaw/acp/profiles/:profile_id` | delete ACP profile | settings ACP | `AcpHarnessService` | `acp_profile_store` tombstone + affected-agent degradation | `gateway.acp.profile.deleted` |
| `POST /api/openclaw/acp/doctor` | run backend/harness doctor | settings ACP/runtime & connectivity | `AcpHarnessService` | `acp_system_state_store` | `gateway.acp.doctor.completed` |
| `GET /api/openclaw/acp/sessions` | list recent/active ACP sessions | settings ACP/debug | `AcpSessionService` | none | view telemetry only |
| `GET /api/openclaw/acp/sessions/:session_key` | read ACP session detail | ACP session drawer / ACP session panel | `AcpSessionService` | none | view telemetry only |
| `GET /api/openclaw/acp/sessions/:acp_session_id/history` | read ACP session history | ACP session drawer | `AcpSessionService` | none | `gateway.acp.session.history.viewed` |
| `POST /api/openclaw/acp/sessions/:session_key/resume` | resume ACP session | ACP session panel / assignment recovery | `AcpSessionService` | session runtime state + watermark | `gateway.acp.session.resuming` / `gateway.acp.session.resumed` / `gateway.acp.session.resume_failed` / `gateway.acp.session.resume_degraded` |
| `POST /api/openclaw/acp/sessions/:session_key/model` | set ACP session model | ACP session panel | `AcpSessionService` | session runtime state + watermark | `gateway.acp.session.option_changed` |
| `POST /api/openclaw/acp/sessions/:session_key/permissions` | set ACP permissions profile | ACP session panel | `AcpSessionService` | session runtime state + watermark | `gateway.acp.session.option_changed` |
| `POST /api/openclaw/acp/sessions/:session_key/timeout` | set ACP timeout | ACP session panel | `AcpSessionService` | session runtime state + watermark | `gateway.acp.session.option_changed` |
| `POST /api/openclaw/acp/sessions/:session_key/cwd` | set ACP working directory | ACP session panel | `AcpSessionService` | session runtime state + watermark | `gateway.acp.session.option_changed` |
| `POST /api/openclaw/acp/sessions/:session_key/reset-options` | clear ACP runtime overrides | ACP session panel | `AcpSessionService` | session runtime state | `gateway.acp.session.option_reset` |
| `POST /api/openclaw/acp/sessions/:session_key/cancel` | cancel active ACP turn | ACP session panel/STOP | `AcpSessionService` | session runtime state | `gateway.acp.session.cancelled` |
| `POST /api/openclaw/acp/sessions/:session_key/close` | close ACP session | ACP session panel | `AcpSessionService` | session runtime state + binding close | `gateway.acp.session.closed` |
| `POST /api/openclaw/acp/validate-path` | validate ACP project root or additional root | ACP profile editor / agent editor | `AcpHarnessService` | none | view telemetry only |
| `PATCH /api/openclaw/agents/:agent_id/runtime-mode` | set agent runtime mode | agent editor | `AcpHarnessService` | `agent_runtime_assignment_store` | `gateway.acp.agent_runtime_assignment.changed` |
| `PATCH /api/openclaw/agents/:agent_id/acp-profile` | assign ACP profile to agent | agent editor | `AcpHarnessService` | `agent_runtime_assignment_store` | `gateway.acp.agent_runtime_assignment.changed` |
| `GET /api/openclaw/channels/state` | read shared channel runtime/account state | settings channels/runtime & connectivity | `ChannelConfigService` | none | view telemetry only |
| `GET /api/openclaw/channels/accounts` | read channel account list | settings channels | `ChannelConfigService` | none | view telemetry only |
| `GET /api/openclaw/channels/bindings` | read channel account bindings | settings channels | `ChannelConfigService` | none | view telemetry only |
| `GET /api/openclaw/context/diagnostics` | read context diagnostics bundle | context inspector / diagnostics | `ContextManifestService` | none | `gateway.context.diagnostics.viewed` |
| `PUT /api/openclaw/context/budget-settings` | update context budget settings | settings context & budget | `ContextManifestService` | budget settings store | `gateway.context.budget_settings.changed` |
| `PUT /api/openclaw/providers/local-runtime-configs/:provider_id/models/:model_ref/metadata` | upsert local model metadata override | settings local providers / model row editor | `LocalProviderConfigService` | local metadata override store | `gateway.local_provider.model_metadata.updated` |
| `DELETE /api/openclaw/providers/local-runtime-configs/:provider_id/models/:model_ref/metadata` | delete local model metadata override | settings local providers / model row editor | `LocalProviderConfigService` | local metadata override store | `gateway.local_provider.model_metadata.deleted` |
| `GET /api/openclaw/context/protected-files` | read protected native files metadata | context inspector / diagnostics | `ContextManifestService` | none | `gateway.context.protected_files.viewed` |
| `POST /api/openclaw/providers/auth/fix-scope` | repair or re-request scopes for a profile | settings auth / issue surfaces | `ProviderAuthStateService` | auth/challenge stores | `gateway.auth.challenge_required` / `gateway.auth.challenge_completed` / `gateway.auth.challenge_failed` / `gateway.auth.state_changed` |
| `PUT /api/openclaw/channels/bindings/:provider_id/:account_id` | save channel account → agent binding | settings channels | `ChannelConfigService` | `channel_binding_store` | `gateway.channel.binding.changed` |
| `DELETE /api/openclaw/channels/bindings/:provider_id/:account_id` | clear channel account binding | settings channels | `ChannelConfigService` | `channel_binding_store` | `gateway.channel.binding.deleted` |
| `GET /api/openclaw/channels/discord/config` | read Discord config | settings channels | `ChannelConfigService` | none | view telemetry only |
| `GET /api/openclaw/channels/discord/rules` | read Discord guild/group rules | settings channels | `ChannelConfigService` | none | view telemetry only |
| `PUT /api/openclaw/channels/discord/config` | save Discord config | settings channels | `ChannelConfigService` | provider config store + runtime apply | `gateway.channel.config.changed` |
| `POST /api/openclaw/channels/discord/probe` | probe Discord runtime health | settings channels/runtime & connectivity | `ChannelConfigService` | runtime state store | `gateway.channel.probe.completed` / `gateway.channel.probe.failed` |
| `GET /api/openclaw/channels/discord/pairings` | read Discord pairing queue | settings channels | `ChannelPairingService` | none | view telemetry only |
| `POST /api/openclaw/channels/discord/pairings/:pair_code/approve` | approve Discord pairing | settings channels | `ChannelPairingService` | pairing allow state | `gateway.channel.pairing.approved` |
| `POST /api/openclaw/channels/discord/pairings/:pair_code/reject` | reject Discord pairing | settings channels | `ChannelPairingService` | pairing allow state | `gateway.channel.pairing.rejected` |
| `GET /api/openclaw/channels/whatsapp/config` | read WhatsApp config | settings channels | `ChannelConfigService` | none | view telemetry only |
| `GET /api/openclaw/channels/whatsapp/rules` | read WhatsApp group rules | settings channels | `ChannelConfigService` | none | view telemetry only |
| `PUT /api/openclaw/channels/whatsapp/config` | save WhatsApp config | settings channels | `ChannelConfigService` | provider config store + runtime apply | `gateway.channel.config.changed` |
| `POST /api/openclaw/channels/whatsapp/login/start` | start WhatsApp QR login | settings channels | `ChannelConfigService` | transient login state | `gateway.channel.login.started` / `gateway.channel.pairing_challenge_issued` |
| `POST /api/openclaw/channels/whatsapp/login/cancel` | cancel WhatsApp QR login | settings channels | `ChannelConfigService` | transient login state | `gateway.channel.login.cancelled` / `gateway.channel.pairing_challenge_cancelled` |
| `POST /api/openclaw/channels/whatsapp/qr/refresh` | refresh WhatsApp QR challenge | settings channels | `ChannelConfigService` | transient pairing challenge | `gateway.channel.pairing_challenge_refreshed` |
| `GET /api/openclaw/channels/whatsapp/login-state` | read WhatsApp login state | settings channels | `ChannelConfigService` | none | view telemetry only |
| `POST /api/openclaw/channels/whatsapp/relink` | relink WhatsApp account | settings channels | `ChannelConfigService` | runtime relink + account state change | `gateway.channel.state.changed` |
| `POST /api/openclaw/channels/whatsapp/logout` | logout WhatsApp account | settings channels | `ChannelConfigService` | runtime logout + account state change | `gateway.channel.logout.completed` / `gateway.channel.state.changed` |
| `GET /api/openclaw/channels/whatsapp/pairings` | read WhatsApp pairing queue | settings channels | `ChannelPairingService` | none | view telemetry only |
| `POST /api/openclaw/channels/whatsapp/pairings/:pair_code/approve` | approve WhatsApp pairing | settings channels | `ChannelPairingService` | pairing allow state | `gateway.channel.pairing.approved` |
| `POST /api/openclaw/channels/whatsapp/pairings/:pair_code/reject` | reject WhatsApp pairing | settings channels | `ChannelPairingService` | pairing allow state | `gateway.channel.pairing.rejected` |
| `POST /api/openclaw/channels/projected-dispatch` | deliver room/panel/forum projection to channel | room/panel/forum projection pipeline | `ChannelProjectionService` | none beyond delivery telemetry | `gateway.channel.projected_dispatch.accepted` / `gateway.channel.projected_dispatch.failed` |
| `POST /api/openclaw/runtime/capability-check` | capability preflight | attachments/auth-sensitive actions | `RuntimeCapabilityCheckService` | capability cache | `gateway.capability.check.completed` |
| `POST /api/openclaw/chat/dispatch` | send chat turn | chat/panel/forum/task/voice | `GatewayProtocolAdapter` | run/watermark/read-models | `gateway.chat.accepted`, `gateway.chat.stream.delta`, `gateway.chat.completed`, `gateway.chat.failed`, `gateway.chat.aborted` |
| `POST /api/openclaw/room-turn/dispatch` | send room turn | room/participant execution | `GatewayProtocolAdapter` | run/watermark/read-models | `room.turn.accepted`, `room.turn.stream.delta`, `room.turn.completed`, `room.turn.failed`, `room.turn.aborted` |
| `POST /api/openclaw/abort` | stop execution | STOP/abort controls | `AbortService` | `abort_state_store` | `gateway.abort.requested`, `gateway.abort.acknowledged`, `gateway.abort.timeout`, `gateway.abort.cleanup_failed` |
| `POST /api/openclaw/context/explain` | dry-run context plan | context inspector | `ContextManifestService` | optional debug projection | `gateway.context.explain` |
| `GET /api/openclaw/mismatch-log` | read mismatch/fallback log | mismatch viewer | `MismatchFallbackLogService` | none | `gateway.mismatch_log.viewed` |
| `POST /api/openclaw/sessions/create` | create runtime session/binding | chat/task/room/agent launch | `SessionTruthService` | binding/session stores | `gateway.session.created` |
| `POST /api/openclaw/sessions/verify` | verify runtime session and effective state | reconnect/reverify flows | `SessionTruthService` | verification projection updates | `gateway.session.verified` |
| `POST /api/openclaw/sessions/reconnect` | reconnect a runtime session | Runtime & Connectivity / recovery | `SessionTruthService` | binding/runtime state updates | `gateway.session.reconnected` / `gateway.session.reconnect_exhausted` |
| `POST /api/openclaw/sessions/close` | close runtime session | drawer/runtime controls | `SessionTruthService` | binding/session close state | `gateway.session.closed` |
| `POST /api/openclaw/sessions/archive` | archive closed session | admin/retention flows | `SessionTruthService` | archive state | `gateway.session.archived` |
| `POST /api/openclaw/runtime/reconnect` | reconnect selected runtime scope(s) | Runtime & Connectivity | `SessionTruthService` | runtime verification/read-model refresh | `gateway.health.changed` / reconnect events |
| `GET /api/openclaw/runtime/connection-log` | read recent connection/reconnect log | Runtime & Connectivity | `SessionTruthService` | none | `gateway.runtime.connection_log.viewed` |
| Route | Purpose | Request type | Success type | Failure states | Emits / updates |
|---|---|---|---|---|---|
| `POST /api/openclaw/channels/discord/clear-token` | remove Discord bot token | `ChannelTokenClearRequest` | `ChannelMutationResult` | confirmation failed | `gateway.channel.config.changed` |
| `POST /api/openclaw/channels/discord/disconnect` | disconnect Discord bot transport without clearing token | `ChannelDisconnectRequest` | `ChannelMutationResult` | transport unavailable | `gateway.channel.state.changed` |
| `POST /api/openclaw/channels/discord/rules/upsert` | create/update Discord guild/group rule | `ChannelRuleMutationRequest` | `ChannelRuleMutationResult` | invalid rule | `gateway.channel.rules.changed` |
| `POST /api/openclaw/channels/discord/rules/delete` | delete Discord guild/group rule | `ChannelRuleDeleteRequest` | `ChannelRuleMutationResult` | rule missing | `gateway.channel.rules.changed` |
| `POST /api/openclaw/channels/whatsapp/login/start` | start WhatsApp login / pairing challenge | `ChannelLoginStartRequest` | `WhatsAppLoginStartResult` | login unavailable | `gateway.channel.login.started` / `gateway.channel.pairing_challenge_issued` |
| `POST /api/openclaw/channels/whatsapp/login/cancel` | cancel WhatsApp login challenge | `ChannelChallengeCancelRequest` | `ChannelMutationResult` | challenge missing | `gateway.channel.login.cancelled` / `gateway.channel.pairing_challenge_cancelled` |
| `POST /api/openclaw/channels/whatsapp/qr/refresh` | refresh WhatsApp QR challenge | `ChannelChallengeRefreshRequest` | `ChannelPairingChallengeResult` | challenge unavailable | `gateway.channel.pairing_challenge_refreshed` |
| `POST /api/openclaw/channels/whatsapp/relink` | relink WhatsApp account | `ChannelMutationRequest` | `ChannelMutationResult` | relink unavailable | `gateway.channel.state.changed` |
| `POST /api/openclaw/channels/whatsapp/logout` | logout WhatsApp account/session | `ChannelMutationRequest` | `ChannelLogoutResult` | logout unavailable | `gateway.channel.logout.completed` / `gateway.channel.state.changed` |
| `POST /api/openclaw/channels/probe` | refresh channel runtime state | `ChannelProbeRequest` | `ChannelProbeResult` | probe failed | `gateway.channel.probe.completed` / `gateway.channel.probe.failed` / `gateway.channel.state.changed` |
| `GET /api/openclaw/channels/accounts` | read channel account list | path/query params | `ChannelAccountsResponse` | none | view telemetry only |
| `GET /api/openclaw/channels/discord/rules` | read Discord guild/group rules | path/query params | `ChannelRuleListResult` | none | view telemetry only |
| `GET /api/openclaw/channels/whatsapp/rules` | read WhatsApp group rules | path/query params | `ChannelRuleListResult` | none | view telemetry only |
| `GET /api/openclaw/channels/pairing-challenges/:challenge_id` | read current pairing challenge | path params | `ChannelPairingChallengeResult` | missing challenge | `gateway.channel.pairing_challenge_viewed` |
## Appendix F - Read-model contract table (normative)
| Store/read-model | Owner service | Durable | Rebuild path | Stale threshold | Primary surfaces |
|---|---|---:|---|---|---|
| `desired_control_state_store` | `SelectorTruthService` | yes | mutation replay | none | selectors/settings/drawer |
| `control_mutation_queue_store` | `SelectorTruthService` | yes | queue replay | none | selectors/debug |
| `replay_cursor_store` | `SessionTruthService` | yes | telemetry replay | none | reconnect/debug |
| `effective_runtime_state_store` | `RuntimeTruthService` | yes | runtime probe + telemetry replay | 60s unless freshly verified | selectors/drawer/indicators |
| `execution_watermark_store` | `ExecutionWatermarkService` | yes | reverse telemetry replay | none | drawer/debug/mismatch/cost |
| `truth_resolver_debug_projection` | `RuntimeTruthDebugService` | yes | resolver replay from current truth stores | on source change | truth resolver debug view |
| `session_binding_store` | `SessionTruthService` | yes | session reconcile + room binding join | none | drawer/room status |
| `provider_auth_catalog_store` | `ProviderAuthStateService` | yes | runtime catalog/probe refresh | on provider/runtime refresh | settings auth |
| `provider_auth_profile_store` | `ProviderAuthStateService` | yes | config read + mutation replay | none | settings auth |
| `provider_auth_state_store` | `ProviderAuthStateService` | yes | probe + challenge resolution | provider TTL-dependent | settings auth banners/drawer |
| `provider_auth_challenge_store` | `ProviderAuthStateService` | yes | challenge replay | until terminal | auth modal/banner |
| `provider_auth_issue_log` | `ProviderAuthStateService` | yes | probe/state change replay | none | settings auth/debug |
| `model_execution_policy_store` | `RuntimeTruthService` | yes | runtime config read + policy replay | none | settings providers/models |
| `provider_capability_catalog_store` | `LocalProviderConfigService` | yes | capability refresh | on runtime/version/config refresh | settings local providers |
| `local_provider_config_store` | `LocalProviderConfigService` | yes | config read + mutation replay | none | settings local providers |
| `local_provider_runtime_state_store` | `LocalProviderConfigService` | yes | probe + state replay | 60s unless freshly probed | settings local providers/runtime & connectivity |
| `local_model_metadata_override_store` | `LocalProviderConfigService` | yes | model edit replay | none | settings local providers/model row editor/debug |
| `search_provider_catalog_store` | `SearchProviderConfigService` | yes | runtime catalog probe | on runtime/version/config refresh | settings web search |
| `search_provider_config_store` | `SearchProviderConfigService` | yes | config read + mutation replay | none | settings web search |
| `search_runtime_state_store` | `SearchProviderConfigService` | yes | probe + config/state replay | 60s unless freshly probed | settings web search/runtime & connectivity |
| `search_execution_watermark_store` | `SearchExecutionWatermarkService` | yes | event replay | none | settings web search/debug/runtime & connectivity |
| `search_fallback_policy_store` | `SearchProviderConfigService` | yes | mutation replay | none | settings web search |
| `acp_system_state_store` | `AcpHarnessService` | yes | doctor/probe replay + config read | 60s unless freshly probed | settings ACP/runtime & connectivity |
| `acp_harness_catalog_store` | `AcpHarnessService` | yes | runtime/plugin catalog probe | on backend/plugin/version refresh | settings ACP/agent editor |
| `acp_profile_store` | `AcpHarnessService` | yes | mutation replay + config read | none | settings ACP/agent editor |
| `agent_runtime_assignment_store` | `AcpHarnessService` | yes | agent config read + mutation replay | none | agent editor/headers/badges |
| `acp_session_runtime_state_store` | `AcpSessionService` | yes | ACP status/session replay | 30s unless freshly checked | settings ACP/debug/headers/drawers |
| `channel_provider_config_store` | `ChannelConfigService` | yes | config read + mutation replay | none | settings channels |
| `channel_runtime_state_store` | `ChannelConfigService` | yes | probe/login/pairing replay | 60s unless freshly probed | settings channels/runtime & connectivity |
| `channel_account_state_store` | `ChannelConfigService` | yes | login/pairing/runtime replay | 60s unless freshly checked | settings channels |
| `channel_binding_store` | `ChannelConfigService` | yes | binding replay | none | settings channels/runtime truth |
| `channel_pairing_queue_store` | `ChannelPairingService` | yes | pairing queue replay | until resolved | settings channels |
| `capability_cache_store` | `RuntimeCapabilityCheckService` | yes | recompute on invalidation | 60s | capability viewer/send affordances |
| `context_budget_resolution_store` | `ContextManifestService` | yes | explain/apply replay | none | context & budget/settings/debug |
| `context_budget_settings_store` | `ContextManifestService` | yes | config read + mutation replay | none | settings context & budget |
| `context_manifest_store` | `ContextManifestService` | yes | rebuild from explain/apply path | none | context inspector/debug |
| `context_engine_runtime_state_store` | `ContextManifestService` | yes | engine health/state replay | 60s unless freshly updated | Runtime & Connectivity / Context Inspector |
| `mismatch_fallback_log` | `MismatchFallbackLogService` | yes | event replay | none | mismatch viewer |
| `switch_segment_store` | `SwitchSegmentService` | yes | event replay | none | mismatch viewer/debug/cost attribution |
| `usage_sample_store` | `UsageSampleService` | yes | provider/reverse telemetry replay | none | cost surfaces |
| `room_cost_subtotal_store` | `UsageSampleService` | yes | aggregate from usage samples | none | room header/budget modal |
| `participant_cost_subtotal_store` | `UsageSampleService` | yes | aggregate from usage samples | none | drawer/room roster |
| `stream_state_store` | `GatewayProtocolAdapter` | yes | delta/replay/terminal event replay | until terminal resolution | chat/room stream UI |
| `abort_state_store` | `AbortService` | yes | abort replay + terminal events | until terminal resolution | STOP/abort UI |
| `indicator_aggregate_store` | `IndicatorAggregateService` | yes | aggregate from truth stores | 5s UI cache | top bar/page badges |
## Appendix G - Error and degrade code taxonomy
Required codes include:
- `model_not_allowed`
- `model_ref_not_qualified`
- `catalog_entry_deleted`
- `binding_orphaned`
- `auth_missing`
- `auth_challenge_pending`
- `auth_profile_mismatch`
- `auth_scope_limited`
- `auth_invalid`
- `auth_expired`
- `capability_unsupported`
- `attachment_transport_unsupported`
- `attachment_url_silent_drop_prevented`
- `local_provider_unreachable`
- `local_provider_probe_failed`
- `local_provider_direct_bypass_forbidden`
- `local_provider_base_url_invalid`
- `local_provider_api_family_mismatch`
- `local_provider_manual_metadata_required`
- `local_provider_usage_unavailable`
- `search_provider_missing`
- `search_credential_missing`
- `search_credential_inactive`
- `search_model_override_invalid`
- `search_fallback_used`
- `search_config_apply_failed`
- `search_filter_unsupported_on_compatibility_path`
- `remote_gateway_token_unsupported_shape`
- `protected_native_context_violation`
- `stream_gap_detected`
- `abort_timeout`
- `usage_unknown`
Additional required codes:
- `acp_session_disconnected_recoverable`
- `acp_resume_failed`
- `acp_boundary_violation`
- `channel_missing_intents`
- `channel_pairing_challenge_expired`
- `channel_pairing_state_unknown`
- `projected_channel_dispatch_failed`
- `prompt_plan_truth_unavailable`
- `ui_spec_out_of_sync`
## Appendix H - Implementation staging / migration spine
1. Introduce canonical schemas and contract package.
2. Introduce desired/effective/watermark/auth stores.
3. Introduce RuntimeTruthResolver and read paths.
4. Introduce auth routes and challenge flows.
5. Introduce catalog CRUD and orphan repair.
6. Introduce capability cache and context explain.
7. Introduce usage subtotals and DOC13 seam.
8. Turn on anti-ghost action tests.
9. Re-audit runtime evidence and pin Appendix A1.
## Appendix I - Selector state, control mutation, and unsupported-control operational rules
- All selector surfaces MUST bind to `TruthResolverResult`.
- Local component state may exist only as transient input buffer.
- A field with `settings_only` may not display as `verified`.
- A rejected field must stay rejected until a new valid mutation is enqueued.
- A control row MUST expose `field_name`, `current_display_value`, `verification_state`, `pending_reason`, and action affordances.
## Appendix J - Participant binding, activity scope, and multi-agent runtime truth
- `active` = currently receiving or sending a turn on-screen.
- `recent` = last active within page-local grace window.
- `background` = live but not current focus.
- `idle` = bound but inactive.
DOC11 MUST NOT collapse `background` participants into fake “no state”; they remain real runtime scopes.
## Appendix K - Protected native context enforcement and diagnostics
- Protected native context is never rewritten by EC/Q.
- Violations are hard failures with event emission.
- Diagnostics MUST show estimate confidence and engine ownership.
Protected native files route:
- `GET /api/openclaw/context/protected-files`
```ts
type ProtectedContextSourceDescriptor = {
source_kind: "workspace_file" | "native_prompt" | "bootstrap_memory" | "plugin_context";
path_or_ref: string;
protection_reason: "strict" | "duplicate_root_memory_ignored" | "native_runtime_owned" | "plugin_owned";
existence_state: "present" | "missing" | "unknown";
size_bytes?: number;
estimated_tokens?: number;
metadata_only: true;
schema_version: 1;
};
type ProtectedNativeFilesResponse = {
files: ProtectedContextSourceDescriptor[];
schema_version: 1;
};
```
## Appendix L - Summary provenance and switch/compaction records
All non-native summaries generated by EC/Q MUST carry provenance:
- who generated it,
- from what sources,
- when,
- whether it superseded earlier EC/Q summary state.
Native compaction is runtime-owned and must be shown as such when visible.
## Appendix M - Destructive action policy table and UI contracts
| Action | Confirm required | Dependency check | Undo | Required telemetry | Backup recommendation |
|---|---|---|---|---|---|
| Delete model | yes | yes | optional restore | `catalog.model.deleted` | `openclaw backup create --verify` |
| Delete local/self-hosted provider config | yes | yes | optional revert to implicit discovery where supported | `gateway.local_provider.config.deleted` | `openclaw backup create --verify` / `--only-config --verify` |
| Delete web-search provider config | yes | yes | no direct undo | `gateway.search.provider_config.deleted` | `openclaw backup create --verify` / `--only-config --verify` |
| Delete ACP profile | yes | yes | no direct undo; affected agents degrade until reassigned | `gateway.acp.profile.deleted` | `openclaw backup create --verify` / `--only-config --verify` |
| Close ACP session | yes | yes | no | `gateway.acp.session.closed` | none |
| Logout WhatsApp account | yes | yes | relink required | `gateway.channel.logout.completed` | `openclaw backup create --only-config --verify` |
| Clear Discord token / disable Discord account | yes | yes | reconfigure required | `gateway.channel.config.changed` | `openclaw backup create --only-config --verify` |
| Remove channel account binding | yes | yes | yes if rebound | `gateway.channel.binding.deleted` | `openclaw backup create --only-config --verify` |
| Remove auth profile | yes | yes | no direct undo | `gateway.auth.profile_removed` | `openclaw backup create --verify` / `--only-config --verify` |
| Force orphan repair closure | yes | yes | no | `gateway.session.closed` | `openclaw backup create --verify` |
| STOP/abort | no confirm in live surface | n/a | no | `gateway.abort.requested` | none |
| Force remove room participant | yes | DOC12-owned | optional per DOC12 | DOC12 policy event + DOC11 runtime effect | per DOC12 policy |
Auth profile removal modal:
- title: `Remove auth profile?`
- body MUST show provider, profile display name, count of impacted scopes, and selected `active_binding_strategy`.
- buttons:
- `Cancel`
- `Remove profile`
- if `switch_to_profile` is selected, the replacement profile picker is required before confirm.
OAuth callback completion rule:
- callback completion MUST verify state / verifier correlation,
- MUST project canonical auth state even when the HTTP response mode is `redirect_html`,
- MUST emit canonical events before redirect completes.
## Appendix N - Minimal smoke-test spine and anti-ghost checklist
A feature cannot ship unless there is a test proving:
- the UI control is visible under the right conditions,
- clicking/submitting it triggers the correct route,
- the route updates the correct store or explicit non-durable state,
- the correct telemetry is emitted,
- the UI shows success/failure/pending/disabled/orphaned states correctly.
## Appendix O - Canonical schema registry reference
This appendix is a registry-only companion to the canonical schema blocks defined in sections 3, 4, 4A, 4B, 4C, 4D, 6, and 7.
Normative rule:
- the authoritative definitions live in the body sections named by each entry below,
- Appendix O MUST NOT duplicate full canonical type bodies,
- if a schema name appears both in Appendix O and another location, the body section named in this appendix wins.
Registry entries:
- `SessionBindingRecord` -> §3.3
- `ReplayCursorState` -> §3.6
- `ControlMutationQueueRecord` -> §3.11
- `GatewayControlMutationRequest` -> §3.11A
- `DesiredControlState` -> §4.5
- `EffectiveRuntimeState` -> §4.6
- `ExecutionWatermark` -> §4.7
- `ParticipantRuntimeStatus` -> §4.8
- `CatalogEntry` -> §4.11
- `ModelCatalogMutationResult` -> §4.13A
- `ModelExecutionPolicy` -> §4.15B
- `GatewayControlMutationFieldResult` -> §4.17
- `SecureBlobRef` -> §4.19
- `ProviderAuthMethodSchema` -> §4A.3
- `ProviderAuthProfileSchema` -> §4A.4
- `ProviderAuthStateSchema` -> §4A.5
- `ProviderAuthChallengeSchema` -> §4A.6
- `ProviderAuthVerificationResultSchema` -> §4A.7
- `ProviderAuthIssueLogEntry` -> §4A.20
- `ProviderCapabilityCatalogEntry` -> §4A.24
- `LocalProviderConfig` -> §4A.25
- `LocalProviderRuntimeState` -> §4A.26
- `LocalModelMetadataOverride` -> §4A.27
- `SearchProviderCatalogEntry` -> §4B.3
- `SearchProviderConfig` -> §4B.5
- `SearchRuntimeState` -> §4B.6
- `SearchExecutionWatermark` -> §4B.7
- `SearchProviderFallbackPolicy` -> §4B.8
- `AcpProjectRootEntry` -> §4C.5
- `AcpHarnessProfile` -> §4C.6
- `AcpSessionRuntimeState` -> §4C.7
- `AcpDurableBinding` -> §4C.10A
- `ChannelRuntimeState` -> §4D.2
- `ChannelAccountBindingRecord` -> §4D.3
- `DiscordChannelConfig` -> §4D.4
- `WhatsAppChannelConfig` -> §4D.5
- `ChannelPairingChallenge` -> §4D.6A
- `ChannelPairingChallengeResult` -> §4D.6A
- `RuntimeReconnectRequest` -> §10.8
- `RuntimeReconnectResult` -> §10.8
- `TruthResolverReadResult` -> §4.3C
- `ConnectionLogReadResult` -> §10.8
- `MismatchFallbackLogQueryRequest` -> §10.10A
- `MismatchFallbackLogQueryResult` -> §10.10A
- `ProtectedNativeFilesResponse` -> Appendix K
- `AcpSessionDetailReadResult` -> §4C.10A
- `AcpSessionHistoryReadResult` -> §4C.10A
- `ContextSourceRef` -> §6.3
- `ContextBudgetResolutionRecord` -> §6.10
- `ContextInjectionManifest` -> §6.12
- `UsageSample` -> §7.1
- `SwitchSegment` -> §7.4
## Appendix P - Provider/model auth management and settings-page verification contract
Auth-specific UI must support:
- browser redirect challenge,
- device-code challenge,
- setup-token entry,
- pasted-token entry,
- CLI verify,
- profile removal safeguards,
- source precedence visibility,
- plugin-managed discovery.
## Appendix Q - Event/read-model de-duplication rule
This appendix is retained only as a carry-forward note.
Normative rule:
- the canonical event-family list is owned by §8.4 + §8.5A + Appendix C,
- the canonical route list is owned by Appendix E,
- the canonical store/read-model list is owned by §12.5 + Appendix F,
- no new normative event, route, or store requirement may exist only in Appendix Q.
If a future revision adds an event, route, or read-model:
1. update the owning canonical section/appendix first,
2. update companion docs/UI spec/ledger second,
3. and do not store the only normative copy of that requirement here.
R12.5 carry-forward note:
- this appendix intentionally removes duplicated enumerations that previously risked drifting away from Appendix C and Appendix F.
## Appendix R - OpenClaw release-alignment notes retained in R14
R14 accounts for the following OpenClaw release-alignment items:
- context-engine slot and legacy default,
- SecretRef auth-token support and auth-mode truth,
- plugin system-context fields,
- prompt-injection policy,
- compaction lifecycle visibility,
- post-compaction section tuning,
- runtime-owned context tokens invalidation after model switch,
- published `openclaw backup create` / `openclaw backup verify` flows and backup guidance in destructive paths,
- `sessions.get` for verification truth,
- documented local/self-hosted provider configuration via first-class provider docs and `models.providers` custom-provider semantics,
- documented implicit local discovery for providers such as Ollama and vLLM,
- documented local proxies and OpenAI/Anthropic-compatible custom-provider patterns,
- documented web-search provider configuration via `tools.web.search`, onboarding, and `openclaw configure --section web`,
- provider auto-detection and active/inactive search credential truth when provider is unset,
- provider ordering neutrality in search pickers/config metadata,
- provider-specific search fields such as Brave `llm-context` mode and Perplexity `baseUrl` / `model`,
- Perplexity OpenRouter / Sonar compatibility-path truth and its restricted filter support,
- config/runtime snapshot preservation after writes for secret-backed auth/config truth,
- remote gateway token preservation and client-usability warning truth,
- ACP ingress provenance metadata / receipt visibility when runtime reports it,
- published context-engine registry/bootstrap hardening across embedded-run, compaction, and subagent boundaries.
Canonical release-alignment support/result types are defined in their operative sections and MUST NOT be redefined in Appendix R.
Canonical locations:
- `TruthResolverReadResult` -> §4.3C
- `ConnectionLogReadResult` -> §10.8
- `MismatchFallbackLogQueryRequest` -> §10.10A
- `MismatchFallbackLogQueryResult` -> §10.10A
## Appendix S - Documentation/spec CI minima and migration verification
CI MUST fail when:
- duplicate canonical definitions reappear,
- a control lacks route/store/test coverage,
- a visible UI surface lacks a declared data source,
- a destructive action lacks confirm/cancel semantics,
- a settings-only field is rendered as verified runtime truth,
- a local/self-hosted provider row shows verified state without a successful probe or runtime verification signal,
- a web-search provider row shows verified state without a successful probe or runtime verification signal,
- an ACP agent row shows active/healthy truth without ACP system/profile/session evidence,
- a channel row shows connected/healthy truth without probe/login/pairing/account evidence,
- a current-model or execution-watermark surface renders without an explicit source projection,
- Appendix E contains wildcard catch-all rows,
- Appendix F omits any store required by §12.5,
- the latest Q UI spec is missing any DOC11-owned surface or control added in the current revision wave,
- the DOC10 integration ledger is missing required DOC11-driven cross-doc mirror rows,
- DOC12 prompt-plan truth is treated as wired/runtime-verified despite the DOC11 deferral rule,
- or any event family emitted by implementation lacks Appendix C payload-minima coverage.
Migration verification MUST include:
- route smoke verification against every Appendix E row,
- store/projection smoke verification against every Appendix F row,
- selector truth smoke verification across chat/settings/drawer/room,
- current-model / execution-watermark verification,
- auth profile removal degradation/recovery verification,
- local/self-hosted provider probe/deletion/revert verification,
- search provider save/probe/test/fallback verification,
- ACP profile/session/assignment verification,
- ACP project-root validation verification,
- Discord pairing/login/binding verification,
- WhatsApp QR/login/pairing/binding verification,
- projected channel dispatch verification,
- and anti-ghost-button verification across all DOC11-owned settings and runtime surfaces.
## Appendix U - Canonical control-to-route/read-model matrix (anti-ghost)
Every interactive control or user-triggered read action exposed in DOC11-owned Q surfaces MUST have a row in this matrix. Pure local dismiss / expand / collapse interactions MAY be `local_ui_only`, but MUST be explicitly marked as such.
Required columns:
- `control_id`
- `surface`
- `user-visible label`
- `route/operation`
- `validation owner`
- `durable owner`
- `read-model(s) refreshed`
- `event proof`
- `failure states`
- `disabled/unsupported rule`
- `linked wireframe/component id`
Completeness rule:
- no interactive DOC11-owned control may remain in DOC11 or the current canonical Q UI spec without an Appendix U row,
- every row MUST map to a route in Appendix E or be explicitly marked `local_ui_only`,
- every non-local row MUST map to at least one read-model in Appendix F or be explicitly marked read-only,
- CI MUST fail if a control appears in the UI spec but not in Appendix U,
- CI MUST fail if Appendix U references a route or read-model missing from Appendix E/F.
Row-count verification rule:
- the final Appendix U row count MUST be greater than or equal to the count of distinct interactive controls in the current canonical Q UI spec inventory for DOC11-owned surfaces.
| control_id | surface | user-visible label | route/operation | validation owner | durable owner | read-model(s) refreshed | event proof | failure states | disabled/unsupported rule | linked wireframe/component id |
|---|---|---|---|---|---|---|---|---|---|---|
| providers_models.current_model | Settings > Providers & Models | Model selector | POST /api/openclaw/runtime/control-mutations | SelectorTruthService | desired-control + queue stores | truth resolver, indicators, participant/runtime status | gateway.control.mutated / gateway.control.rejected | see route contract + Appendix G | disabled when catalog empty or scope locked | SET-PM-SEL-001 |
| providers_models.think_level | Chat / Settings > Providers & Models / Participant Drawer | Think selector | POST /api/openclaw/runtime/control-mutations | SelectorTruthService | desired-control + queue stores | truth resolver, execution watermark, participant/runtime status | gateway.control.mutated / gateway.control.rejected | see route contract + Appendix G | disabled when unsupported or scope locked | CHAT-THINK-001 |
| providers_models.reasoning_mode | Chat / Settings > Providers & Models / Participant Drawer | Reasoning selector | POST /api/openclaw/runtime/control-mutations | SelectorTruthService | desired-control + queue stores | truth resolver, execution watermark, participant/runtime status | gateway.control.mutated / gateway.control.rejected | see route contract + Appendix G | disabled when unsupported or scope locked | CHAT-REASON-001 |
| providers_models.verbose_level | Chat / Settings > Providers & Models / Participant Drawer | Verbose selector | POST /api/openclaw/runtime/control-mutations | SelectorTruthService | desired-control + queue stores | truth resolver, execution watermark, participant/runtime status | gateway.control.mutated / gateway.control.rejected | see route contract + Appendix G | disabled when unsupported or scope locked | CHAT-VERBOSE-001 |
| providers_models.fast_mode | Chat / Settings > Providers & Models / Participant Drawer | Fast Mode | POST /api/openclaw/runtime/control-mutations | SelectorTruthService | desired-control + queue stores | truth resolver, execution watermark, participant/runtime status | gateway.control.mutated / gateway.control.rejected / gateway.control.fast_mode.changed | see route contract + Appendix G | disabled when unsupported, hidden where ACP/native runtime cannot honor it | CHAT-FAST-001 |
| providers_models.delete_model | Settings > Providers & Models | Delete Model | DELETE /api/openclaw/catalog/models/:id | RuntimeTruthService | catalog store / tombstones | catalog snapshot, orphan diagnostics, policy views | catalog.model.deleted | see route contract + Appendix G | disabled when active refs blocked or row non-deletable | SET-PM-ROW-DELETE |
| providers_models.restore_model | Settings > Providers & Models | Restore Model | POST /api/openclaw/catalog/models/:id/restore | RuntimeTruthService | catalog store | catalog snapshot, orphan diagnostics, policy views | catalog.model.restored | see route contract + Appendix G | disabled when model not restorable | SET-PM-ROW-RESTORE |
| providers_models.execution_policy_save | Settings > Providers & Models | Save execution policy | POST /api/openclaw/model-execution-policy | RuntimeTruthService | model_execution_policy_store | policy view, selector defaults, fallback displays | gateway.control.mutated | see route contract + Appendix G | disabled until primary/fallback set is valid | SET-PM-POLICY-SAVE |
| providers_models.execution_policy_reorder | Settings > Providers & Models | Reorder fallback models | POST /api/openclaw/model-execution-policy | RuntimeTruthService | model_execution_policy_store | policy view, fallback displays | gateway.control.mutated | see route contract + Appendix G | disabled when drag/drop unavailable or list locked | SET-PM-POLICY-ORDER |
| auth.remove_profile | Settings > Providers & Models / Auth | Remove Profile | POST /api/openclaw/providers/auth/remove | ProviderAuthStateService | auth/profile stores + degraded scopes | auth profiles/state | gateway.auth.profile_removed / gateway.auth.state_changed | see route contract + Appendix G | disabled when confirmation/strategy invalid | SET-AUTH-REMOVE |
| auth.set_default_profile | Settings > Providers & Models / Auth | Set Default | POST /api/openclaw/providers/auth/set-default | ProviderAuthStateService | auth/profile stores | auth profiles/state | gateway.auth.default_changed / gateway.auth.state_changed | see route contract + Appendix G | disabled when profile/provider mismatch | SET-AUTH-DEFAULT |
| auth.fix_scope | Settings > Providers & Models / Auth | Fix Scope | POST /api/openclaw/providers/auth/fix-scope | ProviderAuthStateService | auth/challenge stores | auth issue log, auth state/challenge state | gateway.auth.challenge_required / gateway.auth.challenge_completed / gateway.auth.challenge_failed / gateway.auth.state_changed | see route contract + Appendix G | disabled when provider does not support scope repair | SET-AUTH-FIXSCOPE |
| auth.callback_complete | Auth callback relay | Complete OAuth Callback | POST /api/openclaw/providers/auth/callback/complete | ProviderAuthStateService | auth/challenge + auth state stores | auth/challenge + auth state stores | gateway.auth.state_changed / gateway.auth.challenge_failed | callback close/success/failure handling | disabled when callback payload invalid | AUTH-CALLBACK-COMPLETE |
| channels.accounts_view | Settings > Channels | View Accounts | GET /api/openclaw/channels/accounts | ChannelConfigService | none | channel_account_state_store | view telemetry only | read-only | never disabled | CH-ACCOUNTS-VIEW |
| channels.discord.rules_view | Settings > Channels > Discord | View Guild/Group Rules | GET /api/openclaw/channels/discord/rules | ChannelConfigService | none | channel_provider_config_store | view telemetry only | read-only | disabled when Discord unavailable | CH-DISCORD-RULES-VIEW |
| channels.discord.set_default_account | Settings > Channels > Discord | Set Default Account | PUT /api/openclaw/channels/discord/config | ChannelConfigService | channel_provider_config_store | channel_provider_config_store, channel_account_state_store, channel_runtime_state_store | gateway.channel.config.changed | see route contract + Appendix G | disabled when selected account unavailable | CH-DISCORD-DEFAULT-ACCOUNT |
| channels.whatsapp.rules_view | Settings > Channels > WhatsApp | View Group Rules | GET /api/openclaw/channels/whatsapp/rules | ChannelConfigService | none | channel_provider_config_store | view telemetry only | read-only | disabled when WhatsApp unavailable | CH-WHATSAPP-RULES-VIEW |
| channels.whatsapp.set_default_account | Settings > Channels > WhatsApp | Set Default Account | PUT /api/openclaw/channels/whatsapp/config | ChannelConfigService | channel_provider_config_store | channel_provider_config_store, channel_account_state_store, channel_runtime_state_store | gateway.channel.config.changed | see route contract + Appendix G | disabled when selected account unavailable | CH-WHATSAPP-DEFAULT-ACCOUNT |
| channels.whatsapp.policy_save | Settings > Channels > WhatsApp | Save WhatsApp Policy | PUT /api/openclaw/channels/whatsapp/config | ChannelConfigService | channel_provider_config_store | channel_provider_config_store, channel_runtime_state_store | gateway.channel.config.changed | see route contract + Appendix G | disabled until form valid | CH-WHATSAPP-POLICY-SAVE |
| acp.profile_save | Settings > ACP / External Harnesses | Save ACP Profile | PUT /api/openclaw/acp/profiles/:profile_id | AcpHarnessService | acp_profile_store | acp_profile_store | gateway.acp.profile.saved | see route contract + Appendix G | disabled until form valid | ACP-PROFILE-SAVE |
| acp.profile_delete | Settings > ACP / External Harnesses | Delete ACP Profile | DELETE /api/openclaw/acp/profiles/:profile_id | AcpHarnessService | acp_profile_store + affected scopes | acp_profile_store + affected scopes | gateway.acp.profile.deleted | see route contract + Appendix G | disabled when active binding strategy unresolved | ACP-PROFILE-DELETE |
| runtime.truth_resolver_open | Runtime & Connectivity / Debug | Open Truth Resolver | GET /api/openclaw/runtime/truth/debug | RuntimeTruthDebugService | none | truth_resolver_debug_projection | gateway.runtime.truth_resolver.viewed | read-only | disabled when scope missing | RTC-TRUTH-OPEN |
| runtime.view_connection_log | Runtime & Connectivity | View Connection Log | GET /api/openclaw/runtime/connection-log | SessionTruthService | none | connection log read | gateway.runtime.connection_log.viewed | read-only | disabled when runtime unavailable | RTC-CONNLOG-OPEN |
| context.view_diagnostics | Context Inspector / Diagnostics | View Diagnostics | GET /api/openclaw/context/diagnostics | ContextManifestService | none | context diagnostics read | gateway.context.diagnostics.viewed | read-only | disabled when diagnostics unavailable | CTX-DIAGNOSTICS-OPEN |
| context.budget_settings_save | Settings > Context & Budget | Save Budget Settings | PUT /api/openclaw/context/budget-settings | ContextManifestService | budget settings store | budget settings store | gateway.context.budget_settings.changed | see route contract + Appendix G | disabled until budget form is valid | CTX-BUDGET-SAVE |
| local_providers.model_metadata_override_save | Settings > Local / Self-Hosted Providers | Save Metadata Override | PUT /api/openclaw/providers/local-runtime-configs/:provider_id/models/:model_ref/metadata | LocalProviderConfigService | local metadata override store | local metadata override store | gateway.local_provider.model_metadata.updated | see route contract + Appendix G | disabled until override form is valid | LOC-META-SAVE |
| local_providers.model_metadata_override_delete | Settings > Local / Self-Hosted Providers | Delete Metadata Override | DELETE /api/openclaw/providers/local-runtime-configs/:provider_id/models/:model_ref/metadata | LocalProviderConfigService | local metadata override store | local metadata override store | gateway.local_provider.model_metadata.deleted | see route contract + Appendix G | disabled when no override exists | LOC-META-DEL |
| acp.session_detail_view | ACP session drawer / ACP session panel | View Full Session Detail | GET /api/openclaw/acp/sessions/:session_key | AcpSessionService | none | ACP session detail read | view telemetry only | see route contract + Appendix G | disabled when no ACP session is selected | ACP-SESSION-DETAIL |
| context.view_protected_files | Context Inspector / Diagnostics | View Protected Files | GET /api/openclaw/context/protected-files | ContextManifestService | none | protected native files read | gateway.context.protected_files.viewed | read-only | disabled when no protected native context exists | CTX-PROTECTED-OPEN |
| runtime.mismatch_log.filter_change | Mismatch/Fallback Log | Change filters | GET /api/openclaw/mismatch-log | MismatchFallbackLogService | none | filtered mismatch log read | gateway.mismatch_log.viewed | read-only | disabled when viewer unavailable | MIS-FILTER-001 |
| acp.view_full_log | ACP Session Drawer | View Full Log | GET /api/openclaw/acp/sessions/:acp_session_id/history | AcpSessionService | none | ACP session history read | gateway.acp.session.history.viewed | read-only | disabled when no ACP session bound | ACP-LOG-OPEN |
| acp.session_resume | ACP Session Drawer | Resume Session | POST /api/openclaw/acp/sessions/:session_key/resume | AcpSessionService | acp_session_runtime_state_store | ACP session state / watermark | gateway.acp.session.resuming / gateway.acp.session.resumed / gateway.acp.session.resume_failed / gateway.acp.session.resume_degraded | see route contract + Appendix G | disabled when resume unsupported or session absent | ACP-RESUME-001 |
| channels.discord.clear_token | Settings > Channels > Discord | Clear Token | POST /api/openclaw/channels/discord/clear-token | ChannelConfigService | channel provider config store | channel runtime/config state | gateway.channel.config.changed | see route contract + Appendix G | disabled when token absent | CH-DISCORD-CLEAR |
| channels.discord.disconnect | Settings > Channels > Discord | Disconnect Bot | POST /api/openclaw/channels/discord/disconnect | ChannelConfigService | channel runtime state | channel runtime state | gateway.channel.state.changed | see route contract + Appendix G | disabled when not connected | CH-DISCORD-DISCONNECT |
| channels.whatsapp.login_start | Settings > Channels > WhatsApp | Start Login | POST /api/openclaw/channels/whatsapp/login/start | ChannelConfigService | transient login/pairing state | channel runtime state | gateway.channel.login.started / gateway.channel.pairing_challenge_issued | see route contract + Appendix G | disabled when channel disabled or runtime unavailable | CH-WHATSAPP-LOGIN |
| channels.whatsapp.refresh_qr | Settings > Channels > WhatsApp | Refresh QR | POST /api/openclaw/channels/whatsapp/qr/refresh | ChannelConfigService | transient pairing state | channel runtime state | gateway.channel.pairing_challenge_refreshed | see route contract + Appendix G | disabled when no active challenge exists | CH-WHATSAPP-QRREFRESH |
| orphan_pane.close | Orphan diagnostics pane | Close | local_ui_only | none | none | local view state only | none | none | always enabled local dismiss | ORPHAN-CLOSE-LOCAL |
| route.delete_acp.profiles.profile_id | settings ACP | Delete ACP profile | DELETE /api/openclaw/acp/profiles/:profile_id | AcpHarnessService | `acp_profile_store` tombstone + affected-agent degradation | `acp_profile_store` tombstone + affected-agent degradation | `gateway.acp.profile.deleted` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.delete_catalog.models.id | settings models | Soft delete model | DELETE /api/openclaw/catalog/models/:id | RuntimeTruthService | catalog tombstone + orphaning | catalog tombstone + orphaning | `catalog.model.deleted` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.delete_channels.bindings.provider_id.account_id | settings channels | Clear channel account binding | DELETE /api/openclaw/channels/bindings/:provider_id/:account_id | ChannelConfigService | `channel_binding_store` | `channel_binding_store` | `gateway.channel.binding.deleted` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.delete_providers.local_runtime_configs.provider_id | settings local providers | Delete local/self-hosted provider config | DELETE /api/openclaw/providers/local-runtime-configs/:provider_id | LocalProviderConfigService | config tombstone + orphaning | config tombstone + orphaning | `gateway.local_provider.config.deleted` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.delete_tools.web_search.provider_configs.provider_id | settings web search | Delete provider config | DELETE /api/openclaw/tools/web-search/provider-configs/:provider_id | SearchProviderConfigService | config store + runtime apply | config store + runtime apply | `gateway.search.provider_config.deleted` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_acp.harnesses | settings ACP/agent editor | Read ACP harness catalog | GET /api/openclaw/acp/harnesses | AcpHarnessService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_acp.profiles | settings ACP/agent editor | Read ACP profiles | GET /api/openclaw/acp/profiles | AcpHarnessService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_acp.sessions | settings ACP/debug | List recent/active ACP sessions | GET /api/openclaw/acp/sessions | AcpSessionService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_acp.system_state | settings ACP/runtime & connectivity | Read ACP global state | GET /api/openclaw/acp/system-state | AcpHarnessService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_channels.bindings | settings channels | Read channel account bindings | GET /api/openclaw/channels/bindings | ChannelConfigService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_channels.discord.config | settings channels | Read Discord config | GET /api/openclaw/channels/discord/config | ChannelConfigService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_channels.discord.rules | settings channels | Read Discord guild/group rules | GET /api/openclaw/channels/discord/rules | ChannelConfigService | none (read-only) | channel_provider_config_store | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_channels.discord.pairings | settings channels | Read Discord pairing queue | GET /api/openclaw/channels/discord/pairings | ChannelPairingService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_channels.pairing_challenges.challenge_id | path params | Read current pairing challenge | GET /api/openclaw/channels/pairing-challenges/:challenge_id | ChannelPairingChallengeResult | missing challenge | missing challenge | `gateway.channel.pairing_challenge_viewed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_channels.state | settings channels/runtime & connectivity | Read shared channel runtime/account state | GET /api/openclaw/channels/state | ChannelConfigService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_channels.accounts | settings channels | Read channel account list | GET /api/openclaw/channels/accounts | ChannelConfigService | none (read-only) | channel_account_state_store | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_channels.whatsapp.config | settings channels | Read WhatsApp config | GET /api/openclaw/channels/whatsapp/config | ChannelConfigService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_channels.whatsapp.rules | settings channels | Read WhatsApp group rules | GET /api/openclaw/channels/whatsapp/rules | ChannelConfigService | none (read-only) | channel_provider_config_store | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_channels.whatsapp.login_state | settings channels | Read WhatsApp login state | GET /api/openclaw/channels/whatsapp/login-state | ChannelConfigService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_channels.whatsapp.pairings | settings channels | Read WhatsApp pairing queue | GET /api/openclaw/channels/whatsapp/pairings | ChannelPairingService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_mismatch_log | mismatch viewer | Read mismatch/fallback log | GET /api/openclaw/mismatch-log | MismatchFallbackLogService | none (read-only) | none (read-only) | `gateway.mismatch_log.viewed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_model_execution_policy | settings providers/models | Read default model primary/fallback policy | GET /api/openclaw/model-execution-policy | RuntimeTruthService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_providers.auth.callback | browser auth return | Provider callback relay / completion | GET /api/openclaw/providers/auth/callback | ProviderAuthStateService | auth/challenge stores | auth/challenge stores | `gateway.auth.challenge_completed` / `gateway.auth.challenge_failed` / `gateway.auth.state_changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_providers.auth.callback.complete | auth callback relay | Complete OAuth callback relay | POST /api/openclaw/providers/auth/callback/complete | ProviderAuthStateService | auth/challenge + auth state stores | auth/challenge + auth state stores | `gateway.auth.state_changed` / `gateway.auth.challenge_failed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_providers.auth.catalog | settings auth | Read auth method catalog | GET /api/openclaw/providers/auth/catalog | ProviderAuthStateService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_providers.auth.challenges | settings auth / auth modal/banner | Read active/pending challenges | GET /api/openclaw/providers/auth/challenges | ProviderAuthStateService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_providers.auth.issues | settings auth/debug | Read auth issue log | GET /api/openclaw/providers/auth/issues | ProviderAuthStateService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_providers.auth.plugin_catalog | settings auth | Read plugin-managed auth catalog | GET /api/openclaw/providers/auth/plugin-catalog | ProviderAuthStateService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_providers.auth.profiles | settings auth | Read auth profiles | GET /api/openclaw/providers/auth/profiles | ProviderAuthStateService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_providers.auth.state | settings auth/runtime cards/drawer | Read effective auth state | GET /api/openclaw/providers/auth/state | ProviderAuthStateService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_providers.capabilities | settings local providers | Read provider capability catalog | GET /api/openclaw/providers/capabilities | LocalProviderConfigService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_providers.local_runtime_configs | settings local providers | Read local/self-hosted provider configs | GET /api/openclaw/providers/local-runtime-configs | LocalProviderConfigService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_providers.local_runtime_state | settings local providers/runtime & connectivity/debug | Read local/self-hosted runtime state | GET /api/openclaw/providers/local-runtime-state | LocalProviderConfigService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_runtime.catalog | selectors/settings | Read current catalog | GET /api/openclaw/runtime/catalog | RuntimeTruthService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_runtime.connection_log | Runtime & Connectivity | Read recent connection/reconnect log | GET /api/openclaw/runtime/connection-log | SessionTruthService | none (read-only) | none (read-only) | `gateway.runtime.connection_log.viewed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_runtime.indicators | top bar/page badges/runtime & connectivity | Aggregated runtime/status read | GET /api/openclaw/runtime/indicators | IndicatorAggregateService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_runtime.truth | indicators/drawer | Truth resolver read | GET /api/openclaw/runtime/truth | RuntimeTruthService | none (read-only) | none (read-only) | `runtime_truth.resolved` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_runtime.truth.debug | truth resolver debug view | Debug truth bundle read | GET /api/openclaw/runtime/truth/debug | RuntimeTruthDebugService | `truth_resolver_debug_projection` refresh | `truth_resolver_debug_projection` refresh | `gateway.runtime.truth_resolver.viewed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_tools.web_search.catalog | settings web search | Read search provider catalog | GET /api/openclaw/tools/web-search/catalog | SearchProviderConfigService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_tools.web_search.configs | settings web search | Read provider configs | GET /api/openclaw/tools/web-search/configs | SearchProviderConfigService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_tools.web_search.executions.latest | settings web search/debug/runtime & connectivity | Read latest search execution watermark | GET /api/openclaw/tools/web-search/executions/latest | SearchExecutionWatermarkService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.get_tools.web_search.state | settings web search/runtime & connectivity | Read effective search runtime state | GET /api/openclaw/tools/web-search/state | SearchProviderConfigService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.patch_agents.agent_id.acp_profile | agent editor | Assign ACP profile to agent | PATCH /api/openclaw/agents/:agent_id/acp-profile | AcpHarnessService | `agent_runtime_assignment_store` | `agent_runtime_assignment_store` | `gateway.acp.agent_runtime_assignment.changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.patch_agents.agent_id.runtime_mode | agent editor | Set agent runtime mode | PATCH /api/openclaw/agents/:agent_id/runtime-mode | AcpHarnessService | `agent_runtime_assignment_store` | `agent_runtime_assignment_store` | `gateway.acp.agent_runtime_assignment.changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.patch_catalog.models.id | settings models | Edit model | PATCH /api/openclaw/catalog/models/:id | RuntimeTruthService | catalog store / override stores | catalog store / override stores | `catalog.model.updated` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_abort | STOP/abort controls | Stop execution | POST /api/openclaw/abort | AbortService | `abort_state_store` | `abort_state_store` | `gateway.abort.requested`, `gateway.abort.acknowledged`, `gateway.abort.timeout`, `gateway.abort.cleanup_failed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_acp.doctor | settings ACP/runtime & connectivity | Run backend/harness doctor | POST /api/openclaw/acp/doctor | AcpHarnessService | `acp_system_state_store` | `acp_system_state_store` | `gateway.acp.doctor.completed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_acp.sessions.session_key.cancel | ACP session panel/STOP | Cancel active ACP turn | POST /api/openclaw/acp/sessions/:session_key/cancel | AcpSessionService | session runtime state | session runtime state | `gateway.acp.session.cancelled` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_acp.sessions.session_key.close | ACP session panel | Close ACP session | POST /api/openclaw/acp/sessions/:session_key/close | AcpSessionService | session runtime state + binding close | session runtime state + binding close | `gateway.acp.session.closed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_acp.sessions.session_key.cwd | ACP session panel | Set ACP working directory | POST /api/openclaw/acp/sessions/:session_key/cwd | AcpSessionService | session runtime state + watermark | session runtime state + watermark | `gateway.acp.session.option_changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_acp.sessions.session_key.model | ACP session panel | Set ACP session model | POST /api/openclaw/acp/sessions/:session_key/model | AcpSessionService | session runtime state + watermark | session runtime state + watermark | `gateway.acp.session.option_changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_acp.sessions.session_key.permissions | ACP session panel | Set ACP permissions profile | POST /api/openclaw/acp/sessions/:session_key/permissions | AcpSessionService | session runtime state + watermark | session runtime state + watermark | `gateway.acp.session.option_changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_acp.sessions.session_key.reset_options | ACP session panel | Clear ACP runtime overrides | POST /api/openclaw/acp/sessions/:session_key/reset-options | AcpSessionService | session runtime state | session runtime state | `gateway.acp.session.option_reset` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_acp.sessions.session_key.resume | ACP session panel / assignment recovery | Resume ACP session | POST /api/openclaw/acp/sessions/:session_key/resume | AcpSessionService | session runtime state + watermark | session runtime state + watermark | `gateway.acp.session.resuming` / `gateway.acp.session.resumed` / `gateway.acp.session.resume_failed` / `gateway.acp.session.resume_degraded` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_acp.sessions.session_key.timeout | ACP session panel | Set ACP timeout | POST /api/openclaw/acp/sessions/:session_key/timeout | AcpSessionService | session runtime state + watermark | session runtime state + watermark | `gateway.acp.session.option_changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_acp.validate_path | ACP profile editor / agent editor | Validate ACP project root or additional root | POST /api/openclaw/acp/validate-path | AcpHarnessService | none (read-only) | none (read-only) | view telemetry only | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_catalog.models | settings models | Create model | POST /api/openclaw/catalog/models | RuntimeTruthService | catalog store | catalog store | `catalog.model.created` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_catalog.models.id.restore | settings models | Restore model | POST /api/openclaw/catalog/models/:id/restore | RuntimeTruthService | catalog restore | catalog restore | `catalog.model.restored` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_channels.discord.clear_token | ChannelTokenClearRequest | Remove Discord bot token | POST /api/openclaw/channels/discord/clear-token | ChannelMutationResult | confirmation failed | confirmation failed | `gateway.channel.config.changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_channels.discord.disconnect | ChannelDisconnectRequest | Disconnect Discord bot transport without clearing token | POST /api/openclaw/channels/discord/disconnect | ChannelMutationResult | transport unavailable | transport unavailable | `gateway.channel.state.changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_channels.discord.pairings.pair_code.approve | settings channels | Approve Discord pairing | POST /api/openclaw/channels/discord/pairings/:pair_code/approve | ChannelPairingService | pairing allow state | pairing allow state | `gateway.channel.pairing.approved` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_channels.discord.pairings.pair_code.reject | settings channels | Reject Discord pairing | POST /api/openclaw/channels/discord/pairings/:pair_code/reject | ChannelPairingService | pairing allow state | pairing allow state | `gateway.channel.pairing.rejected` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_channels.discord.probe | settings channels/runtime & connectivity | Probe Discord runtime health | POST /api/openclaw/channels/discord/probe | ChannelConfigService | runtime state store | runtime state store | `gateway.channel.probe.completed` / `gateway.channel.probe.failed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_channels.discord.rules.delete | ChannelRuleDeleteRequest | Delete Discord guild/group rule | POST /api/openclaw/channels/discord/rules/delete | ChannelRuleMutationResult | rule missing | rule missing | `gateway.channel.rules.changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_channels.discord.rules.upsert | ChannelRuleMutationRequest | Create/update Discord guild/group rule | POST /api/openclaw/channels/discord/rules/upsert | ChannelRuleMutationResult | invalid rule | invalid rule | `gateway.channel.rules.changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_channels.probe | ChannelProbeRequest | Refresh channel runtime state | POST /api/openclaw/channels/probe | ChannelProbeResult | probe failed | probe failed | `gateway.channel.probe.completed` / `gateway.channel.probe.failed` / `gateway.channel.state.changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_channels.projected_dispatch | room/panel/forum projection pipeline | Deliver room/panel/forum projection to channel | POST /api/openclaw/channels/projected-dispatch | ChannelProjectionService | none beyond delivery telemetry | none beyond delivery telemetry | `gateway.channel.projected_dispatch.accepted` / `gateway.channel.projected_dispatch.failed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_channels.whatsapp.login.cancel | settings channels | Cancel WhatsApp QR login | POST /api/openclaw/channels/whatsapp/login/cancel | ChannelConfigService | transient login state | transient login state | `gateway.channel.login.cancelled` / `gateway.channel.pairing_challenge_cancelled` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_channels.whatsapp.login.start | settings channels | Start WhatsApp QR login | POST /api/openclaw/channels/whatsapp/login/start | ChannelConfigService | transient login state | transient login state | `gateway.channel.login.started` / `gateway.channel.pairing_challenge_issued` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_channels.whatsapp.logout | settings channels | Logout WhatsApp account | POST /api/openclaw/channels/whatsapp/logout | ChannelConfigService | runtime logout + account state change | runtime logout + account state change | `gateway.channel.logout.completed` / `gateway.channel.state.changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_channels.whatsapp.pairings.pair_code.approve | settings channels | Approve WhatsApp pairing | POST /api/openclaw/channels/whatsapp/pairings/:pair_code/approve | ChannelPairingService | pairing allow state | pairing allow state | `gateway.channel.pairing.approved` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_channels.whatsapp.pairings.pair_code.reject | settings channels | Reject WhatsApp pairing | POST /api/openclaw/channels/whatsapp/pairings/:pair_code/reject | ChannelPairingService | pairing allow state | pairing allow state | `gateway.channel.pairing.rejected` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_channels.whatsapp.qr.refresh | ChannelChallengeRefreshRequest | Refresh WhatsApp QR challenge | POST /api/openclaw/channels/whatsapp/qr/refresh | ChannelPairingChallengeResult | challenge unavailable | challenge unavailable | `gateway.channel.pairing_challenge_refreshed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_channels.whatsapp.relink | ChannelMutationRequest | Relink WhatsApp account | POST /api/openclaw/channels/whatsapp/relink | ChannelMutationResult | relink unavailable | relink unavailable | `gateway.channel.state.changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_chat.dispatch | chat/panel/forum/task/voice | Send chat turn | POST /api/openclaw/chat/dispatch | GatewayProtocolAdapter | run/watermark/read-models | run/watermark/read-models | `gateway.chat.accepted`, `gateway.chat.stream.delta`, `gateway.chat.completed`, `gateway.chat.failed`, `gateway.chat.aborted` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_context.explain | context inspector | Dry-run context plan | POST /api/openclaw/context/explain | ContextManifestService | optional debug projection | optional debug projection | `gateway.context.explain` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_model_execution_policy | settings providers/models | Update default model primary/fallback policy | POST /api/openclaw/model-execution-policy | RuntimeTruthService | `model_execution_policy_store` + runtime apply | `model_execution_policy_store` + runtime apply | `gateway.control.mutated` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_models.orphan_repair | drawer/settings | Repair orphaned binding | POST /api/openclaw/models/orphan-repair | SessionTruthService | binding/store + switch segment | binding/store + switch segment | `gateway.session.repaired` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_providers.auth.cancel | auth modal | Cancel auth flow | POST /api/openclaw/providers/auth/cancel | ProviderAuthStateService | auth state/challenge stores | auth state/challenge stores | `gateway.auth.state_changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_providers.auth.cli_login | settings auth | Initiate CLI login | POST /api/openclaw/providers/auth/cli-login | ProviderAuthStateService | auth state/challenge stores | auth state/challenge stores | `gateway.auth.state_changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_providers.auth.complete | auth modal/callback relay | Complete auth flow | POST /api/openclaw/providers/auth/complete | ProviderAuthStateService | auth state/challenge/profile stores | auth state/challenge/profile stores | `gateway.auth.state_changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_providers.auth.paste_token | settings auth | Pasted-token path | POST /api/openclaw/providers/auth/paste-token | ProviderAuthStateService | auth/profile stores | auth/profile stores | `gateway.auth.state_changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_providers.auth.probe | settings auth | Verify auth/profile/scopes | POST /api/openclaw/providers/auth/probe | ProviderAuthStateService | auth state store | auth state store | `gateway.auth.state_changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_providers.auth.relogin | settings auth | Restart login for existing profile | POST /api/openclaw/providers/auth/relogin | ProviderAuthStateService | auth/challenge stores | auth/challenge stores | `gateway.auth.challenge_required` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_providers.auth.remove | settings auth | Remove auth profile | POST /api/openclaw/providers/auth/remove | ProviderAuthStateService | auth/profile stores + degraded scopes | auth/profile stores + degraded scopes | `gateway.auth.profile_removed` / `gateway.auth.state_changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_providers.auth.set_default | settings auth | Set default auth profile | POST /api/openclaw/providers/auth/set-default | ProviderAuthStateService | auth/profile stores | auth/profile stores | `gateway.auth.default_changed` / `gateway.auth.state_changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_providers.auth.setup_token | settings auth | Store/setup token path | POST /api/openclaw/providers/auth/setup-token | ProviderAuthStateService | auth/profile stores | auth/profile stores | `gateway.auth.state_changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_providers.auth.start | settings auth / auth banner | Start auth flow | POST /api/openclaw/providers/auth/start | ProviderAuthStateService | challenge store | challenge store | `gateway.auth.challenge_required` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_providers.auth.verify_cli | settings auth | Verify CLI-managed auth | POST /api/openclaw/providers/auth/verify-cli | ProviderAuthStateService | auth state store | auth state store | `gateway.auth.state_changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_providers.local_runtime_configs.provider_id.probe | settings local providers | Probe local/self-hosted provider runtime | POST /api/openclaw/providers/local-runtime-configs/:provider_id/probe | LocalProviderConfigService | runtime state store | runtime state store | `gateway.local_provider.probe.completed` / `gateway.local_provider.probe.failed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_providers.local_runtime_configs.provider_id.revert_to_implicit_discovery | settings local providers | Remove explicit config and revert to implicit discovery | POST /api/openclaw/providers/local-runtime-configs/:provider_id/revert-to-implicit-discovery | LocalProviderConfigService | config removal + catalog refresh | config removal + catalog refresh | `gateway.local_provider.reverted_to_implicit_discovery` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_providers.local_runtime_configs.provider_id.sync_catalog | settings local providers | Sync local/self-hosted provider models into catalog | POST /api/openclaw/providers/local-runtime-configs/:provider_id/sync-catalog | LocalProviderConfigService | catalog store + runtime state store | catalog store + runtime state store | `gateway.local_provider.catalog.synced` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_room_turn.dispatch | room/participant execution | Send room turn | POST /api/openclaw/room-turn/dispatch | GatewayProtocolAdapter | run/watermark/read-models | run/watermark/read-models | `room.turn.accepted`, `room.turn.stream.delta`, `room.turn.completed`, `room.turn.failed`, `room.turn.aborted` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_runtime.capability_check | attachments/auth-sensitive actions | Capability preflight | POST /api/openclaw/runtime/capability-check | RuntimeCapabilityCheckService | capability cache | capability cache | `gateway.capability.check.completed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_runtime.reconnect | Runtime & Connectivity | Reconnect selected runtime scope(s) | POST /api/openclaw/runtime/reconnect | SessionTruthService | runtime verification/read-model refresh | runtime verification/read-model refresh | `gateway.health.changed` / reconnect events | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_sessions.archive | admin/retention flows | Archive closed session | POST /api/openclaw/sessions/archive | SessionTruthService | archive state | archive state | `gateway.session.archived` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_sessions.close | drawer/runtime controls | Close runtime session | POST /api/openclaw/sessions/close | SessionTruthService | binding/session close state | binding/session close state | `gateway.session.closed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_sessions.create | chat/task/room/agent launch | Create runtime session/binding | POST /api/openclaw/sessions/create | SessionTruthService | binding/session stores | binding/session stores | `gateway.session.created` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_sessions.reconnect | Runtime & Connectivity / recovery | Reconnect a runtime session | POST /api/openclaw/sessions/reconnect | SessionTruthService | binding/runtime state updates | binding/runtime state updates | `gateway.session.reconnected` / `gateway.session.reconnect_exhausted` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_sessions.verify | reconnect/reverify flows | Verify runtime session and effective state | POST /api/openclaw/sessions/verify | SessionTruthService | verification projection updates | verification projection updates | `gateway.session.verified` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_tools.web_search.default_provider | settings web search | Set explicit provider or auto mode | POST /api/openclaw/tools/web-search/default-provider | SearchProviderConfigService | runtime state + runtime apply | runtime state + runtime apply | `gateway.search.state.changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_tools.web_search.fallback_policy | settings web search | Update wrapper search fallback policy | POST /api/openclaw/tools/web-search/fallback-policy | SearchProviderConfigService | fallback policy store | fallback policy store | `gateway.search.config.changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_tools.web_search.probe | settings web search | Probe selected provider config | POST /api/openclaw/tools/web-search/probe | SearchProviderConfigService | runtime/config verification fields | runtime/config verification fields | `gateway.search.probe.completed` / `gateway.search.probe.failed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.post_tools.web_search.test | settings web search | Execute test search | POST /api/openclaw/tools/web-search/test | SearchProviderConfigService | search execution watermark | search execution watermark | `gateway.search.execution.recorded` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.put_acp.profiles.profile_id | settings ACP | Create/update ACP profile | PUT /api/openclaw/acp/profiles/:profile_id | AcpHarnessService | `acp_profile_store` | `acp_profile_store` | `gateway.acp.profile.saved` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.put_channels.bindings.provider_id.account_id | settings channels | Save channel account → agent binding | PUT /api/openclaw/channels/bindings/:provider_id/:account_id | ChannelConfigService | `channel_binding_store` | `channel_binding_store` | `gateway.channel.binding.changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.put_channels.discord.config | settings channels | Save Discord config | PUT /api/openclaw/channels/discord/config | ChannelConfigService | provider config store + runtime apply | provider config store + runtime apply | `gateway.channel.config.changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.put_channels.whatsapp.config | settings channels | Save WhatsApp config | PUT /api/openclaw/channels/whatsapp/config | ChannelConfigService | provider config store + runtime apply | provider config store + runtime apply | `gateway.channel.config.changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.put_providers.local_runtime_configs.provider_id | settings local providers | Create/update local/self-hosted provider config | PUT /api/openclaw/providers/local-runtime-configs/:provider_id | LocalProviderConfigService | local provider config store + runtime apply | local provider config store + runtime apply | `gateway.local_provider.config.changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
| route.put_tools.web_search.provider_configs.provider_id | settings web search | Create/update provider config | PUT /api/openclaw/tools/web-search/provider-configs/:provider_id | SearchProviderConfigService | config store + runtime apply | config store + runtime apply | `gateway.search.config.changed` | see route contract + Appendix G | disabled when prerequisites, capability checks, scope locks, or form validity rules fail | see current canonical Q UI spec |
## Appendix V - Final compilation validation gates
Final compilation gates added after compiled-audit review:
- every event family named anywhere in body text, route tables, Appendix E, or Appendix U MUST have a canonical Appendix C row or an explicit canonical alias/supersession rule;
- for every mutable control, Appendix E MUST define the route-emitted canonical event set and Appendix U MUST include that same route-emitted set in its event proof; Appendix U MAY additionally name control-specific visibility/read-model event families only when those families are explicitly defined in Appendix C and in the owning body section;
- if a route row says `view telemetry only`, no body section or control row may invent a named emitted event for that same route;
- no compiled DOC11 section may retain historical event names rejected by the canonical event-name rewrite rules above;
- if a route/control/event mismatch remains after compilation, the compiled DOC11 document fails validation and MUST NOT be used as build input.