diff --git a/rfcs/0029-openclaw-control-model.md b/rfcs/0029-openclaw-control-model.md new file mode 100644 index 00000000..a0ae5ac3 --- /dev/null +++ b/rfcs/0029-openclaw-control-model.md @@ -0,0 +1,712 @@ +--- +title: OpenClaw Control Model +authors: + - Gio Della-Libera +created: 2026-08-11 +last_updated: 2026-09-17 +status: draft +issue: +rfc_pr: https://github.com/openclaw/rfcs/pull/62 +--- + +# Proposal: OpenClaw Control Model + +## Summary + +OpenClaw should provide a framework-neutral Control Model as optional +`@openclaw/gateway-client/model` subpaths above the existing browser transport. +The model would expose immutable state snapshots, typed commands, history/live +reconciliation, and renderer-neutral UI artifacts without depending on Lit, +React, routes, or product presentation. OpenClaw's Control UI and independently +owned product shells could consume the same behavior while retaining their own +components, navigation, theming, authentication, and rollout. + +This document is the submitted draft for RFC 0029. It requests design review +of the proposed Control Model family, but does not claim maintainer acceptance, +implementation approval, release support, or product adoption. + +## Motivation + +OpenClaw already publishes a reference Gateway client. It owns protocol +handshake, authentication helpers, request correlation, timeouts, reconnect +primitives, sequence-gap detection, and event delivery. + +OpenClaw's Control UI builds a richer application model above that client: +session catalogs, history/live reconciliation, connection epochs, chat stream +state, tool lifecycle, approvals, config state, and other capabilities. That +model is assembled inside the Lit application and is not a supported headless +consumer boundary. + +An independently owned UI therefore has two unattractive choices: + +1. host or fork OpenClaw's complete Control UI even when the product needs a + different framework and experience; or +2. consume raw Gateway methods and events and independently reimplement the + state machines already required by Control UI. + +The first choice couples product presentation to OpenClaw's application. The +second creates semantic drift around reconnect, event ordering, history +reconciliation, tool outcomes, approvals, and compatibility. + +Tool-provided UI has a related gap. OpenClaw can materialize Canvas documents +and MCP Apps, but the current projection selects those presentation paths +before another host can choose a trusted native renderer. First-party products +that need native components either add tool-specific interpretation or bypass +the existing projection. + +The desired architecture is: + +```mermaid +flowchart TB + gateway["OpenClaw Gateway"] + transport["@openclaw/gateway-client/browser
transport, authentication, reconnect"] + model["@openclaw/gateway-client/model
sessions, conversations, commands, artifacts"] + controlUi["OpenClaw Control UI
Lit presentation"] + product["Independent product shell
React or native presentation"] + + gateway --> transport --> model + model --> controlUi + model --> product +``` + +One OpenClaw-owned behavioral model can serve multiple presentations without +making OpenClaw own those products. + +## Goals + +- Publish a framework-neutral state and command boundary above the Gateway + client. +- Keep Gateway protocol and server behavior authoritative. +- Provide stable immutable projections for connection, session catalog, and a + selected conversation. +- Reconcile history, live events, reconnects, and tool lifecycle once. +- Return typed command failures without success-shaped fallbacks. +- Preserve renderer-neutral UI artifacts and all applicable OpenClaw-provided + view offers long enough for a host to select its preferred native renderer, + product view model, structured fallback, or sandboxed MCP App. +- Let OpenClaw Control UI become a reference adopter without changing its + presentation. +- Support independently owned browser, desktop, mobile, terminal, and hosted + shells without importing a UI framework. +- Make adoption incremental and tie each layer to conformance and deletion + evidence. + +## Non-Goals + +- Replacing `@openclaw/gateway-client` or introducing another wire protocol. +- Standardizing routes, navigation, layout, CSS, theme, localization, or + product design systems. +- Moving product-owned React, Lit, native, or terminal components into the + Control Model. +- Publishing Control UI's complete internal `ApplicationContext` as-is. +- Defining browser credential storage, tenant authentication, runtime routing, + or host deployment. +- Making UI visibility, disabled state, or command preflight authoritative. +- Loading executable React components or arbitrary JavaScript named by a tool + result. +- Replacing MCP Apps as the sandboxed third-party executable-UI contract. +- Requiring JSON Render or any other renderer library. +- Adding a sidecar, service, or new process boundary. The Control Model is an + in-process library over an existing Gateway client. +- Replacing OpenClaw's existing dashboard/workboard model, registered widget + providers, layout persistence, or `show_widget`/`dashboard` tool semantics. +- Defining generic model-authored layouts or a public component marketplace in + v1. A dashboard-shaped artifact view is presentation, not the authoritative + OpenClaw board model. +- Including config forms, settings navigation, channels, skills, workboards, + or every existing Control UI capability in v1. Configuration requires a + separate authority- and provenance-aware model. + +## Proposal + +The normative candidate contracts and delivery gates are split into companion +documents: + +- [Control Model v1 specification](0029/control-model-v1-spec.md) +- [UI artifact v1 specification](0029/ui-artifact-v1-spec.md) +- [Hosted Control UI policy v1 specification](0029/hosted-control-ui-policy-v1-spec.md) +- [Conformance and adoption plan](0029/conformance-and-adoption-plan.md) +- [Implementation and PR plan](0029/implementation-plan.md) +- [Ownership and support plan](0029/ownership-and-support-plan.md) +- [Owner acceptance record](0029/owner-acceptance-record.md) + +### Review scope + +RFC acceptance may cover the related family shape while keeping each contract's +acceptance gate independent: framework-neutral Control Model v1, UI artifact +v1, and hosted Control UI policy v1. Accepting one contract does not imply +accepting or shipping the others. This RFC would not accept a Lobster product +roadmap, a framework adapter, a generic dashboard system, or writable +configuration. + +This RFC is additive to the hosted Control UI and policy work rather than a +replacement for it: + +- hosted Control UI remains the fastest way for a host to serve the + version-matched OpenClaw application, enforce route and method policy, and + roll back to the incumbent product shell; +- the Control Model is the native-product path for conversation state, + commands, and renderer-neutral artifacts when the host owns presentation; +- the hosted policy decision vocabulary remains the server/runtime authority + for browser lockdown, settings read-only state, and forbidden mutations; the + Control Model may preserve safe denial details, but it does not become the + policy engine; +- Board Model and Config Model evidence tests the same owner-first extraction + pattern against adjacent OpenClaw domains, but each surface keeps its own + contract, release gate, and implementation review; and +- Managed Configuration remains the authority-aware path for governed config + writes. A read-only Config Model projection does not imply write authority. + +The "one shot" upstream ask should therefore be the family shape and sequence: +accept the optional Gateway Client Control Model and UI artifact contracts as +the first supported native surface, while explicitly reserving hosted Control +UI policy, Board Model, Config Model, and Managed Configuration as sibling +contracts. That lets maintainers review the complete architecture without +making Control Model v1 responsible for every UI, dashboard, settings, or +policy feature. + +RFC 0029 therefore includes a hosted-policy sidecar, but that sidecar is not a +Control Model v1 dependency. Hosted Control UI policy is tracked through +[openclaw/openclaw#115423](https://github.com/openclaw/openclaw/issues/115423), +[openclaw/openclaw#115408](https://github.com/openclaw/openclaw/pull/115408), +and [openclaw/openclaw#116013](https://github.com/openclaw/openclaw/pull/116013). +Its acceptance should be judged against hosted route, bootstrap, rollout, and +Gateway/runtime enforcement evidence rather than native conversation-model +conformance. + +### Upstream implementation drafts + +The proposed boundary is now filed upstream as five condensed draft PRs: + +1. [CM1: Control Model session foundation](https://github.com/openclaw/openclaw/pull/127670) +2. [CM2: Control Model conversations](https://github.com/openclaw/openclaw/pull/127671) +3. [CM3: renderer-neutral UI artifacts](https://github.com/openclaw/openclaw/pull/127672) +4. [CM4: conformance, package, performance, compatibility, lifecycle, and security hardening](https://github.com/openclaw/openclaw/pull/127674) +5. [CM5: Control UI command, interaction, and artifact adoption](https://github.com/openclaw/openclaw/pull/127675) + +These are draft review surfaces, not accepted roadmap or merge approval. They +condense the original fork-only evidence stack: + +1. [OC1: Gateway Client model foundation](https://github.com/giodl73-repo/openclaw/pull/230) +2. [OC2: conversation model and commands](https://github.com/giodl73-repo/openclaw/pull/231) +3. [OC3: renderer-neutral UI artifacts](https://github.com/giodl73-repo/openclaw/pull/232) +4. [OC4: Control UI reference adoption](https://github.com/giodl73-repo/openclaw/pull/238) +5. [OC5: conformance and package-hardening slices](https://github.com/giodl73-repo/openclaw/pull/241) +6. [OC5: fixture-family continuation](https://github.com/giodl73-repo/openclaw/pull/244) +7. [OC5: steady-state performance and memory](https://github.com/giodl73-repo/openclaw/pull/245) +8. [OC5: wire-compatibility canary](https://github.com/giodl73-repo/openclaw/pull/246) +9. [OC5: lifecycle performance](https://github.com/giodl73-repo/openclaw/pull/247) +10. [OC5: security review and authority-epoch hardening](https://github.com/giodl73-repo/openclaw/pull/248) +11. [Session-list projection follow-up](https://github.com/giodl73-repo/openclaw/pull/249) +12. [CU4: Control UI ordinary command adoption](https://github.com/giodl73-repo/openclaw/pull/242) +13. [CU5: Control UI interaction and artifact adoption](https://github.com/giodl73-repo/openclaw/pull/243) + +The upstream PRs currently use the already-published fork heads and are draft +until the RFC and owner acceptance settle. OC5 proves finite defaults, +representative fixture families, clean packed-package Node/declaration/browser +consumption, measured steady-state and lifecycle performance, +candidate/predecessor/main wire compatibility, and full-stack security review +with the confirmed finding remediated. The session-list follow-up is additive +evidence that product shells can consume a smaller read-only roster projection +derived from the existing session catalog without adding Gateway methods, +session mutations, chat send, or history behavior. A final whole-series review +then covered OC1-OC5 and CU4-CU5 with independent GPT-5.6 Terra, Claude Opus +5, and Gemini 3.1 Pro Preview passes, followed by a clean Codex branch review. +Accepted lifecycle, observer ownership, canonical-session alias, metadata-bound, +history, roster, routing, and question-state findings were fixed at core head +`a158436f085` in PR #248 / upstream CM4 and Control UI head `0a8ad4188a6` in +PR #243 / upstream CM5. Final focused proof passed 59 Gateway lifecycle/model +tests, 61 integrated Control UI tests, 6 prompt tests, and packed-package +acceptance. Owner acceptance remains open under the +[ownership and support plan](0029/ownership-and-support-plan.md). + +The independent Lobster evidence started as a temporary carry plus six bounded +adopter slices: + +1. [L0: temporary Control Model carry](https://microsoft.ghe.com/bic/lobster/pull/8165) +2. [LM1: adapt canonical snapshots into `SessionView`](https://microsoft.ghe.com/giodl/lobster/pull/63) +3. [LM2: render one allowlisted native table artifact](https://microsoft.ghe.com/giodl/lobster/pull/64) +4. [LM3: route one native refresh action through the model](https://microsoft.ghe.com/giodl/lobster/pull/65) +5. [LM4: route ordinary sends through the model](https://microsoft.ghe.com/giodl/lobster/pull/66) +6. [LM5: route active-run aborts through the model](https://microsoft.ghe.com/giodl/lobster/pull/67) +7. [LM6: hydrate selected-session history through the model](https://microsoft.ghe.com/giodl/lobster/pull/68) + +This series proves native React rendering, actions, send, abort, reconnect, and +history while deleting duplicate Lobster Gateway behavior. It intentionally +stops at LM6: remaining raw paths are host-owned operational/security or +compatibility lanes rather than equivalent Control Model behavior. + +Same-repository Lobster follow-up has since turned the most important adopter +evidence into reviewable product slices: + +- [Lobster PR #9248](https://microsoft.ghe.com/bic/lobster/pull/9248) merged + the temporary OpenClaw v2026.6.33 Control Model and artifact projection carry + after required PullRequest, Build Validation, and POP gates passed. +- [Lobster PR #9384](https://microsoft.ghe.com/bic/lobster/pull/9384) merged + canonical conversation snapshots into Lobster `SessionView` without changing + React renderers, behind the default-off + `EnableOpenClawControlModel` rollout flight. Its exact-head evidence covered + focused desktop tests, Loki schema tests, static checks, Vite build, branch + review, all six Rust E2E shards, all three Playwright runtime shards, both + Git-workspace hard gates, and the multiplayer hard gate. +- [Lobster PR #9605](https://microsoft.ghe.com/bic/lobster/pull/9605) merged + the separate native table follow-up as `476282420292` after required + PullRequest and POP gates passed. It keeps raw `chat.final` as the visual + commit owner while adding renderer-neutral artifact projection, exact + allowlisted native table rendering, artifact-only history hydration, + fallback coverage, live flight rollback, and Gateway/Electron proof. Its + rollout annotations inherit the existing `EnableOpenClawControlModel` gate. +- [Lobster PR #12276](https://microsoft.ghe.com/bic/lobster/pull/12276), authored + independently by Stanislaw Wilczynski, extends the existing Control Model + binding to observe bounded native OpenClaw Question state and broker + Answer/Skip through the runtime-owned channel. It keeps + `operator.questions` out of the browser and remains behind the default-off + `lobster.openclaw_ask_user_enabled` flight. This is independent reuse of the + binding boundary, not evidence that the full model is enabled by default. + +Two adjacent owner-first projections now have separate fork-only evidence: + +- [Board Model fork proof](https://github.com/giodl73-repo/openclaw/pull/240) + extracts the existing selected-session reconciliation into + `@openclaw/gateway-client/model/board` from Control UI while preserving OpenClaw board, + provider, ticket, grant, persistence, and sandbox authority. Control UI is + the reference adopter. Release ancestry shows no stable tag contains the + coordinated board stack. `v2026.8.1-beta.2` is the first tag containing the + full implementation plus the later ownership and UI hardening; the extraction + applies cleanly there with 55 focused tests and a Gateway Client build. + [Lobster Board LB1](https://microsoft.ghe.com/giodl/lobster/pull/70) + independently consumes a private carry through a main-process safe projection + and renders one allowlisted native status widget plus inert unsupported + fallbacks. Its Electron proof uses a mocked beta-generation board protocol; + it is not release admission and does not make pinned LobsterClaw 2026.6.33 + board-capable. +- [Config Model LC1 fork proof](https://microsoft.ghe.com/giodl/lobster/pull/69) + provides `@openclaw/gateway-client/model/config` read-only authored + configuration snapshots and read-scoped schema lookup. Lobster LC1 consumes + it through Electron-owned Gateway transport and renders one native read-only + settings category without exposing raw config or write authority to React. A + real Electron Gateway fixture now proves the populated native page, authored + value boundary, and restart guidance. + +### Proposed OpenClaw PR sequence + +The first implementation review sequence is now visible as draft upstream PRs +CM1-CM5. The drafts remain review aids until RFC intake, owner approval, and +publication gates are explicit. Clean same-repository stacked branches may +replace the current fork-head drafts before merge if maintainers prefer a +non-cumulative diff shape. + +Control UI adoption is also intentionally incremental. OC4 already proves the +first three slices; it does not yet make every Control UI command, interaction, +or artifact path model-backed. + +| Slice | Scope | Status and gate | +| --- | --- | --- | +| CU1: runtime binding | Create one lazy Control Model runtime over the existing Control UI Gateway client and forward connection/event invalidations without changing Lit presentation. | Complete in OC4. | +| CU2: catalog and selection | Drive the active session roster and selected-session lookup from immutable catalog snapshots while retaining unsupported archived/all roster behavior. | Complete in OC4. | +| CU3: selected conversation projection | Drive selected-chat history, live subscription, reconnect, and retryable fallback from the lazy conversation handle. | Complete in OC4; the representative overlap/gap/retired-epoch fixtures are now shared in OC5. | +| CU4: ordinary conversation commands | Route the normal composer send and foreground active-run abort through typed conversation commands. Keep steer/inject, realtime talk, background tasks, no-run abort-all, and other operational callers raw until separately classified. | Filed upstream in [CM5](https://github.com/openclaw/openclaw/pull/127675); fork evidence is [OpenClaw PR #242](https://github.com/giodl73-repo/openclaw/pull/242), stacked on OC5. The adapter preserves session identity, attachment/reply/fencing inputs, reconnect-resume fallback, and structured command errors used by incumbent recovery. | +| CU5: interactions and artifacts | Project selected-session questions and current Canvas/MCP/structured fallbacks through conversation snapshots plus the existing Control UI adapters. Preserve global/operator approval lanes and sandbox ownership where they are not equivalent. | Filed upstream in [CM5](https://github.com/openclaw/openclaw/pull/127675); fork evidence is [OpenClaw PR #243](https://github.com/giodl73-repo/openclaw/pull/243), stacked on CU4. | +| CU6: observation and deletion | Run the model-backed path through an observation window, retain rollback, then delete only the superseded UI-local reducers, requests, and compatibility adapters named by the earlier slices. | Maps to OC7 and cannot precede OC6 publication, rollback proof, and an exact deletion ledger. | + +Board and configuration adoption are not hidden CU slices. They remain the +separate Board Model and Config Model proposals because their authority, +persistence, and release contracts differ from conversation state. + +| Candidate | Scope | Gate | +| --- | --- | --- | +| [CM4: shared conformance and package hardening](https://github.com/openclaw/openclaw/pull/127674) | Promote the proven fixture families into shared Gateway Client/Control UI conformance, finalize finite defaults, and prove package acceptance, steady-state and lifecycle performance, wire compatibility, and security through the fork evidence stack #241 and #244-#248. | Draft upstream review surface filed; OC6 remains blocked on explicit owner acceptance and a chosen support window. | +| OC6: supported model subpaths | Publish the optional model subpaths with compatibility window, migration policy, framework-neutral quickstart, release notes, support ownership, and install/import proof from the packed release artifact rather than a workspace checkout. Replace fork-only consumption only after a released package exists. | OC5 passes on the supported release, predecessor where promised, and `main`; the packed artifact passes clean browser and Node consumer checks; independent-host evidence remains valid. | +| OC7: incumbent-path cleanup | After an observation window and rollback proof, remove only the superseded Control UI reconciliation, standard command, interaction, artifact-adapter, and compatibility paths actually replaced by CU1-CU5. | OC6 is released, Control UI is stable on the model, and deletion evidence identifies each exact old path. | + +Adjacent proposals remain separate from Control Model v1 acceptance: + +| Candidate | Scope | Gate | +| --- | --- | --- | +| HCU1: hosted Control UI policy | Serve the version-matched OpenClaw Control UI in a host runtime, advertise host policy through bootstrap, and enforce route/method lockdown server-side. | Tracked by the hosted-surface umbrella [#115423](https://github.com/openclaw/openclaw/issues/115423), host policy draft [#115408](https://github.com/openclaw/openclaw/pull/115408), and Gateway enforcement draft [#116013](https://github.com/openclaw/openclaw/pull/116013). This is the immediate hosted fallback path, not a Control Model dependency. | +| BM2: Board Model release admission | Reconstruct the Board Model extraction and native-host conformance against an accepted board-capable OpenClaw release, then decide whether `model/board` is supportable. | Stable board-capable tag, or explicit beta admission with complete persistence, grants, tickets, sandbox, and compatibility review. | +| CFG1: read-only Config Model | Extract framework-neutral authored config snapshots and read-scoped schema descriptors into an OpenClaw-owned optional model with Control UI reference adoption. | Config owner review, secret redaction, schema compatibility, and proof that read projection does not imply write authority. | +| CFG2: governed configuration commands | Add provenance, owner, lock reason, candidate preview, validation findings, generation, commit, and activation status only through Managed Configuration contracts. | Separate owner approval and transactional write/activation design; not implied by this RFC or CFG1. | +| POL1: hosted policy decisions for settings and Gateway actions | Reuse the hosted decision envelope for `enabled`, `readOnly`, and `disabled` behavior across Control UI settings and Gateway writes. | Fork-only policy-settings drafts [#196](https://github.com/giodl73-repo/openclaw/pull/196)-[#202](https://github.com/giodl73-repo/openclaw/pull/202) prove the shape. Policy remains the source of policy findings and constraints; Control Model consumers only receive presentation-safe state and command errors. | + +Cross-client user-message identity, generic generated layouts, framework +adapters, and third-party native component SDKs remain separate future +proposals rather than implied follow-up PRs. + +### Module boundary + +Add the framework-neutral, browser-safe Control Model as optional exports from +`@openclaw/gateway-client`: `model`, `model/catalog`, and +`model/session-event-refresh`. The model module graph must not import Lit, +React, DOM components, route definitions, product authentication, localization +catalogs, CSS, or Control UI presentation helpers. + +The model consumes a narrow host-supplied Gateway binding compatible with the +public Gateway Client: + +```ts +export interface ControlGateway { + getSnapshot(): GatewayConnectionSnapshot; + subscribe(listener: () => void): () => void; + subscribeEvents(listener: (event: GatewayEvent) => void): () => void; + request(method: string, params?: unknown, options?: RequestOptions): Promise; +} +``` + +The binding lets the model reuse OpenClaw's browser, Node, or hosted transport +without owning credential persistence, product routing, or socket creation. +It is a construction-time capability owned by the host and model +implementation. It is not exposed through snapshots, conversations, artifacts, +renderer registrations, or framework adapters, so consumers cannot use it to +bypass typed model commands. + +The Control Model exposes immutable snapshots and typed commands: + +```ts +export interface ControlModel { + getSnapshot(): ControlSnapshot; + subscribe(listener: () => void): () => void; + conversation(sessionKey: string): ConversationModel; + sessions: SessionCommands; + dispose(): void; +} +``` + +Subscriptions are invalidation signals. Consumers read the current immutable +snapshot after notification. This works with framework adapters without +embedding framework hooks in the model. + +### V1 capability boundary + +V1 contains: + +- connection phase, accepted protocol/session metadata, and structured errors; +- session catalog snapshots and refresh/reconciliation state; +- one or more lazily selected conversation models; +- canonical ordered messages; +- active run, stream, tool invocation, approval, and question state needed by + conversation presentation; +- typed conversation commands plus explicit catalog and history refresh; and +- renderer-neutral UI artifacts associated with messages or tool invocations. + +V1 excludes broader Control UI capabilities until each has a bounded, +framework-neutral contract and an independent consumer. +Session create, rename, archive, delete, and other administration commands are +not required by v1 conformance. They remain host-owned or future optional model +capabilities until they have the same independent-adopter and deletion proof. + +### Snapshot and event semantics + +Snapshots are serializable except for explicitly documented command handles. +They use stable identifiers, finite retained state, and typed lifecycle states. +They do not expose mutable Control UI objects. + +The model owns: + +- the initial history snapshot; +- live event application; +- connection-epoch retirement; +- duplicate and stale event handling; +- explicit sequence-gap and partial-state presentation; +- reconnect resynchronization; +- tool invocation/result association; +- cancellation and terminal run reconciliation; and +- artifact association and revision ordering. + +Raw Gateway events remain available from the Gateway client. They are not the +Control Model's stable UI contract. + +### Command semantics + +Commands express typed user intent. Candidate v1 commands include session +catalog refresh, conversation history refresh, chat send, active-run abort, +retry where the Gateway exposes a safe contract, answer, approve, deny, and +exact deferred-view materialization. + +The model may expose command availability for presentation. The Gateway remains +authoritative. A command must return a typed result or throw a typed error. It +must not silently treat a rejected, stale, disconnected, or unsupported +operation as success. + +Commands are connection- and session-aware. An operation captured under a +retired connection epoch must not execute against a replacement session unless +the command contract explicitly permits safe retry. + +### Renderer-neutral UI artifacts + +A UI artifact is data and identity, not executable presentation: + +```ts +export interface UiArtifact { + id: string; + revision: number; + structuredContent?: JsonValue; + views: UiArtifactViewOffer[]; + state: "pending" | "ready" | "failed" | "expired"; + source: { + sessionKey: string; + messageId?: string; + toolCallId?: string; + }; + fallback?: McpAppArtifact | CanvasArtifact; +} + +export interface UiArtifactViewOffer { + id: string; + templateUri: string; + dataVersion: number; + availability: "inline" | "deferred"; + data?: JsonValue; + recommended?: boolean; + fallback?: McpAppArtifact | CanvasArtifact; +} +``` + +OpenClaw core and installed extensions may offer zero or more views of the same +artifact, such as calendar, list, table, summary, or an MCP App. A view's +`templateUri` is opaque. It does not grant trust, select a JavaScript import, +or authorize an action. A host may map a locally registered URI to a native +component. It must schema-validate view data before rendering. + +Each view's `dataVersion` selects a schema version within the host's exact local +registration. Registration and component code ship through the host's ordinary +reviewed supply chain; tool output cannot add, replace, or widen a registration. +OpenClaw may identify a recommended default, but the client remains free to +choose any compatible offered view or project the underlying structured content +into its own product view model. If no compatible renderer is registered, the +host may show structured/text output or use an explicitly sandboxed fallback. + +OpenClaw exposes every authorized applicable descriptor, not every fully +materialized payload. A bounded view may be inline. An expensive or sensitive +view is deferred until the client selects it and requests materialization +through a typed, read-only Control Model command. Materialization remains +extension-owned and Gateway-authorized. + +Presentation placement does not define artifact identity. A client may render +the same artifact revision inline in chat, in an expanded panel, or in a +dedicated artifact surface. Its stable `id` and monotonic `revision` let later +turns or tool runs update that logical artifact instead of emitting unrelated +cards. V1 durability means addressable, revisioned session state that survives +history reload and reconnect. It does not require permanent document storage, +cross-session retention, or a collaborative document protocol. + +V1 uses complete immutable revisions. It does not standardize JSON Patch, +JSONL, or a renderer-specific component tree. A later extension may introduce a +negotiated patch dialect after conformance evidence demonstrates a shared need. + +### Security boundary + +The Control Model is presentation support, not an authorization authority. + +- Native renderers are registered and allowlisted by the host. +- Tool output cannot select an import path, module URL, or privileged action. +- Artifact data is untrusted input with finite size and depth. +- Artifact data and structured content remain separate from hidden model or + credential state. +- Component actions call named host bindings that re-enter typed model commands. +- The Gateway independently authorizes every protected action. +- Native action audit and telemetry can correlate the renderer registration, + artifact ID, revision, action name, session, and tool call without recording + raw artifact data. +- MCP Apps and Canvas retain their sandbox, CSP, lifecycle, and capability + boundaries. +- Unknown versions, malformed data, expired state, and stale revisions fail + visibly and do not trigger executable fallback automatically. + +### Ownership boundary + +OpenClaw maintainers own: + +- Gateway Client model contracts and implementation; +- Gateway-to-model normalization and reconciliation; +- stable state, command, error, and artifact semantics; +- compatibility fixtures and release versioning; +- Control UI reference adoption; and +- server-side authorization behavior. + +Independent products own: + +- product navigation, layout, components, design systems, accessibility, and + localization; +- renderer registration and component schemas; +- registration provenance, code review, signing, and deployment through the + product's ordinary component supply chain; +- product authentication, tenant routing, telemetry, deployment, and rollout; +- optional adapters into an existing product view model; and +- product-specific actions that call supported OpenClaw commands. + +Tool and MCP server authors own structured domain results and UI resources. +They do not choose whether a host trusts a native renderer. + +### Extension and client capability split + +Installed and enabled OpenClaw extensions determine which tools, structured +results, UI artifacts, and alternative view offers can be produced. The client +determines which artifact views and native renderers it has installed, +registered, and trusted, and which product view model should consume the +projection. + +The Control Model does not generate UI capabilities independently of either +side. It normalizes the artifact emitted by the active extension, exposes the +current client-rendering decision, and preserves a safe fallback: + +```mermaid +flowchart TB + extension["Installed extension emits artifact and view offers"] + model["Control Model normalizes identity, views, data, lifecycle, and fallback"] + selection{"Client selects a compatible trusted view"} + native["Local view-model projection
and native renderer"] + fallback["Structured or sandboxed
MCP App fallback"] + + extension --> model --> selection + selection -->|compatible registration| native + selection -->|no compatible registration| fallback +``` + +Client capability advertisement may let an extension avoid producing an +unsupported optional artifact, but it is an optimization rather than an +authorization grant. Extensions should preserve useful structured or text +output when no native renderer is available. A client must not claim native +support unless an exact compatible local registration exists. OpenClaw owns +the available view offers and their semantics; Lobster or another host owns +which offer it selects and how it maps that projection into its own view model. + +View discovery is filtered to the authenticated caller, selected session, +enabled extension surface, and current policy. It must not disclose hidden +extensions or unavailable tools. Client renderer advertisement is delivered to +the trusted Gateway and is not exposed verbatim to extensions by default. + +### Compatibility and release + +The Gateway Client model follows the OpenClaw calendar release train and +declares its compatible Gateway protocol window. Additive fields must not break +consumers. Incompatible snapshot or command changes require a documented +migration and a major contract-version decision independent of the wire +protocol number. + +The model subpaths begin as fork-only exports. Publication requires: + +- adoption by OpenClaw Control UI; +- adoption by one independent host; +- exact shared conformance fixtures; +- package-acceptance and browser-safe module-graph checks; +- a declared support and compatibility policy; +- explicit owner acceptance recorded with the + [owner acceptance record](0029/owner-acceptance-record.md); and +- evidence that one duplicate consumer implementation can be deleted. + +Subscriber callbacks run outside the Gateway receive stack. Model ingestion, +normalization, notification, and retained queues must remain bounded; a slow or +throwing subscriber cannot be awaited by protocol event delivery. + +### Delivery shape + +The implementation is intentionally incremental: + +1. Gateway Client model boundary plus connection and session-catalog snapshots; +2. selected-conversation projection and commands; +3. renderer-neutral artifact projection and existing MCP App/Canvas adapters; +4. OpenClaw Control UI adoption of one complete slice; and +5. independent Lobster adoption through `SessionView`, including native + artifact, action, send, abort, and history deletion evidence; +6. package publication and support ownership after compatibility, security, + and package-acceptance gates; and +7. product rollout with live hosted-Gateway proof, flags, rollback, telemetry, + accessibility, localization, and shareable demo evidence. + +No later layer is required to accept an earlier bounded layer. +Adding another capability after v1 requires an independent consumer, a bounded +contract, and a named duplicate implementation or inference path it can delete. + +Existing OpenClaw dashboards and settings follow separate adoption paths. +Lobster can host version-matched dashboard and settings routes immediately. +The hosted policy stack supplies the deployment and lockdown controls for that +path: runtime gates decide whether the hosted bundle is available, bootstrap +declares the host-owned Gateway route and scopes, and server enforcement +blocks forbidden mutations even if a browser affordance is stale or bypassed. +Control Model consumers can mirror disabled/read-only state and safe denial +reasons, but only the Gateway/runtime policy path is authoritative. + +The Board Model proof now demonstrates the optional projection of OpenClaw's +existing board model and a bounded Lobster native adopter, but only with a +mocked beta-generation protocol. No stable OpenClaw tag currently contains the +coordinated board stack; `v2026.8.1-beta.2` is the first fully compatible tag +and passes the extraction proof. The adopter does not change the pinned +2026.6.33 support boundary. The Config Model and LC1 proof demonstrate a native +read-only settings surface over authored values and schema descriptors, +including a real Electron screenshot. Governed writes still require provenance, +authority, validation, candidate diffs, generation, and transactional +activation from Managed Configuration. Neither adjacent model belongs in the +conversation snapshot or expands Control Model v1. + +## Rationale + +### Why not inject a model into Control UI + +Control UI already has an internal capability graph, but its bootstrap, +application context, route ownership, and presentation lifecycle are +application internals. Making that graph injectable would still require an +independent host to load the Lit application and track private module changes. +The reusable boundary belongs below the application. + +### Why not use the Gateway Client browser transport directly + +The browser transport deliberately exposes protocol methods and events. Session +catalogs, conversation snapshots, tool outcomes, and UI artifacts remain a +distinct optional state-and-command layer under `gateway-client/model`; every UI +otherwise reimplements them. + +### Why not publish Control UI's application context + +The current context includes theme, navigation, overlays, browser settings, +native bridges, and capabilities whose state mixes domain and presentation +concerns. Publishing it wholesale would freeze application internals and make +framework-independent use difficult. V1 extracts only the proven independent +slice. + +### Why immutable snapshots + +Immutable snapshots work with React external stores, Lit controllers, native +bridges, tests, and non-UI consumers. They keep ordering and mutation inside +the owner package and avoid making raw event accumulation a renderer +responsibility. + +### Why UI artifacts are not components + +A renderer identifier plus validated data supports native first-party +presentation without letting untrusted tool output load code. It also preserves +MCP Apps as the executable third-party boundary and keeps renderer choice with +the host. + +### Why not standardize JSON Render in v1 + +JSON Render is a useful adopter and comparator: it demonstrates schema-defined +catalogs, named actions, and streamed revisions. Making its component tree or +patch dialect normative would couple OpenClaw state reuse to one renderer +before two OpenClaw consumers prove the need. The v1 artifact contract can +carry validated JSON data that a host renders with JSON Render or another +library. + +### Why OpenClaw owns the model + +The model interprets OpenClaw protocol behavior and must change atomically with +Gateway and Control UI semantics. Product-owned copies would drift. Product +presentation remains outside OpenClaw, so upstream ownership does not absorb +independent UX. + +## Unresolved questions + +- Do maintainers accept the optional Gateway Client model subpaths as the + correct owner boundary for OC6 publication? +- Do the nominated package, protocol, Control UI, security, release, and RFC + owners accept the responsibilities, deputies, and escalation paths in the + [ownership and support plan](0029/ownership-and-support-plan.md)? +- Which release vehicle and version window should carry the first supported + model subpaths? +- What observation window and rollback evidence must pass before OC7 can + delete incumbent Control UI paths? +- Should Board Model and Config Model proceed as the separate BM2/CFG1 proposals + above, or remain fork-only evidence until a later RFC? +- If cross-client user-message delivery becomes required, what identity + contract aligns host `clientMessageId`, model idempotency, retry/reconnect, + persisted history, and renderer deduplication? diff --git a/rfcs/0029/conformance-and-adoption-plan.md b/rfcs/0029/conformance-and-adoption-plan.md new file mode 100644 index 00000000..415d8482 --- /dev/null +++ b/rfcs/0029/conformance-and-adoption-plan.md @@ -0,0 +1,300 @@ +# Control Model conformance and adoption plan + +This plan turns RFC 0029 into independently reviewable gates. A model subpath, +UI artifact, or adopter is not supported until source behavior, fixtures, live +proof, and deletion agree. + +## Evidence principles + +- The Gateway protocol and server are authoritative for wire behavior and + authorization. +- OpenClaw Control UI is the executable behavioral reference until shared + fixtures replace UI-local interpretation. +- Raw Gateway events are evidence inputs, not the stable Control Model API. +- A source harness proves reconciliation; a real Gateway proves integration; a + second host proves framework neutrality. +- Native rendering is not proof of action authorization. +- Every proof records repository, exact head, OpenClaw version, command, + result, and known gap. +- Every adopter names duplicate code that becomes deletable. + +## Acceptance layers + +| Layer | Review surface | Required proof | Deletion unlocked | +| --- | --- | --- | --- | +| M1 Gateway Client model boundary | [CM1 #127670](https://github.com/openclaw/openclaw/pull/127670) | Browser-safe module graph, lifecycle, immutable store contract | Consumer scaffolding for connection/session snapshots | +| M2 conversation projection | [CM2 #127671](https://github.com/openclaw/openclaw/pull/127671) | Shared history/live/reconnect/tool/approval corpus | Per-consumer chat reducers and event folding | +| A1 UI artifacts | [CM3 #127672](https://github.com/openclaw/openclaw/pull/127672) | Native, structured-only, MCP fallback, malformed, stale, history cases | Tool-specific presentation interpretation | +| O1 Control UI adoption | [CM4 #127674](https://github.com/openclaw/openclaw/pull/127674), [CM5 #127675](https://github.com/openclaw/openclaw/pull/127675), and later CU6 | Existing Control UI behavior unchanged as runtime, catalog, selected conversation, ordinary commands, interactions, and artifacts move through the model in bounded slices | Only the UI-local capability/reducer/request code replaced by each observed slice | +| H1 independent host | Lobster PRs [#9384](https://microsoft.ghe.com/bic/lobster/pull/9384) and [#12276](https://microsoft.ghe.com/bic/lobster/pull/12276) | Real hosted Gateway projected into the existing host view model; independent follow-on reuses the binding for bounded Question observation and Answer/Skip brokerage | Host-owned Gateway reconciliation for adopted slices without browser `operator.questions` authority | +| H2 native artifact | Lobster/M PR 2 | One allowlisted component plus denied action and fallback | One bespoke tool-output rendering path | +| C1 shared conformance | [CM4 #127674](https://github.com/openclaw/openclaw/pull/127674) | Shared fixtures, finite defaults, browser/Node package acceptance, compatibility canaries, performance bounds, and security review | Publication uncertainty | +| R1 publication | OpenClaw PR 6/release | Accepted conformance, two consumers, compatibility window, migration policy, release and support ownership, and clean install/import proof from the packed release artifact | Fork-only distribution | +| D1 incumbent cleanup | OpenClaw PR 7 | Observation window, rollback proof, and exact deletion ledger | Superseded Control UI reconciliation | + +## Additive adoption map + +Control Model conformance is not the only path for OpenClaw UI in a host. The +supported architecture is additive: + +| Surface | Authority retained | Control Model relationship | +| --- | --- | --- | +| Hosted Control UI | OpenClaw owns the version-matched app; host runtime owns auth, route selection, rollout, and server-side policy enforcement. | Independent deployment/fallback path tracked by [openclaw/openclaw#115423](https://github.com/openclaw/openclaw/issues/115423), [#115408](https://github.com/openclaw/openclaw/pull/115408), and [#116013](https://github.com/openclaw/openclaw/pull/116013). It can use the same Gateway, but Control Model v1 does not gate or replace hosted policy enforcement. | +| Native conversation UX | OpenClaw owns conversation semantics; host owns React/native composition. | Primary v1 target: immutable snapshots, typed commands, and renderer-neutral artifacts. | +| Native board/dashboard UX | OpenClaw owns board identity, widgets, grants, tickets, layout, persistence, and sandbox semantics. | Sibling Board Model proposal. Dashboard-shaped conversation artifacts do not replace the board model. | +| Native settings UX | OpenClaw owns schema meaning and config read/write semantics; Managed Configuration owns governed writes and activation. | Sibling Config Model proposal. V1 may show safe command-denial details, but it does not define settings writes. | +| Policy and lockdown | Policy/Gateway/runtime enforcement owns allowed operations, read-only state, disabled state, and denial reasons. | Model consumers may project presentation-safe state and errors; they must not treat UI affordances as authorization. Fork-only settings-constraint drafts [giodl73-repo/openclaw#196](https://github.com/giodl73-repo/openclaw/pull/196)-[#202](https://github.com/giodl73-repo/openclaw/pull/202) are sibling evidence, not Control Model conformance. | + +RFC 0029 has sidecar specifications for Control Model v1, UI artifact v1, and +Hosted Control UI policy v1. These contracts share an owner-first family shape, +but each has an independent conformance and acceptance gate. + +## Evidence to date + +Fork-only evidence now covers the full bounded V1 thesis: + +| Evidence | Result | +| --- | --- | +| OC1 | Immutable bounded catalog snapshots, explicit host binding, epoch-safe refresh, typed errors, and subscriber isolation. | +| OC2 | Lazy conversations, deterministic history/live reconciliation, bounded messages/runs/tools/interactions, typed commands, reconnect, and retention. | +| OC3 | Sanitized renderer-neutral artifacts, history/reconnect revisions, selected-only deferred materialization, MCP App/Canvas fallback, and provenance/identity hardening. | +| OC4 | Initial Control UI adoption: lazy runtime binding, canonical active-session catalog, and selected-chat history/subscription state without visual or startup-budget regression. Ordinary commands, interactions, artifacts, and operational callers remain outside this draft. | +| OC5 current slices | Centralized finite defaults; reusable catalog, history/live overlap, reconnect, approval authorization, run, tool, question, artifact, and retained-bounds fixtures; packed protocol/client installation; every Gateway Client export imported from the tarball; declaration consumption; browser bundling; repair of a package-only browser export failure; asserted steady-state projection and retained-memory bounds; an asserted candidate/predecessor/main wire-compatibility matrix; asserted initial projection, selected-view materialization, inactive eviction, and reconnect/resync lifecycle bounds; and an independent security review with authority-epoch cache remediation. Filed upstream in [CM4 #127674](https://github.com/openclaw/openclaw/pull/127674); the latest fork slice is [OpenClaw PR #248](https://github.com/giodl73-repo/openclaw/pull/248), stacked on lifecycle PR #247. | +| Session-list follow-up | [OpenClaw PR #249](https://github.com/giodl73-repo/openclaw/pull/249) derives a product-shell read-only session roster from the existing bounded `sessionCatalog`, covering stable identity, title, status, model/provider, navigation metadata, and sanitized worktree summary without adding Gateway requests, session mutations, chat send, or history behavior. | +| Whole-series review | Independent GPT-5.6 Terra, Claude Opus 5, and Gemini 3.1 Pro Preview reviews covered OC1-OC5 and CU4-CU5, followed by a clean Codex branch review. Accepted findings were fixed at core head `a158436f085` in PR #248 and Control UI head `0a8ad4188a6` in PR #243. Final focused proof passed 59 Gateway lifecycle/model tests, 61 integrated Control UI tests, 6 prompt tests, and packed-package acceptance. | +| CU4 | Fork-only Control UI ordinary-command adoption: selected composer sends and connected exact-run aborts route through the existing conversation handle while reconnect-resume, steer/inject, background/non-selected, realtime, replay, and session-wide abort paths remain raw. Session identity, attachments, reply/fencing inputs, retry metadata, and active-leaf recovery details are preserved. | +| CU5 | Fork-only selected-session interaction and artifact adoption: exact pending question answer/cancel commands route through the cached conversation identity while Control UI retains prompt lifecycle and raw fallback. Validated ready Canvas/MCP artifact snapshots feed only existing sandboxed adapters, with canonical-first provenance and occurrence-aware compatibility dedupe. Global/operator approval queues remain raw. | +| LM1-LM3 | Existing `SessionView` adaptation, exact native table rendering, visible fallback, and a host-owned action routed through the model. | +| LM4-LM6 | Ordinary send, active-run abort, and selected-session history cut over to the model, deleting equivalent raw Lobster paths. | +| Independent Lobster follow-on | Merged Lobster PR [#12276](https://microsoft.ghe.com/bic/lobster/pull/12276) independently extends the existing binding for bounded native Question observation and Answer/Skip brokerage behind a default-off flight, without exposing `operator.questions` to the browser. | +| Board Model + LB1 | Existing Control UI board reconciliation extracted to `@openclaw/gateway-client/model/board`; 55 focused tests, Gateway Client build, and clean review. Lobster LB1 independently renders one safe native status widget and inert unsupported fallbacks through a main-process projection. Its mocked beta protocol is evidence only; release admission remains open. | +| Config Model + LC1 | Read-only authored config snapshots and schema lookup consumed by a native Lobster settings category through Electron-owned transport; principal-scoped cache, structured failure states, focused tests, and clean review. | + +The independent-adopter gate is therefore demonstrated, not merely planned. +Publication is still blocked on upstream acceptance, explicit acceptance of the +[ownership and support plan](ownership-and-support-plan.md), and a released +dependency through PR 6. The bounded steady-state projection and +retained-memory threshold slice is measured in PR #245, the wire-compatibility +canary is measured in PR #246, and the lifecycle performance scenarios are +measured in PR #247. The security gate is reviewed and remediated in PR #248. +Control UI CU5 is filed upstream in +[CM5 #127675](https://github.com/openclaw/openclaw/pull/127675), with fork +adopter evidence in +[OpenClaw PR #243](https://github.com/giodl73-repo/openclaw/pull/243). +The whole-series review closes the technical review gate but does not satisfy +the explicit acceptance records required by the +[ownership and support plan](ownership-and-support-plan.md). +Incumbent cleanup remains CU6/PR 7 after +observation and rollback proof. Product shipment is additionally blocked on +Lobster CI, live hosted-Gateway proof, rollout and rollback controls, telemetry, +and UX quality. + +## Shared fixture families + +| Family | Minimum cases | +| --- | --- | +| Store | read/subscribe race, immutable identity, unsubscribe, disposal | +| Scheduling | receive-stack isolation, bounded reconciliation queue, slow/throwing subscriber | +| Connection | connect, reconnect, offline, terminal error, retired epoch | +| Sessions | initial list, live create/update/delete, observer outage, resync | +| History | initial load, pagination/truncation, live-before-history, duplicate persisted/live | +| Runs | start, stream, progress, success, failure, cancellation, disconnect | +| Tools | call/result association, out-of-order result, duplicate ID, bounded progress | +| Approvals/questions | allowed action, denial, expiry, reconnect, stale action | +| Commands | success, forbidden, conflict, timeout, abort, unsupported, idempotent retry | +| Artifacts | multiple view descriptors, lazy materialization, client selection, native, unknown, malformed, fallback, revisions, history, expiry | +| Capability split | extension absent/disabled, renderer absent, version mismatch, stale/private advertisement, authorization-filtered discovery | +| Bounds | messages, progress, artifacts, bytes/depth, inactive conversations | + +Each fixture identifies: + +- wire/projection schema version; +- canonical source behavior; +- initial state; +- ordered inputs; +- expected snapshots; +- expected commands or failures; and +- one mutation that must fail in a deliberately nonconforming implementation. + +## Validation ladder + +### Per-commit + +- formatting, lint, typecheck, and diff hygiene; +- affected package tests; +- browser-safe import graph; +- fixture schema validation; +- no framework or product imports in core; and +- no subscriber/render work in the Gateway receive stack. + +### Per-PR + +- complete `@openclaw/gateway-client/model` tests; +- Gateway protocol compatibility tests; +- current Control UI tests for affected behavior; +- source fixture and real loopback Gateway proof; +- memory/retention bounds under representative history and progress; and +- independent review of error, reconnect, and authorization semantics. + +### Native artifact adoption + +- exact local registry and schema version; +- multiple OpenClaw view offers and a client-selected non-default view; +- deferred descriptors with only the selected payload materialized; +- valid and invalid artifact data; +- allowed and denied action; +- stale artifact action; +- unknown renderer; +- extension-installed but renderer-unsupported and renderer-installed but + extension-absent cases; +- MCP App/structured fallback; +- theme, accessibility, localization, and responsive behavior owned by the + adopter; and +- no dynamic import derived from artifact data. + +### Hosted adoption + +- real product authentication and Gateway route; +- cold start and reconnect; +- history/live overlap; +- mid-stream disconnect and resync; +- tenant/session isolation; +- rollback to the incumbent path; and +- telemetry without raw tool data or credentials. + +## Compatibility + +Before publication, test: + +- the exact supported OpenClaw release; +- the declared predecessor release where compatibility is promised; +- OpenClaw `main` as a drift canary; +- browser and Node host bindings; and +- a packed release artifact installed into clean browser and Node consumers, + including every supported subpath and declaration entrypoint; and +- every supported serialized fixture version. + +The Control Model contract version and Gateway wire protocol version are +distinct. A wire-compatible server may still require an additive model +projection update. An incompatible model change requires migration guidance +and a declared support-window decision. + +The first asserted compatibility slice is fork +[OpenClaw PR #246](https://github.com/giodl73-repo/openclaw/pull/246). It checks +the candidate schema, published predecessor +`@openclaw/gateway-protocol@2026.7.2-beta.7`, and current `main` for the exact +methods, authorization scopes, request payloads, and representative events used +by the Control Model. Blacksmith Testbox `tbx_01m05spdcyy97ht168pfy3tqv1` +passed against `main@63401b730b55b40f691b004308cab45b66c8eb89`. The predecessor +accepts the baseline ordinary-send contract but rejects `expectedRunId`; the +canary records that capability boundary instead of promising an unsupported +fenced-send downgrade. + +## Performance and memory gates + +The package must measure: + +- initial session and conversation projection time; +- per-event reconciliation cost; +- snapshot allocation rate during streaming; +- retained bytes for messages, progress, tools, and artifacts; +- descriptor enumeration and selected-view materialization latency/bytes; +- inactive conversation eviction; and +- reconnect/resync latency. + +No renderer callback runs in the Gateway receive loop. Slow subscribers must +not block protocol event processing. Unbounded history, progress, artifact, or +listener retention blocks release. + +The first asserted performance slice is fork +[OpenClaw PR #245](https://github.com/giodl73-repo/openclaw/pull/245). It runs +six measured batches after warmup, with 1,000 artifact-heavy cycles and 4,000 +events per batch. The gate requires p95 projection latency at or below 4,000 ms, +retained heap growth at or below 2 MiB, retained heap slope at or below 256 KiB +per batch, and exact finite snapshot lengths with truncation evidence. +Blacksmith Testbox `tbx_01m05mrxhzajxpdqb6ggdzx4y1` passed at 2,211.45 ms p95, +52,272 bytes retained growth, and 8,482.51 bytes/batch retained slope. Initial +projection, selected-view materialization, inactive eviction, and +reconnect/resync are asserted in fork +[OpenClaw PR #247](https://github.com/giodl73-repo/openclaw/pull/247). +Blacksmith Testbox `tbx_01m05veqewr76wd05191gt6sb8` passed with: + +- 14.32 ms initial projection p95 for 200 sessions, 200 messages, and 50 + artifacts, below a 100 ms ceiling; +- 0.58 ms selected deferred-view materialization p95 across 100 views, below a + 10 ms ceiling; +- 26.23 ms inactive eviction p95 per 1,000 handles, with exactly 50 retained + and 5,950 disposed, below a 250 ms ceiling; and +- 8.07 ms reconnect/resync p95 for authoritative 200-message history, below a + 100 ms ceiling. + +## Security gates + +The following are blocking: + +- native renderer registration from tool-provided data; +- component/module import paths derived from artifact metadata; +- action execution without model command and server authorization; +- success-shaped state after forbidden/conflicting commands; +- cross-session or retired-epoch artifact/action confusion; +- credentials, hidden model context, capability URLs, or unbounded payloads in + logs/errors; +- implicit executable fallback for unknown artifacts; +- loss of MCP App sandbox/CSP/expiry behavior; and +- deletion of the incumbent path before rollback proof; +- unreviewed renderer registration or unsupported artifact data version; +- eager materialization of unselected deferred views; +- discovery that reveals unauthorized extension/tool/view availability; and +- verbatim extension access to unrelated client renderer inventory. + +The first full-stack security review is recorded in fork +[OpenClaw PR #248](https://github.com/giodl73-repo/openclaw/pull/248). Review +found one medium-severity authority-boundary flaw: selected deferred-view data +could remain materialized after disconnect or connection-epoch replacement. +The fix clears materialized payloads on both transitions, preserves only the +authorized descriptor from refreshed history, and requires a fresh +materialization request under the new epoch. Post-fix security review found no +remaining actionable vulnerabilities. Blacksmith Testbox +`tbx_01m05yh2krdyggq814g2jzyxf6` passed 55 Gateway Client security/conformance +tests and 46 Control UI Gateway-store tests. + +The subsequent whole-series review exercised the complete OC1-OC5 and CU4-CU5 +stack across lifecycle/concurrency, security/compatibility/package, and Control +UI ownership/routing/artifact lenses. Accepted findings were fixed on the +owning branches. The final Codex branch review against +`42a4d0a9b8b3da55123217b8aa1ac495238d4ffd` reported no accepted or actionable +findings. + +## Independent adopter proof + +The first independent adopter should: + +1. consume the Gateway Client model through an existing supported Gateway + route; +2. choose among OpenClaw-provided views and adapt the selected projection into + its existing view model rather than create another shared vocabulary; +3. render one representative conversation; +4. register one native artifact; +5. exercise one denied action; +6. fall back safely when registration is absent; +7. reconnect mid-stream; and +8. identify exact reducer/projection code deleted after parity. + +LM1-LM6 satisfy this bounded proof. Future work should not extend the stack +merely to remove every raw Gateway call. Remaining raw lanes must be classified +by ownership first; host operational/security behavior is not Control Model +duplication. + +## Promotion and deletion ledger + +Every adoption PR records: + +1. incumbent implementation; +2. owner behavior preserved; +3. exact conformance fixtures; +4. real integration proof; +5. rollout and rollback control; +6. observation window; and +7. deletion commit or follow-up owner. + +No deletion credit is granted because a package compiles or a demo renders. diff --git a/rfcs/0029/control-model-v1-spec.md b/rfcs/0029/control-model-v1-spec.md new file mode 100644 index 00000000..3b38ff22 --- /dev/null +++ b/rfcs/0029/control-model-v1-spec.md @@ -0,0 +1,378 @@ +# Control Model v1 specification + +This document defines the candidate behavioral contract for +`@openclaw/gateway-client/model`. It specifies framework-neutral state and +commands above the Gateway Client browser transport. It does not define +presentation, product authentication, or another wire protocol. + +Status: submitted draft sidecar for RFC 0029. It has not been accepted or +released upstream; implementation evidence remains draft and review-gated. + +## Scope + +A conforming v1 model provides: + +- explicit lifecycle and disposal; +- immutable connection and session-catalog snapshots; +- lazily created conversation snapshots; +- history/live/reconnect reconciliation; +- typed tool, approval, question, and run state needed by chat; +- typed commands with structured failure; +- renderer-neutral UI artifacts; and +- finite retained state with observable partial/lag conditions. + +## Host binding + +The model consumes one host-owned Gateway binding. The binding must provide: + +- the current connection snapshot and an invalidation subscription; +- Gateway event subscription; +- correlated request execution; +- the accepted hello/protocol metadata required for feature detection; and +- typed request and connection errors. + +The binding is a construction-only capability for the host and model +implementation. It must not be exposed through public snapshots, conversation +handles, artifacts, renderer registrations, or framework adapters. + +The host owns: + +- socket creation and route selection; +- credentials, signing, and device-token persistence; +- product authentication and tenant admission; +- reconnect policy outside shared Gateway-client behavior; and +- logging and telemetry sinks. + +The model must not start a network connection at import or construction time. +It must not persist credentials. + +## Handshake and capability advertisement + +The client may advertise a bounded capability object during the initial +session handshake or subscription request so the server can filter or rank +offered artifact views and avoid sending unsupported large artifacts. +Advertisement is advisory only; it does not install a renderer, disclose the +full local registry, grant trust, or authorize an operation. + +Suggested capability object (client to server): + +```json +{ + "clientId": "product/instance-version", + "capabilities": { + "artifactViews": [ + { + "templateUri": "clawpilot://widgets/table", + "artifactVersion": 1, + "dataVersions": [1], + "surfaces": ["inline", "expanded"] + } + ], + "sandboxFallbacks": ["mcp-app", "canvas"], + "structuredFallback": true, + "progressiveRevisions": true, + "supports_actions": true, + "max_artifact_size_bytes": 65536 + } +} +``` + +Server guidance: + +1. If an offered view matches an advertised template URI, artifact version, and + data version, the server may rank that view higher or include bounded inline + data. +2. If the renderer is unsupported but a structured/text fallback exists, + send the fallback instead. +3. If artifact size exceeds `max_artifact_size_bytes`, send a bounded + structured fallback or expose the view as deferred. V1 publishes complete + immutable revisions; it does not standardize JSON Patch, JSONL, or + fragment/CID transport. +4. Never expose private or scrubbed fields to clients lacking required + authorization regardless of advertised capabilities. +5. Treat capability advertisement as potentially stale or incomplete; the + client may still decline or ignore offers at render time. + +V1 does not define a renderer dialect or component-vocabulary negotiation +carrier. A future dialect registration may use an exact `templateUri` that pins +both its protocol and reviewed component catalog. Until richer bounded +negotiation is specified, a dialect mismatch is detected by the client registry +at render time and must fail to structured/text or an explicit sandboxed +fallback. + +Privacy guidance: + +- advertise exact supported template/version pairs, not unrelated installed + component inventory; +- keep renderer capability metadata between the trusted client and Gateway by + default rather than forwarding it verbatim to extensions; +- let extensions ask bounded compatibility questions when needed; and +- require the host registry to validate the selected view again before native + rendering, because the advertisement may be stale. + +## Root lifecycle + +Construction is inert except for validating options. `start()` may subscribe to +an already managed Gateway binding; alternatively, construction may start +subscriptions when the API makes that behavior explicit. The selected shape +must have one unambiguous lifecycle. + +`dispose()` is idempotent and must: + +- unsubscribe from Gateway state and events; +- abort or retire model-owned refreshes; +- wake model waiters with a terminal disposed error; +- retire conversation epochs; +- release retained snapshots not reachable by the caller; and +- prevent later events from mutating published state. + +No subscription callback may fire after its unsubscribe function returns, +except a callback already executing on the same stack. + +Gateway event callbacks must not synchronously run consumer render or +subscriber work. The model may enqueue bounded reconciliation work and publish +outside the protocol receive stack. It must not await subscribers. One +subscriber exception must not prevent other subscribers or future Gateway +events from being processed. + +## Snapshot contract + +Snapshots are immutable values. A consumer must be able to: + +1. read a snapshot; +2. subscribe; +3. read again to close the read/subscribe race; and +4. compare snapshot identity to determine whether state changed. + +Every state transition publishes a new root or capability snapshot identity. +Unchanged state must retain identity where practical to avoid unnecessary +renderer work. + +Snapshots use JSON-compatible data except documented opaque handles. Dates are +ISO-8601 strings or integer epoch milliseconds consistently within one public +type family. + +## Connection snapshot + +The connection projection contains: + +- phase: stopped, connecting, connected, reconnecting, offline, or disposed; +- a monotonically increasing connection epoch; +- accepted protocol version and declared capabilities where available; +- current session/instance identity safe for presentation; +- structured last error and reconnect classification; and +- whether state is complete, stale, partial, or resynchronizing. + +A transport connection alone does not imply conversation readiness. Readiness +requires the required initial snapshots or an explicit partial state. + +## Session catalog + +The catalog contains stable session keys and the Gateway-authoritative fields +needed to list and identify sessions. Unknown additive fields must not break +projection. + +The model owns: + +- initial list loading; +- live `sessions.changed` reconciliation; +- explicit deleted-session handling; +- refresh after sequence gaps or observer outages; +- duplicate suppression; +- connection-epoch retirement; +- bounded retry for retryable observer errors; and +- typed loading, refreshing, stale, and error state. + +Any future optional catalog mutation must specify reconciliation and rollback. +A failed mutation must not leave success-shaped catalog state. + +## Conversation model + +`conversation(sessionKey)` returns a stable model handle for that normalized +session key until release or root disposal. A host may release inactive +conversation handles through an explicit API. The package must bound inactive +retention. + +The conversation snapshot contains: + +- normalized session identity; +- loading, ready, stale, partial, terminal, and error state; +- canonical ordered messages with stable IDs; +- the active run and stream projection; +- tool invocations and outcomes; +- approval and question requests; +- UI artifacts; +- command availability hints; and +- the connection and history revisions used to derive the snapshot. + +## History and live reconciliation + +The model must define one deterministic merge for: + +- an initial or refreshed history response; +- live messages received before, during, or after history loading; +- duplicate live and persisted messages; +- live tool calls and later persisted tool results; +- run start, progress, terminal, abort, and disconnect; +- sequence gaps; +- history truncation or pagination; and +- reconnection to a replacement Gateway client. + +Stable server IDs are authoritative. Where the server does not provide an ID, +the model may derive a bounded provisional key, but it must expose provisional +status and reconcile it when canonical state arrives. + +A reconnect must not duplicate a message, tool invocation, approval, question, +or UI artifact. Events from a retired connection epoch must not mutate the +current conversation. + +When a gap prevents complete reconciliation, the model publishes explicit +partial/stale state and requests an authoritative refresh. It must not silently +continue with success-shaped complete state. + +## Tool and run projection + +Every tool invocation has: + +- a stable call ID; +- tool identity safe for display; +- finite structured input or a redacted/unavailable marker; +- pending, running, succeeded, failed, cancelled, or unknown outcome; +- live progress with finite retention; +- structured output or a redacted/unavailable marker; +- associated UI artifact IDs; and +- timestamps/revisions needed for deterministic ordering. + +Approved-but-failed execution remains distinct from approval denial. +Cancellation remains distinct from failure. Unknown output is not success. + +Progress retention must be bounded by count and bytes. Truncation is explicit. + +## Approval and question projection + +An approval or question contains: + +- a stable request ID and owning session/run/tool identity; +- typed presentation-safe description; +- exactly the actions currently allowed by the Gateway contract; +- pending, answered, expired, cancelled, or unavailable lifecycle; +- an optional deadline; and +- structured source/authority information safe for presentation. + +The model must reject a locally requested action that is not in the current +allowed set, but that preflight is not authorization. The Gateway independently +authorizes the request. + +When the server provides a safe denial reason, policy source, or responsible +owner, the projection preserves it so adapters can present an actionable +explanation rather than a generic disabled state. Adapters must not widen or +replace the server-provided allowed-action set. + +## Commands + +Candidate v1 commands are: + +- refresh session catalog; +- load/refresh conversation history; +- send chat content and supported attachments; +- abort the active run; +- answer a question; +- approve or deny a pending request; and +- materialize one exact deferred UI view for the current artifact revision; and +- retry only where the Gateway exposes a safe retry contract. + +Session creation, rename, archive, delete, and other administration operations +are not required by v1 conformance. A later optional capability must add its own +independent-adopter, authorization, reconciliation, rollback, and deletion +evidence. + +Each command defines: + +- required current state; +- exact Gateway method and parameter contract; +- whether it is idempotent; +- abort behavior; +- stale/retired epoch behavior; +- optimistic state, if any; +- success result; and +- typed failures. + +The model must not retry a non-idempotent command automatically unless the +Gateway contract provides an idempotency key and the retry preserves it. + +## Error contract + +Public errors distinguish at least: + +- disconnected or not ready; +- disposed; +- unsupported by negotiated capability; +- invalid input; +- stale connection/session epoch; +- forbidden; +- conflict; +- not found or expired; +- timeout or abort; +- retryable transport/startup failure; +- sequence gap/partial state; and +- malformed or incompatible server data. + +Errors preserve safe canonical codes, retryability, and retry-after hints where +available. Arbitrary server details, credentials, raw headers, and unbounded +payloads must not enter public messages or logs. + +## Bounds + +V1 must define finite defaults for: + +- retained inactive conversations; +- messages per loaded page and total retained pages; +- live progress lines and bytes; +- pending tool/approval/question entries; +- UI artifacts per message/conversation; +- artifact data bytes/depth; +- observer retry delay; +- refresh concurrency; and +- command timeout inheritance. + +The package must expose truncation, pagination, or partial state rather than +silently dropping retained state. + +The implementation must also bound queued reconciliation work between Gateway +event delivery and snapshot publication. Exceeding that bound produces an +explicit lag/partial state and authoritative refresh rather than unbounded +memory growth. + +## Framework neutrality + +The published runtime graph must not import: + +- Lit, React, Vue, Svelte, or framework adapters; +- DOM custom elements or browser storage; +- Control UI routes, theme, localization, CSS, or components; +- product authentication or telemetry; or +- Node-only modules from the browser entry. + +Framework adapters may live in separate optional packages or adopter +repositories. + +## Required conformance evidence + +Before v1 support is claimed, shared fixtures must cover: + +- read/subscribe race closure and immutable identity; +- initial session list plus live create/update/delete; +- retryable observer outage and authoritative refresh; +- history/live overlap; +- duplicate and out-of-order message/tool events; +- sequence gap and explicit partial state; +- reconnect with retired-epoch event rejection; +- active stream completion, cancellation, and disconnect; +- approval allowed/denied/expired paths; +- typed command rejection and conflict; +- artifact association and revision ordering; +- bounds and truncation; and +- disposal during every active wait. + +At least OpenClaw Control UI and one independent host must consume the same +fixtures before publication. diff --git a/rfcs/0029/hosted-control-ui-policy-v1-spec.md b/rfcs/0029/hosted-control-ui-policy-v1-spec.md new file mode 100644 index 00000000..64616b57 --- /dev/null +++ b/rfcs/0029/hosted-control-ui-policy-v1-spec.md @@ -0,0 +1,181 @@ +# Hosted Control UI policy v1 specification + +This document defines the candidate hosted-policy contract for serving the +version-matched OpenClaw Control UI from a host runtime. It is related to the +Control Model because both surfaces let a product host OpenClaw behavior +without forking OpenClaw semantics. It remains a sibling contract: hosted +policy governs deployment, route access, and runtime enforcement, while the +Control Model governs native conversation state, commands, and artifacts. + +Status: draft. The active implementation surfaces are +[openclaw/openclaw#115423](https://github.com/openclaw/openclaw/issues/115423), +[openclaw/openclaw#115408](https://github.com/openclaw/openclaw/pull/115408), +and [openclaw/openclaw#116013](https://github.com/openclaw/openclaw/pull/116013). +Settings-constraint evidence is currently fork-only in +[giodl73-repo/openclaw#196](https://github.com/giodl73-repo/openclaw/pull/196)-[#202](https://github.com/giodl73-repo/openclaw/pull/202). + +## Scope + +A conforming v1 hosted-policy implementation provides: + +- an explicit host decision for whether the hosted OpenClaw Control UI route is + enabled; +- a bounded bootstrap payload that declares host-owned route, Gateway, rollout, + and lockdown decisions safe for the browser; +- server-side route and method enforcement that remains authoritative even when + browser state is stale or bypassed; +- policy decision states for enabled, disabled, and read-only affordances; +- safe denial reasons and owner/source metadata where policy permits display; +- rollback to the incumbent host shell or disabled route without changing the + OpenClaw bundle; and +- conformance evidence that blocked operations fail at the Gateway/runtime + boundary, not only in UI controls. + +This contract does not define a native renderer, a Control Model adapter, a +generic dashboard system, writable configuration authority, or a replacement +for Managed Configuration. + +## Ownership + +OpenClaw owns: + +- the version-matched Control UI bundle; +- the policy vocabulary consumed by the hosted UI; +- Gateway method names and protected operation classification; +- browser-safe bootstrap schema compatibility; and +- default denial and fallback behavior. + +The host runtime owns: + +- route selection and admission; +- product authentication and tenant/device gating; +- rollout, kill switches, and rollback; +- server-side route and method enforcement; +- policy source binding; and +- operational telemetry and audit sinks. + +Policy remains authoritative at the Gateway/runtime boundary. A browser control +may hide, disable, or annotate an operation, but that affordance is never +authorization. + +## Bootstrap payload + +The host may expose a bounded bootstrap payload to the hosted Control UI before +or during application startup. The shape may evolve, but v1 semantics must +cover: + +```ts +export interface HostedControlUiPolicyBootstrap { + version: 1; + hostedUi: { + enabled: boolean; + routeBase: string; + gatewayBase: string; + rollout?: HostedRolloutState; + }; + lockdown: { + allowedRoutes: string[]; + allowedGatewayMethods: string[]; + deniedGatewayMethods?: HostedPolicyDecision[]; + }; + settings?: { + constraints: HostedSettingsConstraint[]; + }; +} + +export interface HostedPolicyDecision { + target: string; + state: "enabled" | "readOnly" | "disabled"; + reasonCode?: string; + message?: string; + owner?: string; +} + +export interface HostedSettingsConstraint { + key: string; + state: "enabled" | "readOnly" | "disabled"; + reasonCode?: string; + message?: string; + owner?: string; +} + +export interface HostedRolloutState { + flight?: string; + enabled: boolean; + fallbackRoute?: string; +} +``` + +The bootstrap payload must not include credentials, bearer tokens, raw policy +documents, hidden model context, unrestricted Gateway method lists, or +unbounded error details. + +## Route policy + +The host runtime must decide whether the hosted OpenClaw Control UI route is +available for the current product, tenant, user, device, and rollout state. + +When hosted UI is disabled, the route must fail closed or redirect to an +explicit host-owned fallback. The browser bundle must not infer availability by +probing protected Gateway methods. + +Allowed route declarations are presentation hints. The server remains +authoritative for every request. + +## Gateway method policy + +Gateway method enforcement must happen server-side. A conforming v1 +implementation: + +- classifies protected Gateway methods before exposure to the hosted browser; +- denies forbidden methods even if a stale UI control still calls them; +- preserves safe structured denial codes; +- does not leak raw policy source data in denial payloads; +- keeps read-only decisions distinct from unavailable or unsupported methods; + and +- logs or meters denial outcomes without recording sensitive request payloads by + default. + +The hosted Control UI may use bootstrap decisions to hide, disable, or annotate +controls. Those controls improve usability only; they do not authorize the +operation. + +## Settings constraints + +Settings constraints describe browser-safe policy state for settings controls: + +- `enabled`: the setting may be shown and edited subject to ordinary schema and + Gateway validation; +- `readOnly`: the setting may be shown but mutation is disabled and server-side + writes must be denied or redirected to a governed path; and +- `disabled`: the setting is unavailable in this hosted context. + +Settings constraints do not create write authority. Governed writes require the +separate Managed Configuration contract for provenance, validation findings, +candidate preview, generation, commit, activation, and rollback. + +## Rollback and compatibility + +Hosted policy must be independently rollbackable from Control Model adoption. +A host can disable the hosted route, restrict methods, or return to an +incumbent shell without changing native Control Model consumers. + +Unknown additive bootstrap fields must be ignored. Missing required v1 fields +must fail closed rather than enabling hosted UI or protected Gateway methods by +default. + +## Required conformance evidence + +Before v1 support is claimed, evidence must cover: + +- hosted route disabled and enabled states; +- route rollback to an incumbent host surface; +- bootstrap payload shape and unknown-field compatibility; +- denied Gateway method blocked server-side despite a direct browser call; +- read-only setting visibly disabled and write denied server-side; +- disabled setting unavailable without leaking raw policy details; +- stale bootstrap state corrected by authoritative Gateway/runtime denial; +- safe denial reason preservation; +- audit/telemetry without raw sensitive payloads; and +- coexistence with Control Model consumers without making either surface a + dependency of the other. diff --git a/rfcs/0029/implementation-plan.md b/rfcs/0029/implementation-plan.md new file mode 100644 index 00000000..4e1e06bc --- /dev/null +++ b/rfcs/0029/implementation-plan.md @@ -0,0 +1,447 @@ +# Control Model implementation and PR plan + +This plan is a proposed review sequence, not an accepted roadmap. It keeps each +OpenClaw layer independently useful and delays publication until two consumers +prove the contract. + +The implementation evidence is now filed upstream as five draft review PRs: + +| Upstream draft | Condensed scope | Fork evidence | +| --- | --- | --- | +| [CM1 #127670](https://github.com/openclaw/openclaw/pull/127670) | Gateway Client model foundation, immutable connection/session snapshots, host binding, lifecycle, and shared event-refresh policy. | OC1 [#230](https://github.com/giodl73-repo/openclaw/pull/230), session-list follow-up [#249](https://github.com/giodl73-repo/openclaw/pull/249) | +| [CM2 #127671](https://github.com/openclaw/openclaw/pull/127671) | Lazy conversation models, bounded history/live state, runs, tools, approvals, questions, and typed commands. | OC2 [#231](https://github.com/giodl73-repo/openclaw/pull/231) | +| [CM3 #127672](https://github.com/openclaw/openclaw/pull/127672) | Renderer-neutral UI artifacts, view offers, revisions, deferred materialization, and MCP/Canvas fallback. | OC3 [#232](https://github.com/giodl73-repo/openclaw/pull/232) | +| [CM4 #127674](https://github.com/openclaw/openclaw/pull/127674) | Initial Control UI reference adoption plus conformance, package, performance, compatibility, lifecycle, and security hardening. | OC4 [#238](https://github.com/giodl73-repo/openclaw/pull/238), OC5 [#241](https://github.com/giodl73-repo/openclaw/pull/241), [#244](https://github.com/giodl73-repo/openclaw/pull/244)-[#248](https://github.com/giodl73-repo/openclaw/pull/248) | +| [CM5 #127675](https://github.com/openclaw/openclaw/pull/127675) | Control UI ordinary commands, selected questions, and safe artifact-adapter adoption. | CU4 [#242](https://github.com/giodl73-repo/openclaw/pull/242), CU5 [#243](https://github.com/giodl73-repo/openclaw/pull/243) | + +These PRs are drafts until RFC intake, owner acceptance, and publication gates +settle. They currently use the published fork heads; clean same-repository +stacked branches may replace them before merge. + +The session-list follow-up is additive evidence over CM1's catalog boundary. It +derives a smaller read-only roster projection from `sessionCatalog` for product +shells and deliberately adds no Gateway request, session mutation, chat send, or +history behavior. + +## Source extraction rules + +- Move behavior only after a shared fixture captures it. +- Keep protocol and schema types in their current owner packages. +- Do not copy Control UI helpers that import presentation, localization, + browser storage, routing, or DOM behavior. +- Prefer pure normalization and capability factories over a new universal + application framework. +- Keep OpenClaw Control UI behavior unchanged during adoption. + +## CM1 / OpenClaw PR 1: Gateway Client model foundation + +### Scope + +- Add optional `@openclaw/gateway-client/model` subpaths. +- Define host Gateway binding, immutable external-store contract, lifecycle, + structured errors, and bounds configuration. +- Isolate bounded reconciliation and subscriber notification from the Gateway + receive stack. +- Project connection state and session catalog. +- Reuse canonical protocol types without re-exporting the entire protocol. +- Add Gateway Client model documentation and browser-safe import checks. + +### Explicit exclusions + +- Conversation messages and streaming. +- UI artifacts. +- React/Lit adapters. +- Public npm publication. + +### Proof + +- Store race/disposal tests. +- Slow/throwing subscriber and reconciliation-queue saturation tests. +- Session list plus create/update/delete reconciliation. +- Connection-epoch retirement. +- Retryable observer outage and authoritative refresh. +- Model graph contains no framework, DOM component, or product import. + +### Deletion target + +One duplicate session-catalog reducer in an adopter, after later adoption. + +## CM2 / OpenClaw PR 2: selected conversation and commands + +### Scope + +- Add lazy conversation models. +- Extract deterministic history/live merge. +- Project messages, runs, tools, approvals, and questions. +- Add typed conversation commands, catalog/history refresh, and command errors. +- Add finite progress and inactive-conversation retention. + +### Proof + +- Shared fixture corpus consumed by Control Model and current Control UI tests. +- History/live overlap and duplicate suppression. +- Sequence gap plus explicit partial state and refresh. +- Mid-stream reconnect and retired-epoch rejection. +- Allowed, forbidden, conflict, timeout, abort, and disposal command paths. + +### Deletion target + +Control UI and independent-host reducers for the adopted conversation slice. + +## CM3 / OpenClaw PR 3: renderer-neutral UI artifacts + +### Scope + +- Define and validate v1 artifacts. +- Preserve all applicable OpenClaw core/extension view offers and let the + client select among compatible views. +- Enumerate authorized descriptors cheaply and materialize only the selected + deferred view. +- Preserve sanitized artifact data through live projection and history. +- Preserve one artifact identity across inline chat and dedicated product + surfaces, including higher revisions published by later turns. +- Adapt existing MCP App and Canvas previews into explicit fallbacks. +- Add revision, expiry, bound, and structured failure behavior. +- Keep renderer registries outside the model. + +### Proof + +- Known and unknown template URIs. +- Multiple offered views with client-owned selection. +- Authorization-filtered discovery and selected-only materialization. +- Malformed/oversized data. +- Increasing, duplicate, stale, and conflicting revisions. +- History reload and reconnect. +- Inline and dedicated projections of the same artifact ID. +- Later-turn revision without creating a duplicate artifact. +- MCP App fallback and expiry. +- Proof that metadata cannot select an import or register a component. + +### Deletion target + +Tool-specific native rendering interpretation and duplicate Canvas/MCP +association logic. + +## CM4 / OpenClaw PR 4: Control UI reference adoption + +OC4 is the initial reference-adoption draft, not the entire Control UI +migration. It completes the runtime, catalog, and selected-conversation +projection slices and is filed upstream as part of +[CM4 #127674](https://github.com/openclaw/openclaw/pull/127674). CU4 adds +ordinary foreground commands, and CU5 adds selected-session question commands +plus safe Canvas/MCP artifact projection; both are filed upstream as +[CM5 #127675](https://github.com/openclaw/openclaw/pull/127675). Operational +callers and global/operator interaction queues remain outside these bounded +adoption slices. + +### Completed scope + +- Adapt the existing Control UI Gateway store to the model binding. +- Move the active session catalog and selected-chat history/subscription route + to Control Model snapshots. +- Keep Lit components, routes, styling, and behavior unchanged. +- Retain retryable Gateway fallback when the lazy model cannot load. + +### Proof + +- Existing focused Control UI tests. +- Shared model fixtures. +- Real browser/Gateway chat flow. +- No regression in catalog selection, reconnect, or history. +- Bundle and startup impact measured. + +### Deletion target + +Superseded UI-local catalog and selected-history capability after publication, +observation, and rollback proof. + +### Remaining Control UI adoption slices + +| Slice | Scope | Explicit boundary | +| --- | --- | --- | +| CU1 runtime binding | Lazy Control Model runtime over the existing Gateway store. | Complete in OC4; no new process, route, or framework adapter. | +| CU2 catalog and selection | Active roster and selected-session lookup from catalog snapshots. | Complete in OC4; archived/all rosters remain raw until separately modeled. | +| CU3 selected conversation projection | History, live subscription, reconnect, and retryable fallback from the conversation handle. | Complete in OC4; OC5 now owns representative overlap/gap/retired-epoch fixtures. | +| CU4 ordinary conversation commands | Standard composer send and foreground active-run abort through `ControlModelConversation`. Filed upstream in [CM5 #127675](https://github.com/openclaw/openclaw/pull/127675); fork evidence is [OpenClaw PR #242](https://github.com/giodl73-repo/openclaw/pull/242), stacked on OC5. | Preserves session identity, attachment/reply/fencing inputs, reconnect-resume and steer fallback, structured active-leaf recovery errors, and raw no-run/session-wide abort ownership. Do not absorb realtime talk, background-task history, or other operational paths without separate ownership proof. | +| CU5 interactions and artifacts | Exact selected-session question answer/cancel commands plus Canvas, MCP App, and structured fallback through snapshot projections. Filed upstream in [CM5 #127675](https://github.com/openclaw/openclaw/pull/127675); fork evidence is [OpenClaw PR #243](https://github.com/giodl73-repo/openclaw/pull/243), stacked on CU4. | Preserves the incumbent prompt lifecycle, expiry deadline, local resolution publication, and raw fallback. Global/operator approval lanes remain outside the slice because their ownership and resolver semantics differ. Artifact data never selects executable code. | +| CU6 observation and deletion | Roll out the model-backed route, retain rollback, and remove only named incumbent reducers/requests/adapters. | Post-OC6 and implemented as OC7 with an exact deletion ledger. | + +Board and settings are separate Board Model and Config Model adoption series, +not CU7/CU8. Their authority and persistence contracts are non-normative to +Control Model v1. + +### CU4 result + +CU4 reuses the selected conversation handle already owned by OC4 rather than +creating a second runtime or command client. Ordinary selected sends pass +message content, attachments, idempotency, reply targets, expected leaf/run +fences, queue mode, and authoritative session identity through +`ControlModelConversation.send`. Connected exact-run stops use +`ControlModelConversation.abort`. + +Reconnect-resume sends, steer/inject, background or non-selected routes, +realtime talk, skill-workshop revisions, queued replay, and session-wide +`sessions.abort` remain on their incumbent paths. Model command errors retain +structured Gateway details so existing active-leaf recovery and retry behavior +remain visible rather than becoming generic failures. + +### CU5 result + +CU5 reuses the exact cached selected-conversation route and its authoritative +agent identity, including main aliases. Pending question answer/cancel commands +pass through the conversation model with the incumbent question deadline while +Control UI retains submitting/error state, response validation, local +resolution confirmation, shared-client publication, and raw compatibility +fallback. + +Validated ready artifact snapshots feed only the existing sandboxed Canvas and +MCP App presentation adapters. Model metadata cannot choose an import, module, +custom element, or executable template. Correlation prefers canonical message +and tool-call provenance; ordered tool-only matching is restricted to +source-less compatibility data, and occurrence/timestamp evidence prevents +reused tool IDs or persisted/live overlap from hiding distinct views. + +Global/operator approval queues remain raw because they are not selected- +conversation commands and use different resolver ownership. Unknown, +malformed, failed, source-less, and unsupported artifacts continue through the +incumbent compatibility behavior. + +## Lobster/M evidence series + +The bounded independent-adopter series is complete in fork-local drafts. It +uses Lobster's existing hosted Gateway seam and keeps M's `SessionView` as the +passive renderer vocabulary. + +| Slice | Scope and result | Deletion or boundary proved | +| --- | --- | --- | +| L0 | Temporarily carries OC1-OC3 into Lobster and preserves sanitized artifacts through the pinned OpenClaw history projection. | Source-resolved evidence only; not the publication shape. | +| LM1 | Maps canonical conversation snapshots into existing `SessionView` while preserving host-owned raw operational lanes. | React does not parse Gateway events and no second M view model is introduced. | +| LM2 | Renders an exact allowlisted `clawpilot://widgets/table` v1 artifact with schema bounds, durable history identity, and visible fallback. | One trusted native first-party artifact works without importing Control UI. | +| LM3 | Adds one host-owned Refresh action, validates current artifact identity/revision, and dispatches through `conversation.send`. | Native components receive no raw Gateway authority; stale and denied actions fail visibly. | +| LM4 | Routes ordinary sends through `ControlModelConversation.send` while retaining Lobster attachment preprocessing and operational turn tracking. | Deletes the duplicate raw ordinary `chat.send` request path. | +| LM5 | Routes active foreground aborts through `ControlModelConversation.abort`. | Deletes duplicate raw active-run abort dispatch while retaining no-run abort-all recovery. | +| LM6 | Explicitly refreshes and projects canonical selected-session history through the model. | Deletes duplicate selected-session raw `chat.history` normalization. | + +The series stops at LM6. Operator/security approvals, no-run abort-all, +session administration, memory, automation compatibility, attachment +preprocessing, and host run ownership remain outside this bounded deletion +case. + +Cross-client user-message correlation is a separate future contract rather +than LM7. It must align Lobster `clientMessageId`, model idempotency, +retry/reconnect, non-renderer callers, persisted history, canonical user +identity, and renderer deduplication. + +The adjacent native adopter evidence is also complete: + +| Slice | Scope and result | Boundary proved | +| --- | --- | --- | +| Config LC1 | Consumes a private read-only Config Model through Electron-owned transport and renders authored values plus schema guidance in native React. | Read projection can remain OpenClaw-owned without giving React raw config or write authority. | +| Board LB1 | Consumes a private Board Model through main-process routing, renders one exact native status-summary widget, and keeps HTML/Canvas/MCP/unknown widgets inert. | OpenClaw board semantics can drive a product-owned Dashboard without importing Control UI or granting renderer authority. | + +Independent follow-on adoption is now visible in +[Lobster PR #12276](https://microsoft.ghe.com/bic/lobster/pull/12276). +It reuses the existing Control Model binding for bounded native Question +observation and Answer/Skip brokerage, keeps `operator.questions` out of the +browser, and remains behind a default-off rollout flight. This strengthens the +runtime-owned binding evidence without broadening the V1 publication contract. + +Board LB1 uses a mocked beta-generation board protocol because pinned +LobsterClaw 2026.6.33 predates boards. It is conformance evidence, not release +admission. + +## CM4 / OpenClaw PR 5: shared conformance and package hardening + +Filed upstream in +[CM4 #127674](https://github.com/openclaw/openclaw/pull/127674). Fork evidence: +[giodl73-repo/openclaw#241](https://github.com/giodl73-repo/openclaw/pull/241). +Its first slice centralizes finite defaults, adds an authoritative/malformed +catalog fixture pair, proves clean packed-package Node/declaration/browser +consumption, and fixes a package-only browser export failure found by that +proof. Its second slice promotes representative history/live overlap, +gap-triggered authoritative refresh, retired-epoch rejection, and approval +authorization/terminal-state behavior into the shared corpus. A test-only +continuation in +[giodl73-repo/openclaw#244](https://github.com/giodl73-repo/openclaw/pull/244) +adds representative run, tool, question, artifact, and retained-bounds +families, including exact non-active abort targeting and selected-only deferred +materialization. A second test-only continuation in +[giodl73-repo/openclaw#245](https://github.com/giodl73-repo/openclaw/pull/245) +adds an asserted artifact-heavy projection benchmark with exact finite-snapshot +checks and Testbox-oriented thresholds for p95 latency, retained heap growth, +and retained heap slope. Its Blacksmith Testbox proof projects 24,000 measured +events at 2,211.45 ms p95 per 4,000 events, 52,272 bytes retained growth, and +8,482.51 bytes/batch retained slope. A third test-only continuation in +[giodl73-repo/openclaw#246](https://github.com/giodl73-repo/openclaw/pull/246) +adds an asserted wire-compatibility matrix for the candidate protocol, +published predecessor `@openclaw/gateway-protocol@2026.7.2-beta.7`, and current +OpenClaw `main`. It preserves baseline catalog, subscription, history, ordinary +send, exact abort, approval, question, and representative event contracts while +recording run-fenced send as a candidate-era capability rather than claiming +unsupported predecessor behavior. A fourth test-only continuation in +[giodl73-repo/openclaw#247](https://github.com/giodl73-repo/openclaw/pull/247) +asserts initial catalog/conversation projection, selected deferred-view +materialization, bounded inactive-conversation eviction, and authoritative +reconnect/resync latency. Its Blacksmith Testbox proof passed at 14.32 ms, +0.58 ms, 26.23 ms, and 8.07 ms p95 respectively, with exact disposal and +resync invariants. A fifth continuation in +[giodl73-repo/openclaw#248](https://github.com/giodl73-repo/openclaw/pull/248) +records the independent full-stack security review and fixes its one accepted +finding by retiring materialized deferred-view payloads on disconnect and +connection-epoch replacement. Refreshed history may restore the inert +descriptor, but the payload requires fresh server materialization under the +new authority context. Post-fix review found no actionable vulnerabilities. + +A final whole-series review covered OC1-OC5 and CU4-CU5 with independent +GPT-5.6 Terra, Claude Opus 5, and Gemini 3.1 Pro Preview passes, followed by a +clean Codex branch review. Accepted findings were fixed at core head +`a158436f085` in PR #248 and Control UI head `0a8ad4188a6` in PR #243. Focused +proof passed 59 Gateway lifecycle/model tests, 61 integrated Control UI tests, +6 prompt tests, and packed-package acceptance. OC5 still does not satisfy the +named-owner or publication gates. + +### Preconditions + +- RFC scope and ownership boundary are accepted for implementation. +- OC1-OC4 evidence is reviewed against current source. +- Control UI and independent-host fixtures agree on the bounded contract. + +### Scope + +- Promote the proven fixture families into shared conformance assets. +- Finalize finite defaults and explicit truncation/partial-state behavior. +- Add browser and Node import/package acceptance checks. +- Add compatibility canaries for the supported release, predecessor where + promised, and `main`. +- Measure projection, reconciliation, retained-memory, and resync bounds. +- Complete malformed-data, authorization, retired-epoch, and subscriber + isolation security coverage. +- Keep the subpaths private or fork-only until the release/support gate passes. + +### Deletion target + +None. This PR hardens the contract before publication. + +## OpenClaw PR 6: supported model subpaths + +### Preconditions + +- PR 5 conformance, compatibility, performance, package, and security gates + pass. +- Named package, protocol, Control UI, security, and release owners accept the + obligations in the + [ownership and support plan](ownership-and-support-plan.md). +- The independent-host proof remains valid against the candidate release. + +### Scope + +- Publish the optional `@openclaw/gateway-client/model` subpaths. +- Document supported versions, compatibility window, and migration policy. +- Add a framework-neutral quickstart and release notes. +- Pack the release artifact and prove clean browser and Node consumers can + install it, resolve every supported subpath, and consume its declarations + without workspace-only files or dependencies. +- Define support ownership and deprecation policy. +- Keep framework adapters outside the core model unless separately justified. + +### Deletion target + +Fork-only source carries after adopters move to a released dependency. + +## OpenClaw PR 7: incumbent-path cleanup + +### Preconditions + +- PR 6 is released and CU1-CU5 are adopted by Control UI. +- The model-backed path has an agreed observation window and rollback proof. +- The exact superseded implementation is named and no supported fallback + depends on it. + +### Scope + +- Remove only the superseded Control UI reconciliation, standard command, + interaction, artifact-adapter, and compatibility paths named by CU1-CU5. +- Retain operational, diagnostic, or unsupported-capability paths that the + Control Model does not own. +- Update ownership docs and deletion ledger. + +### Deletion target + +The exact incumbent UI-local paths identified by CU1-CU5 adoption. + +## Productization after publication + +1. Land the supported package surface from PR 6 and replace Lobster's temporary source + carry with a released OpenClaw dependency. +2. Resolve Lobster required checks and land the bounded stack behind a runtime + flag with rollback. +3. Run a live hosted-Gateway proof covering authentication, reconnect, + history, streaming, native artifact action, send, and abort. +4. Add safe telemetry for projection lag, fallback, validation failure, action + outcome, and rollback without recording raw artifact data. +5. Finish Fluent quality, accessibility, localization, security review, and + shareable screenshots or recordings. + +## Adjacent surfaces + +- **Hosted Control UI and policy:** keep the hosted `/openclaw` route, + bootstrap policy, rollout gates, and server-side method enforcement as the + immediate deployment and lockdown path for hosts that can use OpenClaw's + version-matched application. This path is additive to Control Model. It + proves host-owned auth/routing/rollout and policy enforcement, while Control + Model proves framework-neutral conversation state, commands, and artifacts + for native product shells. A stale or bypassed UI affordance is never + authoritative; Gateway/runtime policy remains the enforcement point. The + linked hosted-policy surfaces are umbrella issue + [openclaw/openclaw#115423](https://github.com/openclaw/openclaw/issues/115423), + host-policy draft [#115408](https://github.com/openclaw/openclaw/pull/115408), + and Gateway enforcement draft + [#116013](https://github.com/openclaw/openclaw/pull/116013). Policy-settings + constraints remain fork-only sibling evidence in + [giodl73-repo/openclaw#196](https://github.com/giodl73-repo/openclaw/pull/196)-[#202](https://github.com/giodl73-repo/openclaw/pull/202). + RFC 0029 now carries + [Hosted Control UI policy v1](hosted-control-ui-policy-v1-spec.md) as a + sibling sidecar with its own route, bootstrap, rollout, and + Gateway/runtime-enforcement gates. That sidecar is related evidence, not a + Control Model v1 dependency. +- **Dashboards and widgets:** host OpenClaw's existing dashboard routes first. + The first fork-only Board Model proof now extracts selected-session board + reconciliation into `@openclaw/gateway-client/model/board` and keeps Control + UI as the reference adopter. Lobster Board LB1 proves the native adapter + boundary with a mocked beta protocol. A future BM2 proposal must reconstruct + the proof against an admitted board-capable release and must not recreate + dashboards from generic conversation artifacts. +- **Settings:** host OpenClaw settings first, read-only when Lobster lacks + secure write authority. The read-only Config Model and Lobster LC1 proof now + render selected authored values with descriptors and reload impact through a + main-process adapter. A future CFG1 may upstream only that read projection. + Effective defaults, provenance, owner, writability and lock reason, + validation findings, candidate diff, generation, and transactional + apply/reload status remain the separate CFG2/Managed Configuration work. +- **Canvas and MCP Apps:** preserve them as explicit sandboxed fallbacks rather + than converting their executable state into trusted native React. + +## Deferred work + +- Channels, skills, nodes, workboards, and admin surfaces. +- JSON Patch/JSONL artifact dialect. +- Model-visible component catalogs and generic generated layouts. +- Third-party native component SDK. +- Stable framework-specific adapters. +- Cross-client user-message identity and retry correlation. +- First-class action-run lifecycle, typed interaction payloads, stateful + artifact evolution, and durable document semantics. + +Each deferred surface requires a separate owner-first slice and deletion case. + +## Fork-only proposal policy + +This plan names OC5-OC7, BM2, CFG1, and CFG2 for maintainer review. OC5 has +fork-linked hardening drafts for package/shared-fixture evidence and bounded +projection/retained-memory thresholds, candidate/predecessor/main wire +compatibility, lifecycle performance bounds, and reviewed security hardening. +The complete OC1-OC5 and CU4-CU5 stack is review-clean and the condensed +Control Model evidence is now visible upstream through draft PRs CM1-CM5. +OC6, OC7, BM2, CFG1, and CFG2 remain proposals only. Any further implementation +drafts should remain in the author's forks until RFC intake and the relevant +OpenClaw owners approve the surface. diff --git a/rfcs/0029/owner-acceptance-record.md b/rfcs/0029/owner-acceptance-record.md new file mode 100644 index 00000000..6493c00e --- /dev/null +++ b/rfcs/0029/owner-acceptance-record.md @@ -0,0 +1,60 @@ +# Control Model owner acceptance record + +Use this record to accept, replace, or decline an ownership nomination for +RFC 0029. It does not assign ownership by default. Silence, team membership, +code review, or approval of an evidence PR does not count as acceptance. + +OC6 remains blocked until every surface has an explicit accepted owner and the +shared release decisions below are recorded. + +## Shared publication decisions + +| Decision | Required record | +| --- | --- | +| Release vehicle | Package, release train, and first supported version | +| Compatibility window | Supported OpenClaw versions and promised predecessor behavior | +| Required gates | Conformance, package, security, performance, and compatibility checks that block release | +| Regression response | Triage owner, response path, and escalation expectation | +| Security response | Private reporting and incident escalation path | +| Rollback authority | Who can halt, deprecate, or roll back a partial or bad publication | +| Observation window | Minimum evidence required before OC7/CU6 deletes incumbent paths | + +## Surface decisions + +Record one decision for each surface: + +| Surface | Accountable team | Decision | DRI | Deputy | Acceptance link | +| --- | --- | --- | --- | --- | --- | +| Gateway Client package and model API | `@openclaw/maintainer` | Pending | Pending | Pending | Pending | +| Gateway protocol compatibility | `@openclaw/maintainer` | Pending | Pending | Pending | Pending | +| Control UI reference adopter | `@openclaw/maintainer` | Pending | Pending | Pending | Pending | +| Security review and incident escalation | `@openclaw/openclaw-secops` | Pending | Pending | Pending | Pending | +| npm release and rollback | `@openclaw/openclaw-release-managers` | Pending | Pending | Pending | Pending | +| RFC contract approval | `@openclaw/openclaw-rfc-approvers` | Pending | Pending | Pending | Pending | + +Valid decisions are: + +- **Accept:** confirm the accountable team, DRI, deputy, and obligations. +- **Replace:** name the replacement accountable team, DRI, or deputy. +- **Decline:** state why the surface should not be published or who must decide. + +## Comment template + +Copy this block into the RFC review: + +```text +Surface: +Decision: Accept | Replace | Decline +Accountable team: +DRI: +Deputy: +Supported versions and compatibility window: +Required release and security gates: +Regression and security response path: +Rollback or deprecation authority: +Ownership-transfer conditions: +Acceptance applies to OC6 publication: Yes | No +``` + +An acceptance is complete only when every field is explicit or links to an +existing owner-controlled policy that supplies the answer. diff --git a/rfcs/0029/ownership-and-support-plan.md b/rfcs/0029/ownership-and-support-plan.md new file mode 100644 index 00000000..617aea4c --- /dev/null +++ b/rfcs/0029/ownership-and-support-plan.md @@ -0,0 +1,108 @@ +# Control Model ownership and support plan + +This plan names the existing OpenClaw teams and proposed directly responsible +individuals for the Control Model publication surface. It is a nomination +packet, not evidence that any person or team has accepted ownership. + +OC6 cannot begin until every accountable owner records acceptance on the RFC or +publication PR. One person may cover multiple roles, but each role keeps a +separate acceptance and escalation obligation. + +## Owner nominations + +| Surface | Accountable owner | DRI nominee | Deputy nominee | Evidence | Status | +| --- | --- | --- | --- | --- | --- | +| Gateway Client package and model API | `@openclaw/maintainer` | `@steipete` | `@vincentkoc` | Maintainer-team authority; highest recent Gateway Client contribution and review activity. | Acceptance pending | +| Gateway protocol compatibility | `@openclaw/maintainer` | `@steipete` | `@vincentkoc` | Maintainer-team authority; highest recent protocol contribution and both are RFC approvers. | Acceptance pending | +| Control UI reference adopter | `@openclaw/maintainer` | `@steipete` | `@vincentkoc` | Maintainer-team authority and sustained Control UI ownership. `@shakkernerd` is the proposed implementation reviewer for UI-specific behavior. | Acceptance pending | +| Security review and incident escalation | `@openclaw/openclaw-secops` | `@steipete` | `@vincentkoc` | Existing CODEOWNERS security team and current secops membership. | Acceptance pending | +| npm release and rollback | `@openclaw/openclaw-release-managers` | `@steipete` | `@vincentkoc` | Existing CODEOWNERS release boundary plus maintainer and release-history evidence. The release team must confirm or replace the individual nominees. | Acceptance pending | +| RFC contract approval | `@openclaw/openclaw-rfc-approvers` | `@steipete` | `@vincentkoc` | Current RFC approver team membership. | Acceptance pending | + +The team names above are existing GitHub teams. The individual nominations are +based on current team membership, repository contribution history, and the +existing CODEOWNERS boundaries as observed on 2026-08-16. They must be replaced +if the relevant teams choose different DRIs. + +## Required acceptance + +Each accountable owner must comment on the RFC or OC6 publication PR with: + +1. the surface accepted; +2. the named DRI and deputy; +3. the supported OpenClaw versions and compatibility window; +4. the required release, security, and rollback checks; +5. the triage expectation and response path for regressions or security + reports; +6. the conditions for deprecation or ownership transfer; and +7. a link to the accepting comment or approval. + +Silence, code review, team membership, or approval of a lower-stack evidence PR +does not count as support ownership. + +Use the [owner acceptance record](owner-acceptance-record.md) to make each +decision explicit and comparable. The record is a template, not an assignment +or default acceptance. + +## Role obligations + +### Gateway Client package + +- Own the exported model subpaths, declarations, browser/Node compatibility, + finite defaults, and migration policy. +- Review breaking or behavior-changing projection updates. +- Keep package acceptance and conformance fixtures release-blocking. + +### Gateway protocol + +- Own the wire methods, scopes, request/event schemas, and supported predecessor + boundary used by the model. +- Classify additive model projection changes separately from incompatible wire + changes. +- Approve changes to command authorization or artifact materialization RPCs. + +### Control UI + +- Keep Control UI as the executable reference adopter for the supported slice. +- Confirm behavior parity, rollback, and the exact incumbent code eligible for + deletion. +- Keep product presentation, routing, and renderer registration outside the + model. + +### Security + +- Review trust-boundary changes, artifact normalization, selected-view + materialization, action authorization, epoch retirement, payload bounds, and + logging/error exposure. +- Route security reports through the existing OpenClaw security process. +- Block publication when a finding can cross session, agent, connection, or + authorization boundaries. + +### Release + +- Own packed-artifact verification, npm publication, release notes, rollback, + and support-window recording. +- Require clean browser and Node consumers to install every supported subpath + from the exact release artifact. +- Confirm the predecessor/main compatibility canaries before publication. +- Prevent a tag or release note from claiming support when publication or + install proof is partial, and record the deprecation or rollback action if a + bad artifact cannot be withdrawn. + +## Ownership changes + +If a DRI or deputy cannot continue, the accountable team must name a +replacement before the next behavior-changing release. Until then, release of +the affected surface remains blocked; maintainership must not silently fall to +the RFC author, an adopter, or an unacknowledged reviewer. + +## Publication decision + +OC5 technical evidence is complete enough to request owner acceptance: +conformance, package acceptance, performance, compatibility, lifecycle, and +security gates all have fork-only proof. The complete OC1-OC5 and CU4-CU5 stack +also passed independent GPT-5.6 Terra, Claude Opus 5, and Gemini 3.1 Pro Preview +reviews plus a clean Codex branch review after accepted findings were fixed. +OC6 remains blocked until the acceptance records above are explicit and the +owners choose the supported version window, release vehicle, observation +period, and rollback authority. diff --git a/rfcs/0029/prototypes/a2ui-artifact-envelope.mjs b/rfcs/0029/prototypes/a2ui-artifact-envelope.mjs new file mode 100644 index 00000000..cf020846 --- /dev/null +++ b/rfcs/0029/prototypes/a2ui-artifact-envelope.mjs @@ -0,0 +1,163 @@ +import assert from "node:assert/strict"; + +const A2UI_V08_CORE_V1_URI = "openclaw-renderer://a2ui/v0.8/core-v1"; +const ACTION_KEYS = new Set([ + "beginRendering", + "surfaceUpdate", + "dataModelUpdate", + "deleteSurface", + "createSurface", +]); +const MAX_MESSAGES = 32; +const MAX_BYTES = 64 * 1024; + +function selectView(artifact, capability) { + return ( + artifact.views.find( + (view) => + view.templateUri === capability.templateUri && + view.dataVersion === capability.dataVersion, + ) ?? null + ); +} + +function validateA2uiMessages(messages) { + assert.ok( + Array.isArray(messages) && messages.length > 0 && messages.length <= MAX_MESSAGES, + "A2UI message count is outside the accepted bounds", + ); + assert.ok( + Buffer.byteLength(JSON.stringify(messages), "utf8") <= MAX_BYTES, + "A2UI messages exceed the accepted byte budget", + ); + for (const message of messages) { + assert.ok(message && typeof message === "object" && !Array.isArray(message)); + const keys = Object.keys(message).filter((key) => ACTION_KEYS.has(key)); + assert.equal(keys.length, 1); + assert.equal(message.version, undefined, "A2UI v0.8 messages are unversioned"); + assert.notEqual(keys[0], "createSurface", "createSurface requires unsupported A2UI v0.9"); + } +} + +function fallbackFor(artifact, capability) { + const declared = artifact.views.flatMap((view) => (view.fallback ? [view.fallback] : [])); + const accepted = declared.find((fallback) => + capability.sandboxFallbacks?.includes(fallback.kind), + ); + return accepted + ? { kind: accepted.kind, fallback: accepted } + : { kind: "structured", data: artifact.structuredContent }; +} + +function renderArtifact(artifact, capability) { + const view = selectView(artifact, capability); + if (!view) { + return fallbackFor(artifact, capability); + } + try { + validateA2uiMessages(view.data.messages); + } catch (error) { + return { + ...fallbackFor(artifact, capability), + reason: error instanceof Error ? error.message : String(error), + }; + } + return { kind: "a2ui", messages: view.data.messages }; +} + +const artifact = { + version: 1, + id: "artifact-summary-1", + revision: 0, + structuredContent: { title: "Deployment status", status: "Ready" }, + views: [ + { + id: "a2ui", + templateUri: A2UI_V08_CORE_V1_URI, + dataVersion: 1, + availability: "inline", + fallback: { + kind: "mcp-app", + viewId: "mcp-app-summary-1", + uiResourceUri: "ui://example/summary", + }, + data: { + messages: [ + { + surfaceUpdate: { + surfaceId: "main", + components: [ + { + id: "root", + component: { + Column: { children: { explicitList: ["title", "status"] } }, + }, + }, + { + id: "title", + component: { + Text: { text: { literalString: "Deployment status" } }, + }, + }, + { + id: "status", + component: { Text: { text: { literalString: "Ready" } } }, + }, + ], + }, + }, + { beginRendering: { surfaceId: "main", root: "root" } }, + ], + }, + }, + ], + state: "ready", + source: { sessionKey: "session-1", toolCallId: "tool-1" }, +}; + +const supported = renderArtifact(artifact, { + templateUri: A2UI_V08_CORE_V1_URI, + dataVersion: 1, +}); +assert.equal(supported.kind, "a2ui"); +assert.equal(supported.messages[0].surfaceUpdate.components.length, 3); + +const sandboxFallback = renderArtifact(artifact, { + templateUri: "openclaw-renderer://a2ui/v0.9/core-v1", + dataVersion: 1, + sandboxFallbacks: ["mcp-app"], +}); +assert.equal(sandboxFallback.kind, "mcp-app"); +assert.equal(sandboxFallback.fallback.uiResourceUri, "ui://example/summary"); + +const structuredFallback = renderArtifact(artifact, { + templateUri: "openclaw-renderer://a2ui/v0.9/core-v1", + dataVersion: 1, + sandboxFallbacks: [], +}); +assert.deepEqual(structuredFallback, { + kind: "structured", + data: artifact.structuredContent, +}); + +const wrongDataVersion = renderArtifact(artifact, { + templateUri: A2UI_V08_CORE_V1_URI, + dataVersion: 2, + sandboxFallbacks: [], +}); +assert.equal(wrongDataVersion.kind, "structured"); + +const invalidDialectArtifact = structuredClone(artifact); +invalidDialectArtifact.views[0].data.messages[0].version = "v0.9"; +const invalidDialect = renderArtifact(invalidDialectArtifact, { + templateUri: A2UI_V08_CORE_V1_URI, + dataVersion: 1, + sandboxFallbacks: [], +}); +assert.equal(invalidDialect.kind, "structured"); +assert.match(invalidDialect.reason, /unversioned/); + +console.log("PASS supported A2UI v0.8 composition selected"); +console.log("PASS unsupported dialect used explicit MCP App fallback"); +console.log("PASS unsupported catalog/data version used structured fallback"); +console.log("PASS invalid A2UI version failed closed to structured fallback"); diff --git a/rfcs/0029/ui-artifact-v1-spec.md b/rfcs/0029/ui-artifact-v1-spec.md new file mode 100644 index 00000000..cfd8bab7 --- /dev/null +++ b/rfcs/0029/ui-artifact-v1-spec.md @@ -0,0 +1,530 @@ +# UI artifact v1 specification + +This document defines a renderer-neutral UI artifact projected by +`@openclaw/gateway-client/model`. An artifact lets a host select native +first-party presentation while preserving structured output and sandboxed +third-party fallback. + +Status: submitted draft sidecar for RFC 0029. It has not been accepted or +released upstream; implementation evidence remains draft and review-gated. + +## Principles + +- An artifact is data and identity, not executable code. +- Installed and enabled extensions determine which artifacts can be produced. +- The client determines which native artifact renderers it supports and trusts. +- A template URI is a lookup key, not a trust or authorization claim. +- Native rendering is host-registered and allowlist-only. +- Unknown artifacts remain useful through structured/text output. +- Third-party executable UI remains sandboxed through MCP Apps or another + explicitly supported sandbox contract. +- UI actions never bypass model commands or Gateway authorization. + +## Artifact shape + +```ts +export type JsonValue = + | null + | boolean + | number + | string + | JsonValue[] + | { [key: string]: JsonValue }; + +export interface UiArtifact { + version: 1; + id: string; + revision: number; + structuredContent?: JsonValue; + views: UiArtifactViewOffer[]; + state: "pending" | "ready" | "failed" | "expired"; + source: UiArtifactSource; + error?: UiArtifactError; +} + +export interface UiArtifactViewOffer { + id: string; + templateUri: string; + dataVersion: number; + availability: "inline" | "deferred"; + data?: JsonValue; + recommended?: boolean; + fallback?: UiArtifactFallback; +} + +export interface UiArtifactSource { + sessionKey: string; + messageId?: string; + toolCallId?: string; + toolName?: string; +} +``` + +The final field names may change during implementation, but every accepted +shape must retain the semantics below. + +## Identity and revisions + +`id` is stable for one logical artifact in one conversation. A view ID is +stable within that artifact. Neither identity may be derived only from +`templateUri`. + +`revision` is a non-negative integer that increases monotonically for accepted +updates to that artifact. A duplicate revision with byte-equivalent normalized +content is ignored. A duplicate revision with different content is a +structured conflict. A lower revision is stale and must not replace current +state. + +An artifact's presentation location is not part of its identity. A client may +project the current revision inline beside its source message, in an expanded +panel, or in a dedicated artifact surface. Later conversation turns and tool +runs may publish a higher revision for the same logical `id`, subject to normal +authorization and reconciliation rules. + +V1 durability is scoped to the owning session: current artifact identity and +revision survive authoritative history reload and reconnect. Permanent +document storage, cross-session retention, collaborative editing, and merging +concurrent user-authored revisions are outside this contract. A product may +persist or promote an artifact through a separate explicitly authorized +operation. + +An artifact from a retired connection epoch may be reconciled only through +authoritative history. It must not update live state directly. + +## Offered views and template URI + +OpenClaw core and installed extensions may contribute zero or more applicable +views for an artifact. Multiple views may represent the same structured +content as a calendar, list, table, summary, form, dashboard, or sandboxed app. +View order is deterministic but is not a requirement that the client render +the first view. + +A dashboard-shaped artifact view is only a presentation of one conversation +artifact. It does not replace OpenClaw's authoritative dashboard/workboard +model, registered widget providers, board identity, layout, persistence, +focus, docking, or `show_widget`/`dashboard` tool operations. A native client +for those capabilities requires a separate projection of the existing board +model. + +OpenClaw exposes all authorized applicable view descriptors. It does not need +to eagerly compute every view payload: + +- `inline` includes bounded validated `data` in the offer. +- `deferred` omits `data` until the client selects the view and requests + materialization through a typed Control Model command. + +A deferred view must not perform external work, access protected data, or +consume a tool invocation merely because its descriptor was enumerated. + +`templateUri` is a bounded absolute URI. Schemes are not globally trusted. +Hosts may register product-specific schemes such as +`clawpilot://widgets/calendar` or use a standardized `ui://` resource +identifier. + +The URI: + +- does not identify a JavaScript module to import; +- is always an opaque registry key and is never dereferenced, including when it + uses the `ui://` scheme; +- does not grant network, tool, command, credential, or DOM authority; +- does not select native rendering unless the host registry contains an exact + compatible registration; and +- must be preserved as opaque data when unknown. + +Hosts should match exact URIs or an explicitly versioned registry rule. Generic +wildcard registrations require a separate security review. + +`dataVersion` is a positive integer interpreted only by the exact host +registration. A registration declares the versions it accepts and any pure, +bounded migration into its current schema. Tool output cannot declare a +migration. + +A client selects a view by exact compatibility, product policy, surface, +accessibility, and user preference. OpenClaw may mark one view as recommended, +but the recommendation neither grants trust nor overrides the client choice. +A client may ignore every offered view and project `structuredContent` into its +own product view model. + +A client should keep a compatible user selection stable across artifact +revisions and reconnects. It must not silently switch to a newly recommended +view while the current choice remains valid. A fallback caused by an invalid, +expired, or unavailable selection is observable to the product UX. + +## Data and structured content + +An inline or materialized view's `data` contains component-shaped untrusted +JSON. A native renderer registration must provide a schema and reject invalid +data before component construction. + +`structuredContent` contains model- or transcript-relevant domain output when +available. It is not a private channel for secrets, hidden instructions, +credentials, or host-only state. Hosts may show it when native rendering is +unavailable. + +Artifact data must have finite encoded bytes, depth, collection lengths, and +string lengths. Oversized artifacts become structured failures while ordinary +text/tool output remains available. + +## Lifecycle + +- `pending`: identity is known but complete render data is not ready. +- `ready`: the current revision passed model-level validation. +- `failed`: artifact materialization failed; `error` contains a safe code and + message. +- `expired`: a referenced resource or interactive view is no longer available. + +Lifecycle transitions are monotonic within one revision unless a higher +revision explicitly recovers the artifact. Expired interactive fallback must +not be reopened with stale credentials or capability URLs. + +## Native renderer registry + +The host registry maps a supported template URI and artifact version to: + +- a local component factory; +- a validation schema; +- optional migration from older data versions; +- named action bindings; +- presentation metadata such as supported surfaces; and +- an explicit fallback policy. + +Registration is deployment-owned code or configuration. Tool output cannot add +or modify registrations. + +Registration provenance follows the host's ordinary component supply chain, +including code review, dependency policy, signing, and deployment controls +where those controls apply. The Control Model does not create a second runtime +component marketplace. + +The Control Model does not import or execute registry components. A framework +adapter reads artifacts and invokes the host registry. + +## Native catalogs, MCP Apps, and renderer dialects + +These are complementary presentation paths, not competing artifact lifecycles: + +- A host-native view names one exact, versioned registration in the client's + reviewed component catalog. Web and mobile may advertise and register + different sets. +- An MCP App view uses OpenClaw's existing MCP `ui://` resource + materialization, sandbox, CSP, bridge, expiry, and authorization contracts. + The iframe is the security boundary for third-party executable UI; it is not + required for trusted native views. +- A compositional renderer such as A2UI may be registered as one exact, + versioned dialect view. Its payload may describe a tree of components from a + reviewed vocabulary rather than one component-specific schema. V1 does not + define dynamic vocabulary negotiation; an exact dialect registration must pin + the supported catalog or validate and decline unsupported components locally. + +The artifact envelope remains responsible for stable identity, revisions, +source provenance, view selection, action fencing, history/reconnect behavior, +and fallback. A renderer dialect remains responsible for its component +vocabulary, layout semantics, incremental message format, and renderer-local +validation. This separation allows A2UI or another dialect to be added without +making it the mandatory format for native components or changing MCP Apps. + +## Capability discovery + +An exact local renderer registration is the authority for native-renderer +support. A client may advertise a bounded set of supported template URI and +data-version pairs during connection or tool invocation when the Gateway +contract provides such a carrier. OpenClaw can use that information to filter +or rank view offers, but the client makes the final selection against its +current registry. + +The Gateway filters discovery to views authorized for the authenticated caller, +selected session, enabled extension surface, and current policy. Enumeration +must not reveal hidden extensions, unavailable tools, tenant-external +capabilities, or view data that would require a denied operation. + +Capability advertisement: + +- is optional and may be stale; +- lets an extension omit an unsupported optional native artifact; +- does not install a component or grant trust; +- does not authorize an OpenClaw operation; and +- must not be required for structured/text or sandboxed fallback output. + +Renderer advertisement is bounded client metadata delivered to the trusted +Gateway. The Gateway must not forward the client's complete renderer inventory +verbatim to extensions by default. It may answer an extension's bounded +compatibility question or select/rank offers without disclosing unrelated +client capabilities. + +An extension that emits an artifact remains responsible for useful structured +or text output when practical. The client independently resolves all offered +views against its current registry. If no exact compatible registration exists, +it uses an accepted declared fallback or renders the structured/text result. + +## Deferred materialization + +The candidate Control Model command is conceptually: + +```ts +materializeView(input: { + artifactId: string; + artifactRevision: number; + viewId: string; + signal?: AbortSignal; +}): Promise; +``` + +The final method name may differ. The contract must: + +- require the current artifact revision and exact offered view ID; +- be read-only and idempotent for that revision; +- re-enter Gateway authentication, session scope, extension availability, and + policy checks; +- enforce finite time, result bytes, depth, and retained cache; +- return the same view ID with `availability: "inline"` and validated data; +- reject stale, removed, unsupported, forbidden, expired, and oversized views + distinctly; and +- avoid materializing any unselected sibling view. + +## Actions + +A native component may emit only a named action declared by its local +registration. The action handler receives: + +- artifact ID and revision; +- normalized action name; +- schema-validated action data; +- current session/message/tool source; and +- an abort signal. + +The handler maps the action to a supported Control Model command or a +product-owned operation. It must verify that the artifact revision is current. +Every OpenClaw operation re-enters Gateway authorization. + +The host records a safe correlation tuple for attempted actions: registration +identity/version, artifact ID/revision, action name, session key, and tool call +ID when present. It must not record raw artifact data by default. + +Artifact data must not contain an executable callback, JavaScript expression, +module reference, or unrestricted Gateway method name. + +## Fallback + +Candidate fallback kinds are: + +```ts +type UiArtifactFallback = + | { + kind: "mcp-app"; + viewId: string; + uiResourceUri?: string; + } + | { + kind: "canvas"; + viewId?: string; + url: string; + sandbox: "strict" | "scripts"; + }; +``` + +Fallback is explicit. An unknown URI does not cause arbitrary HTML, URL, or +module execution. + +MCP App fallback uses the existing OpenClaw materialization, sandbox, CSP, +bridge, expiry, and authorization contracts. Canvas fallback uses existing +host URL and sandbox policy. Structured/text output remains available when no +executable fallback is accepted. + +An MCP App's materializable resource URI appears only as +`fallback.uiResourceUri`; it is never inferred from or dereferenced through +`templateUri`. + +## Streaming + +V1 exposes complete immutable artifact revisions. A source may update an +artifact progressively by publishing higher revisions. + +V1 does not standardize: + +- RFC 6902 patches; +- JSONL framing; +- JSON Render component trees; +- renderer-owned state mutation; or +- client-authored merge semantics. + +A future dialect may add patches if it defines: + +- a base revision; +- finite patch count and bytes; +- atomic validation; +- failure and resynchronization; +- unknown operation handling; +- history persistence; and +- conformance across at least two renderers. + +## History and portability + +The Gateway's sanitized history projection must preserve enough artifact +identity, data, source, revision, and explicit fallback metadata to reproduce +the same safe presentation after reload. + +If the source contract cannot persist an interactive artifact, history must +retain structured content and mark the interactive state expired or +unavailable. It must not silently omit the entire tool result. + +## Security failures + +The following fail artifact rendering without failing the surrounding message: + +- unknown artifact version; +- invalid or unsupported URI; +- data schema failure; +- size/depth/count violation; +- stale/conflicting revision; +- unknown native registration; +- expired fallback; +- unsupported sandbox request; and +- action requested against a stale revision. + +Failures are observable and safe to log after redaction. They must not contain +raw credentials, capability URLs, hidden model context, or unbounded tool data. + +### Appendix: carrying an A2UI dialect + +This appendix shows how a future A2UI view can be carried without redefining +the artifact envelope. It is non-normative for v1. + +OpenClaw today hosts A2UI v0.8 JSONL inside the sandboxed Canvas web surface. It +accepts messages such as `surfaceUpdate`, `dataModelUpdate`, `beginRendering`, +and `deleteSurface`, and rejects v0.9. That is evidence for the sandboxed +fallback path only. No shipped client currently renders A2UI through a reviewed +native component catalog. + +One possible future native adopter advertises an exact registration that pins +the dialect and reviewed catalog, for example +`openclaw-renderer://a2ui/v0.8/core-v1`, plus the artifact and data versions it +accepts. The selected view carries bounded A2UI messages as data. For a dialect +view, `dataVersion` versions this carrier envelope independently from the A2UI +dialect and catalog version in `templateUri`. + +```json +{ + "version": 1, + "id": "artifact-summary-1", + "revision": 0, + "structuredContent": { + "title": "Deployment status", + "status": "Ready" + }, + "views": [ + { + "id": "a2ui", + "templateUri": "openclaw-renderer://a2ui/v0.8/core-v1", + "dataVersion": 1, + "availability": "inline", + "data": { + "messages": [ + { + "surfaceUpdate": { + "surfaceId": "main", + "components": [ + { + "id": "root", + "component": { + "Column": { + "children": { + "explicitList": ["title", "status"] + } + } + } + }, + { + "id": "title", + "component": { + "Text": { + "text": { "literalString": "Deployment status" } + } + } + }, + { + "id": "status", + "component": { + "Text": { + "text": { "literalString": "Ready" } + } + } + } + ] + } + }, + { + "beginRendering": { + "surfaceId": "main", + "root": "root" + } + } + ] + } + } + ], + "state": "ready", + "source": { + "sessionKey": "session-1", + "toolCallId": "tool-1", + "toolName": "example.table" + } +} +``` + +The host may render this view only when its reviewed local registry accepts +`openclaw-renderer://a2ui/v0.8/core-v1` and data version 1. Otherwise it renders +`structuredContent` or an accepted explicit fallback. + +The dialect payload must not carry executable callbacks or module URIs. +Interactive components resolve only to named, schema-validated host actions. + +A2UI incremental messages and Control Model artifact revisions solve different +problems. A2UI messages update one renderer surface efficiently. The artifact +revision is the authoritative, reconnectable conversation projection. A future +normative dialect must define: + +- the base artifact revision and ordered A2UI message sequence; +- a finite message count, byte budget, component depth, and catalog; +- surface identifiers scoped to the artifact and view instance so one + artifact's messages cannot modify another artifact's surface; +- atomic validation before a message affects the visible surface; +- the checkpoint persisted into sanitized history; +- behavior for duplicate, missing, reordered, or unsupported messages; +- reconnect resynchronization from an authoritative checkpoint; and +- how A2UI user actions map to named, schema-validated host actions that + re-enter Gateway authorization. + +Until those rules and cross-client conformance exist, an implementation may +carry a complete bounded A2UI message set inside one immutable artifact revision +but must not claim patch-stream compatibility. + +The runnable +[`a2ui-artifact-envelope.mjs`](./prototypes/a2ui-artifact-envelope.mjs) +prototype illustrates exact dialect/catalog selection, composed component +messages, explicit MCP App fallback, structured fallback, and fail-closed +version validation. It is protocol-shape evidence, not renderer conformance. + +## Required conformance evidence + +Fixtures must cover: + +- registered native URI; +- multiple compatible views with a non-first client selection; +- deferred view enumeration without payload computation; +- selected materialization and proof that sibling views remain unmaterialized; +- unknown URI with structured output only; +- unknown URI with accepted MCP App fallback; +- malformed and oversized data; +- duplicate, stale, conflicting, and increasing revisions; +- history reload; +- reconnect with a retired live revision; +- inline and dedicated-surface projections of the same artifact identity; +- a later turn publishing a higher revision of an existing artifact; +- expired fallback; +- allowed, denied, unknown, and stale-revision actions; +- component schema evolution; +- registration provenance and data-version rejection/migration; and +- client-owned projection into a product view model without native rendering; +- authorization-filtered discovery and renderer-advertisement privacy; +- stable user selection across revisions and reconnect; and +- proof that tool output cannot register or import native code.