Skip to content

Commit ce174bd

Browse files
Meirtzclaude
andauthored
provider: drop --gpu-mode from CAPE run face (flag removed upstream; pool-level sharing) (#159)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 2321acc commit ce174bd

3 files changed

Lines changed: 68 additions & 11 deletions

File tree

plugins/providers/cape/README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ What matched the previously assumed contract:
3535
timeout and on null-exit-code terminal states, so only the status
3636
JSON `state` field can be trusted.)
3737
* `cape run` flags: `--image`, `--gpus`, `--cpu-cores`, `--memory-gb`,
38-
`--gpu-mode whole`, `--isolation-policy`, `--runtime-adapter`,
38+
`--isolation-policy`, `--runtime-adapter`,
3939
`--max-duration-seconds`, `--session-key`,
4040
`--bind src:dst[:ro|rw]`, `--env K=V`, and the `-- <workload>`
4141
remainder are all real, with matching semantics.
@@ -97,6 +97,19 @@ What the verification changed:
9797
behavior kept bookkeeping (and its port) forever. A 404 in the
9898
CLI's stderr JSON now confirms deletion.
9999

100+
Later upstream CLI changes tracked:
101+
102+
* **`--gpu-mode` was removed from `cape run`.** Requests now inherit
103+
exclusive/shared GPU behavior from the target *pool*; there is no
104+
per-request override. Sending the old flag is an unknown-argument
105+
error (argparse exits 2 with a usage message before anything is
106+
submitted), so the provider no longer emits it.
107+
* **`cape status` gained cotenancy fields**: `gpu_mode` (derived,
108+
`null` | `"exclusive"` | `"shared"`), `gpu_cotenant_count`, and
109+
`gpu_cotenant_counts`. The provider's status parsing only reads
110+
`state` and the `request_id` echo, so the new fields are tolerated
111+
(ignored) without changes.
112+
100113
### Networking: pin a host-network runtime adapter
101114

