feat(sandbox): add canonical main process (#2726)

* feat(sandbox): add canonical main process

Closes #2710

Persist and supervise one canonical workload per sandbox, attach sandbox connect to its retained session, and make every unexpected main-process exit terminal.

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* feat(sandbox): simplify canonical main process contract

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(sandbox): preserve legacy VM main compatibility

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(sandbox): preserve main status across driver updates

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(sandbox): satisfy macOS process lint

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(sandbox): gate Linux exit acknowledgement publisher

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* refactor(sandbox): simplify main process plumbing

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(supervisor): make controlling tty ioctl portable

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(supervisor): initialize canonical process environment

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* perf(supervisor): optimize retained main session

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* feat(sandbox): detach main session on ctrl-c

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(sandbox): use explicit main detach keys

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(dev): atomically stage Docker supervisor

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* fix(test): align Docker main environment assertion

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

* feat(sdk): expose canonical main process fields

Signed-off-by: Drew Newberry <anewberry@nvidia.com>

---------

Signed-off-by: Drew Newberry <anewberry@nvidia.com>
This commit is contained in:
Drew Newberry
2026-08-20 22:18:23 +00:00
committed by GitHub
parent 9ae3760768
commit ef296806f5
69 changed files with 3800 additions and 821 deletions
+5 -1
View File
@@ -2,7 +2,11 @@ from . import datamodel_pb2, openshell_pb2
# Sandbox messages and phase enums moved into openshell.proto. Keep aliases on
# datamodel_pb2 so existing Python callers and E2E tests continue to work.
for _name in ("Sandbox", "SandboxSpec", "SandboxTemplate"):
for _name in (
"Sandbox",
"SandboxSpec",
"SandboxTemplate",
):
if not hasattr(datamodel_pb2, _name):
setattr(datamodel_pb2, _name, getattr(openshell_pb2, _name))
+4
View File
@@ -130,6 +130,7 @@ def _normalize_bearer(
class SandboxStatusRef:
phase: int
current_policy_version: int
exit_code: int | None = None
class _ImmutableLabels(dict[str, str]):
@@ -1092,6 +1093,9 @@ def _sandbox_ref(sandbox: openshell_pb2.Sandbox) -> SandboxRef:
status=SandboxStatusRef(
phase=status.phase if status else 0,
current_policy_version=status.current_policy_version if status else 0,
exit_code=status.exit_code
if status is not None and status.HasField("exit_code")
else None,
),
labels=sandbox.metadata.labels if sandbox.metadata else {},
)
+9
View File
@@ -1772,6 +1772,15 @@ def test_sandbox_ref_retains_gateway_labels() -> None:
assert dict(ref.labels) == {"aiq": "deep-research", "env": "dev"}
def test_sandbox_ref_includes_main_process_result() -> None:
proto = _make_sandbox_proto("sandbox-1", "job-1")
proto.status.exit_code = 0
status = _sandbox_ref(proto).status
assert status.exit_code == 0
def test_returned_labels_are_immutable() -> None:
proto = _make_sandbox_proto("sandbox-1", "job-1", {"aiq": "deep-research"})
ref = _sandbox_ref(proto)