ubq 0.0.7(beta)

This page as markdown

The background server

The optional background server.

ubq server starts it. ubq server stop stops it. It is optional (01-agent-ideation.md, section 13). Decisions: D36 and D37.

What it does#

  1. It keeps 8 test processes that are started. Each one ran the coverage preload and waits, in a read of stdin, for a test file (src/warm.ts). A runner that finds the server asks it for a process for each test file. With no server, or with no process for the request, the runner starts the process itself.
  2. It makes the counters of a file when the file changes. It watches the project folder. For a changed source file it writes the cache entry of the counters, with the source with no types. The first test process that loads the file after an edit then needs no parser, and Node does not load its code that removes types.

It does not run tests, it does not keep results, and it loads no file of the project. A result is never reused (D8).

Rule: the same result with and without the server#

The runner is the same process and the same code in the two cases. Only the start of a test process differs. So the output, the exit code, and the store are the same by construction.

A process of the server is equal to a process that the runner starts, but for these points:

The server stops, and the runner starts its own processes, if the runner has a different runtime or a different ubq (stamp in src/pool.ts: the path and version of the runtime, and the newest change time of the files of ubq).

Parts#

File Contents
src/server.ts the server command, the server process, the watcher
src/pool.ts the socket path, the request, the frames, warmProcess for the runner
src/warm.ts the program of a process that waits

The socket is .ubq/server.sock, with mode 600. A runner can ask the server to run any file as the user of the server, so only that user can connect. Errors of the server go to .ubq/server.log. The state is in the project folder, so two git worktrees have two servers.

A request is one line of JSON. The answer to run is a stream of frames (one tag character, a length of 4 bytes, the data): i the process ID, o stdout, e stderr, p the events of fd 3, x the exit code and signal, n no process. The runner sends signals to the process itself: it has the process ID, and the user is the same.

The server stops after 30 minutes with no request and no change of a file.

Measured (2026-11-12)#

Script: experiments/server/warm.mjs, and the benchmark suite.

Case No server Server
One test process, start to end 45 ms 17 ms
ubq with 3 files 80 ms 55 ms
ubq with 2 files, first run after an edit of a source file 120 ms 75 ms
ubq --repeat 50 of one file with 9 tests 2200 ms 760 ms
ubq mutate of one file, 12 changes 930 ms 630 ms
ubq with 200 files 850 ms 850 ms
ubq with 1 file (runs in the process of the runner) 51 ms 51 ms

A run of many files does not get faster: the server starts a new process for each one that it gives out, and that start costs the same in the server as in the runner. The gain is for small runs, for --repeat, and for ubq mutate.

Why no MCP interface, and no HTTP (D37)#

The interface for an agent is the ubq command and its text.

The socket protocol is internal. It can change with each version.

If an MCP interface is wanted later, it is a small adapter: one tool for each command, which calls the same functions as the CLI and returns the same text.

Not built#