Skip to content

feat(inspect-ai,harbor): managed sessions, integrations/common, trace export and Harbor environment - #364

Draft
tholop wants to merge 6 commits into
integration/0.7.1-pierre-sdk-20261016from
feat/harbor-capsem
Draft

tholop wants to merge 6 commits into
integration/0.7.1-pierre-sdk-20261016from
feat/harbor-capsem

Conversation

@tholop

@tholop tholop commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

Based on integration/0.7.1-pierre-sdk-20261016 (#362, which already includes #336–#340), so the diff shows only the commits below. Each commit is self-contained: merge the branch, cherry-pick single commits into #362, or ask for any of them as separate PRs.

Summary

Running Inspect and Harbor benchmarks on Capsem needs three things the 0.7.1 branch doesn't have yet: sandboxes whose VMs cannot leak when an eval dies, a way to export what happened inside a VM, and a Harbor environment. This branch adds them:

  1. feat(service): stream ledger rows via GET /vms/{id}/export/rows.ndjson.gz (all non-security session tables, gzipped NDJSON).

  2. feat(sdk): vm.export and ATIF trajectory conversion over the rows plus the existing WARC export.

  3. feat(inspect-ai): run sample VMs as managed sessions (Hypervisor.managed()). Optional design change, worth a look before Inspect 0.1.0 ships; commits 4–6 build on it, so skipping it means reworking the shared controller. What it buys over the _lifecycle.py / _cleanup.py / label-sweep cleanup that feat(sdk): integrate Pierre stack and Inspect for 0.7.1 #362 carries from feat(inspect-ai): add VM-mode Capsem SandboxEnvironment integration #338–feat(inspect-ai): add host-side Dockerfile and Compose build image support #340:

    • VMs can't leak: the service owns each sample's lease and reclaims the VM when it expires, so a crashed or killed eval process (including SIGKILL/OOM, where atexit and signal handlers never run) no longer leaves VMs behind for a later label sweep or a manual inspect sandbox cleanup capsem.
    • Trace export works after stop: the service keeps a stopped managed session's data until it is closed, so a sample's ledger is fully drained before export, without persisting the VM under a temporary name.
    • Less code: about 340 lines of lifecycle/cleanup modules go away, plus their tests.

    The cost is inspect eval --no-sandbox-cleanup (task_cleanup(cleanup=False)), which now raises NotImplementedError. That answers my question on #338; the option can come back if the hypervisor gains a way to turn a leased VM into a normal one.

  4. feat(integrations): move the framework-agnostic controller, exec, file transfer and container builds into a shared integrations/common package (capsem_integrations) used by both Inspect and Harbor.

  5. feat(inspect-ai): ATIF-v1.7 trace export for Inspect .eval logs.

  6. feat(harbor): harbor_capsem:CapsemEnvironment, a Harbor environment on Capsem VMs and OCI containers, with credential brokering and ATIF export.

Other branches from the same benchmark runs

Not part of this PR. They are independent of each other and of this stack, and each merges cleanly onto the integration branch:

  • feat/container-exec: user and cwd on exec requests (instead of cd … && su … -c wrapping), plus a vm.container.exec(...) helper.
  • feat/files-multipart: Python vm.files.upload/download that splits transfers above the 10 MiB request limit.
  • fix/mitm-sse-streaming-abort: stream SSE model responses instead of buffering to EOF (long gemini-cli turns hit the client's 60 s header timeout), and record calls the guest aborted mid-stream.
  • fix/provision-credential-sync-launch-wait: POST /vms/create could kill a healthy VM that took longer than 500 ms to bind its owner socket.
  • feat/vertex-ai-mitm: recognise Vertex AI hosts and paths as model traffic and broker their tokens (credential-sensitive, worth its own look).

Notes for Review

About 15k added lines: tests ~35%, lockfiles ~29% (uv.lock for the two new uv projects, integrations/common and integrations/harbor), SDK Python ~12% (export and ATIF conversion), Harbor ~8%, Inspect ~7% (it also deletes ~700 lines of lifecycle/cleanup code), Rust service/logger ~4% (the rows endpoint and keeping a stopped managed session's data until close), shared integrations/common ~2%, and the rest gate config, docs and generated SDK operations.

Testing

CI covers unit, lint and gate checks; it has no KVM. Live runs on a KVM host at this exact head:

  • tests/ironbank/test_sdk_live.py: the Inspect and Harbor live acceptance tests pass for both the wheel and the sdist, covering VM, OCI container and host-built image sandboxes and managed quiesce → export → close.
  • Inspect with --sandbox capsem and trace export: inspect_evals/vimgolf_single_turn (3 samples) and inspect_harbor/terminal_bench_2 (fix-git) complete with no errors; every exported trajectory passes Harbor's trajectory_validator, with every agent step linked to its LLM call.
  • Harbor on terminal_bench_2 fix-git: oracle and terminus-2 solve the task, with valid trajectories.
  • In-VM gemini-cli over Vertex AI with brokered credentials needs the Vertex support from feat/vertex-ai-mitm in the service; with that branch (and the SSE fix) merged in, it solves the task with a valid trajectory.

No VMs leaked in any run.

…n.gz

Add `GET /vms/{id}/export/rows.ndjson.gz?tables=` to stream a session's
SQLite ledger rows as gzipped NDJSON (`application/gzip` attachment,
single-member gzip stream) alongside the existing `export.warc.gz` body
archive:

- Line 1 emits a `capsem.rows.v1` header (`type`, `format`, `format_version`,
  `vm_id`, `exported_at`, `tables`).
- Snapshots per-table max row IDs up front via a single-SELECT
  `LedgerQuery::TableWatermarks` query and bounds paged `LedgerQuery::TableRows`
  reads by `max_id` (`MAX_LEDGER_QUERY_ROWS = 2_000`, flushing `page_buf` at
  `64 KiB` via `GzipChunkEncoder` in `capsem-archive`).
- Wraps multi-query `execute_query_many` batches in a read transaction on the
  reader worker connection.
- Defaults to the 16 non-security ledger tables (`DEFAULT_EXPORT_LEDGER_TABLES`)
  when `?tables=` is omitted, and accepts any subset of the 18 exportable
  tables (`EXPORTABLE_LEDGER_TABLES`, joining `security_rule_runs` and
  `security_decision_runs` when requested).
- Retains stopped lease-owned managed VM session directories across
  `POST /vms/{id}/stop` until `POST /vms/managed/{request_id}/close` (or
  `DELETE /vms/{id}/delete`) so callers can export both `rows.ndjson.gz` and
  `bodies/export.warc.gz` after stopping a managed VM and before closing the
  lease.
- Registers `exportVmRows` in OpenAPI (`crates/capsem-api`,
  `sdk/specification/openapi.json`) and regenerates the Python, TypeScript, and
  Rust SDK operation bindings.
Add VM.export (quiesce, rows, warc, events, atif) and capsem.trace helpers
(parse_rows_ndjson_gz, load_rows_tables, WarcBodyReader, rows_to_atif,
enrich_atif_trajectory) backed by GET /vms/{id}/export/rows.ndjson.gz and
GET /vms/{id}/bodies/export.warc.gz without sqlite3 or local session.db:

- Stream-decompresses `.rows.ndjson.gz` line-by-line via `gzip.GzipFile` and
  writes exported files atomically via sibling tempfiles and `os.replace`.
- Downloads `rows.ndjson.gz` and `bodies/export.warc.gz` sequentially after
  quiescing.
- Stops managed VMs during `quiesce_vm_for_export` (skipping `persist` while
  disarming background lease renewal via `on_stop` before `POST /vms/{id}/stop`)
  so the service drain barrier flushes and seals session telemetry before export.
Start each sample VM through capsem.Hypervisor.managed() and keep the
ManagedSession lease in SdkCapsemController, so the service reclaims the VM
when the lease ends (sample_cleanup, cancellation, or a crashed evaluator)
instead of the adapter tracking process-owned VMs and sweeping leftovers by
label.

Remove _lifecycle.py, _cleanup.py and the leftover-sweep configuration;
task_cleanup(cleanup=False) now raises NotImplementedError because a lease
cannot outlive its owner. Sample VMs keep their inspect-capsem-task and
capsem-vm-prefix labels, truncated to 255 UTF-8 bytes by truncate_label_value.
Update the README, the usage docs and the tests accordingly.
…ions/common

Move the framework-agnostic container pipeline (capsem_integrations.containers),
guest execution and user helpers (_exec.py, _users.py), staged tar transfer
(_transfer.py), and ManagedSession controller (SdkCapsemController and
truncate_label_value in _controller.py) out of inspect_capsem into a shared uv
project under integrations/common (capsem-integrations / capsem_integrations, no
PyPI publish).

Update inspect_capsem to depend on capsem-integrations, support neutral
CAPSEM_ALLOWED_HOST_PATHS with CAPSEM_INSPECT_ALLOWED_HOST_PATHS fallback, wire
integrations/common through PythonPackageConfig (pytest_scratch), capsem-gate
SDK checks, Citadel boundary/surface/lockfile audits, fast-gate prewarm, CI
scope classification, and Codecov.
@codecov-commenter

codecov-commenter commented Oct 17, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.66080% with 383 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.3%. Comparing base (e4a981f) to head (44305a0).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
sdk/python/capsem/_trace_bodies.py 74.4% 35 Missing and 24 partials ⚠️
sdk/python/capsem/_trace_atif.py 76.9% 19 Missing and 22 partials ⚠️
...es/capsem-service/src/ledger_routes/rows_export.rs 82.0% 18 Missing and 18 partials ⚠️
sdk/python/capsem/_trace_ledger.py 81.9% 25 Missing and 11 partials ⚠️
...grations/inspect-ai/inspect_capsem/trace_export.py 77.1% 23 Missing and 11 partials ⚠️
sdk/python/capsem/_trace_enrich.py 84.8% 11 Missing and 14 partials ⚠️
integrations/harbor/harbor_capsem/_transfer.py 80.0% 17 Missing and 7 partials ⚠️
crates/capsem-logger/src/ledger_server/queries.rs 67.3% 17 Missing ⚠️
integrations/harbor/harbor_capsem/_trace.py 88.1% 5 Missing and 12 partials ⚠️
sdk/python/capsem/trace.py 87.1% 7 Missing and 8 partials ⚠️
... and 14 more
Additional details and impacted files
@@                            Coverage Diff                            @@
##           integration/0.7.1-pierre-sdk-20261016     #364      +/-   ##
=========================================================================
+ Coverage                                   69.4%    76.3%    +6.9%     
=========================================================================
  Files                                       1878     1498     -380     
  Lines                                     154231   125487   -28744     
  Branches                                  114221   115027     +806     
=========================================================================
- Hits                                      107037    95850   -11187     
+ Misses                                     40349    22621   -17728     
- Partials                                    6845     7016     +171     
Flag Coverage Δ
harbor 88.5% <88.5%> (?)
inspect-ai 91.0% <87.3%> (-2.6%) ⬇️
integration 19.5% <ø> (+<0.1%) ⬆️
integrations-common 92.1% <100.0%> (?)
linux-unit 74.2% <82.9%> (+<0.1%) ⬆️
python-sdk 94.8% <82.1%> (-3.3%) ⬇️
unit 74.7% <87.4%> (+7.2%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
TypeScript SDK 97.0% <ø> (ø)
Python SDK 94.8% <82.1%> (-3.3%) ⬇️
Integrations Common 92.1% <100.0%> (∅)
Inspect AI Extension 91.0% <87.3%> (-2.6%) ⬇️
Harbor Extension 88.5% <88.5%> (∅)
Network 85.0% <ø> (+0.1%) ⬆️
Security 83.6% <ø> (ø)
Tooling 89.5% <ø> (ø)
Monitoring 86.3% <71.9%> (-0.1%) ⬇️
Virtualization 69.9% <ø> (-0.1%) ⬇️
Confined Port Router 76.9% <ø> (ø)
Private Network 77.6% <ø> (ø)
Assets 81.8% <ø> (ø)
Gateway API 94.7% <93.3%> (-0.1%) ⬇️
Rust SDK 95.3% <100.0%> (+<0.1%) ⬆️
Configuration 88.3% <ø> (ø)
Credentials 84.4% <ø> (ø)
Host Foundation 77.1% <ø> (-0.1%) ⬇️
Cache 92.4% <ø> (ø)
Cryptography 91.6% <ø> (ø)
Core Platform 55.8% <ø> (ø)
Runtime 69.0% <ø> (-0.1%) ⬇️
Daemon 41.6% <ø> (ø)
Service 75.6% <83.6%> (+0.1%) ⬆️
Process 47.8% <ø> (ø)
Proxy 43.5% <ø> (ø)
Ledger 63.0% <ø> (ø)
Admin 70.3% <ø> (ø)
CLI 49.0% <ø> (ø)
MCP Server 94.9% <ø> (ø)
MCP Transport 62.6% <ø> (ø)
MCP Aggregator 66.2% <ø> (ø)
MCP Builtin 63.1% <ø> (ø)
Gateway 76.5% <100.0%> (+<0.1%) ⬆️
TUI 68.8% <ø> (ø)
System Tray 56.2% <ø> (ø)
Guard 92.2% <ø> (ø)
UI 83.4% <ø> (ø)
Release Site 25.6% <ø> (ø)
Builder ∅ <ø> (∅)
Mock Server 71.5% <ø> (ø)
Bench 54.5% <ø> (ø)
Files with missing lines Coverage Δ
crates/capsem-gateway/src/main.rs 46.4% <100.0%> (+0.1%) ⬆️
crates/capsem-logger/src/ledger_protocol.rs 88.8% <100.0%> (+0.9%) ⬆️
crates/capsem-service/src/ledger_routes.rs 78.5% <ø> (ø)
crates/capsem-service/src/router_runtime.rs 85.1% <100.0%> (+<0.1%) ⬆️
...ntegrations/common/capsem_integrations/__init__.py 100.0% <100.0%> (ø)
...grations/common/capsem_integrations/_controller.py 98.1% <100.0%> (ø)
integrations/common/capsem_integrations/_exec.py 100.0% <100.0%> (ø)
...tegrations/common/capsem_integrations/_transfer.py 97.8% <ø> (ø)
integrations/common/capsem_integrations/_users.py 100.0% <ø> (ø)
.../common/capsem_integrations/containers/__init__.py 100.0% <100.0%> (ø)
... and 56 more

... and 406 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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