docs: align bridge architecture to app mainline
This commit is contained in:
parent
991ecb0ae2
commit
90b39eebbb
@ -1,129 +1,103 @@
|
||||
# ACP Forwarding Topology
|
||||
|
||||
This document describes the bridge-only production forwarding model for `xworkmate-bridge.svc.plus`.
|
||||
Last Updated: 2026-04-13
|
||||
|
||||
See also: [adr-unified-bridge-entrypoints.md](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-bridge/docs/architecture/adr-unified-bridge-entrypoints.md)
|
||||
本文件描述当前 `xworkmate-app <-> xworkmate-bridge` 主链下的 bridge-only forwarding topology。
|
||||
|
||||
See also:
|
||||
|
||||
- [XWorkmate Core Module Inventory](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/docs/architecture/xworkmate-core-module-inventory-2026-04-13.md)
|
||||
- [ADR: Unified Bridge Entry Points](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-bridge/docs/architecture/adr-unified-bridge-entrypoints.md)
|
||||
|
||||
## App-Facing Mainline
|
||||
|
||||
对 app 来说,当前主链只有两类面向 bridge 的交互:
|
||||
|
||||
- `assistant` surface 进入 ACP control-plane:`acp.capabilities`、`xworkmate.routing.resolve`、`session.*`
|
||||
- `settings` surface 进入 gateway runtime / connection flow:`acp.capabilities`、`xworkmate.gateway.*`
|
||||
|
||||
不管 bridge 内部还保留哪些 provider / gateway mode / capability flag,app-facing 公共入口都只有 bridge origin。
|
||||
|
||||
## Topology
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
U["xworkmate-app"] --> B["https://xworkmate-bridge.svc.plus"]
|
||||
subgraph APP["xworkmate-app"]
|
||||
A1["AssistantPage"]
|
||||
A2["SettingsPage"]
|
||||
A3["https://xworkmate-bridge.svc.plus"]
|
||||
A1 --> A3
|
||||
A2 --> A3
|
||||
end
|
||||
|
||||
B -->|POST /acp/rpc| RPC["ACP RPC handler"]
|
||||
B -->|WS /acp| WS["ACP WebSocket handler"]
|
||||
subgraph BRIDGE["xworkmate-bridge"]
|
||||
B1["POST /acp/rpc"]
|
||||
B2["GET /acp (WebSocket)"]
|
||||
B3["acp.capabilities"]
|
||||
B4["xworkmate.routing.resolve"]
|
||||
B5["session.*"]
|
||||
B6["xworkmate.gateway.*"]
|
||||
B7["bridge-owned provider catalog"]
|
||||
B8["bridge-owned routing"]
|
||||
B9["bridge-owned gateway runtime"]
|
||||
|
||||
RPC --> R{"method"}
|
||||
WS --> R
|
||||
A3 --> B1
|
||||
A3 --> B2
|
||||
B1 --> B3
|
||||
B1 --> B4
|
||||
B1 --> B5
|
||||
B1 --> B6
|
||||
B2 --> B5
|
||||
B3 --> B7
|
||||
B4 --> B8
|
||||
B5 --> B8
|
||||
B6 --> B9
|
||||
end
|
||||
|
||||
R -->|acp.capabilities| CAP["built-in provider catalog"]
|
||||
R -->|xworkmate.routing.resolve| ROUTE["bridge-owned routing resolve"]
|
||||
R -->|session.start / session.message| RUN["bridge-owned execution"]
|
||||
R -->|xworkmate.gateway.*| GWAPI["gateway runtime proxy"]
|
||||
R -->|session.cancel / session.close| LIFE["session lifecycle"]
|
||||
subgraph UPSTREAM["Independent upstream services"]
|
||||
C1["https://acp-server.svc.plus/codex/acp/rpc"]
|
||||
C2["https://acp-server.svc.plus/opencode/acp/rpc"]
|
||||
C3["https://acp-server.svc.plus/gemini/acp/rpc"]
|
||||
C4["wss://openclaw.svc.plus"]
|
||||
end
|
||||
|
||||
RUN --> ACP1["codex -> https://acp-server.svc.plus/codex/acp/rpc"]
|
||||
RUN --> ACP2["opencode -> https://acp-server.svc.plus/opencode/acp/rpc"]
|
||||
RUN --> ACP3["gemini -> https://acp-server.svc.plus/gemini/acp/rpc"]
|
||||
|
||||
GWAPI --> GW["wss://openclaw.svc.plus"]
|
||||
B7 --> C1
|
||||
B7 --> C2
|
||||
B7 --> C3
|
||||
B8 --> C1
|
||||
B8 --> C2
|
||||
B8 --> C3
|
||||
B9 --> C4
|
||||
```
|
||||
|
||||
## Three-Layer View
|
||||
|
||||
This view separates what the app sees, what the bridge owns, and what the
|
||||
real upstream production targets are. The upstream ACP and gateway services
|
||||
exist independently, but for the app they are all accessed through the single
|
||||
public bridge origin: `https://xworkmate-bridge.svc.plus`.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph L1["APP 视角"]
|
||||
APP["xworkmate-app"]
|
||||
APPENTRY["https://xworkmate-bridge.svc.plus<br/>统一代理入口"]
|
||||
APPMETHODS["bridge methods<br/>acp.capabilities / session.* / xworkmate.gateway.*"]
|
||||
APP --> APPENTRY
|
||||
APPENTRY --> APPMETHODS
|
||||
end
|
||||
|
||||
subgraph L2["Bridge 视角"]
|
||||
BRIDGE["xworkmate-bridge<br/>唯一上游发现真源"]
|
||||
|
||||
CAP["Bridge-owned ACP routing catalog"]
|
||||
CAP1["codex"]
|
||||
CAP2["opencode"]
|
||||
CAP3["gemini"]
|
||||
|
||||
GW["Bridge-owned gateway routing"]
|
||||
GW1["gatewayProviderId=openclaw"]
|
||||
|
||||
BRIDGE --> CAP
|
||||
CAP --> CAP1
|
||||
CAP --> CAP2
|
||||
CAP --> CAP3
|
||||
|
||||
BRIDGE --> GW
|
||||
GW --> GW1
|
||||
end
|
||||
|
||||
subgraph L3["上游视角"]
|
||||
U1["https://acp-server.svc.plus/codex/acp/rpc"]
|
||||
U2["https://acp-server.svc.plus/opencode/acp/rpc"]
|
||||
U3["https://acp-server.svc.plus/gemini/acp/rpc"]
|
||||
U4["wss://openclaw.svc.plus<br/>reported as openclaw.svc.plus:443"]
|
||||
end
|
||||
|
||||
APPMETHODS --> BRIDGE
|
||||
|
||||
CAP1 --> U1
|
||||
CAP2 --> U2
|
||||
CAP3 --> U3
|
||||
GW1 --> U4
|
||||
```
|
||||
|
||||
Important distinction:
|
||||
|
||||
- the upstream services are independent production services, not embedded
|
||||
inside the bridge
|
||||
- for the app, ACP discovery, session execution, and gateway runtime traffic
|
||||
are all proxied through `https://xworkmate-bridge.svc.plus`
|
||||
- upstream authentication is unified through
|
||||
`Authorization: Bearer $INTERNAL_SERVICE_TOKEN`
|
||||
- `acp.capabilities.providerCatalog` currently advertises only the ACP
|
||||
single-agent providers: `codex`, `opencode`, and `gemini`
|
||||
- `gateway` is not part of that provider catalog; it is exposed through the
|
||||
separate `xworkmate.gateway.*` bridge-owned runtime path
|
||||
- for `gatewayProviderId=openclaw`, the bridge rewrites the upstream target to
|
||||
`wss://openclaw.svc.plus`
|
||||
|
||||
## Production Truth
|
||||
|
||||
The production upstream services exist independently. The bridge owns the
|
||||
routing map used to proxy app traffic to them:
|
||||
当前 production forwarding 事实:
|
||||
|
||||
- `codex` -> `https://acp-server.svc.plus/codex/acp/rpc`
|
||||
- `opencode` -> `https://acp-server.svc.plus/opencode/acp/rpc`
|
||||
- `gemini` -> `https://acp-server.svc.plus/gemini/acp/rpc`
|
||||
- gateway -> `wss://openclaw.svc.plus`
|
||||
- canonical app-facing origin: `https://xworkmate-bridge.svc.plus`
|
||||
- canonical app-facing ACP paths:
|
||||
- `POST /acp/rpc`
|
||||
- `GET /acp`
|
||||
- current built-in single-agent provider catalog:
|
||||
- `codex`
|
||||
- `opencode`
|
||||
- `gemini`
|
||||
- current production gateway forwarding target:
|
||||
- `openclaw -> wss://openclaw.svc.plus`
|
||||
|
||||
Upstream auth is unified and bridge-internal:
|
||||
对 app 而言:
|
||||
|
||||
- `Authorization: Bearer $INTERNAL_SERVICE_TOKEN`
|
||||
|
||||
Canonical APP-facing paths stay on the bridge origin:
|
||||
|
||||
- `POST https://xworkmate-bridge.svc.plus/acp/rpc`
|
||||
- `GET https://xworkmate-bridge.svc.plus/acp`
|
||||
- provider catalog、routing、gateway runtime 都是 bridge-owned metadata / behavior
|
||||
- upstream URL 存在,但不是 app 的直接合同
|
||||
- gateway backend、provider IDs、可选 capability flag 也都不是 app shell 模块分类
|
||||
|
||||
## Invariants
|
||||
|
||||
- app-facing cloud entry is only `https://xworkmate-bridge.svc.plus`
|
||||
- app traffic reaches upstream ACP and gateway services only through the
|
||||
bridge proxy
|
||||
- upstream ACP and gateway routes use the same bearer token contract:
|
||||
`Authorization: Bearer $INTERNAL_SERVICE_TOKEN`
|
||||
- `acp.capabilities` returns the built-in production catalog
|
||||
- no production `xworkmate.providers.sync`
|
||||
- no app direct call to `acp-server.svc.plus/*`
|
||||
- no app direct call to `openclaw.svc.plus`
|
||||
- openclaw gateway runtime status is reported as `openclaw.svc.plus:443`, but the app still talks only to the bridge
|
||||
- app traffic reaches upstream ACP and gateway services only through the bridge
|
||||
- app does not call `acp-server.svc.plus/*` or `openclaw.svc.plus` directly
|
||||
- upstream auth stays bridge-internal:
|
||||
- `Authorization: Bearer $INTERNAL_SERVICE_TOKEN`
|
||||
- `acp.capabilities` is the provider / capability discovery source
|
||||
- `xworkmate.routing.resolve` is the routing resolution source
|
||||
- `xworkmate.gateway.*` is the gateway runtime method family
|
||||
- bridge may expose additional routing metadata, but that metadata must not be interpreted as extra app surfaces or legacy module shells
|
||||
|
||||
@ -8,37 +8,51 @@ Accepted
|
||||
|
||||
2026-04-11
|
||||
|
||||
## Last Reviewed
|
||||
|
||||
2026-04-13
|
||||
|
||||
## Context
|
||||
|
||||
`xworkmate-bridge` currently proxies app traffic to four independent upstream
|
||||
production services:
|
||||
`xworkmate-bridge` 代理 app 流量到独立 upstream ACP / gateway 服务,但如果把这些 upstream 直接暴露成 app-facing entrypoint,会带来几个问题:
|
||||
|
||||
- `codex` -> `https://acp-server.svc.plus/codex/acp/rpc`
|
||||
- `opencode` -> `https://acp-server.svc.plus/opencode/acp/rpc`
|
||||
- `gemini` -> `https://acp-server.svc.plus/gemini/acp/rpc`
|
||||
- `gateway` -> `wss://openclaw.svc.plus`
|
||||
- app 必须知道 provider-specific 或 gateway-specific hostname
|
||||
- routing truth 会在 URL 形状与 bridge 逻辑之间分裂
|
||||
- auth contract 更难保持统一
|
||||
- upstream implementation detail 会泄漏到 app shell / module 心智
|
||||
|
||||
These upstream services exist independently, but exposing them directly as
|
||||
APP-facing endpoints creates several problems:
|
||||
|
||||
- the APP would need to know provider-specific or gateway-specific hostnames
|
||||
- routing truth would be split between URL shape and bridge-side routing logic
|
||||
- auth handling would be harder to keep consistent
|
||||
- upstream implementation details would leak into client contracts
|
||||
|
||||
The bridge already acts as the single public integration surface for ACP
|
||||
discovery, task execution, and gateway runtime operations.
|
||||
当前 app 主链已经收敛到 `assistant + settings` 两个 surface,因此 bridge 入口也必须保持同一口径:统一公共入口,bridge 内部负责 provider / gateway 路由细节。
|
||||
|
||||
## Decision
|
||||
|
||||
For APP traffic, the canonical public entry point is the bridge origin:
|
||||
For APP traffic, the canonical public entry point remains:
|
||||
|
||||
- `https://xworkmate-bridge.svc.plus`
|
||||
|
||||
The canonical APP-facing ACP paths are:
|
||||
Canonical app-facing contract families are:
|
||||
|
||||
- `POST /acp/rpc`
|
||||
- `GET /acp` for WebSocket ACP
|
||||
1. ACP control-plane
|
||||
- `POST /acp/rpc`
|
||||
- `GET /acp`
|
||||
2. Gateway runtime methods
|
||||
- `xworkmate.gateway.connect`
|
||||
- `xworkmate.gateway.request`
|
||||
- `xworkmate.gateway.disconnect`
|
||||
|
||||
Bridge-owned metadata may still include:
|
||||
|
||||
- `providerCatalog`
|
||||
- `gatewayProviders`
|
||||
- `resolvedExecutionTarget`
|
||||
- `resolvedProviderId`
|
||||
- `resolvedGatewayProviderId`
|
||||
|
||||
这些字段属于 bridge 返回给 app 的 routing/capability metadata,不属于 app shell taxonomy。
|
||||
|
||||
换句话说:
|
||||
|
||||
- app 可以消费这些字段来展示当前可用能力或执行结果
|
||||
- app 不应该把 provider/gateway 矩阵抬升成新的顶层模块、别名页面、或直接 URL 合同
|
||||
|
||||
The APP should not depend on provider-specific public URLs such as:
|
||||
|
||||
@ -47,76 +61,37 @@ The APP should not depend on provider-specific public URLs such as:
|
||||
- `/gemini/acp/rpc`
|
||||
- `/openclaw/`
|
||||
|
||||
Provider choice remains bridge-owned routing, not URL-owned routing.
|
||||
If the bridge reports execution-target metadata such as `single-agent`,
|
||||
`multi-agent`, or `gateway`, the app should treat those values as routing
|
||||
results, not as shell-level surface categories.
|
||||
|
||||
APP-facing routing should be modeled in three layers:
|
||||
|
||||
- `executionTarget`
|
||||
- `single-agent`
|
||||
- `multi-agent`
|
||||
- `gateway`
|
||||
- `singleAgentProviders`
|
||||
- `codex`
|
||||
- `opencode`
|
||||
- `gemini`
|
||||
- `gatewayProviders`
|
||||
- `local`
|
||||
- `openclaw`
|
||||
|
||||
For APP integration, `gatewayProviders` is the stable gateway-facing concept.
|
||||
|
||||
APP and UI code should consume bridge state in two phases:
|
||||
|
||||
1. `acp.capabilities`
|
||||
- discover `singleAgentProviders`
|
||||
- discover `gatewayProviders`
|
||||
2. `xworkmate.routing.resolve`
|
||||
- determine `resolvedExecutionTarget`
|
||||
- determine `resolvedProviderId` or `resolvedGatewayProviderId`
|
||||
- determine unavailable state
|
||||
|
||||
The APP should treat `resolvedProviderId` and `resolvedGatewayProviderId` as
|
||||
mutually exclusive routing outputs depending on `resolvedExecutionTarget`.
|
||||
|
||||
Gateway access remains bridge-owned via JSON-RPC methods:
|
||||
|
||||
- `xworkmate.gateway.connect`
|
||||
- `xworkmate.gateway.request`
|
||||
- `xworkmate.gateway.disconnect`
|
||||
|
||||
Upstream authentication is unified for both ACP and gateway routes:
|
||||
|
||||
- `Authorization: Bearer $INTERNAL_SERVICE_TOKEN`
|
||||
If the bridge reports gateway provider IDs such as `local` or `openclaw`, the
|
||||
app should treat them as bridge-owned gateway backend identifiers, not as
|
||||
independent app entrypoints.
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
- APP integration stays stable behind one public origin
|
||||
- provider and gateway topology remain internal bridge concerns
|
||||
- auth contract is consistent across all upstream forwarding
|
||||
- bridge can change upstream mappings without changing APP contracts
|
||||
- provider and gateway topology remain bridge concerns
|
||||
- auth handling remains consistent across ACP and gateway forwarding
|
||||
- app architecture docs can stay focused on `assistant + settings` instead of a fake module matrix
|
||||
|
||||
### Trade-offs
|
||||
|
||||
- direct provider-specific bridge URLs, if exposed at all, must be treated as
|
||||
aliases or operator/debug paths, not primary client contracts
|
||||
- documentation must clearly distinguish canonical APP paths from independent
|
||||
upstream targets
|
||||
- docs must clearly separate canonical app contracts from independent upstream services
|
||||
- optional bridge metadata must be documented as metadata, not as surface taxonomy
|
||||
|
||||
## Path Naming Guidance
|
||||
|
||||
Use these terms consistently in docs:
|
||||
Use these terms consistently:
|
||||
|
||||
- `canonical APP-facing path`: `/acp/rpc` and `/acp`
|
||||
- `independent upstream service`: `acp-server.svc.plus/*` and
|
||||
`wss://openclaw.svc.plus`
|
||||
- `bridge-owned routing`: bridge logic that selects and proxies to upstreams
|
||||
- `gatewayProvider`: the APP-facing identifier for a gateway backend such as
|
||||
`local` or `openclaw`
|
||||
- `canonical app-facing path`: `/acp/rpc` and `/acp`
|
||||
- `gateway runtime method family`: `xworkmate.gateway.*`
|
||||
- `independent upstream service`: `acp-server.svc.plus/*`, `wss://openclaw.svc.plus`
|
||||
- `bridge-owned routing`: provider / gateway selection performed inside bridge
|
||||
- `routing metadata`: execution target and resolved provider/gateway identifiers returned to the app
|
||||
|
||||
Avoid describing upstream URLs as if the APP should call them directly.
|
||||
|
||||
If provider-specific public bridge paths are ever introduced, they should be
|
||||
documented as optional aliases only. They should not replace `/acp/rpc` as the
|
||||
canonical APP-facing contract.
|
||||
Avoid describing upstream URLs, provider IDs, or gateway mode IDs as if they
|
||||
were independent app modules or alternate primary entrypoints.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user