Skip to content

Package storage

The Host has two on-disk package stores, both node_modules-style:

ts
// projects/matrix-3/packages/host-service/src/types.ts:11-14
interface IHostPackageStorageConfig {
  readonly globalDir: string;  // default "packages/global"
  readonly systemDir: string;  // default "packages/system"
}

Resolved as <home>/packages/global/node_modules/... and <home>/packages/system/node_modules/....

Why two stores

StoreOwnerPurposeSurvives hivecast install?
systemthe wrapperBundled defaults: system, host-control, gateway, web, edge, director, chat, inference-settings, flowpad, smithersreseeded every install
globalthe userAnything installed via matrix install <ref>preserved across installs

The wrapper's hivecast install and hivecast seed write into system/ only; user installs go into global/. This means an upgrade (apt upgrade hivecast or hivecast install on top of an existing home) refreshes the bundled defaults without touching the user's add-ons.

Resolution order

When matrix up <package-ref> (or any cli.ts resolver) needs a package directory, it tries — in this order (projects/matrix-3/packages/host-service/src/cli.ts:432-449):

  1. <home>/packages/global/node_modules/<package-ref>/package.json
  2. <home>/packages/system/node_modules/<package-ref>/package.json
  3. require.resolve('<package-ref>/package.json') (Node module resolution from the Host's own node_modules — this is how dev checkouts work)

The <package-ref> can be @open-matrix/foo, @matrix/foo, or just foo. Aliases in packageTargetRefs (cli.ts:416-430) translate ambiguous targets:

  • system -> @open-matrix/system
  • chat -> @open-matrix/chat -> @matrix/chat -> chat
  • @open-matrix/foo -> @matrix/foo (and vice versa)

This forgives operators who type the wrong scope.

Layout

<home>/packages/system/
  node_modules/
    @open-matrix/
      system/                      # the system package
        package.json
        matrix.json
        dist/
      host-control/
      system-gateway-http/
      matrix-web/
      matrix-edge/
      director/
      chat/
      inference-settings/
      flowpad/
      smithers/

<home>/packages/global/
  node_modules/
    @open-matrix/
      claude-agent/                # installed by `matrix install`
      codex-agent/
    @some-org/
      custom-package/

The wrapper's hivecast seed rsync's dist/node_modules/ into packages/system/. User matrix install writes into packages/global/node_modules/ via the Matrix package CLI.

Required package shape

For the Host to start a package as a default runner, the package must contain at least one of (assertPackageSupportsDefaultRunCommand, cli.ts:451-465):

  • matrix.service.json — declares a service-style runtime entry point
  • matrix.json with a webapp block — declares a browser-served app

If neither exists, matrix up refuses with:

Cannot start "<target>" with the default Host runner because <packageDir>
does not declare matrix.service.json or matrix.json webapp metadata.
Install or build a Host-runnable Matrix package, or pass an explicit
runtime command after --.

You can still spawn arbitrary processes by passing -- followed by the exact command:

bash
matrix up /path/to/package -- node my-runner.js --port 8080

The Host's role is supervision, not opinion: if you give it a command, it runs it.

Layout creation

HostStateStore.ensureLayout (host-state-store.ts:55-68) creates these directories on every start and init. They will exist as empty node_modules-shaped trees even on a freshly-init'd home; populating them is the wrapper's or user's job.

Customizing storage paths

json
// host.json
{
  "packageStorage": {
    "globalDir": "vendor/global",
    "systemDir": "vendor/system"
  }
}

Both are joined relative to <home>. Absolute paths are not supported. Cross-Host package sharing is not a feature; if you want shared packages, use a shared parent <home>.

See also