17 KiB
Yonexus — Project Plan
1. Goal
Yonexus is a cross-instance communication system for OpenClaw, implemented as two separate plugins:
Yonexus.ServerYonexus.Client
Together they provide:
- communication between multiple OpenClaw instances
- a central WebSocket hub model
- client pairing and authentication
- heartbeat-based client liveness tracking
- rule-based message dispatch
- out-of-band pairing notification to a human administrator via Discord DM
- TypeScript interfaces for higher-level plugin/runtime integrations
This project is no longer a role-switched single plugin. It is now explicitly split into two installable plugins with distinct responsibilities.
2. Plugin Split
2.1 Yonexus.Server
Yonexus.Server is installed only on the main OpenClaw instance.
Responsibilities:
- start and maintain the WebSocket server
- accept incoming client connections
- maintain the client registry
- handle pairing flow
- verify authentication proofs
- track heartbeat and connection state
- route or relay messages to connected clients
- rewrite inbound client messages before rule dispatch
- send Discord DM pairing notifications to the human administrator
2.2 Yonexus.Client
Yonexus.Client is installed on follower OpenClaw instances.
Responsibilities:
- connect to the configured Yonexus server
- generate and persist local keypair on first use
- persist local client identity and secret
- perform pairing confirmation
- perform authenticated reconnect
- send periodic heartbeats
- expose client-side messaging and rule registration APIs
3. Deployment Model
A Yonexus network contains:
- exactly one OpenClaw instance running
Yonexus.Server - one or more OpenClaw instances running
Yonexus.Client
Topology rules:
Yonexus.Servermust be reachable via fixed IP/domain or otherwise stable addressable endpointYonexus.Clientinstances do not need stable public IP/domain- all
Yonexus.Clientinstances connect outbound to theYonexus.ServerWebSocket endpoint - no direct client-to-client communication is required in v1
- inter-client communication, if needed, is relayed by
Yonexus.Server
4. Configuration Model
4.1 Yonexus.Server Config
followerIdentifiers: string[]
notifyBotToken: string
adminUserId: string
listenHost?: string
listenPort: number
publicWsUrl?: string
Semantics:
followerIdentifiers: allowlist of client identifiers permitted to pair/connectnotifyBotToken: Discord bot token used to send pairing notificationsadminUserId: Discord user id of the human administrator who receives pairing codes by DMlistenHost: local bind host for WebSocket serverlistenPort: local bind port for WebSocket serverpublicWsUrl: optional canonical external URL advertised/documented for clients
4.2 Yonexus.Client Config
mainHost: string
identifier: string
notifyBotToken: string
adminUserId: string
Semantics:
mainHost: WebSocket endpoint ofYonexus.Serveridentifier: unique identity of this client inside the Yonexus networknotifyBotToken: kept aligned with shared config expectations if future client-side notification behaviors are neededadminUserId: human administrator identity reference shared with the Yonexus system
4.3 Validation Rules
Yonexus.Server
- must provide
followerIdentifiers - must provide
notifyBotToken - must provide
adminUserId - must provide
listenPort - must be deployed on a reachable/stable endpoint
Yonexus.Client
- must provide
mainHost - must provide
identifier - must provide
notifyBotToken - must provide
adminUserId
Shared
- invalid or missing required fields must fail plugin initialization
- unknown client identifiers must be rejected by
Yonexus.Server
4.4 Shared Terminology Baseline
These names are normative across umbrella docs, protocol docs, and implementation repos:
identifier: the unique logical name of a client/follower instance.rule_identifier: the exact-match application route key.builtin: reserved rule namespace for protocol/system frames.pairingCode: short-lived out-of-band code delivered to the human admin.secret: server-issued shared secret used for reconnect proof construction.publicKey/privateKey: client-held signing keypair.nextAction: server-directed next step returned byhello_ack.
Implementations should avoid introducing alternative synonyms for these fields unless there is a versioned migration plan.
5. Runtime Lifecycle
5.1 Yonexus.Server Startup
On OpenClaw gateway startup:
- initialize persistent client registry
- start WebSocket server
- register builtin protocol handlers
- register application rule registry
- start heartbeat/status sweep timer
5.2 Yonexus.Client Startup
On OpenClaw gateway startup:
- load local persisted identity, private key, and secret state
- generate keypair if absent
- connect to
mainHost - perform pairing or authentication flow depending on local state
- start heartbeat schedule after successful authentication
- attempt reconnect when disconnected
6. Server Registry and Persistence
Yonexus.Server must maintain a registry keyed by client identifier.
Each client record contains at minimum:
identifierpublicKeysecret- pairing state
- pairing expiration data
- pairing notification metadata
- connection status
- security counters/window data
- heartbeat timestamps
- last known session metadata
The registry must use:
- in-memory runtime state for active connections and recent security windows
- persistent on-disk storage for durable trust state
6.1 Proposed Server Record Shape
interface ClientRecord {
identifier: string;
publicKey?: string;
secret?: string;
pairingStatus: "unpaired" | "pending" | "paired" | "revoked";
pairingCode?: string;
pairingExpiresAt?: number;
pairingNotifiedAt?: number;
pairingNotifyStatus?: "pending" | "sent" | "failed";
status: "online" | "offline" | "unstable";
lastHeartbeatAt?: number;
lastAuthenticatedAt?: number;
recentNonces: Array<{
nonce: string;
timestamp: number;
}>;
recentHandshakeAttempts: number[];
createdAt: number;
updatedAt: number;
}
7. Pairing and Authentication
7.1 First Connection and Key Generation
When a client connects to the server for the first time:
Yonexus.Clientgenerates a public/private key pair locally- the private key remains only on the client instance
- the public key is sent to
Yonexus.Serverduring handshake
If the server sees that:
- the client identifier is allowed, and
- there is no valid
secretcurrently associated with that identifier
then the server must enter pairing flow.
7.2 Pairing Flow
Step A: Pairing Request Creation
Yonexus.Server generates:
- a random pairing string
- an expiration time
The pairing string must not be sent to the client over WebSocket.
Instead, Yonexus.Server uses notifyBotToken to send a Discord DM to adminUserId containing:
- the client
identifier - the generated
pairingCode - the expiration time
Step B: Pairing Confirmation
The client must provide the pairing code back to the server before expiration.
How the client operator obtains the pairing code is intentionally out-of-band from the Yonexus WebSocket channel. The server only trusts that the code came through some human-mediated path.
If the client sends the correct pairing code before expiration:
- pairing succeeds
Step C: Secret Issuance
After successful pairing:
Yonexus.Servergenerates a randomsecretYonexus.Serverreturns thatsecretto the clientYonexus.Serverstores clientpublicKey+secretYonexus.Clientstores private key + secret locally
If Discord DM delivery fails:
- pairing must not proceed
- server should mark the pairing attempt as failed or pending-error
- client must not receive a usable pairing code through the protocol channel
If pairing expires before confirmation:
- pairing fails
- the client must restart the pairing process
7.3 Reconnect Authentication Flow
After pairing is complete, future client authentication must use:
- the stored
secret - a 24-character random nonce
- current UTC Unix timestamp
The client builds a proof payload from:
secretnoncetimestamp
Logical concatenation order:
secret + nonce + timestamp
Implementation recommendation:
- use a canonical serialized object and sign its bytes rather than naive string concatenation in code
The client signs the proof using its private key and sends it to the server.
The server verifies:
- identifier is known and paired
- public key matches stored state
- proof contains the correct
secret - timestamp difference from current time is less than 10 seconds
- nonce does not collide with the recent nonce window
- handshake attempts in the last 10 seconds do not exceed 10
If all checks pass:
- authentication succeeds
- the client is considered authenticated for the session
If any check fails:
- authentication fails
- server may downgrade or revoke trust state
7.4 Unsafe Condition Handling
The connection is considered unsafe and must return to pairing flow if either is true:
- more than 10 handshake attempts occur within 10 seconds
- the presented nonce collides with one of the last 10 nonces observed within the recent window
When unsafe:
- existing trust state must no longer be accepted for authentication
- the client must re-pair
- server should clear or rotate the stored
secret - server should reset security windows as part of re-pairing
8. Heartbeat and Client Status
The server must track each client’s liveness state:
onlineunstableoffline
8.1 Heartbeat Rules
Each client must send a heartbeat to the server every 5 minutes.
8.2 Status Transitions
online
A client is online when:
- it has an active authenticated WebSocket connection, and
- the server has received a recent heartbeat
unstable
A client becomes unstable when:
- no heartbeat has been received for 7 minutes
offline
A client becomes offline when:
- no heartbeat has been received for 11 minutes
When a client becomes offline:
- the server must close/terminate the WebSocket connection for that client
8.3 Status Evaluation Strategy
The server should run a periodic status sweep timer.
Recommended interval:
- every 30 to 60 seconds
9. Messaging Model
Yonexus provides rule-based message dispatch over WebSocket.
9.1 Base Message Format
All application messages must use the format:
${rule_identifier}::${message_content}
9.2 Server-Side Rewriting
When Yonexus.Server receives a message from a client, before rule matching it must rewrite the message into:
${rule_identifier}::${sender_identifier}::${message_content}
This ensures server-side processors can identify which client sent the message.
9.3 Builtin Rule Namespace
The reserved rule identifier is:
builtin
It is used internally for:
- handshake
- pairing
- heartbeat
- protocol/system messages
User code must not be allowed to register handlers for builtin.
10. TypeScript API Surface
10.1 Yonexus.Client API
sendMessageToServer(message: string): Promise<void>
Rules:
- sends message to connected
Yonexus.Server - message must already conform to
${rule_identifier}::${message_content}
registerRule(rule: string, processor: (message: string) => unknown): void
Rules:
- rejects
builtin - rejects duplicate rule registration unless explicit override support is added later
10.2 Yonexus.Server API
sendMessageToClient(identifier: string, message: string): Promise<void>
Rules:
- target client must be known and currently connected/authenticated
- message must already conform to
${rule_identifier}::${message_content}
registerRule(rule: string, processor: (message: string) => unknown): void
Rules:
- rejects
builtin - rejects duplicate rule registration unless explicit override support is added later
- processors are invoked with the final received string after any server-side rewrite
11. Hooks and Integration
11.1 Yonexus.Server Hooking
Yonexus.Server must register hooks so that when OpenClaw gateway starts:
- the WebSocket server is started
- the server registry is initialized
- builtin protocol handling is enabled
- heartbeat sweep begins
11.2 Yonexus.Client Behavior
Yonexus.Client must:
- connect outbound to
mainHost - manage local trust material
- handle pairing/authentication transitions
- emit heartbeats after authentication
- reconnect after disconnect with retry/backoff behavior
12. Storage Strategy
12.1 Yonexus.Server Storage
Server persists at minimum:
- identifier
- public key
- secret
- trust state
- pairing code + expiry if pairing is pending
- pairing notification metadata
- last known status
- metadata timestamps
May persist or reset on restart:
- recent nonces
- recent handshake attempts
Recommended v1:
- clear rolling security windows on restart
- keep long-lived trust records
12.2 Yonexus.Client Storage
Client persists at minimum:
- identifier
- private key
- secret
- optional last successful pair/auth metadata
Security notes:
- private key must never be sent to the server
- secret must be treated as sensitive material
- encryption-at-rest can be a future enhancement, but any plaintext local storage must be documented as a limitation if used initially
13. Error Handling
Structured errors should exist for at least:
- invalid configuration
- unauthorized identifier
- pairing required
- pairing expired
- pairing notification failure
- handshake verification failure
- replay/nonce collision detected
- unsafe handshake rate detected
- target client not connected
- duplicate rule registration
- reserved rule registration
- malformed message
14. Initial Implementation Phases
Phase 0 — Protocol and Skeleton
- finalize split-plugin configuration schema
- define persistent data models
- define builtin protocol messages
- define startup hooks for both plugins
- define rule registry behavior
- define Discord DM notification flow
Phase 1 — Transport MVP
- Yonexus.Server WebSocket server startup
- Yonexus.Client WebSocket client startup
- reconnect logic
- builtin protocol channel
- persistent registry/state scaffolding
Phase 2 — Pairing and Authentication
- client keypair generation
- pairing request creation
- Discord DM notification to admin user
- pairing confirmation flow
- secret issuance and persistence
- signed proof verification
- nonce/replay protection
- unsafe-condition reset to pairing
Phase 3 — Heartbeat and Status Tracking
- client heartbeat sender
- server heartbeat receiver
- periodic sweep
- status transitions: online / unstable / offline
- forced disconnect on offline
Phase 4 — Public APIs and Dispatch
sendMessageToServersendMessageToClientregisterRule- first-match dispatch
- server-side sender rewrite behavior
Phase 5 — Hardening and Docs
- integration tests
- failure-path coverage
- restart recovery checks
- protocol docs
- operator setup docs for server/client deployment
15. Non-Goals for Initial Version
Not required in the first version unless explicitly added later:
- direct client-to-client sockets
- multi-server clustering
- distributed consensus
- offline message queues or guaranteed delivery to disconnected clients
- advanced rule matching beyond exact string match
- message ordering guarantees across reconnects
- end-to-end payload encryption beyond the pairing/authentication requirements
- management UI
- admin-side approve/deny control plane beyond human relay of pairing codes
- encryption-at-rest hardening beyond documenting current local storage limitations
16. v1 Decisions Locked for Current Implementation
The following implementation-boundary decisions are now treated as settled for v1:
- Signing algorithm default: Ed25519.
mainHostshould be configured as a fullws://orwss://URL in v1.- Human relay of the pairing code is sufficient for v1; richer admin approve/deny control can wait.
heartbeat_ackremains optional.- Client reconnect uses exponential backoff.
- Rule identifiers are exact-match strings only in v1.
- Outbound sends to offline clients fail immediately rather than queueing.
17. Open Questions To Confirm Later
- On unsafe condition, should the old public key be retained or should the client generate a new keypair?
- Should future versions support explicit key rotation without full re-pairing?
- Should offline clients support queued outbound messages from server in a later version?
- Are richer admin approval workflows worth adding after v1 stabilizes?
- Should encryption-at-rest become a hard requirement in v2?
18. Immediate Next Deliverables
After this plan, the next files to create should be:
FEAT.md— feature checklist derived from this planREADME.md— concise system overview for both pluginsplugin.server.jsonor equivalent server plugin manifestplugin.client.jsonor equivalent client plugin manifest- implementation task breakdown