---
{
  "n": 15,
  "title": "Running the suites in parallel, landed and then superseded",
  "abstract": "",
  "refs": [],
  "seen": [
    "agent",
    "user"
  ],
  "data": {},
  "created": 1789946676.921342,
  "updated": 1789968310.171669,
  "deleted": 0.0,
  "completed": 0.0,
  "outcome": "",
  "type": "doc"
}
---
The Opus agent's design for parallel test runs, delivered complete and unwritten because the journal's own write gate blocked it. Kept so the work is not lost while the pytest conversion finishes.

## What blocked it
Every file write by a subagent is intercepted by the journal's PreToolUse gate — 'nothing is open, so this write would not be filed' — because the subagent has no work of its own. It fires on the Write tool and on any Bash write, including outside the repo. The one thing that clears it, journal work start, was refused by the permission layer as a shared-resource change. So the agent did the whole design by reading, wrote nothing, ran nothing, and handed it back. The tree is exactly as it was.

## pytest is not installed anywhere
Checked on every interpreter on the machine: /opt/homebrew/bin/python3 (3.14, the default), python3.13, python3.12 and /usr/bin/python3. No pytest, no pytest-xdist, no pytest-timeout. pipx exists, uv does not. Homebrew's pythons are externally managed, so the clean route is a venv at the repo root: python3 -m venv .venv && .venv/bin/pip install pytest pytest-xdist pytest-timeout. Verified installable by building a throwaway venv on 3.13 — pytest 9.1.1 and xdist 3.8.0 install cleanly.

## The one environment variable that matters
AGENT_JOURNAL_HOME has exactly one reader, engine/viewer.py, and it resolves the machine-wide journal registry. Unset, the suites read and write the developer's real ~/.journal/journals.json. conftest must set it before any project module is imported — not only for worker isolation but to stop the tests touching the real registry. tests/kit.py and the current tests/conftest.py each set it to their own mkdtemp, so within one process the value depends on import order.

## Where the real contention is
Not the records — every Record in the suites comes from mkdtemp, and xdist workers are separate processes, so those are already isolated. It is ports and the shared registry. Two fixed machine-wide bands: engine/viewer.py PORTS = range(8420, 8440), scanned and bound by available/start/restart; engine/services.py PORTS = range(8440, 8500), scanned with live probes. Everything else binds port 0 and is already safe. engine.services.PORTS must stay a range — test_services.py reads PORTS.start.

## Suites that must stay serial
viewer: test_viewer_port.py (uses the fixed band directly and kills a spawned viewer), test_serve.py (writes port 8439 into the registry), test_hook_server.py (spawns hook.sh against a live server), test_watch.py. services: test_services.py, features/plugins/test_services.py. install: test_install.py, test_cli.py (run journal.py and the installer as subprocesses). plugins: features/plugins/test_host.py, test_run.py, test_install.py (features.unload/load mutate the global registry). The command must be pytest -n auto --dist loadgroup --timeout=120; the default --dist load ignores the groups and the table does nothing.

## In-process state that grouping does not fix
features.load()/unload() and bus.on() are in-process globals. They do not collide across xdist workers, which are separate processes, but they do collide with other tests in the same worker. test_hook_server.py registers a global bus handler that sleeps a second and never removes it, so every later agent.updated in that worker pays for it. kit.done() calls sys.exit(), so any module still calling it at import time is a collection error rather than a failure.

## Superseded
The parallel runner landed, and then the suite was cut to the generated runs plus one test.py per feature (doc 17, and CLAUDE.md under Tests). Run it with .venv/bin/python -m pytest -q --timeout=300 -n auto.
