Skip to content

Add rec serve --host to choose the listen address - #31

Open
wcole3 wants to merge 3 commits into
recursive-oss:mainfrom
wcole3:serve-host-arg
Open

wcole3 wants to merge 3 commits into
recursive-oss:mainfrom
wcole3:serve-host-arg

Conversation

@wcole3

@wcole3 wcole3 commented Oct 24, 2026 •

Copy link
Copy Markdown

Summary

rec serve always listened on 127.0.0.1, so the model was only reachable from the Mac running it ( #8 ). Folks in that thread had a copy clever solutions, this just adds the host argument. Feel free to close if you had plans to implement in house.

This adds a --host ADDRESS option so the server can listen on another address. For example, rec serve --host 0.0.0.0 lets other machines on a trusted network use the Mac's model from pi, OpenCode or any OpenAI- or Anthropic-compatible client. A warning is written to the terminal on serve that you should only use this on trusted networks.

The default is unchanged. Without --host, rec still listens only on 127.0.0.1.

🤖 Spec planned by Opus 5.5; implemented by Flint :)

What changed

  • New option: rec serve --host ADDRESS takes an IPv4 or IPv6 address, such as 127.0.0.1, 0.0.0.0, 192.168.1.5 or ::.
    • Hostnames such as localhost are rejected. Go and Python can resolve a name to different addresses, so the port check, the readiness probe and the engine could disagree.
    • Zoned IPv6 addresses (fe80::1%en0) are also rejected, because they can't appear unescaped in a URL.
    • Equivalent spellings are normalized: 0:0::0 becomes ::, and ::ffff:127.0.0.1 becomes 127.0.0.1.
  • Listen address and client URL are now separate. The status has a new listen field for the address the engine binds to. endpoint stays the URL that clients on the same Mac use.
    • For 0.0.0.0 and ::, the endpoint is the loopback address (127.0.0.1 or [::1]), so rec launch, rec launch --args and rec status work unchanged.
    • A running server is reused only if both the listen address and the endpoint match.
  • Engine: rec-mlx takes --host and binds to it. Python's ThreadingHTTPServer is IPv4-only, so the Server subclass was modified to pick the address family, and for :: it also accepts IPv4 clients by clearing IPV6_V6ONLY before binding.
  • Clients: validateClientStatus now accepts any IP-address endpoint instead of only 127.0.0.1. It still requires http, a port and /v1.
  • Ready panel: for 0.0.0.0 or ::, it shows a Listen row. For any non-loopback address, it prints a warning: the server is reachable from the network over unencrypted HTTP.
  • Docs: rec serve --help and the README document --host. The README has a collapsible section with models.json (pi) and opencode.json (OpenCode 2.x) configs for connecting from another machine.
  • Test fakes: cli/testdata/fake-rec-mlx and tests/data/smoke-mlx-server.py accept --host.

Security

Requests still need the bearer token from rec auth token. With a non-loopback --host, the token and all traffic cross the network over plain HTTP. The ready panel warns about this, and the README says to use it only on trusted networks.

Testing

  • go test -race ./... and go vet ./... pass.
  • make test-engine passes the engine pytest suite (68 tests) and the smoke test.
  • New tests cover:
    • host parsing and normalization;
    • the client endpoint for wildcard, IPv6 and LAN addresses;
    • not reusing a server on a different host;
    • --host reaching the engine;
    • client endpoint validation;
    • the engine binding IPv6 and dual-stack ::, accepting both IPv4 and IPv6 clients.
    • Renamed TestContextReachEngine to TestHostAndContextReachesEngine to reflect new behavior.
  • Manual: I loaded the README's pi and OpenCode configs in the pinned pi 0.73.1 and OpenCode 2.0.29, and both list rec/flint.
  • Known issue, not from this change: tests/protocol_test.py fails intermittently at its queue-full check with "Connection reset by peer". It fails about as often with main's server.py as with this branch (3 of 5 runs each). Possibly the issue described in ( Raise the HTTP server's listen backlog to 32 #27 )
Instructions for running locally

These steps build this branch and start the model so other computers on your network can use it. I've tested this to confirm it doesn't impact with an actual install of rec.

1. Download this branch

cd ~
git clone https://github.com/wcole3/rec.git
cd rec
git checkout serve-host-arg

2. Build it (this assumes you've setup build tools)

This takes about 5 to 15 minutes.

cd ~/rec
rm -rf .build/bundle-mlx
uv python install 3.12.12
PY_ROOT="$(uv python dir)/cpython-3.12.12-macos-aarch64-none"
"$PY_ROOT/bin/python3.12" packaging/build-mlx-runtime.py --python-root "$PY_ROOT" --output .build/bundle-mlx
python3 packaging/package.py --runtime-dir .build/bundle-mlx --manifest packaging/flint-int4-mlx.json --output .build/rec-flint

The last line it prints should end in .build/rec-flint. Name chosen to avoid conflict with tests, same binary as rec.

3. Start the model

If you already use rec, stop its server first. If this prints an error saying no instance is reachable, nothing was running, which is fine.

rec stop

Then start the test build:

cd ~/rec
.build/rec-flint serve --host 0.0.0.0
  • First run: it downloads the model, about 23 GB. If rec is already installed and has the model, it reuses it.
  • Firewall prompt: if macOS asks whether to allow incoming network connections, click Allow.
  • When it's ready: you'll see flint is ready. in a box, followed by a warning that the server is reachable from the network. Leave this window open; closing it stops the model.

4. Get the address and key

cd ~/rec
ipconfig getifaddr en0
.build/rec-flint auth token

5. Connect from another computer

On another Mac or Linux computer on the same network, open a terminal. In the command below:

  • replace ADDRESS with the address from step 5;
  • replace KEY with the key from step 5.

Then run it:

curl http://ADDRESS:11500/v1/chat/completions \
  -H "Authorization: Bearer KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"flint","messages":[{"role":"user","content":"Say hello."}],"max_tokens":512}'

To use the model from pi or OpenCode on that computer, follow Connect pi or OpenCode on another machine in the README.

6. Stop and clean up

  • Stop the model: click the first Terminal window and press Control-C.
  • Remove the test build: run rm -rf ~/rec.
  • What stays behind: the downloaded model, in ~/.local/share/rec, where a regular rec install also keeps it.

@strin strin left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks—this is the host-binding approach I'd prefer for #8. Listen/client separation, IP normalization, instance matching and IPv6 URL formatting look useful; five targeted Go tests passed locally.

Before merge, please make the direct rec-mlx serve --host entry point enforce the intended auth policy: require a nonempty API-key file for non-loopback binds, retaining loopback behavior. Add a test rejecting that configuration before constructing the model and a mocked bind test with an explicit key. That follow-up passed 20 local auth/defaults/serving tests; the wildcard dual-stack test was deliberately excluded. Real remote/IPv6 qualification remains pending.

@wcole3

wcole3 commented Nov 5, 2026

Copy link
Copy Markdown
Author

Added logic for the auth check on serve with --host. The --host validation requires a IP address; a minor pain point might be someone expecting to be able to run with --host localhost that will give an error message which is hopefully informative enough to nudge towards --host 127.0.0.1, but if you would like that case to be handled explicitly please let me know.

Additional tests added for cli behavior in test_cli_defaults but I can relocate them if desired since they are not really testing "defaults", I added them there primarily to reuse the existing Mock logic.

Also added a short addition in the status printout when listening. Please let me know if you would like any additional changes.

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