Skip to content

Python surface: plugin protocol, modulex-py, wheels, release CI#5

Merged
hartsock merged 5 commits into
mainfrom
pr4/python-surface
Jun 5, 2026
Merged

Python surface: plugin protocol, modulex-py, wheels, release CI#5
hartsock merged 5 commits into
mainfrom
pr4/python-surface

Conversation

@hartsock

@hartsock hartsock commented Jun 5, 2026

Copy link
Copy Markdown
Owner

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:

  1. Plugin protocol 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.
  2. modulex-py (PyO3 0.28, the inverse-embedding trick): Python hosts the engine —
    Engine.from_config(), @engine.step("name") decorator (or register_step), run_routine() -> Report, and serve_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 wheel modulex-py; bare modulex is TAKEN on PyPI and never used) + release.yml (cargo publish + wheel matrix + PyPI upload on v* tags; requires CARGO_REGISTRY_TOKEN / PYPI_API_TOKEN secrets before first release).

Test plan

  • just check green: 101 Rust tests, clippy -D warnings clean across all features
  • 8 plugin-protocol tests (payload shape, response mapping, stderr tail, soft skips)
  • pytest 5/5 against the real wheel (maturin build + install): decorator registration, str/None returns, exception→soft-failure, register-after-build error, monotonic generations
  • Live e2e: subprocess plugin ran through the real engine + CLI; a Python-hosted serve_stdio() answered routine_run over real stdio JSON-RPC with a Python-registered step in the report

hartsock and others added 3 commits June 5, 2026 18:31
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>
@hartsock hartsock added the risk:low Scoped, tested, no CI/build changes label Jun 5, 2026
hartsock and others added 2 commits June 5, 2026 18:47
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>
@hartsock hartsock changed the base branch from pr3/forge-board-steps to main June 5, 2026 22:48
@hartsock hartsock merged commit 890587d into main Jun 5, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:low Scoped, tested, no CI/build changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant