Skip to content

feat(sandbox-morph): Morph Cloud sandbox provider worker (Infinibranch)#94

Open
rohitg00 wants to merge 7 commits into
mainfrom
sandbox-morph
Open

feat(sandbox-morph): Morph Cloud sandbox provider worker (Infinibranch)#94
rohitg00 wants to merge 7 commits into
mainfrom
sandbox-morph

Conversation

@rohitg00
Copy link
Copy Markdown
Contributor

@rohitg00 rohitg00 commented May 7, 2026

Member of the sandbox worker family. Read sandbox-CONTEXT.md (lands via #92) for family terminology and ABI contract.

What ships

sandbox-morph/ — narrow Rust iii worker wrapping Morph Cloud. Morph's wedge is Infinibranch: snapshot a running VM and branch it into N siblings with live process state preserved. This is the only worker in the family that registers sandbox::*::branch.

Functions

REST endpoint paths verified via context7 against cloud.morph.so/docs/api-reference/*:

Function Wired
sandbox::morph::create POST /api/instance?snapshot_id=<image> body {metadata, ttl_seconds, ttl_action}
sandbox::morph::exec POST /api/instance/{id}/exec body {command: [argv]}
sandbox::morph::stop POST /api/instance/{id}/stop; 404 / 409 = idempotent success
sandbox::morph::list GET /api/instance; reconciles in_flight
sandbox::morph::snapshot POST /api/instance/{id}/snapshot
sandbox::morph::branch POST /api/instance/{id}/branch?count=N body {snapshot_metadata, instance_metadata}
sandbox::morph::expose_port stub — Morph's expose_http_service lacks a clean REST equivalent today
sandbox::morph::fs::* not registered — Morph fs ops go through SSH; not advertised as capability

Capabilities advertised: ["branch","snapshot","expose_port"]. No fs.

Caller contract caveat

Morph requires a snapshot_id query parameter on create — there's no default rootfs to fall back on. The worker rejects empty image upfront with WorkerError::BadInput rather than letting the upstream return a confusing 4xx.

Tests

15 wiremock-backed cases including the branch happy-path (returns {sandbox_ids: [...]} from Morph's instances:[{id}, {id}] response), count: 0 rejection, snapshot-id extraction, 401 → S503, 5xx rollback, list reconciliation. Clippy clean (with doc_markdown allowed for URL-shaped doc comments).

Live test status

Wired against documented shapes; not yet run end-to-end (no MORPH_API_KEY available during this PR's authoring). Pattern-matches the e2b/daytona fix cycle that was live-verified.

Pin

iii-sdk = "=0.11.6".

rohitg00 added 6 commits May 7, 2026 11:31
Mirrors the sandbox::e2b::* ABI under sandbox::morph::*, plus registers
sandbox::morph::branch — the Infinibranch primitive that returns N
sibling sandbox_ids from a running parent, preserving live process state.
Capabilities advertised: branch, snapshot, expose_port. fs::read/write
deferred for v0 (Morph FS ops are SSH-shaped; revisit when the family
agrees on a channel-vs-SSH bridge).

Auth header: Authorization: Bearer MORPH_API_KEY. Default base URL:
https://cloud.morph.so/api.

Tests pass; clippy clean; fmt clean. HTTP bodies stubbed pending
verified pass against Morph REST. A latency benchmark (target <300ms p99
for branch) lands in a follow-up commit before the wedge feature is
declared stable.
Same structural fix as sandbox-daytona:

- `do_list` reconciles in_flight against the upstream count when the
  client succeeds; falls back to the local counter on Err. Response
  gains a `reconciled` boolean.
- `SandboxRecord.started_at` switches to a pass-through RFC3339 string
  to match the e2b/daytona shape.

Adds three branch tests that ride along with the port: missing
sandbox_id rejection, count == 0 rejection, and stub pass-through.
These pin the input contract for sandbox::morph::branch ahead of the
live REST wiring; when MORPH_API_KEY lands, only the client body
changes.

8/8 tests pass; clippy clean; fmt clean. Live REST wiring deferred
pending Morph credentials.
Replaces the stubbed /sandboxes paths with the actual Morph Cloud
surface verified through context7's mirror of cloud.morph.so/docs:

- create:  POST /api/instance?snapshot_id=<image>  body {metadata, ttl_seconds, ttl_action}
- exec:    POST /api/instance/{id}/exec            body {command: [argv]}
- stop:    POST /api/instance/{id}/stop            (404/409 = idempotent success)
- list:    GET  /api/instance                      response: array of {id, status, refs:{snapshot_id}, ...}
- snapshot:POST /api/instance/{id}/snapshot
- branch:  POST /api/instance/{id}/branch?count=N  body {snapshot_metadata, instance_metadata}

Caller's `image` field is treated as Morph's `snapshot_id` query
parameter — Morph is snapshot-first, there is no default rootfs to
fall back on, so empty image is rejected upfront with a BadInput
error rather than letting the upstream return a confusing 4xx.

`expose_port` and `fs::read/write` remain stubbed: Morph routes
those through `expose_http_service` and SSH respectively, neither of
which has a clean REST equivalent in the public docs. The worker no
longer advertises `fs` capability so callers see the supported set
correctly via capability negotiation.

15 wiremock-backed integration tests cover happy create, allowlist +
empty-image rejection, 401/5xx mapping, exec command-array shape, 404
and 409 idempotent stop, list reconciliation, list fallback, branch
sibling-id extraction, branch count=0 rejection, and snapshot id
return. Clippy + fmt clean.
… engine

Verified the register_function/register_function_with handler signatures
are unchanged between 0.11.3 and 0.11.6 (handler closure stays `Fn(R) ->
Result<O,E>` — single-arg, no engine-supplied ctx; our HandlerCtx is
captured by the closure, not an SDK parameter). All wiremock tests
still pass. Pin moves to whatever the engine actually ships.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2026

Warning

Rate limit exceeded

@rohitg00 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 55 minutes and 22 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 55bc5833-d31d-4fea-8bea-f41feb651030

📥 Commits

Reviewing files that changed from the base of the PR and between 2e53b44 and ef9bf22.

⛔ Files ignored due to path filters (1)
  • sandbox-morph/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • sandbox-morph/Cargo.toml
  • sandbox-morph/README.md
  • sandbox-morph/iii.worker.yaml
  • sandbox-morph/src/client.rs
  • sandbox-morph/src/config.rs
  • sandbox-morph/src/handler.rs
  • sandbox-morph/src/lib.rs
  • sandbox-morph/src/main.rs
  • sandbox-morph/tests/integration.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sandbox-morph

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Stops shadowing the bare sandbox::* namespace. Routes through the
sandbox router worker (provider="morph"); direct invocation stays
supported and stable.

Handler logic, tests, S-code mapping (branch capability) unchanged.
@rohitg00
Copy link
Copy Markdown
Contributor Author

Refactored to register only sandbox::provider::morph::*. Caller-facing sandbox::* now owned by the new sandbox router worker in #119. Direct invocation of sandbox::provider::morph::* stays supported.

Validation: cargo fmt --check + cargo clippy -D warnings + tests clean. Depends on #119 for end-to-end use via the router; this PR is independently mergeable if direct invocation is sufficient.

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.

1 participant