Agent-readable wiki

Multica Mental Model Wiki

A mental model of how Multica makes humans and agents first-class peers through workspace-scoped data, strict state ownership, package boundaries, polymorphic execution, and real-time invalidation — enabling the same logic to run safely on web and desktop.

Pages

  1. The Mental ModelThe unifying picture: agents are teammates (not tools), multiplexing work like Multics for small human+AI teams. Workspace is the atomic unit of isolation and visibility. Server data lives only in TanStack Query; client UI state lives only in Zustand. WS events never write state — they only invalidate. The same business logic runs on web and desktop because three package layers plus thin platform adapters enforce the boundaries.
  2. The State Ownership ContractReact Query owns every server fact (issues, agents, members, inbox, workspace list). Zustand owns only ephemeral client facts (current tab, filters, drafts, modal open/closed). Copying a query result into a store creates two drifting sources of truth. WS listeners only call queryClient.invalidateQueries; they never call store setters. Auth and workspace stores are the sole exceptions allowed to call api.* directly because queries cannot run until they exist. Selectors must return stable references or the UI will re-render forever.
  3. Workspace Isolation & Multi-TenancyEvery row is filtered by workspace_id. Every query key must contain the current wsId; changing workspace automatically swaps the visible data because the cache key changes. X-Workspace-ID header routes requests. setCurrentWorkspace(null, null) must be called explicitly before leave/delete or the next render will see stale data and hard-reload. Desktop tabs are grouped per workspace; cross-workspace push is rewritten by the navigation adapter into a workspace switch, never a navigation inside the current tab router.
  4. The Package Contract: Core, UI, ViewsThree layers with iron boundaries. packages/core exports raw .ts/.tsx (no build step), contains every Zustand store and the API client, and forbids react-dom, localStorage, and process.env. packages/ui contains only pure Base UI + shadcn components and may never import @multica/core. packages/views contains all business screens and may never import next/* or react-router-dom; it receives routing via NavigationAdapter injected by the platform layer. Each consuming app supplies its own thin adapter (web/platform/navigation.tsx, desktop/.../navigation.tsx) and wraps its root with CoreProvider. This is why the identical page component works in both apps and survives HMR.
  5. Backend Safety RailsThree UUID rules prevent silent zero-row deletes and 204 responses that lie. Resource path params that can be human slugs (MUL-123) or UUIDs must go through loadIssueForUser / loadAgentForUser first; the resolved .ID is then used for all subsequent queries. Pure-UUID inputs from the wire use parseUUIDOrBadRequest which returns 400 on failure. Trusted round-trips (sqlc results, test fixtures) may use MustParseUUID which panics — a deliberate signal that unvalidated user input reached it. WS listeners (activity, autopilot, runtime sweeper) exist only to invalidate queries; they never mutate client state. Every handler that performs a write must ask “where did this UUID come from?”
  6. Navigation, Tabs & Route IsolationuseNavigation().push and AppLink are the only allowed navigation primitives in shared code. Desktop distinguishes three route categories: session routes (real tabs under WorkspaceRouteLayout), transition flows (create workspace, accept invite — rendered as WindowOverlay state, never real routes), and error states (never rendered; stale tabs are healed by dropping the tab group). The navigation adapter detects cross-workspace push and calls switchWorkspace instead of letting the memory router navigate inside the wrong tab. Web uses ordinary Next.js routes plus searchParams; desktop memory router + tab-store keep each workspace’s tabs isolated by construction.
  7. Agent Execution & Runtime ModelAssignee is polymorphic (assignee_type + assignee_id). An agent can be assigned exactly like a member; it claims work, streams progress, posts comments, and may create new issues. Two runtime surfaces exist: local daemon (CLI-managed, talks to desktop via daemon-ipc-bridge) and cloud runtimes. The daemon advertises available CLIs; the sweeper reaps dead runs. Every completed execution becomes a reusable skill stored under the workspace; future agents can invoke the same skill without re-learning. Health derivation and version detection live in packages/core/runtimes so both apps see identical status without duplication.
  8. Core Invariants & Safe Evolution RulesThe system stays coherent only while these hold: (1) server facts never live in Zustand, (2) WS events only invalidate, (3) every workspace-scoped query keys on wsId, (4) mutations are optimistic then invalidate, (5) core never touches react-dom or framework routers, (6) views never import next/* or react-router, (7) UUIDs from the wire are validated before any write query, (8) desktop destructive workspace ops call setCurrentWorkspace(null) before the mutation. Violating any produces drift, white-screens on older desktop builds, or cross-workspace data leaks. To change safely: add the Zod response schema in the same PR, write a test that feeds it malformed data, run make check, and only then consider the contract stable. These rules are the price of a desktop app that is always older than the server it talks to.

Complete Markdown

The complete agent-readable Markdown files are published separately from this HTML page.