* test(#3673): add failing tests for dispatch.maxConcurrency axis and dispatch-capacity CLI route Extends tests/host-integration.test.cjs with negotiateHostCapabilities maxConcurrency negotiation coverage (test matrix rows 1-15, including a fast-check property test) and a new #3673 dispatch-capacity CLI route describe block spawning the real gsd-tools.cjs (rows 16-25). Extends tests/host-integration-validator-parity.test.cjs with an all-19-descriptor maxConcurrency presence/validity sweep (row 26) and adds a hostile-input validator test to host-integration.test.cjs (row 27). The dispatch.maxConcurrency field does not exist yet, so these tests fail. * feat(#3673): add dispatch.maxConcurrency axis, negotiation, validator parity, and the dispatch-capacity query Adds a numeric dispatch.maxConcurrency sub-field to the Host-Integration Interface (ADR-1239 Phase 1), following the existing dispatch.isolation sub-field pattern: DispatchCapability interface, SAFE_DEFAULTS/PROFILE_BASELINES floors, and a negotiateHostCapabilities branch that passes through a positive safe integer and fails closed to 1 otherwise (no engine-side reduction, per the design doc's explicit rejection of a min(host,engine) rule). capability-validator.cjs gains parity validation for the new field (optional, positive safe integer or the "undocumented" sentinel — mirroring isolation's "added after existing descriptors" treatment). gsd-tools.cjs gains a new `query dispatch-capacity` route, a pure-read sibling of `dispatch-isolation` with no side effects: live env (GSD_DISPATCH_MAX_CONCURRENCY) > descriptor > fallback-to-1 precedence. All 19 capabilities/*/capability.json descriptors now declare dispatch.maxConcurrency: claude carries the one cited value (20, per code.claude.com/docs/en/sub-agents); the other 18 carry "undocumented" (not yet researched for this axis). * docs(#3673): document dispatch.maxConcurrency and add its citation row to the capability matrix Updates docs/reference/host-integration-interface.md's dispatch struct entry (also backfilling the previously-undocumented isolation/backgroundDispatch sub-fields found stale in the same table) and adds fail-closed/live-transport precedence prose for the new maxConcurrency field. Adds a dispatch.maxConcurrency row (with citation) to all 19 host sections in docs/reference/host-integration-capability-matrix.md — required for tests/host-integration-descriptors.test.cjs's kimi-code matrix-parity check, which asserts every declared dispatch sub-axis is documented there. Updates docs/how-to/add-or-update-a-host-integration.md's dispatch checklist and example descriptor block to mention maxConcurrency (and, likewise backfilling a stale gap, isolation/backgroundDispatch). * fix(#3673): extract shared maxConcurrency validator, drop dead reserved-name check Exports isPositiveSafeInteger from src/host-integration.cts as the single source of truth for the dispatch.maxConcurrency positive-safe-integer contract; negotiateHostCapabilities and gsd-tools.cjs's routeDispatchCapacity now both call it instead of independently reimplementing the same predicate. Also removes the __proto__/constructor/prototype reserved-name branch from capability-validator.cjs's maxConcurrency check — copy-pasted from the string-enum fields above it, but unreachable for a numeric field (the generic positive-safe-integer branch already rejects any string) and absent from maxDepth, the field the code's own comment claims to mirror. --------- Co-authored-by: sim <sim@local>
94 lines
5.3 KiB
Markdown
94 lines
5.3 KiB
Markdown
# Reference: the Host-Integration Interface
|
|
|
|
This is the normative reference for the GSD Host-Integration Interface (ADR-1239)
|
|
— the versioned, negotiated contract over which any host embeds GSD'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](../explanation/interface-versioning-policy.md)
|
|
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 (`GSD_DISPATCH_MAX_CONCURRENCY`) is read only at
|
|
the CLI query layer (`gsd-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
|
|
|
|
- [How-to: author a host-plugin](../how-to/author-a-host-plugin.md)
|
|
- [Tutorial: embed GSD in a new host](../tutorials/embed-gsd-in-a-new-host.md)
|
|
- [Interface versioning policy](../explanation/interface-versioning-policy.md)
|
|
- ADR-1239 (the design) · `docs/reference/host-integration-capability-matrix.md` (per-host values + citations)
|