# remchild — remote child plan `remchild` is a separate module that runs on a remote machine. Veskslop feeds it work and it feeds results back. Planned uses: STT (local SAM/VOX stack or an AI model), TTS, later video (a window or an MPV file), and finally the full desktop control app. Status: PoC on branch `remchild` (client + server + naming). No Web UI yet. ## What the PoC does | Piece | Path | Notes | | --- | --- | --- | | Client | `client/main.c` | C11, libc only, no runtime dependencies. Linux. | | Word lists | `words.json`, `client/words.h` | Two lists of 128 words. The header is generated for the client. | | Server | `server/server.mjs` | Node, no dependencies. Proves the listener, allowlist, heartbeat, ping test, and history. | | Test | `client/remchild --once` | One hello, one ping, one pong, exit. | Build and run: ```sh cc -O2 -o remchild/client/remchild remchild/client/main.c node remchild/server/server.mjs --port 8799 --ips 192.168.1.5 ./remchild/client/remchild --server 192.168.1.5:8799 ``` The client prints its assigned name, connects, and shows `p` (ping test) and `q` (quit). The server prints each connect, state change, and disconnect, and appends to `server/clients.jsonl`. ## Naming Each client gets two words. The pair comes from the first non-loopback MAC address (fallback `/etc/machine-id`): 1. Hash the identity bytes with FNV-1a 64. 2. `first = hash % 128`, `second = (hash / 128) % 128`. 3. If both indexes are equal, `second = (second + 1) % 128`, so `Queer Queer` becomes `Queer Groin`. The lists share words on purpose, so `Funky Monkey`, `Funky Queer`, and `Queer Monkey` are all possible. A random MAC changes the name; a stable machine-id does not. ## Heartbeats and states The client sends `heartbeat` on this schedule after boot: | Time since start | Interval | | --- | --- | | 0 to 60 s | 10 s | | 60 s to 6 min | 30 s | | after 6 min | 5 min | The server keeps three states from `lastSeen`: | State | Age | Effect | | --- | --- | --- | | `active` | under 12 min | Jobs and commands may be sent | | `stale` | 12 to 30 min | No jobs or commands | | `gone` | over 30 min | Considered offline; a late heartbeat revives it | First and last seen are kept in memory and written to `clients.jsonl`. `SIGINT` prints the table. ## Transport Newline-delimited JSON over TCP on a dedicated port (PoC: 8799). No Discord connection, no HTTP framework. | Direction | Message | Meaning | | --- | --- | --- | | client → server | `hello` | `id`, `name`, `platform`, `version` | | server → client | `welcome` | Assigned name, heartbeat interval, server name | | client → server | `heartbeat` | `ts` | | both | `ping` / `pong` | `seq` echo, for the test button and latency | | server → client | `job` | Later: a unit of work with an ID and a payload | | client → server | `job-result` | Later: `id`, `ok`, `data` or `error` | Binary payloads (PCM, video frames) get their own framing later: a length-prefixed binary frame on the same socket, or a second UDP channel for audio where loss is cheap. ## Server side in Veskslop The PoC server moves into the daemon as `poc/src/remchild-server.ts`: - Separate listener, default port 8799 (`VESKSLOP_REMCHILD_PORT`). - Settings: `preferences.remchild.allow` (IP list) and `preferences.remchild.allowNonLan`. - The launch argument `--allow-non-lan` (or `VESKSLOP_REMCHILD_ALLOW_NON_LAN=1`) is required before a non-LAN address in the list is accepted. Without the list, only LAN and Tailscale ranges pass. - The daemon never scans for clients. It listens, records, and dispatches. - The client registry feeds a Status section later; no Web UI work is needed now. ## Phases | Phase | Work | | --- | --- | | M1 (done) | PoC: naming, listen, allowlist, heartbeat, ping test, history | | M2 | Move the server into the daemon, add settings, the launch-arg unlock, and tests. Add `job` dispatch and `job-result` with retry and dedupe. | | M3 | Audio: the daemon sends call audio to the client, the client runs VAD and STT (SAM/VOX or an AI model), and returns annotated text and voice commands. The same path carries TTS back. | | M4 | Video: stream a window or play a file with MPV, and return frames or a stream URL. | | M5 | Desktop: a rust/ImGui control app (Dear ImGui over GLFW or SDL2). The terminal UI in the PoC is a placeholder because the current host has no GUI dev libraries. | ## Open questions - Track-to-user audio mapping for remote STT (see [../docs/voice-audio-split.md](../docs/voice-audio-split.md)). - Job retry policy and idempotency keys. - Whether audio uses the same socket or a second channel. - Wake-on-LAN or reconnect backoff for a client that drops.