← Về thư mục
📄 / / profiles / r-and-d / raw_evidence / OpenAI4S / openai4s / compute / README.md

Remote-compute host backend

中文说明

The host side of the evolving host.compute job surface lives here, together with the separate registry that the purpose-built remote-science services use to find a real GPU host. Heavy work leaves the machine one of two ways: through a discovered byoc:<id> provider, or over an ssh:<alias> connection the user already has. This is orchestration and transport code. There is no scheduler here, no GPU runtime, and no scientific model.

Where this fits

The Python host.compute SDK turns every call into a compute_<operation> Host RPC. HostDispatcher creates one ComputeManager per session on first use and maps ComputeError into a structured soft failure. The native compute_submit, compute_result, compute_cancel and compute_close tools expose a bounded slice of that control plane; the richer SDK compatibility calls reach the same manager.

For byoc:*, the manager looks for provider shims under skills/remote-compute-<id>/, stages a job archive, and runs the confined openai4s_compute_provider helper with credentials on stdin. For ssh:*, it shells out to local ssh/scp against a user-configured alias. Harvested files land below the configured data directory, in hpc/<job_id>/.

Files

File Responsibility
__init__.py Exports the two names the rest of the host needs: ComputeManager and the structured ComputeError.
manager.py Both transports. It discovers BYOC provider Skills, routes byoc:* and ssh:*, refuses a submission once the session's concurrency limit is reached, stages inputs and the job templates, and tracks live jobs and warm sandboxes through poll, cancel, close and harvest. An alias an agent names has to be one the user actually offered — the registry, or their own ~/.ssh/config — and every ssh/scp argv is assembled in one place, so that guard is structural rather than remembered at each call site. Output from a machine we do not control is drained so the remote never blocks on write, but retained only up to a cap: a chatty login profile or a forced command otherwise had the whole per-command timeout to make the daemon allocate. The credential is picked out by the environment-variable names the provider declares, and it reaches the helper on stdin rather than through the environment. The helper's environment is otherwise the daemon's own, minus every name the helper's own scrub_secret_env would drop (credential-shaped names and the baseline secret prefixes) and the provider's declared secret_env names. That filter runs before exec, not only inside the helper, because a process can always read its own exec-time environment block and an in-process scrub does not change it. The helper is started in a session of its own: on macOS that is what makes the profile's process-info denies hold, so it cannot read the daemon's environment either. A staged input may name an Artifact version_id instead of a path: the manager stages only the frozen snapshot bytes the Host resolved for that version, refuses a version the Host did not resolve, and persists the declared versions on the job row so every later result carries them. The ssh transport takes no staged inputs at all — upload explicitly before submit.
stage11.py Stage 11 product hooks, opt-in via OPENAI4S_STAGE11_DURABLE_REMOTE_COMPUTE: the flag itself, the first-access recovery projection over the durable jobs the lazy manager rehydrates when it is constructed, and the harvest Artifact provenance receipts that bind a verified manifest — remote environment, input versions, job receipt, per-file checksums — to exact workspace paths. A terminal re-poll's cached harvest deliberately yields no receipts, and nothing here resubmits.
states.py The job vocabulary and its transition table, enforced when a status is written. unknown is deliberately live: the remote operation may or may not have landed, so it is rehydrated and reconciled rather than forgotten. A terminal state cannot be re-opened by a late probe — including by a write of the same status, because the write carries the evidence that established it, so two pollers that both read running would otherwise each commit their own manifest over the other's.
manifest.py What a harvest actually produced: {path, size, sha256} per file plus one digest over the set, and the reconciliation of that record against the globs the job declared in outputs. A declared pattern matching nothing is why a job that exited 0 can still be failed — and the size/hash pair is the only thing that can see a transfer truncated at rc==0. A declaration may also ask for visibility: hidden or residency: remote; neither is counted missing, and a stay-remote pattern is excluded from the transfer rather than merely excused from the reconciliation.
registry.py Remembers which SSH host aliases exist, which one is the default, and what fold/score_mutations-style capability metadata has been provisioned on each, written atomically to <data_dir>/remote_compute.json. Native registration probes the host before it records verification; a host seeded from the legacy environment variable may sit there unverified. is_known_alias is also the authorization side of the ssh transport: registered here, or present in the user's own ~/.ssh/config. It stores no SSH private keys and no provider tokens.
safe_archive.py Enumerate-then-extract for a harvest arriving from a machine we do not control — used by both transports, since the bytes are attacker-shaped either way. Traversal, absolute paths, links, device nodes and decompression bombs are refused before a byte is written, and rejection is all-or-nothing: a partial extraction of a hostile archive is still a compromise. tarfile.extractall defends against none of it on the Python versions in scope.

Subdirectories

Directory Responsibility
templates/ Shell templates staged into a BYOC job. They run the submitted command, handle the time and deadline limits, and package output and logs for harvest. See its README.

Current lifecycle

  1. submit validates the provider family, checks the concurrency count, and — for ssh:* — refuses a destination the user never offered. The job row is claimed before the remote call, so there is something to reconcile against however the submit ends.
  2. A BYOC submission creates or reuses a provider sandbox, builds in.tar.gz from the wrapper, the command and the inputs, and calls the helper's create and submit operations. The submit carries the container's absolute expiry alongside the harvest margin and the term grace, so the wrapper's watchdog is actually armed: nothing on the host used to produce those values, and a container could be reclaimed mid-job, taking the results with it. An SSH submission creates a remote work directory and starts run.sh under nohup.
  3. result polls the exact job this session owns. On the BYOC path the helper's wait stages out.tar.gz; the SSH path builds its own archive of the work directory remotely and pulls it back. Both go through the same hostile-input-safe extractor and land under hpc/<job_id>/. The SSH work directory itself stays on the remote host — only the staged tarball is removed.
  4. cancel signals the remote process group and confirms it is gone, or terminates the BYOC sandbox. close terminates each job individually and marks it cancelled only on confirmation; anything unconfirmed stays live, keeps its slot, and is reported back by name. Because BYOC jobs share one sandbox per provider, a confirmed sandbox termination ends every live job riding it — including ones this handle never named.

Persistence, approval, and maturity boundaries