Files
claudemesh/.artifacts/specs/2026-05-02-workspace-view.md
Alejandro Gutiérrez 82ee89d0dc
Some checks failed
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled
feat(cli+docs): colorize --help output + workspace view spec
Help text was a wall of monochrome ASCII. Now section headers print
bold-clay, the program title is brand-orange, each verb's syntax is
tinted cyan, and `(alias: ...)` parentheticals are dimmed so they
read as secondary metadata. The styles helper already gates on TTY +
NO_COLOR, so non-interactive output stays unchanged.

Adds .artifacts/specs/2026-05-02-workspace-view.md — the v0.4.0
spec for a per-user virtual workspace that aggregates reads across
all joined meshes while keeping writes mesh-scoped. Roadmap entry
added under v0.3.0.
2026-05-02 22:28:46 +01:00

8.2 KiB

Workspace view — per-user superset over joined meshes

Status: spec / not started Target: v0.4.0 Author: Alejandro Date: 2026-05-02

Why

Users routinely belong to multiple meshes — work, personal, side projects, ECIJA + flexicar + openclaw + prueba1 in our own dogfood. Today's CLI is mesh-scoped: every read or write either auto-picks the default mesh or forces an interactive picker. Common questions like "who's online across all my meshes?" or "any new @-mentions anywhere?" require N round-trips, one per mesh.

A few verbs already aggregate implicitly (peer list, inbox, list), but the surface is patchy and inconsistent.

We want the equivalent of "all my Slacks in one sidebar" — without breaking the per-mesh trust model that v0.3.0 was built around.

What it is NOT

  • Not a literal universal mesh. A single global mesh everyone joins collapses the trust boundary, blows up broadcast fan-out (O(users²)), and turns into spam. See the universal-mesh discussion rejected in this same session.
  • Not federation. Federation is the broker-side equivalent (already roadmapped under v0.3.0). Workspace is purely client-side.
  • Not identity stitching for other peers. Mou@openclaw and Mou@flexicar-2 may or may not be the same human. Don't auto-merge. Stitching MY identities is fine — local config knows.

What it IS

A virtual layer that aggregates reads across the meshes the user has joined, while keeping writes mesh-scoped. Pure projection over existing per-mesh tables. Zero broker changes. Zero protocol changes.

                    ┌──────────────────────────────┐
                    │          workspace           │
                    │   (per-user view, client)    │
                    └─┬────────┬────────┬─────────┬┘
                      │        │        │         │
                ┌─────▼──┐ ┌───▼──┐ ┌───▼──┐ ┌────▼──┐
                │ mesh A │ │ B    │ │ C    │ │  ...  │
                └────────┘ └──────┘ └──────┘ └───────┘
                  (each remains its own crypto + trust domain)

Surface

New verbs (all read-only, all aggregating)

claudemesh me                # overview: meshes, online peers, unread, tasks
claudemesh me topics         # all subscribed topics, namespaced
claudemesh me notifications  # cross-mesh @-mentions feed
claudemesh me activity       # cross-mesh recent send/recv/topic-post
claudemesh me search "<q>"   # full-text across memory + topics + tasks

claudemesh me (no subcommand) prints a one-screen dashboard:

  workspace — agutmou (4 meshes · 23 peers visible · 2 unread @you)

  meshes
    openclaw       7 peers · 3 topics · last activity 2m
    flexicar-2     5 peers · 1 topic  · last activity 18m
    prueba1        4 peers · idle
    ECIJA          7 peers · 2 topics · 1 @you · last activity 4h

  unread @-mentions
    ECIJA · #incident-2026-05-02 · 1 from coronel-abos
    openclaw · #deploys · 1 from claudemesh-2

  pending tasks (3)
    ECIJA  ship-F4-cliente   high   claimed by you
    ...

Default-aggregation rule for existing verbs

When --mesh is omitted on a read-only verb, aggregate. When --mesh is omitted on a write verb, fall back to current behavior (default mesh or interactive picker). Already-aggregating verbs keep working unchanged.