102115
The client-assigned-port scheme (`AGENTIX_BIND_PORT` plus a direct

plugins/providers/cape/agentix/provider/cape.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,7 @@ async def run(
560560
561561
cape run --controller-url U --token T --pool P --user USR
562562
--task-id ID --image IMG --gpus N [--cpu-cores N]
563-
[--memory-gb N] --gpu-mode whole --isolation-policy X
563+
[--memory-gb N] --isolation-policy X
564564
[--runtime-adapter Y] --max-duration-seconds T
565565
--session-key KEY [--bind src:dst[:ro|rw]]...
566566
[--env K=V]... -- <workload argv>
@@ -587,7 +587,7 @@ async def run(
587587
argv += ["--cpu-cores", str(int(cpu_cores))]
588588
if memory_gb is not None:
589589
argv += ["--memory-gb", str(int(memory_gb))]
590-
argv += ["--gpu-mode", "whole", "--isolation-policy", cfg.isolation_policy]
590+
argv += ["--isolation-policy", cfg.isolation_policy]
591591
if cfg.runtime_adapter:
592592
argv += ["--runtime-adapter", cfg.runtime_adapter]
593593
argv += ["--max-duration-seconds", str(int(cfg.max_duration_seconds))]

plugins/providers/cape/tests/test_cape_provider.py

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@
99
Real-contract facts the fake reproduces:
1010
1111
* `run` parses argv with the real flag table: `--pool` / `--user` /
12-
`--task-id` are required and there is NO `--workspace` flag —
13-
violations exit 2 with an argparse-style usage error, exactly like
14-
the real parser.
12+
`--task-id` are required and there is NO `--workspace` and NO
13+
`--gpu-mode` flag (removed upstream; requests inherit
14+
exclusive/shared from the target pool) — violations exit 2 with an
15+
argparse-style usage error, exactly like the real parser.
1516
* Request ids look like the real ones (`req-%06d`).
1617
* `logs` returns EMPTY stdout/stderr while the request is running
1718
(CAPE has no streaming logs); output appears only once the request
@@ -34,9 +35,11 @@
3435
`FAKE_CAPE_SERVER_STATE` overrides the server request's state.
3536
3637
The fake keeps the status JSON minimal ({state, request_id,
37-
exit_code}) — the provider only ever reads `state` and the
38-
`request_id` echo. `cape wait` is deliberately not modeled: the
39-
provider must never call it (its exit code is untrustworthy).
38+
exit_code}) plus the new upstream cotenancy fields (`gpu_mode`,
39+
`gpu_cotenant_count`, `gpu_cotenant_counts`) which the provider must
40+
tolerate — it only ever reads `state` and the `request_id` echo.
41+
`cape wait` is deliberately not modeled: the provider must never call
42+
it (its exit code is untrustworthy).
4043
"""
4144

4245
from __future__ import annotations
@@ -130,7 +133,9 @@ def build_parser():
130133
run.add_argument("--hard-gpu-enforcement", action="store_true")
131134
run.add_argument("--port", type=int, action="append", default=[])
132135
run.add_argument("--locality")
133-
run.add_argument("--gpu-mode", default="whole")
136+
# NO --gpu-mode: upstream removed the flag (requests inherit
137+
# exclusive/shared from the target pool), so sending it makes
138+
# argparse exit 2 with a usage error, like any unknown flag.
134139
run.add_argument("--isolation-policy", default="default")
135140
run.add_argument("--cwd")
136141
run.add_argument("--env", action="append", default=[])
@@ -204,6 +209,12 @@ def do_status(args):
204209
record["exit_code"] = 124
205210
elif state in TERMINAL:
206211
record["exit_code"] = 0
212+
# New upstream cotenancy fields: gpu_mode is DERIVED from the target
213+
# pool (null | "exclusive" | "shared"); the provider must tolerate
214+
# (ignore) all three.
215+
record["gpu_mode"] = None
216+
record["gpu_cotenant_count"] = 0
217+
record["gpu_cotenant_counts"] = []
207218
print(json.dumps(record))
208219
209220
@@ -397,7 +408,9 @@ async def test_create_returns_sandbox_and_emits_real_run_face(cape_env: dict[str
397408
assert "--cwd" not in argv
398409
assert _flag(argv, "--image") == "docker://task-image:1"
399410
assert _flag(argv, "--gpus") == "2"
400-
assert _flag(argv, "--gpu-mode") == "whole"
411+
# Upstream removed --gpu-mode (pool-level exclusive/shared);
412+
# sending it would make the real CLI exit 2 before submitting.
413+
assert "--gpu-mode" not in argv
401414
assert _flag(argv, "--isolation-policy") == "default"
402415
assert _flag(argv, "--max-duration-seconds") == "14400"
403416
assert _flags(argv, "--bind") == [
@@ -506,6 +519,37 @@ def test_fake_cape_rejects_workspace_flag_and_requires_pool_user_task_id(
506519
assert flag in proc.stderr
507520

508521

522+
def test_fake_cape_rejects_dropped_gpu_mode_flag(cape_env: dict[str, Any]) -> None:
523+
# Upstream removed `cape run --gpu-mode` entirely (requests now
524+
# inherit exclusive/shared from the target pool). Sending the flag
525+
# is an unknown-argument error: argparse exits 2 with usage on
526+
# stderr, so a provider that still emits it never submits anything.
527+
binary = str(cape_env["binary"])
528+
legacy = [
529+
binary, "run", "--pool", "p", "--user", "u", "--task-id", "t",
530+
"--gpu-mode", "whole", "--image", "img", "--", "sh", "-c", "true",
531+
] # fmt: skip
532+
proc = subprocess.run(legacy, capture_output=True, text=True)
533+
assert proc.returncode == 2
534+
assert "--gpu-mode" in proc.stderr
535+
536+
537+
async def test_run_face_never_emits_dropped_gpu_mode_flag(cape_env: dict[str, Any]) -> None:
538+
# Regression for the upstream flag removal: the run argv must not
539+
# contain --gpu-mode for any GPU count (0 or N). The fake parser
540+
# would exit 2 on it, but assert explicitly so the failure reads as
541+
# a contract violation rather than a create() error.
542+
provider = _provider(cape_env)
543+
for resource, gpus in ((None, 0), (SandboxResource(gpu=4), 4)):
544+
sandbox = await provider.create(_sandbox_config(resource=resource))
545+
try:
546+
argv = _log_entries(cape_env, verb="run")[-1]["argv"]
547+
assert "--gpu-mode" not in argv
548+
assert _flag(argv, "--gpus") == str(gpus)
549+
finally:
550+
await provider.delete(sandbox.sandbox_id)
551+
552+
509553
async def test_fake_cape_logs_empty_while_running_populated_when_terminal(
510554
cape_env: dict[str, Any],
511555
) -> None:

0 commit comments

Comments
 (0)