cmux-api: PlatformMachines.resize, so cmux-platform can grow a machine in place
Why
cmux-platform right-sizes NEXT Cloud machines automatically: it snapshots a machine, then grows it in place one size step. It reaches cmux-api's CloudDO only through the PlatformMachines service binding, which had snapshot but no way to resize an existing machine. Resize existed only inside create/fork's grow-to-fit phase and as the person-facing op cloud.machine.resize.
Leo, 2026-10-31: "we ofc wanted to have the thing be having snapshots of whatever vms to do the right-sizing"
cmux-platform: CloudDO (the team's machine ledger, plan checks and provider calls) still lives here and has not moved, so the binding method that reaches it lands here. The consumer side (MachinesBinding / MachineProvider.resize) is in manaflow-ai/cmux-platform.
Contract
resize(caller: PlatformCaller, machine: string, shape: PlatformShape): Promise<PlatformReply<PlatformMachine>>
- Runs as the caller's own
cloud.machine.resize({ machine, size: { cpu, memory_mb, disk_mb } }), so the team's plan limits, grow-only and one-change-per-machine apply as they do for a person's machine. It is not a cleanup op: it answers only wherePLATFORM_MACHINESis"on". ok: truereturns the machine's metadata with the provider-confirmed shape (the same answergetgives).- The shape is already at the target (read from the provider, not the row): it returns the metadata and makes no provider call, so a retry after success is a no-op success.
- Refusals:
validation.invalid: bad caller, machine id or shape (positive safe integers, same check ascreate), before any team is reachedplatform.machines_off:PLATFORM_MACHINESis not onauth.forbidden: the user may no longer use the team's machinescloud.machine.not_found: not a platform machine this user made, including another person's machine and the caller's own non-platform machinecloud.provider.unsupported: a vm-api transport machinemutation.indeterminate(retryable): the machine is still provisioning, another change on it is still running, or this resize's provider call has not settled yetcloud.size.grow_only,cloud.size.locked,cloud.machine.not_running,cloud.plan.required: from the reducer, passed through- A final provider failure: the ledger's recorded error,
retryable: false. The row is restored, so a later call starts a new attempt.
- Keys: each call gets a fresh idempotency key. A per-revision key recorded a refusal (for example
grow_only) under the key that the next call, for a different size or after a plan change, would reuse. That call then gotidempotency.conflict, which the first test run caught. A duplicate grow can't happen because a pending change answersmutation.indeterminatebefore any intent (newPlatformHost.changing, read from CloudDO's pending set), the reducer's busy check covers races, and a finished grow is detected from the provider's shape.
Tests
test/platform-machines.test.ts:
- grows a bound platform machine one step and returns the exact shape; a same-shape retry makes no second provider call; a shrink is refused with
cloud.size.grow_only; a provider refusal is final and the next call grows; a provider call that doesn't answer stays pending (mutation.indeterminateon retry, no second intent) and the alarm finishes it - refuses another person's platform machine and the caller's own non-platform machine (
not_found), and a user who left the team (auth.forbidden) - extends "validates every argument" (bad machine id, fractional, NaN or null shape) and the machines-off case
cmux-ci (Linux pool), at the head: bun run typecheck (all backend packages), bun run lint:size, and bun run test -- test/platform-machines.test.ts test/cloud-resize.test.ts test/cloud-start-pause.test.ts test/cloud-do.test.ts: 4 files, 38 tests passed.
Agent session: agent session
š¤ Generated with Claude Code
![View with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png)
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Adds PlatformMachines.resize(caller, machine, shape) so cmux-platform can grow a machine it booted one size step, through the person's own cloud.machine.resize op.
- Runs as the caller's own
cloud.machine.resize, so the team's plan limits, grow-only, and one-change-per-machine rules apply. - A same-shape retry is a no-op success: the provider's confirmed shape (not the row) decides "already there", with no second provider call.
- Uses a fresh idempotency key per call, so a refusal like
grow_onlyis never recorded under a key a later call would reuse. - Pending changes answer retryable
mutation.indeterminatebefore any intent is submitted, via the newPlatformHost.changing. - Failed provider calls return the ledger's recorded error with
retryable: false, and the row is restored so a later call starts a new attempt. - Refusals mirror
createand the resize op:validation.invalid,platform.machines_off,auth.forbidden,cloud.machine.not_found,cloud.provider.unsupported, and the reducer'scloud.size.grow_only,cloud.size.locked,cloud.machine.not_running,cloud.plan.required. - Tests cover growth, idempotent retry, shrink refusal, pending and failed provider states, and ownership and access checks.