Skip to content

Run the emitter host from an installed microcosm-emitter command - #21

Merged
anth-volk merged 5 commits into
mainfrom
feat/installed-emitter-command
Oct 20, 2026
Merged

anth-volk merged 5 commits into
mainfrom
feat/installed-emitter-command

Conversation

@anth-volk

@anth-volk anth-volk commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #17

First of a stack of four pull requests that move Microcosm's upload transport into the emitter (plan step 2, E1 then H1). This one makes the emitter installable on its own and runnable as the build's child process. The next pull request adds the typed interface package whose client starts this command.

What changes

  • microcosm-emitter command (microcosm_emitter.cli). uv tool install microcosm-emitter installs it outside the build's environment.
    • microcosm-emitter host --socket … --parent-pid … runs the existing service host, configured on standard input as before.
    • microcosm-emitter version prints the emitter version and accepted socket protocol versions as JSON.
  • Started as the build's child, never as a service. The command is meant to be started exactly as today's launcher starts python -m microcosm_emitter.host (host/launcher.py:101): no separate environment, so it inherits the build's environment variables and user and finds the same Hugging Face credential (HF_TOKEN, then HUGGINGFACE_TOKEN, then the saved hf auth login token, core/auth.py:105–109). PR 2 adds the launcher and the test that proves this.
  • Parent identification without psutil in the build. --parent-created-at is now optional. Without it, the host reads the named process's creation time and then checks again that the process is its own parent (os.getppid()); otherwise it refuses to start. A match after the read proves the creation time is the parent's, not that of an unrelated process given the same process ID after the parent exited. Launchers that pass the creation time behave as before.
  • hello request. Only a host started by microcosm-emitter host answers {"action": "hello"}: with the emitter version, the accepted protocol versions (microcosm_emitter.compatibility.SUPPORTED_PROTOCOL_VERSIONS, now (1,)) and the loaded and skipped modules, typed as compatibility.HelloResponse. A host started with python -m microcosm_emitter.host (the in-process launcher) answers hello with an error, as for any unknown action.
  • --skip-unavailable-modules starts the host without requested modules that this emitter does not install, so a newer build can still use an older emitter's modules. A request to a skipped module is refused like one to an unknown module, and the host still refuses to start if none of the requested modules is installed. Without the flag, a missing module still stops startup.
  • python -m microcosm_emitter.host keeps working for Microcosm builds pinned to microcosm-emitter==0.2.1.
  • README: a section on installing the emitter on its own.

Release

The fragment is added: merging releases the next minor version of microcosm-emitter to PyPI.

Validation

Red-green by commit (macOS, Python 3.14; only the test files this pull request adds or changes):

Commit Red (tests first) Green
3b33e27 Run the emitter host from an installed microcosm-emitter command tests/host/test_installed_command.py, tests/test_package_names.py: 7 failed, 4 passed (2 existing tests, and 2 new guards for existing behaviour: python -m microcosm_emitter.host still starts, and hello without a description is refused) 11 passed; re-run after commit: 11 passed

ruff check and ruff format --check pass. CI runs the full suite.

Overlap with open pull requests

Max's #5, #6 and #7 also edit README.md; #5 also edits pyproject.toml (its dev dependencies, which this does not touch). Expect at most a small README rebase.

Review fixes (2026-10-20)

Each fix was written red, then green; only the test files this pull request adds or changes were run.

Commit Finding Red (tests first) Green
8f599da Expect the missing-module error in the module selection test test_module_selection_refuses_missing_and_duplicate expected "exactly one" for a missing module; load_module raises ModuleNotInstalledError. This was the CI failure on every pull request of the stack. tests/host/test_runtime.py: 1 failed, 14 passed (as in CI) 15 passed
33c54d2 Read the parent's creation time before the final parent check parent_process read create_time() after its only getppid() check. tests/host/test_installed_command.py: 2 failed, 8 passed 10 passed
1ecadcf Let the parent-exit test see a host that outlives its parent The test's short-lived build gave the host its standard error pipe, so subprocess.run(capture_output=True) returned only after the host exited and the final psutil.wait_procs assertion never ran. The build now gives the host no output pipes and reports its creation time. Test-only fix. Against a host whose parent check always succeeds, the new test fails at "the host kept running after its parent exited" (1 failed); the old one failed only by a 30-second TimeoutExpired Real host: 10 passed
22fca11 Type the hello payload and test skipped modules README and fragment did not say that only microcosm-emitter host answers hello; no tests for --skip-unavailable-modules with every module missing or for a request to a skipped module; optional TypedDict. The two skip tests passed against unchanged code (they guard existing behaviour: 11 passed); the TypedDict test failed (1 failed, 11 passed). README and fragment changes need no red step 12 passed

