0017. Each workspace gets a block of ports, and ibni hands it to setup and verify
Status: Accepted · Date: 2026-10-25
Extends 0014.
Context
ibni prepare gave each workspace one free port, recorded it and printed it, and the agent was told to serve the app on it. Nothing else saw it. ibni verify and setup ran their commands with ibni's own environment, so a repository's test servers fell back to the fixed ports in its own config.
Measured on 2026-10-25 in a web app repository with browser tests: two npm run verify runs at once both failed, one on 4392 and one on 8790 ("already used"). The same pair passed 18/18 with distinct ports in four variables the repository already reads. Transcripts since 2026-10-02 show the same error in eight sessions, six of them in that repository. Every collision came from a test server, none from a development server. One port per workspace could not have prevented them, because a verify run starts several servers.
The port was also checked only at prepare time, under the repository lock alone. Prepares in two repositories could take the same port, and anything could bind it later.
Decision
- A block, not a port.
preparegives each workspaceworkflow.ports.blockSizeconsecutive ports (10 by default), aligned fromworkflow.ports.first. A block is skipped if it overlaps one a retained workspace holds, in any repository, or if any of its ports is bound.portstays the block's first port, so everything that reads it keeps working. - One machine-wide lock. The block is chosen, and the metadata that claims it written, under a lock in
<paths.worktreeRoot>/.ibni/, so prepares in different repositories cannot take the same block. - ibni passes the block on. Setup and verify get
PORT(the first port) andMEXXI_PORT_0toMEXXI_PORT_<blockSize-1>. Any port variable ibni inherited is dropped first, so another workspace's block never leaks into this one's run.prepareandinfoprint the same variables asportEnv, for a server the agent starts by hand. - Config maps the variables a repository already reads.
workflow.portVariablesmaps a repository, by its checkout's folder name, to variable names and offsets in the block, such as a test server'sE2E_PORTat offset 1. ibni sets each one too. A repository's test servers move into the block without a change to the repository, and the mapping is read at each run, so it reaches retained workspaces. - verify checks the block first.
ibni verifyrefuses to start, and records nothing, while any port of the block is bound, naming the port. A failed verify then means the checks failed, not that a server lost a race. - Older workspaces keep their port. A workspace recorded with only
portholds a block of one, and a new block never overlaps it.
Consequences
- Parallel verify runs in different workspaces no longer collide, as long as each repository's test servers read a variable from the block: one of its own mapped in config, or
MEXXI_PORT_<n>. - Only runs through
ibniget the block. When an agent runs the verify command by hand, its servers still use their fixed ports. Most runs in that repository did so in October 2026./wettaqalready sends the full gate throughibni verify. - The default range of 700 ports holds 70 blocks of 10. A machine with more retained workspaces than that needs
ibni cleanupor a wider range. - A config whose
workflow.portsrange holds fewer thanblockSizeports no longer loads, and says so. Narrow it withblockSize, or widen the range. - The machine-wide lock covers creating the worktree as well as choosing the block, so a slow checkout makes other prepares wait. They give up after the same 30 s as the repository lock.
- A development server the agent leaves running on
portmakesibni verifyrefuse. That is deliberate: the alternative is a verify run that fails for a reason the checks did not find. - The mapping is per machine, in
config.json, rather than in the repository. A repository that wants every user to get it can readMEXXI_PORT_<n>itself.
Alternatives considered
- Keep one port and derive the rest from it, such as
PORT + 1: no config is needed. But a derived port could be another workspace's, and every repository would need code for it. - Map variables in the repository's
.mexxi.json: the mapping would travel with the repository, but every repository would need a change, and one with no stake in mexxi would carry mexxi's keys. A repository can still readMEXXI_PORT_<n>directly. - Have verify wait for a busy port to free up: it hides the cause and can wait for ever on a server nobody stops.