LearnNewsExamplesServices
Frontmatter
id17344
titleOne global transport leaf serves four servers whose transports have diverged
stateOpen
labels
bugaiarchitectureagent-ostech-debt
assignees[]
createdAtAug 18, 2026, 1:09 PM
updatedAtAug 18, 2026, 1:09 PM
githubUrlhttps://github.com/neomjs/neo/issues/17344
authorneo-opus-vega
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]

One global transport leaf serves four servers whose transports have diverged

Open Backlog/active-chunk-17 bugaiarchitectureagent-ostech-debt
neo-opus-vega
neo-opus-vega commented on Aug 18, 2026, 1:09 PM

Context

Memory Core and the Knowledge Base ran as local non-containerized processes before the Docker migration. That migration is complete and non-container versions can no longer exist (operator, 2026-08-18). Their stdio transport is therefore migration debt.

ai/configBase.mjs:519 still declares it as the default:

transport: leaf('stdio', 'NEO_TRANSPORT', 'string'),

Every working deployment overrides it — the containerized stack sets NEO_TRANSPORT=streamable-http on both servers. So the retired mode is what a boot gets by not choosing. That is not dead code; it is a trap with a live entry point.

The Problem

The obvious repairs both fail, for the same underlying reason.

Removing the leaf breaks a live consumer. transport is global — it sits beside debug and publicUrl, and all four MCP servers read it through BaseServer.mjs:738, which still constructs new StdioServerTransport() at :766. Neural Link connects over stdio by design (neural-link/Server.mjs:31: "the stdio MCP transport is connected EARLY"), and #15184 — its Streamable HTTP move — is open. Neural Link needs stdio today.

Flipping the default just relocates the trap. Set the global default to streamable-http and Neural Link becomes the seat that silently enters the wrong mode when someone omits NEO_TRANSPORT=stdio. Same defect, different victim.

The root cause is that one leaf now describes four servers that no longer agree. MC and KB are container-only HTTP. Neural Link is stdio until #15184. A single global value cannot express that, so some server is always one forgotten env var away from a mode it cannot serve.

A second consumer makes this more than a boot switch. communityBatchTool.mjs:30:

const areHostedCommunityToolsVisible = (transport = aiConfig.transport) => transport === 'streamable-http';

Tool visibility keys on transport. Deleting the leaf does not delete that decision — it makes it unconditional, which is a behavioural change to a capability gate rather than a cleanup.

The Architectural Reality

  • ai/configBase.mjs:519 — the global leaf, default 'stdio'; :516 documents both values as supported.
  • BaseServer.mjs:738 — shared read, plus a !this.aiConfig ? 'stdio' fallback for config-less local servers; :747 validates the two-value set; :766 builds the stdio transport.
  • memory-core/Server.mjs:404if (this.aiConfig.transport === 'stdio'), wrapping resolveStdioIdentity() and (at :411) the wake-subscription auto-bootstrap that #16310 exists because nothing invokes.
  • memory-core/Server.mjs:54 — JSDoc still calls stdio "(the default)".
  • HealthService.mjs — 12 references projecting stdioIdentity into the healthcheck identity block, which on HTTP is permanently {source: 'unresolved', bound: false} by construction and reports nothing about the request-bound identity that actually resolves.

Deliberately not in scope, and verified as such: localWakeAdapters.mjs:950 and ProcessSupervisorService.mjs:659 use stdio as spawn() pipe descriptors, and services.host.mjs:47 mentions it in prose. Those are unrelated to wire transport and a text search over stdio will surface them.

The Fix

  1. Make transport per-server rather than one global leaf. Precedent: #16184 separated Fleet MCP target selection from wire transport — the same split, already accepted once.
  2. Narrow MC and KB to streamable-http, so stdio is unrepresentable for them. No env var required, no trap in either direction, and memory-core/Server.mjs:404 becomes dead by construction rather than merely unused.
  3. Leave Neural Link on stdio; #15184 owns its move.
  4. Re-key communityBatchTool's visibility gate on the surviving per-server value so it cannot drift.

Once (2) lands, the stdioIdentity and healthcheck-identity cleanup becomes safe — but it should follow #16310, which needs to re-home the wake bootstrap out of that branch before it is deleted. Sequencing matters: deleting first would remove the code #16310 has to relocate.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
transport configBase.mjs:519 per-server; MC/KB accept streamable-http only global leaf honoured through a deprecation window config docs default is the retired mode
BaseServer validation BaseServer.mjs:747 rejects stdio for MC/KB, accepts for Neural Link current two-value set one set for four servers
hosted community-tool visibility communityBatchTool.mjs:30 keys on the per-server value unchanged while global exists gate silently unconditional if the leaf is deleted

Decision Record impact

none — no ADR governs transport selection; #16184 is precedent, not authority.

Acceptance Criteria

  • Transport is resolved per server; MC and KB cannot be configured to stdio.
  • Neither MC/KB nor Neural Link requires an env override to reach its only valid transport — the default is correct for each.
  • communityBatchTool visibility keys on the per-server value and its behaviour is unchanged on a streamable-http MC.
  • Existing NEO_TRANSPORT=streamable-http deployments keep booting, or the required deployment change is named in the migration note.
  • Red-proof: a config attempting stdio on MC fails closed with a named error; on main it boots and enters resolveStdioIdentity(). A test asserting streamable-http boots would pass on main and prove nothing.
  • spawn() pipe usages of stdio are untouched — asserted, not assumed, since they share the search term.

Out of Scope

  • Neural Link's transport migration — #15184.
  • Re-homing the wake-subscription bootstrap — #16310.
  • Deleting stdioIdentity and replacing the healthcheck identity block — follows #16310; noted there rather than duplicated here.
  • Process-level stdio pipe descriptors.

Avoided Traps

  • Deleting the leaf now. Breaks Neural Link, whose HTTP move is still open, and silently makes a capability gate unconditional.
  • Flipping the global default. Moves the trap from MC/KB to Neural Link without closing it. I proposed this before checking the consumers and it is wrong.
  • Treating every stdio occurrence as debt. Three of the matches are spawn() descriptors; an over-scoped sweep would break process supervision.
  • Deleting memory-core/Server.mjs:404 as dead code. It is reachable on any boot that omits NEO_TRANSPORT, and it guards functionality #16310 wants to keep.

Related

#16310 (wake bootstrap gated on stdio — the consumer that must move first) · #15184 (Neural Link Streamable HTTP) · #16184 (precedent: transport separated from target selection) · #15188 (transport value renamed to streamable-http) · #16205 (provisioning templates still emitted the retired stdio shape after the 9-seat flip)

Live latest-open sweep: latest 20 open checked 2026-08-18T11:07:04Z, plus a six-term state=all title sweep and an A2A recency scan over the last 12 messages — nearest neighbours #16310 / #15184 / #16184, none covering per-server transport resolution; no in-flight claim.

Origin Session ID: 9ccc2fa1-8843-4796-8e85-5e151c0392d2

Retrieval Hint: query_raw_memories("global transport leaf stdio default retired MC KB streamable-http per-server Neural Link 15184")