Re-run after the last commit: tests/host/test_installed_command.py, tests/host/test_runtime.py, tests/test_package_names.py: 30 passed. tools/check-quality.sh and tools/check-changelog.sh pass.

🤖 Generated with Claude Code

`uv tool install microcosm-emitter` now installs a `microcosm-emitter`
command. A build starts `microcosm-emitter host` as its own child process,
so the host inherits the build's environment variables and user and finds
the same Hugging Face credential.

- The host reads its parent's creation time itself when no launcher
  passes it, and refuses to start if the named process is not its parent.
- A `hello` request returns the emitter version, the socket protocol
  versions it accepts and the modules it loaded.
- `--skip-unavailable-modules` starts without requested modules this
  emitter does not install.
- `python -m microcosm_emitter.host` keeps working for existing launchers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk and others added 4 commits October 20, 2026 16:39
`load_module` raises `ModuleNotInstalledError` ("No installed module is
named ...") when no entry point provides the module, so the host can skip
it with `--skip-unavailable-modules`. The test still expected the
duplicate-module message for that case and failed in CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without `--parent-created-at`, the host checked `os.getppid()` and only then
read the named process's creation time. If the parent exited in between,
the process ID could already name an unrelated process. The host now reads
the creation time first and checks again that the named process is still
its parent, so a match proves the time it read is its parent's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test's short-lived build started the host without redirecting its
standard error, so the host inherited the pipe that `subprocess.run` reads
to the end. The run therefore returned only after the host had exited, and
the final `psutil.wait_procs` assertion never ran: a host that kept running
failed the test only through the run's timeout. The build now gives the host
no output pipes and reports its creation time, and the test waits for that
same process to exit.

Checked against a host whose parent check always succeeds: the test now
fails at "the host kept running after its parent exited".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `microcosm_emitter.compatibility` defines `EmitterDescription` (what
  `microcosm-emitter version` prints) and `HelloResponse` (what `hello`
  returns); the command and the host build them by name.
- Tests: with `--skip-unavailable-modules`, a host whose requested modules
  are all missing refuses to start, and a message or query to a skipped
  module is refused while the host keeps serving the others.
- README and changelog fragment: only a host started by
  `microcosm-emitter host` answers `hello`; one started with
  `python -m microcosm_emitter.host` refuses it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk
anth-volk merged commit ffc2d30 into main Oct 20, 2026
10 checks passed
@anth-volk
anth-volk deleted the feat/installed-emitter-command branch October 20, 2026 22:47
anth-volk added a commit that referenced this pull request Oct 21, 2026
Brings in #21's release commit `8b3aa29`: both version lines of the
emitter's pyproject.toml and uv.lock move to 0.4.0, CHANGELOG.md gains the
0.4.0 entry, and #21's consumed changelog fragment is deleted. No
conflicts. The interface package's own version and the emitter's exact pin
on it are still 0.3.0; the next commit sets them to 0.4.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk added a commit that referenced this pull request Oct 21, 2026
#21 released 0.4.0, so main's emitter is at 0.4.0 while this branch's
interface package and the emitter's exact pin on it were still 0.3.0.
`tools/bump-version.py --check` refused that ("Both distributions must
have the same version"), and tests/test_package_names.py would fail on CI.

Both are now 0.4.0, and `uv lock` records the interface at 0.4.0. With
this branch's `.added` fragment, the release this merge starts publishes
both distributions as 0.5.0 (`bump-version.py --check` prints
"0.4.0 -> 0.5.0").

Checked: `bump-version.py --check` exits 0; tests/test_package_names.py,
tests/test_workflows.py and tests/interface/test_launcher.py pass (46).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Install the emitter on its own and run its host as the build's child process

1 participant