Skip to content

feat(service,sdk): attach labels to VMs at create and expose them in list - #336

Open
tholop wants to merge 1 commit into
0.7from
feat/sdk-vm-labels
Open

tholop wants to merge 1 commit into
0.7from
feat/sdk-vm-labels

Conversation

@tholop

@tholop tholop commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Lets clients attach key/value labels to the VMs they create or fork and inspect them again when listing or querying VMs. The Inspect AI integration (#338) uses this to identify and clean up only the ephemeral VMs it owns.

Changes

  • Service & API (capsem-api, capsem-service, OpenAPI):
    • Adds optional key/value string labels (Option<HashMap<String, String>>) to ProvisionRequest (POST /vms/create), ForkRequest (POST /vms/{id}/fork), SandboxInfo (GET /vms/list and GET /vms/{id}/info), VmInstance, SessionCreationParams, and PersistentVmEntry.
    • Forking (ForkRequest) inherits the source VM's labels when labels is omitted/null, replaces them when a non-empty map is provided, and clears them when {} is passed.
    • Validates labels server-side via naming::validate_vm_labels:
      • Up to 64 entries (MAX_VM_LABELS).
      • Keys reuse validate_vm_name (1..=64 ASCII characters starting with [A-Za-z0-9] and containing only [A-Za-z0-9_-]).
      • Values are up to 255 UTF-8 bytes (MAX_VM_LABEL_VALUE_LEN) with no ASCII or Unicode control characters.
      • Normalizes empty maps via naming::non_empty_labels so {} is stored and serialized identically to None.
    • Consolidates ProvisionOptions and PersistentVmEntry test constructors via test_provision_options and test_persistent_entry in crates/capsem-service/src/tests.rs (reducing tests/lifecycle.rs and tests/assets_registry.rs by ~100 lines) and updates the four corresponding oversized-Rust-file line ratchets in config/gate.toml.
  • SDKs & CLI (sdk/python, sdk/rust, sdk/typescript, capsem CLI):
    • Regenerates openapi.json and threads labels through the Python, Rust, and TypeScript SDKs plus capsem create and capsem fork (-l / --label KEY=VALUE).

Notes for Review

  • Server-side label filtering follow-up: Per your feedback on feat(inspect-ai): add inspect-capsem SandboxEnvironment integration #291 against divergence between the REST API and SDK wrappers, there is no client-only Hypervisor.list(labels=...) filter (inspect-capsem filters the returned SandboxInfo.labels list in the caller). If you would like server-side filtering in capsem-service, we are happy to send a small follow-up adding GET /vms/list?label=k=v (repeatable, AND semantics) across OpenAPI, capsem-service, and the generated SDKs.
  • Advisory label namespace: Labels are currently advisory user metadata with no reserved prefix (inspect-capsem uses managed-by=inspect-capsem and inspect-capsem-prefix=<slug>) — let us know if you want a reserved namespace convention.

@codecov-commenter

codecov-commenter commented Oct 7, 2026 •

Copy link
Copy Markdown

❌ 3 Tests Failed:

Tests completed Failed Passed Skipped
6043 3 6040 0
View the top 3 failed test(s) by shortest run time
capsem-sdk::operations::tests::every_contract_operation_has_http_cases
Stack Traces | 0.011s run time
thread 'operations::tests::every_contract_operation_has_http_cases' (131335) panicked at .../src/operations/tests.rs:108:5:
assertion `left == right` failed
  left: {"attachNetworkMember", "callMcpTool", "createNetwork", "createVm", "createVmExposure", "createVmPreviewSession", "deleteNetwork", "deleteVm", "deleteVmExposure", "detachNetworkMember", "downloadVmFile", "execVm", "exportVmBodies", "forkVm", "getAssetStatus", "getHypervisorInfo", "getHypervisorLogs", "getMcpDefault", "getMcpInfo", "getNetwork", "getNetworkLogs", "getPanics", "getTriage", "getUpdateStatus", "getVmContainer", "getVmEventBodies", "getVmHistory", "getVmInfo", "getVmLogs", "getVmStatsDetail", "getVmStatsSummary", "getVmStatus", "getVmTimeline", "injectCredential", "listImages", "listMcpServers", "listMcpTools", "listNetworks", "listVmExposures", "listVmFiles", "listVms", "pauseVm", "persistVm", "pullImage", "purgeVms", "refreshMcpServer", "restartHypervisor", "resumeVm", "revokeVmPreviewSessions", "runVm", "startVm", "stopVm", "updateHypervisor", "uploadVmFile"}
 right: {"attachNetworkMember", "callMcpTool", "createNetwork", "createVm", "createVmExposure", "createVmPreviewSession", "deleteNetwork", "deleteVm", "deleteVmExposure", "detachNetworkMember", "downloadVmFile", "execVm", "exportVmBodies", "forkVm", "getAssetStatus", "getHypervisorInfo", "getHypervisorLogs", "getMcpDefault", "getMcpInfo", "getNetwork", "getNetworkLogs", "getPanics", "getTriage", "getUpdateStatus", "getVmContainer", "getVmEventBodies", "getVmHistory", "getVmInfo", "getVmLogs", "getVmStatsDetail", "getVmStatsSummary", "getVmStatus", "getVmTimeline", "listImages", "listMcpServers", "listMcpTools", "listNetworks", "listVmExposures", "listVmFiles", "listVms", "pauseVm", "persistVm", "pullImage", "purgeVms", "refreshMcpServer", "restartHypervisor", "resumeVm", "revokeVmPreviewSessions", "runVm", "startVm", "stopVm", "updateHypervisor", "uploadVmFile"}
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
capsem-assets::oci::cache::inventory::tests::cancelled_partial_materialization_releases_its_barrier_before_invalidating_inventory
Stack Traces | 0.383s run time
thread 'oci::cache::inventory::tests::cancelled_partial_materialization_releases_its_barrier_before_invalidating_inventory' (129653) panicked at .../cache/inventory/tests.rs:205:39:
called `Result::unwrap()` on an `Err` value: cache changed during inventory observation
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
capsem-assets::oci::worker::tests::incompatible_receipts_are_observed_once_until_their_metadata_changes
Stack Traces | 2.62s run time
thread 'oci::worker::tests::incompatible_receipts_are_observed_once_until_their_metadata_changes' (134454) panicked at .../oci/worker/tests.rs:221:10:
called `Result::unwrap()` on an `Err` value: TimedOut { label: "foreign-cache-observed", attempts: 8, timeout: 2s }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
capsem-service::bin/capsem-service::container_setup::tests::registry::catalog_observation_schedules_one_owned_worker_and_refuses_rebinding
Stack Traces | 2.68s run time
thread 'container_setup::tests::registry::catalog_observation_schedules_one_owned_worker_and_refuses_rebinding' (267991) panicked at .../container_setup/tests/registry.rs:27:6:
called `Result::unwrap()` on an `Err` value: TimedOut { label: "service-cache-observed", attempts: 8, timeout: 2s }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
capsem-assets::oci::worker::tests::worker_reconciles_multiple_offline_images_and_only_retries_changed_observations
Stack Traces | 2.69s run time
thread 'oci::worker::tests::worker_reconciles_multiple_offline_images_and_only_retries_changed_observations' (134593) panicked at .../oci/worker/tests.rs:362:6:
called `Result::unwrap()` on an `Err` value: TimedOut { label: "released-inventory", attempts: 8, timeout: 2s }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
capsem-core::fs_monitor::tests::a_workspace_swapped_for_a_host_link_is_never_walked_or_read
Stack Traces | 360s run time
No failure message available

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@tholop
tholop force-pushed the feat/sdk-vm-labels branch from bc9bdb5 to 2514565 Compare October 8, 2026 13:07
@tholop
tholop changed the base branch from integration/0.7-clients-inspect to 0.7 October 8, 2026 13:12
@tholop tholop changed the title feat(service,sdk): add persistent VM labels and server-side label filtering feat(service,sdk): attach labels to VMs at create and expose them in list Oct 8, 2026
…list

Allow callers to attach optional key/value string labels to VMs at
creation time, inherit or override them on fork, and return them on
every SandboxInfo entry in /vms/list and /vms/{id}/info so external
orchestrators and garbage collectors can identify their own VMs.

- capsem-api / capsem-service / OpenAPI: add optional labels
  (Option<HashMap<String, String>>) to ProvisionRequest, ForkRequest,
  SandboxInfo, VmInstance, SessionCreationParams, and PersistentVmEntry,
  validated server-side via naming::validate_vm_labels (<= 64 entries,
  keys reuse validate_vm_name: 1..=64 ASCII chars starting with
  [A-Za-z0-9] and containing only [A-Za-z0-9_-]; values <= 255 UTF-8
  bytes with no control chars) and normalized via
  naming::non_empty_labels so {} is treated identically to None.
- Fork inheritance: /vms/{id}/fork inherits the source VM's labels when
  ForkRequest.labels is omitted/null, replaces them when a non-empty map
  is provided, and clears them when {} is provided.
- List cache fingerprint: include running and inactive VM labels in
  list_response_fingerprint via append_labels_fingerprint so label
  differences invalidate cached /vms/list responses.
- SDKs (Python, Rust, TypeScript): thread optional labels through
  Hypervisor.create / CreateOptions and VM.fork / ForkOptions and
  regenerate OpenAPI models across all three SDKs.
- CLI & TUI: add repeatable -l / --label KEY=VALUE to capsem create and
  capsem fork, and display labels in the TUI session detail pane.
- Service test helpers & Citadel ratchets: consolidate ProvisionOptions
  and PersistentVmEntry test constructors via test_provision_options and
  test_persistent_entry in crates/capsem-service/src/tests.rs (reducing
  tests/lifecycle.rs and tests/assets_registry.rs by ~100 lines) and
  update the four corresponding oversized-Rust-file line ratchets in
  config/gate.toml.
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.

2 participants