Files
stack/docs/plans/2026-10-20_multi-instance.md

5.0 KiB

Brief: several Mosaic Stack instances on one host

Sage, 2026-10-20, for Mos's order mos-0974 (MVP, DECISIONS Q176-Q178, Vikunja #414). Gitea #1688. T1 item 1. Owner Darkwing, reviewer Filbert, gate owner Sage. Base: refactor at the brief commit. This row blocks both MVP cases: Case 1 (Jason's software factory) and Case 2 (Melanie's creative writing hub) run as two instances on one homelab host, beside the current mosaic-stack test instance.

Design first. The first deliverable is agents/darkwing/work/queue-190/DESIGN.md. Filbert reviews it before any code, as Q175 did. Then the build follows the design, with tests and mutants.

The goal

Two or more instances run side by side on dragon-lin. Each one has its own config file, data root (store, broker.sock, host.lock, state, runs, sessions, workspaces), businesses, bus unit, Console unit and port, tester port, Discord unit, Claude session env file, stack-active pointer and activation log. No instance's commands, units or tools address another instance's state by default or by mistake. On one uid this is addressing only, not a security boundary: a same-uid process can still read another instance's files. Isolation against that is row 190b (#1693, mos-0986), one Linux user per instance. The current instance keeps working unchanged: its config path, data root, unit names and ports stay as they are, and no re-pin is needed for it.

Known single-instance points (GAP-MAP.md, measured at 9ae8c73ac)

The design starts with a full inventory. Grep every script and package, not only these:

  • packages/cli/systemd/mosaic-bus.service.in: mosaic-bus@<business> sets no MOSAIC_CONFIG and reads the fixed EnvironmentFile=-%h/.config/mosaic-dev/secrets/claude-session.env.
  • packages/webui/systemd/mosaic-webui.service.in and scripts/webui-service.sh: one mosaic-webui.service.
  • packages/discord/systemd/[email protected] and scripts/discord-service.sh.
  • scripts/stack-copy.sh:8 hardcodes BUSINESS=mosaic-stack, and :95 reads mosaic-webui.service.
  • scripts/stack-release.sh: unit names, the Console port and the tester port default, COPIES (:28), the pointer under $DATA/state (:1474), and rehearse.
  • scripts/mosaic-config.mjs, scripts/bootstrap.sh, scripts/reset.sh, scripts/land-work.sh, packages/webui/src/cli.mjs, packages/business/src/{business,vars}.mjs, and packages/conversation/src/guard.mjs each name the default config path or data root.

What the design must settle

  1. Instance identity. How a name maps to its config file and how every tool picks the instance (an --instance NAME flag, MOSAIC_CONFIG, or both), and what happens with neither (the current default, unchanged). Name rules, and refusal of a name that collides.
  2. Config canon. AGENTS.md invariant 2 says ~/.config/mosaic-dev/config.json is the sole system config, created only by scripts/bootstrap.sh. Several instances mean several config files. The design proposes the exact wording change and how bootstrap creates an instance config without overwriting one. Mos rules on the canon change before the build.
  3. Unit names and templates. The bus, Console and Discord units per instance, how MOSAIC_CONFIG and the Claude env file reach each unit, and how the current unit names stay valid for the default instance.
  4. Ports. Each instance's Console and tester ports, with a refusal when two instances claim the same port.
  5. Stack copies and the pointer. Whether copies are shared between instances (read-only trees, so sharing may be fine) and how the pointer, activation log, backups and flags_drift stay per instance. stack-release.sh and stack-copy.sh take the instance.
  6. Secrets. The per-instance Claude session env file and the secrets slot root, resolved from the instance's config dir (the Q175 design resolves its slot the same way: <configDir>/secrets/mosaic-stack/<slot>).
  7. Isolation proof. What the tests show: two instances started side by side on stand-in units and scratch roots, each reads only its own config and data root, a command aimed at one never touches the other, and the default instance's paths and unit names are byte-identical to today.

Boundaries

  • No test touches ~/.mosaic-dev, ~/.config/mosaic-dev, the live units or a slot file. Use the stand-in systemctl, units and scratch roots that packages/cli/tests/stack-release.test.mjs already builds.
  • Standing up a real second instance on dragon-lin is a later window under Q173, with a backup first and a STATE report after. Exposing a new Console through the tunnel or Traefik changes who can reach it from outside, so it waits for Jason. SSO (Q175, #412) comes first for any instance a second user can reach.
  • Merge-chaining (Q177): rows that need instances (human users, kill switch, lifecycle) may build on this row's candidate. This row keeps its own review and gate.

Gate

Filbert approves DESIGN.md, then the manifest. The cli suite and every scripts/test-*.sh are green on Sage's gate, and the design's mutants are killed.