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
- 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.
- 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.
- 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.
- 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.
- 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?”
- 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.
- 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.
- 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.