Files
msd-core/docs/reference/host-integration-interface.md
Jakub Zych a9a7a328e6 refactor: hard-fork GSD -> MSD (Make Software Done)
Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD
across contents and paths, upstream package/repo coordinates -> @golem15/msd-core
and golem15com/msd-core. Deep links into upstream history, sibling upstream
packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is.

Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line,
package/plugin identity, regenerated lockfile, install-tree fixtures, derived
registries and benchmark baseline; migration checksum baseline re-locked
(MSD keeps its own install state, so no install had applied the old sums);
sort-order and regex-escaped expectations in tests adjusted.
2026-10-06 01:47:40 +02:00

5.3 KiB

Reference: the Host-Integration Interface

This is the normative reference for the MSD Host-Integration Interface (ADR-1239) — the versioned, negotiated contract over which any host embeds MSD's orchestration loop. The published surface is the SDK entry (src/host-integration-sdk.cts); this document specifies every symbol on it.

The governing principle: every axis value a host declares must come from that host's authoritative documentation. Where docs are silent, the host declares the undocumented sentinel and the engine degrades fail-closed — it never assumes a capability.


Protocol version

PROTOCOL_VERSION (a positive integer) is the interface version. It governs the negotiated capability set; see the versioning policy for what a bump means.

The nine negotiated axes

HOST_INTEGRATION_AXES is the closed vocabulary. Each axis takes a documented value (or the undocumented sentinel):

Axis Values
embeddingMode imperative | declarative
commandSurface slash-file | slash-programmatic | slash-toml | palette | prose-only
dispatch struct: { namedDispatch, nested, maxDepth, background, subagentToolkit, backgroundDispatch, isolation, maxConcurrency }
modelMode active | passive
hookBus host | engine | none
stateIO filesystem | sandboxed-storage | session-log-append
transport mcp | native-extension
runtime node | bun | sandboxed-web | python | go | rust | electron | other
effortSurface argv | none

dispatch.isolation (harness-worktree | orchestrator-worktree | none, added #2584) and dispatch.maxConcurrency (a positive integer, added #3673) are two dispatch sub-fields with their own per-field fail-closed floors — unlike the other five dispatch fields, they are not gated on namedDispatch. dispatch.maxConcurrency degrades to 1 (strictly sequential) on anything other than a positive safe integer (missing, the undocumented sentinel, zero, negative, fractional, an unsafe integer, or a non-numeric type); there is no negotiated host/engine reduction the way maxDepth has one — the resolved value passes through unchanged. Its live transport (MSD_DISPATCH_MAX_CONCURRENCY) is read only at the CLI query layer (msd-tools query dispatch-capacity), never inside this pure negotiation module, and takes precedence over the descriptor value, which in turn takes precedence over the 1 fallback.

Classification + negotiation

  • profileOf(axes) → 'programmatic-cli' | 'declarative-cli' | 'ide' | null.
  • negotiateHostCapabilities(host, engine) → { protocolVersion, effective, points, warnings } — the in-process negotiation. Pure; never throws.
  • handleHandshakeRequest(request) / buildHandshakeRequest(descriptor) — the serialized (out-of-process) form of the same negotiation, JSON-safe across a wire boundary. The two are consistent: a serialized request yields the same effective axes as the in-process call.
  • degradationFor(point, axes) → { level, fallback, unknown? } for one of the six interface points (command | dispatch | model | hooks | state | artifact).
  • hookEventSurfaceFor(hookEvents) → the host-fireable hook events for a dialect ('claude' | 'gemini' | 'opencode-subset'), or null if unknown.
  • shouldFlattenDispatch(dispatch) → true when the orchestrator must run inline (fail-closed).

The engine adapters

All five satisfy a common { kind, runtime, install, uninstall } shape and are constructed fail-closed (they throw if a required host primitive is absent):

Adapter Factory Selected by
Embedding (declarative) createDeclarativeAdapter({runtime}) embeddingMode: 'declarative'
Embedding (imperative) createImperativeAdapter({runtime}) embeddingMode: 'imperative' (also exposes the composed registry)
Model createModelAdapter({modelMode}, {sendRequest?}) modelMode ('active' needs a host sendRequest)
Hook bus createHookBus({bus}, {hostEmit?}) hookBus ('host' needs a host hostEmit)
State IO createStateIO({io}, {backend?}) stateIO ('sandboxed-storage'/'session-log-append' need a host backend)

The declarative + imperative adapters delegate install/uninstall in-process to the same installRuntimeArtifacts engine function bin/install.js uses, so adapter output is byte-identical to a first-party install (gated by the differential attribution check, tests/emitted-attribution.test.cjs, ADR-2719).

Profiles

PROFILE_BASELINES fixes the three reference profiles:

Profile Baseline
programmatic-cli imperative, slash-file, host bus, filesystem, mcp, node
declarative-cli declarative, slash-file, host bus, filesystem, mcp, node
ide imperative, palette, active model, engine bus, sandboxed-storage, sandboxed-web

See also