Python surface: plugin protocol, modulex-py, wheels, release CI#5
Merged
Conversation
WHAT: New crate (lib + bin). Server: hand-rolled newline-delimited JSON-RPC 2.0 loop, MCP protocol 2024-11-05; methods initialize/ notifications-initialized/ping/tools-list/tools-call; -32600 for id-without-method (silence would hang clients), -32601 unknown method, -32700 parse errors. Tools: routine_run (only/skip/dry_run/format), routine_list, step_run, report_get (generation-keyed), steps_list. isError=true ONLY for engine faults; per-step failures stay data inside the report. Bin flags: --config, --probe (dry-run first routine), --tools. Leash provenance banner goes to stderr — stdout belongs to the protocol. modulex-core grows a 'test-support' feature exposing MockSpawner to downstream crates' tests (dev-deps only). WHY: The multi-agent surface from the plan: newt-agent and Claude Code drive the same deterministic routine via MCP. Crate is a library so modulex-py (PR 4) can serve_stdio() with Python-registered steps. 12 server tests; verified live by piping JSON-RPC through the binary. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WHAT: Seven new builtin step types. gitlab.rs: gitlab-mr-authored / gitlab-mr-review (glab mr list per project), gitlab-group-mrs (--per-page; scan=recent adds a 7-day --created-after window), and the derived mr-sla-check (counts pending review projects from this run's prior results; response_hours param). github.rs: github-pr-scan (gh pr list --json number,title,author,updatedAt → compact lines). board.rs: board-scan (### lane (N tasks) + stems per lane) and chores-check (due:/Due: YYYY-MM-DD lines → OVERDUE/due-today/upcoming buckets). Auth failures (401/unauthorized/forbidden/gh-auth-login hint) are soft: all-failed → skip with the exact login command; partial → auth noise filtered, real results kept (Python-handler parity). ALL forge steps now resolve spec.env credential references and inject them into the subprocess env (missing credential → soft skip naming the variable, never a value). Example config grows the review-queue and board phases. WHY: Phase 2/4/5 of the morning routine from gilabot#1892, ported from gila-plugin-morning's proven handlers. The spec.env wiring was a fresh-eyes catch: the example showed credentials on forge steps that the handlers silently ignored. 93 tests total. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WHAT: (1) steps/python.rs — the subprocess plugin protocol modulex-plugin/1 (type="python", any language): one JSON object stdin→stdout; interpreter leash-gated and declared; credentials via injected env only; non-zero exit/bad JSON/timeout → failed step with stderr tail; missing script → soft skip. examples/standup_notes.py is the reference plugin. (2) crates/modulex-py — PyO3 0.28 extension module (inverse embedding: Python hosts the engine, libpython never links into the Rust binaries): Engine.from_config, register_step + @engine.step decorator sugar, run_routine → Report (to_text/to_json/to_dict), serve_stdio (MCP with Python steps included, sharing one engine so generations stay continuous), GIL detached around block_on with re-attach from runtime workers; Python exceptions become failed steps (soft). pytest suite (5 tests). (3) Server::with_engine (Arc) so Python and the bin share the server loop. (4) Packaging: maturin pyprojects (bin wheels for modulex-cli/-mcp, extension wheel for modulex-py), release.yml (cargo publish + wheel matrix + PyPI on v* tags; needs CARGO_REGISTRY_TOKEN/PYPI_API_TOKEN secrets). README gains the Python extension story. WHY: 'Every crate usable from Python; extend the MCP server with Python' — the signature PyO3 pattern. Verified live: wheel built via maturin, 5/5 pytest green, subprocess plugin ran through the real engine, and a Python-registered step answered a routine_run over MCP from a Python-hosted server. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WHAT: Centered 256px logo (from docs/logos/, PR #4) atop the README. WHY: Shawn made logos; the README is the front door (and the future PyPI long_description). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stacked on #3 — retarget after it merges. Completes the four-PR plan.
Two tiers of Python extensibility, neither linking libpython into the Rust binaries:
modulex-plugin/1(type = "python", language-agnostic, leashed subprocess): one JSON object stdin→stdout; interpreter exec-gated and auto-declared in the default grant; credentials via injected env only; soft failure semantics throughout. Reference plugin:examples/standup_notes.py.modulex-py(PyO3 0.28, the inverse-embedding trick): Python hosts the engine —Engine.from_config(),@engine.step("name")decorator (orregister_step),run_routine() -> Report, andserve_stdio()— the MCP server with Python steps included, sharing one engine so generations/report store stay continuous. GIL detached around the runtime bridge; Python exceptions become failed steps, not dead routines.Packaging: maturin pyprojects (bin wheels
modulex-cli/modulex-mcp, extension wheelmodulex-py; baremodulexis TAKEN on PyPI and never used) +release.yml(cargo publish + wheel matrix + PyPI upload on v* tags; requiresCARGO_REGISTRY_TOKEN/PYPI_API_TOKENsecrets before first release).Test plan
just checkgreen: 101 Rust tests, clippy-D warningsclean across all featuresmaturin build+ install): decorator registration, str/None returns, exception→soft-failure, register-after-build error, monotonic generationsserve_stdio()answeredroutine_runover real stdio JSON-RPC with a Python-registered step in the report