Run this procedure periodically to pull upstream monty changes and update ExMonty.
Overview
ExMonty pins a specific monty git revision in native/ex_monty/Cargo.toml. Upstream
monty is under active development (~200+ commits since Jan 2025). This procedure walks
through pulling, assessing, and integrating changes.
Track tagged releases, not main. Upstream cuts v0.0.x tags; target the latest tag
unless the user explicitly asks to chase main (e.g. for a specific unreleased fix).
The bleeding edge is fine for upstream contributors but exposes us to in-flight refactors.
Phase 1: Pull and Assess
1.1 Fetch latest upstream and find the latest tag
cd ../monty && git fetch origin --tags
git tag --sort=-version:refname | head -5
(The monty repo is at ../monty relative to the ex_monty project root.)
Pick the highest v0.0.x tag. That's the target rev for this update.
1.2 Identify our current pin
grep 'rev = ' native/ex_monty/Cargo.toml
git describe --tags <OUR_PINNED_REV> # context: which tag are we at/after?
1.3 Review changes since our pin
This is the critical step. Focus on the public API surface.
cd ../monty
git diff <OUR_PINNED_REV>..<TARGET_TAG> -- crates/monty/src/lib.rs
This file contains all public re-exports. Any change here directly affects us. Look for:
- Renamed types or functions — will cause compile errors
- Changed signatures (new params, different return types) — silent breakage risk
- Removed exports — will cause compile errors
- New exports — opportunities for new features
Then review the implementation files behind our dependencies:
# Our full API surface — review changes in these modules:
git diff <OUR_PINNED_REV>..<TARGET_TAG> -- \
crates/monty/src/run.rs \
crates/monty/src/object.rs \
crates/monty/src/os.rs \
crates/monty/src/resource.rs \
crates/monty/src/exception_public.rs \
crates/monty/src/exception_private.rs \
crates/monty/src/io.rs \
crates/monty/src/run_progress.rs \
crates/monty/src/fs/dispatch.rs \
crates/monty/src/fs/path_security.rs \
crates/monty/src/fs/mount_table.rs
For every update, explicitly re-check these security invariants rather than
relying on compilation: PrintWriterCallback output must remain fallible and
budgeted by ExMonty; mount matching must be component-aware; host paths must be
canonicalized; symlinks must not escape a mount; read-only and write quotas must
fail closed; resume_pending and ResolveFutures::resume must preserve call-ID
ownership and retry semantics.
1.4 Read the commit log for context
git log --oneline <OUR_PINNED_REV>..<TARGET_TAG> -- crates/monty/
Pay attention to commits that mention: breaking changes, API changes, new features, bug fixes, or anything related to types we use.
Phase 2: Classify Changes
Sort what you found into categories:
Breaking (must fix before bump)
Changes to types/functions we directly use:
| We use | Where in our code |
|---|---|
MontyRun::new(), .run(), .start(), .clone() | lib.rs, interactive.rs |
MontyObject variants + encoding | types.rs (22KB — largest module) |
OsFunction variants | types.rs, interactive.rs |
MontyException, ExcType, StackFrame | error.rs, interactive.rs |
FunctionCall::resume(), FunctionCall::resume_pending(), ResolveFutures::resume() | interactive.rs, resources.rs |
RunProgress variants | interactive.rs |
ExtFunctionResult variants | interactive.rs |
PrintWriter, PrintWriterCallback | lib.rs, interactive.rs, output.rs |
ResourceLimits, LimitedTracker, ResourceError | lib.rs, types.rs, resources.rs |
Serialize/Deserialize on MontyRun, Snapshot, FutureSnapshot | serialization.rs |
Non-breaking improvements
- Bug fixes in monty internals (no API change)
- New
MontyObjectvariants (we'll hit a match-not-exhaustive warning) - New
OsFunctionvariants (same) - Performance improvements
New capabilities
- New public API types/functions we might want to expose
Phase 3: Update
3.1 Switch to path dependency for development
Edit native/ex_monty/Cargo.toml:
# Comment out the git dep:
# monty = { git = "https://github.com/pydantic/monty.git", rev = "..." }
# Use local path for iteration:
monty = { path = "../../../monty/crates/monty" }(Assumes ../monty relative to ex_monty project root, i.e. ../../../monty/crates/monty
relative to the Cargo.toml.)
3.2 Build and fix compile errors
cd native/ex_monty && cargo check 2>&1
Common fixes by category:
Renamed types/functions — Find-and-replace in our Rust source.
Changed method signatures — Update call sites in our NIFs. Check if new parameters need to be threaded through from Elixir.
New MontyObject variants — Add arms to the match in types.rs:encode_monty_object().
Decide on Elixir encoding (tagged tuple, struct, etc.).
New OsFunction variants — Add arms to encode_os_function() in types.rs
and handling in interactive.rs.
New ExcType variants — Usually handled automatically by ExcType::from_str(),
but verify.
Serialization breakage — If MontyRun, Snapshot, or FutureSnapshot changed
their serialized format, existing serialized data is incompatible. Consider bumping
a version marker if we persist these.
3.3 Update Elixir side if needed
If new types or call patterns were added, update:
lib/ex_monty.ex— main APIlib/ex_monty/native.ex— NIF bindingslib/ex_monty/sandbox.ex— handler dispatch- Relevant structs in
lib/ex_monty/
3.4 Run the test suite
mix test
All 90+ tests should pass. If upstream changed behavior (not just API), some assertions may need updating.
3.5 Update documentation
If new progress tags, types, or call signatures were added, update the docs:
ExMontymoduledoc —@type progress, "Progress Values" list, interactive examplesExMonty.FunctionCall/ExMonty.OsCallmoduledocs — field descriptions, usage contextExMonty.Sandboxmoduledoc — handler dispatch behavior- Typespec changes on any public function whose signature changed
This is easy to miss — any new tag or struct field that reaches the Elixir side needs corresponding doc updates.
3.6 Add tests for new functionality
If new MontyObject variants or OsFunction variants were added, write tests
exercising them through ExMonty.eval/2 or the sandbox.
Phase 4: Pin and Ship
4.1 Switch back to git dependency
monty = { git = "https://github.com/pydantic/monty.git", rev = "<NEW_COMMIT_HASH>" }Use the full 40-char hash of the monty commit the target tag points at
(git rev-parse <TARGET_TAG>^{commit}). Pinning to a hash rather than the tag itself
means the dep can't silently move if a tag is force-updated.
4.2 Verify clean build from git dep
cd native/ex_monty && cargo update -p monty
cd ../.. && mix clean && mix test
4.3 Update CHANGELOG.md
If the update includes breaking changes, new features, or notable bug fixes,
add an entry to CHANGELOG.md under the [Unreleased] section summarizing
what changed from the ExMonty user's perspective.
4.4 Commit
Update monty to <short-hash>
- <summary of breaking changes fixed>
- <summary of new features added>
- <summary of upstream improvements picked up>Quick Reference: One-liner Diff Check
To quickly check if there are API changes worth pulling, against the latest tag:
cd ../monty && git fetch origin --tags && \
LATEST_TAG=$(git tag --sort=-version:refname | head -1) && \
git diff $(grep -oP 'rev = "\K[^"]+' ../ex_monty/native/ex_monty/Cargo.toml)..$LATEST_TAG \
-- crates/monty/src/lib.rs
If this diff is empty, there are no public API changes — only internal improvements. Still worth updating periodically for bug fixes.
When to Update
- When upstream cuts a new tag (
v0.0.x) — that's the natural cadence - Immediately if upstream fixes a bug affecting us (then either chase
mainor wait for the next tag, depending on severity — ask the user) - Before any ExMonty release to pick up the latest stable tag
- When a new MontyObject/OsFunction variant is needed for a feature we want