Verb Today After workspace
peer list aggregates unchanged
inbox aggregates unchanged
list aggregates (lists meshes) unchanged
notification list mesh-scoped aggregates by default
topic list mesh-scoped aggregates with namespacing
task list mesh-scoped aggregates by default
state list mesh-scoped aggregates by default
memory recall mesh-scoped aggregates by default
info / stats / ping mesh-scoped unchanged (per-mesh diagnostics)
send, topic post, state set, remember, ... mesh-scoped unchanged (writes pick a mesh)

Rendering rules for aggregated views

  1. Topic namespacing. #deploys exists in two meshes — they're different rooms. Render as openclaw/#deploys. Inside a mesh-scoped command, keep the bare #deploys shorthand.
  2. Peer name collisions. Mou@openclaw notation when the same display name resolves in more than one mesh. Single resolution = bare name.
  3. Time-grouped activity. me activity sorts globally by ts descending; mesh tag is shown as a dim suffix.
  4. Unread roll-up. me notifications is a per-row [mesh][topic][snippet] list, newest first.

API surface (REST)

Mirror the read aggregations server-side so the dashboard + future mobile/web UIs share the same endpoints.

GET /v1/me                 # workspace overview
GET /v1/me/meshes          # joined meshes + summary stats
GET /v1/me/topics          # all subscribed topics, all meshes
GET /v1/me/notifications   # cross-mesh @-mentions
GET /v1/me/activity        # unified activity feed
GET /v1/me/peers           # already implicit; formalize
GET /v1/me/search?q=...    # full-text across tables

Auth: needs a user-scoped api key (one issued per user, sees all their meshes), which we don't have today — current keys are mesh- scoped. Two options:

  • (a) Per-user key. New token type cm_u_... issued by the dashboard, scopes to all meshes the issuing user belongs to. Cheaper to build; harder to reason about because the blast radius is larger if leaked.
  • (b) Multi-mesh aggregation. Accept N mesh-scoped keys concurrently; CLI auto-mints them via the existing withRestKey pattern, one per joined mesh. No new key type. More round-trips on cold start, but rotation/revocation stays simple.

Recommendation: (b). Reuses today's auth model, doesn't widen the blast radius, and the ephemeral keys we already mint per-command keep the surface area minimal. The CLI orchestrates the fan-out client- side.

Storage

Pure projection at first. The cross-mesh queries are SELECT joins over mesh_member, mesh_topic, mesh_topic_member, mesh_notification, mesh_topic_message, mesh_task, presence.

If me queries become hot (likely once dashboards land), add a materialized user_workspace_view refreshed on writes. Don't optimize early.

Effort

Component Effort
CLI verbs (me, me topics, etc.) 1.5 days
Default-aggregation rule across existing verbs 0.5 day
REST endpoints /v1/me/* 1 day
Multi-mesh apikey orchestration in withRestKey 0.5 day
Tests + docs 0.5 day
Total ~4 days

Open questions

  1. me as namespace vs. flag. Could be claudemesh --workspace topics instead of claudemesh me topics. The verb form is shorter and reads better; sticking with it.
  2. Notification ordering. All notifications globally interleaved by ts, or per-mesh sections? Default to interleaved with mesh tag prefix; users can --by-mesh to group.
  3. Search relevance. Cross-mesh full-text search is easy when each mesh has its own pg full-text index. Cross-mesh ranking is the harder problem (IDF varies). Punt to v0.4.1 — start with simple tied-rank merge.
  4. Web dashboard. Should the web dashboard's main view become a workspace view by default? Yes, but that's downstream of this spec — once /v1/me/* exists, the web rewrite is the obvious next step.

Out of scope (v0.4.0)

  • Federation / cross-broker workspace.
  • Identity stitching for non-self peers.
  • Cross-mesh search ranking sophistication.
  • Cross-mesh write fan-out (me broadcast is intentionally NOT a verb — too easy to misuse).
  • Mobile/web parity beyond the REST endpoints.

Why we ship this

Because "I want one Slack-like sidebar for all my claudemesh meshes" is the highest-frequency UX gap users hit, and the answer is two days of plumbing on top of what already exists. Federation is the right answer for cross-organization reach; workspace is the right answer for one user, many meshes. Both compose.