decocms / decocms/studio

RFC: a real screen at / — path = context, and the home-org model for a personal layer

Open
#4,326 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
407
Forks
57
Avg merge
5h 6m
Merged PRs (30d)
880

Description

TL;DR

It's agents all the way down. There's always a chat; the URL says who it is. The logo takes you to yourself.

  • Today / is a redirect, not a screen. Studio has no surface for the user — you're bumped into an org before anything happens.
  • Proposal: path = context. A topbar breadcrumb (deco > org > agent) is the product's only navigation and the chat's scope selector. Same rule the API already follows (/api/:org/...) — surfaced for humans.
  • Everything is an agent; only the zoom changes. Root = you across all your orgs · org = its pilot · deeper = one agent or connection. Same primitive at every level.
  • Your deco is an org. The personal home is a home org (every user already gets one at signup) that has memory about you and aggregates your other orgs as connections/agents — no new backend primitives, no god-scope.
  • Share at any level mints an MCP URL for that scope — your whole deco, one org, or one agent — connectable from any external client with the caller's own role (#4263).
  • Pilotable this week with zero core changes: hand-wire one org with org-connections + reporting agents. Smallest core PR after: kill the redirect, root screen, breadcrumb.

Problem

studio.decocms.com/ is not a screen. The root route immediately redirects into an org (apps/mesh/src/web/index.tsxlastOrgSlug → first org → onboarding). There is no surface that represents the user: no place to see all your orgs at a glance, no chat that isn't already inside an org, no cross-org status.

For single-org users this is invisible. For anyone with several orgs (agencies, platform operators, most internal users) the product forces an org choice before any value, and there is no way to ask a question that spans orgs ("what needs me this morning?").

Design principle: path = context

