Skip to content

feat: serve the BitMind gateway from the server process - #3

Merged
IshmaelRogers merged 1 commit into
mainfrom
feat/bitmind-gateway-in-process
Sep 3, 2026
Merged

feat: serve the BitMind gateway from the server process#3
IshmaelRogers merged 1 commit into
mainfrom
feat/bitmind-gateway-in-process

Conversation

@IshmaelRogers

Copy link
Copy Markdown

Next slice of the enclave work (bit-mind CopilotKit#20): the BitMind gateway now boots with the OpenBot server, and server/src/bitmind/main.ts retires.

Why now

The gateway was a separate process for one reason, stated in main.ts: the server refused to start without the Intelligence contract, and the enclave has none. PR #2 removed that reason. Two processes with two configurations, two supervision units and two shutdown paths is a cost the enclave was paying for a constraint that no longer exists.

What changed

  • server/src/bitmind/mount.tsbitmindGatewayFrom(env) returns a gateway or nothing, and serveBitmindGateway(mount) binds it.
  • server/src/index.ts mounts it after the server's own serve() and stops it on SIGINT/SIGTERM alongside the listeners.
  • server/src/bitmind/main.ts and the bitmind:dev / bitmind:start scripts are gone.
  • docs/bitmind-gateway.md documents the variable table and the port decision.

Two decisions worth reviewing

A listener of its own, not a path on the server's port. The gateway keeps BITMIND_GATEWAY_HOST/PORT (loopback, 4310) rather than answering under the server's port. The server port is the published one — an ingress, a compose mapping — and adding /bitmind/v1 to it would export a service-token-authenticated relay wherever that port goes, without anyone choosing it. bit-mind's openbot-single-host-enclave.md requires the gateway be reachable only on loopback or an approved private path; this keeps that property a configuration fact rather than an operator's responsibility to notice. The subprocess boot test asserts both halves: the gateway answers on its own port, and the server's port returns 404 for /bitmind/v1/attestation with the service token in hand.

All or nothing on configuration. Setting no BITMIND_* variable mounts nothing — an ordinary deployment is unaffected and needs no opinion. Setting any of them validates all of them, so BITMIND_GATEWAY_PORT alone, or a service token with no agent token, is a boot failure naming the missing variable. The alternative — boot, then refuse every relay — makes a deployment mistake look like BitMind's fault.

The mount is not gated on OPENBOT_RUNTIME_MODE=standalone: the gateway relays to a downstream AG-UI agent, which is orthogonal to whether this server also serves chat. The mode gates chat, threads and routines; it has no bearing on whether this deployment is the one BitMind talks to. Say if you would rather it refuse outside standalone.

Verification

Local gate on 519a875 (Actions still blocked account-wide by org billing):

FORMAT=ok LINT=ok TYPECHECK=ok
bun test server/tests --timeout 60000 → 1698 pass, 0 fail
cd agent-langgraph && bun test tests/  → 31 pass, 0 fail
bun test tests/helm-standalone.test.ts → 1 pass, 0 fail

New tests: server/tests/bitmind-mount.test.ts (10) pins the gate — nothing mounted from a bare environment, each half-configured shape a boot failure, the listen defaults, a malformed port refused, and the mounted gateway is the real one (401 without the token, an honest isolated_computers: false with it). server/tests/standalone-boot.integration.test.ts gains a second real-process boot for the claims only a boot can settle.

One thing I got wrong and want to name: my first edit inserted the mount block twice — the stray copy landed inside resolveRequestActor, which would have started a new listener on every authenticated CopilotKit request. Lint caught it as an unused binding; the tests did not, because standalone has no path through that function. Fixed before the commit, and the reason the gate is run before every push rather than after.

Not in this slice

  • No Helm surface for the BITMIND_* variables. The enclave runs the server directly per bit-mind's enclave note, not through the chart, and chart values need their own CI target and key check. Worth doing if the gateway ever ships in a charted deployment.
  • Still owed by a maintainer with workflow scope: standalone in the chart job's matrix.target in .github/workflows/ci.yml.

Refs bit-mind#20. Next fork slice: tool descriptors and interrupt emission, so bit-mind's approval gate has a real producer.

Standalone mode removed the reason the gateway ran on its own: the server
refused to boot without the Intelligence contract, and the enclave has none.
It now boots with the server, so the enclave supervises one process and one
signal stops both, and src/bitmind/main.ts retires.

On a listener of its own, not a path on the server's port. The server port is
what an operator publishes; putting a service-token-authenticated relay on it
would export the enclave's private surface wherever that port goes without
anybody choosing it. Loopback stays the default.

Setting none of the BITMIND_* variables mounts no gateway; setting any of them
requires the rest, so a half-configured enclave fails at boot rather than
answering BitMind with a server that cannot relay.
@IshmaelRogers
IshmaelRogers merged commit 5d0314f into main Sep 3, 2026
@IshmaelRogers
IshmaelRogers deleted the feat/bitmind-gateway-in-process branch September 3, 2026 04:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants