f385070dbf
Follows the architecture pass on #100. Three classes of change: 1. sys.path — the ten Python entrypoints used two idioms: seven pinned /usr/local/lib/bullpen (system layout only), three resolved ../lib from realpath(__file__) first. The second form is a superset: it works in both layouts and still falls back to /usr/local/lib. Unified on it. Proven: on noether, where /usr/local/lib/bullpen does not exist, `bullpen-dispatcher --roster` now runs straight from the checkout — it could not before. 2. Role hosts from config — deus-seize, bullpen-selfimprove, bullpen-up's ROSTER keys, and the lurker's opencode host/model now come from bullpen_config (env > /etc/bullpen/bullpen.conf > origin-fleet default), so behaviour here is unchanged. bullpen-up in particular was a silent no-op on any other fleet because its ROSTER was keyed by literal hostnames. 3. Advice the system gives BACK — @dispatcher answered every routing question with `sic hertz room-ask …` and @callboy reported artifacts as `sic hertz cat …`. On a foreign fleet that is an instruction to talk to a machine that does not exist. Now cfg.ROOM_HOST. tests/test_no_hardcoded_hosts.py enforces (3) by AST, not grep: prose in docstrings and comments stays (why a thing runs on boltzmann is worth keeping), but a hostname inside a string the program actually uses fails the test. It caught three sites I had missed by eye — the dispatcher's proxy default, its "on <host>" routing regex, and the grinder's timeout message. The routing regex is the interesting one: those names are real fleet knowledge, not addresses, so they moved to cfg.FLEET_HOSTS rather than being deleted. Verified both directions — with the default list "on boltzmann" matches and "on buildbox" does not; with BULLPEN_FLEET_HOSTS="buildbox chatbox" it is exactly reversed. Also verified: DRY_RUN=1 bullpen-selfimprove composes a brief with zero mentions of the origin fleet under overridden hosts; deus-seize still runs; 52/52 across both portability suites. NOT fixed, flagged: deus-seize and bullpen-selfimprove pass the room post-secret as an lmcp-tool argv value, so it is visible in `ps` on the room host (and in curl's argv inside lmcp-tool). lmcp-tool has no stdin form for argument values; closing it means changing lmcp-tool, which is deployed fleet-wide.