The backend already has this rule: all org-scoped API routes are /api/:org/... and the org is resolved from the path (resolveOrgFromPath, and #4263 extends path-derived org resolution to MCP OAuth sessions). The proposal is to surface the same rule for humans:

URL path              chat scope                     tool surface
/                     you (all your orgs)            fan-out across memberships
/:org                 the org's pilot (decopilot)    /api/:org/mcp aggregate
/:org/agents/:id      that agent                     its virtual MCP
/:org/connections/:id that connection                its tools + management tools

Concretely: a topbar breadcrumb (logo = / = home, then org, then entity) that is also the context selector for a chat that is always available. You never navigate to a chat; you navigate to a scope and speak. Every breadcrumb level corresponds to an MCP endpoint, so what the web UI shows at a path is what an external MCP client attached to that same path would get.

Properties that fall out:

  • Threads are stamped with the scope they were created in; the thread list filters by current scope.
  • A URL becomes a shareable context, not just a screen.
  • Web, IDE clients (via #4263), and messaging channels are all just clients parked at different depths of the same path tree.
Share = mint an MCP URL for this scope

Every breadcrumb level carries a Share action that produces the MCP URL for exactly that scope, ready to paste into any external client (Claude Desktop/Code, Cursor, etc.):

  • Share at an agent → that agent's virtual-MCP URL.
  • Share at an org → the org's aggregate (/api/:org/mcp).
  • Share at the rootyour entire deco: the home scope's aggregate, i.e. all your orgs behind one URL.

Sharing exposes an endpoint, not access — the client still authenticates via OAuth (#4263) and lands with the caller's own role in each org. This makes the breadcrumb, the chat scope, and the connectable surface the same object at every zoom level: the UI is one client of a scope; Share hands the same scope to any other client.

Implementation option A: home org (composition over construction)

Instead of building a new "personal layer" with new primitives, represent the personal home as an org the user elects as home — and plug the user's other orgs into it:

  • Every user already has a candidate: signup creates a default org per user.
  • The home org holds memory about the user. It's not just an aggregator: as an org it already has memory/context primitives, so the personal scope accumulates who you are, what you're working on, and how you like reports — the thing none of the individual orgs should own.
  • Org-as-connection: an org's aggregate endpoint (/api/:org/mcp) is an MCP resource. Once #4263 lands, it supports external OAuth clients with DCR — which includes another Studio org's connection. With auth_mode: per_user, the connection holds the member's own token for the target org, so actions execute with that user's real role there. No god-scope, no shared identity.
  • Org-as-agent: wrap each org-connection in a virtual MCP with reporting instructions ("represent org X: report status, surface findings, escalate what needs attention"). The home org's chat then addresses orgs as sub-agents.
  • Home view as an MCP app: the actual home screen (org cards, needs-attention queue, morning brief) ships as an App UI inside the home org — iterable without touching studio core routing.
  • Scheduled reporting: the event bus already supports cron (EVENT_PUBLISH with cron); a recurring event in the home org can fan out "brief me" to each org-agent and post a digest.

This is pilotable with zero core changes: hand-wire one org with org-connections + reporting agents, evaluate, then productize (a "home org" setting, a first-class "Studio org" connection type, / rendering the home org, the breadcrumb topbar).

Implementation option B: dedicated personal layer (/api/me/* fan-out)

The alternative explored in #4038: a person-scoped route layer above orgs that fans out over listOrganizations(user) and calls each org's existing scoped endpoints, aggregating client-side first and server-side later. Never a god-scope — it only ever reads the caller's own memberships.

Tradeoffs

Dimension A: home org B: /api/me/* fan-out
New primitives None — orgs, connections, virtual MCPs, apps, event bus New route family + aggregator, personal thread storage
Time to pilot Days (hand-wireable today with API keys; per-user OAuth after #4263) Weeks (new backend surface before anything is real)
Identity Per-user OAuth per org-connection → correct attribution; API-key interim loses per-member attribution Session identity throughout
Isolation Unchanged — home org has exactly the powers of its member's memberships Unchanged by design, but a new cross-org code path to review
Freshness Pull (tool calls) + cron digests; no live push without extra wiring Same problem; aggregator could cache/poll centrally
Failure modes Aggregation-of-aggregations: double tool-name prefixing (mitigated by the short namespace codes in #4263) and possible connection cycles (org A ↔ org B) — needs a guard before productizing Fan-out latency/error handling in one place; N sequential org calls on landing
Org proliferation Blesses the existing default org rather than adding one Neutral
UX coherence Home is "just an org", so all existing chrome works; risk: home inherits org UI that doesn't fit a personal surface Purpose-built screen; risk: a second UI system to maintain
Long-term Doubles as Studio-to-Studio federation (orgs consuming orgs), useful beyond the home Purpose-built; doesn't generalize

Shared risks regardless of option:

  • Digest fatigue: daily reports are compelling for a week and then ignored. The home surface should show deltas and needs-attention items that can be dismissed, not a recurring report.
  • Breadcrumb ≠ toolbar: the topbar must stay a single line of path; if it accretes actions it recreates the toolbox this is meant to remove.
  • Mid-thread context switches: changing scope inside a running thread should either start a new thread or be an explicit re-scope action — silent context swaps would make thread history unreliable.
  • Single-org users must lose nothing: for them / should feel like today's org home with one extra crumb, not an empty cockpit.

Relationship to existing work

  • #4263 is on the critical path for option A's per-user org-connections (aggregate/virtual endpoints as OAuth resources, path-derived org role resolution, short tool namespaces for double-prefixing).
  • #3368 (org home redesign) is complementary: it improves the screen inside an org; this issue is about the level above it. The tile board is reusable for org cards.
  • #4038 (closed) proposed option B's fan-out model and a /me route; this issue supersedes its framing — the breadcrumb + home-org approach needs no new person-scoped route family to start.
  • #4230 (sidebar: team threads / my threads / agents) pairs naturally: scope-stamped threads are what that separation needs to be principled — "my threads" are root-scope threads, team threads are org-scope. Landing both gives the navigation model (breadcrumb = zoom, sidebar = threads within the current zoom) in one coherent step.

Suggested sequence

  1. Pilot (no core changes): one org hand-wired with org-connections + per-org reporting agents + a minimal home App UI. Validate the interaction model.
  2. Land #4263 (currently blocked on a missing module — see review notes there).
  3. Smallest core PR: stop the / redirect; render a root screen (org cards via client-side membership fan-out + composer scoped by an org chip); topbar breadcrumb derived from the route tree.
  4. Productize option A if the pilot holds: "home org" setting, "Studio org" connection type with per-user OAuth, cycle guard for org-to-org connections, scheduled digests via event bus cron.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with apps/mesh/src/web/index.tsx and the path-derived API resolution described in the issue, then read the related work in #4263, #3368, #4038, and #4230. The proposal is complete only after the pilot and implementation direction are validated, with agreement on the root screen, breadcrumb, scope handling, and home-org approach.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, backend-api-design, frontend, full-stack
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.