# Request

> The payload is the tag byte 1 followed by the request as JSON: the image, the limits and the mounts.

Canonical: https://provider.diverge.network/2.3.0/endpoints/containers-tools-run/request/
Specification revision: 2.3.0

The payload of the request is the tag byte followed by the container
as JSON, read as [Notation](/2.3.0/endpoints/#notation) provides.

```text
[1][container JSON …]
```

- **`image`.** Exactly one of three objects: `{"client":{"name":…,"digest":…}}`,
  an image the client holds, which the server pulls from its own
  registry and fetches from the client by digest;
  `{"server":{"name":…,"digest":…}}`, an image the server obtains
  from a source of its own; `{"registry":{"reference":…}}`, a
  reference the server pulls. `name` is a repository path without a
  host; `digest` is `<algorithm>:<hex>`; `reference` is what the
  server's runtime accepts.
- **`memory`, `disk`.** Limits in bytes: the memory the container may
  use, and the bytes the container may write to its own filesystem.
  Neither governs a mount.
- **`volume_mounts`.** Ordered; the server applies them in order.
  `host_name` is a `name` in the client's listing; `host_relative_path`
  and `container_path` are components. An empty `container_path`
  names the root, which the server refuses.
- **`identity_file_mounts`, `identity_directory_mounts`.** Each names
  content by `identity`, `<size>:<base64url sha256>` with the hash of
  the bytes for a file and of the manifest for a directory, as the
  [volumes::stat response](/2.3.0/endpoints/volumes-stat/response/)
  defines the manifest. Every one is mounted read-only before the
  container starts.
- **`fuse_file_mounts`, `fuse_directory_mounts`.** Each names a path,
  an `id` the client chose, and `readonly`, absent meaning `false`.
  Every one is mounted before the id is sent.
- **Paths.** No component of any mount's path is empty, `.` or `..`.
  No two mounts have one path. No mount lies inside a FUSE directory
  mount. A FUSE file mount may lie inside a directory another mount
  provides. No mount's path is the root.
- **Ids.** No two FUSE mounts, on either list, have one `id`.

- **Malformed.** A payload with no byte, a payload whose first byte
  is not `1`, and a payload whose bytes after the tag do not
  parse as the request are malformed. [Endpoints](/2.3.0/endpoints/) states
  how a server answers a request it cannot read.

The request is defined by `diverge-provider-sdk/src/endpoints/containers/tools/run/client/request/frame.rs`:

```rust
//! What a client's request frame carries for a tool container run.

use crate::decode::Decode;
use crate::encode::{Encode, Writer};
use crate::shared::containers::request::Container;

/// Ask a provider to create a tool container.
///
/// A [`Container`] and nothing else: the image, the limits, the
/// mounts. What makes it a tool container rather than the other kind is not in
/// the request — it is the image, and it is what the caller does on
/// the channels once it runs: the five MCP exchanges in
/// [`shared::mcp`](crate::shared::mcp), into the server the container
/// runs.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Frame(
    /// What to run.
    pub Container,
);

/// This frame's tag among the scope-opening requests.
///
/// One byte at the front of the payload, which is what tells a reader
/// which request it holds. The frame layer does not discriminate them
/// — [`ClientFrame::Request`](crate::frame::client::ClientFrame::Request)
/// is one type carrying bytes — so the distinction has to be in the
/// bytes, and each request owns the value that names it.
///
/// See the table in [`endpoints`](crate::endpoints) for the whole
/// allocation. The values are chosen across modules that do not know
/// about each other, so the table is the only place they can be seen
/// at once.
const TAG: u8 = 1;

/// JSON, matching [`images::check`](crate::endpoints::images::check)
/// rather than the postcard [`volumes`](crate::endpoints::volumes)
/// uses. One of these is sent per container rather than per
/// filesystem event, so there is no throughput to optimize for — and
/// it names an image the same way a check does, which is reason
/// enough for the two to look alike on the wire.
impl Encode for Frame {
    /// The ordinary JSON failure. The tag cannot fail.
    type Error = serde_json::Error;

    fn encode(&self, out: &mut Writer<'_>) -> Result<(), Self::Error> {
        out.extend_from_slice(&[TAG]);
        serde_json::to_writer(out, &self.0)
    }
}

impl Decode<'_> for Frame {
    /// Three ways to fail, and only one of them is JSON.
    type Error = FrameError;

    fn decode(bytes: &[u8]) -> Result<Self, Self::Error> {
        let (tag, rest) = bytes.split_first().ok_or(FrameError::Empty)?;
        if *tag != TAG {
            return Err(FrameError::UnexpectedTag(*tag));
        }
        serde_json::from_slice(rest).map(Frame).map_err(FrameError::Body)
    }
}

/// A tool container run request that could not be read.
#[derive(Debug)]
pub enum FrameError {
    /// No bytes at all, so not even a tag.
    Empty,
    /// A tag naming some other request.
    ///
    /// A reader that dispatched on the tag will not see this. One that
    /// assumed which request it held, and was wrong, will — which is
    /// the point of checking a tag rather than skipping it.
    UnexpectedTag(u8),
    /// The request did not parse.
    Body(serde_json::Error),
}

impl std::fmt::Display for FrameError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            FrameError::Empty => {
                f.write_str("tools run request frame is empty")
            }
            FrameError::UnexpectedTag(tag) => {
                write!(f, "expected tools run request tag {TAG}, found {tag}")
            }
            FrameError::Body(error) => {
                write!(f, "tools run request did not parse: {error}")
            }
        }
    }
}

impl std::error::Error for FrameError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            FrameError::Body(error) => Some(error),
            FrameError::Empty | FrameError::UnexpectedTag(_) => None,
        }
    }
}
```

The container is defined by `diverge-provider-sdk/src/shared/containers/request/container.rs`:

```rust
//! Asking for a container.

use serde::{Deserialize, Serialize};

use super::{FuseMount, IdentityMount, Image, VolumeMount};

/// Ask a provider to create a container.
///
/// Everything here is what a caller may CHOOSE, and it is the same for
/// every kind of container: what differs between an agent and a tool
/// server is asked once the container runs, on a channel, not here.
/// What a caller may not choose is not here at all rather than here
/// and ignored — the container's name, its published ports, its
/// entrypoint and its environment are the provider's, because they
/// are how the provider reaches the container and how the container
/// reaches back. There is no environment: the mounts are the caller's
/// only provisioning channel, and a field that is accepted and
/// ignored is a field callers will believe in.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Container {
    /// The image, and who supplies it.
    ///
    /// See [`Image`]. Which variant it is decides how the image is
    /// named, which is why the source and the name are one field
    /// rather than two that only make sense together.
    pub image: Image,
    /// How much memory the container may have, in BYTES.
    ///
    /// A ceiling, not a hint. A process that exceeds what the
    /// container is allowed is killed by the kernel rather than told —
    /// no failed allocation to catch, no warning first — and the
    /// container will not see this number in its own
    /// `/proc/meminfo`, which reports the host's. An image that sizes
    /// itself off what it thinks it has will size itself wrong.
    ///
    /// Bytes rather than megabytes because a unit that has to be
    /// spelled out in prose is a unit half of everyone gets wrong.
    pub memory: u64,
    /// How much the container may WRITE, in BYTES.
    ///
    /// Its own filesystem only — what it adds to or changes over the
    /// image it came from. The image's layers are read-only and are
    /// not counted, so a container starts at nothing however large the
    /// image is.
    ///
    /// # It does not govern the mounts
    ///
    /// A volume is storage that already existed, with a size of its
    /// own that a [`volume`](crate::endpoints::volumes) stated when it
    /// was made, and hash-mounted content is read-only. A number here
    /// that silently applied to either would be this request deciding
    /// how much of somebody else's storage a container may fill.
    ///
    /// Bytes rather than megabytes, for the reason
    /// [`memory`](Self::memory) gives.
    pub disk: u64,
    /// Volumes the provider offers, made visible inside the container.
    ///
    /// Ordered, and a provider applies them in order. See
    /// [`VolumeMount`] for how one is named without a host path.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub volume_mounts: Vec<VolumeMount>,
    /// Files the caller holds, by content, mounted read-only.
    ///
    /// Each names a file by its identity — see [`IdentityMount`].
    /// The provider MUST mount every one before the container starts,
    /// fetching what it does not hold from the caller by that
    /// identity.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub identity_file_mounts: Vec<IdentityMount>,
    /// Directories the caller holds, by content, mounted read-only.
    ///
    /// Each names a directory by its identity — see
    /// [`IdentityMount`]. No mount's path, in any of the three lists, is
    /// a prefix of another's: mounting INTO a directory the image owns
    /// is the point, and mounts stacking on each other is not.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub identity_directory_mounts: Vec<IdentityMount>,
    /// Files the caller serves LIVE, mounted one each over FUSE.
    ///
    /// Each names a file by a path, an id of the caller's, and
    /// whether the container may write it — see [`FuseMount`]. The
    /// provider MUST mount every one before the container starts, and
    /// every open and every changed close inside the container is one
    /// ask back to the caller, by that id. The file is overwritten in
    /// place only; a program that replaces its file by rename needs a
    /// directory mount. A path may lie inside a directory another
    /// mount provides; it may not equal another mount's path.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub fuse_file_mounts: Vec<FuseMount>,
    /// Directories the caller serves LIVE, mounted one each over FUSE.
    ///
    /// Each names a directory by a path, an id of the caller's, and
    /// whether the container may change it — see [`FuseMount`]. The
    /// whole tree under the path is the caller's: every listing,
    /// read, write, creation, removal and rename inside the container
    /// is one ask back to the caller, by that id. No other mount may
    /// lie inside it.
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub fuse_directory_mounts: Vec<FuseMount>,
}
```

and by `diverge-provider-sdk/src/shared/containers/request/image.rs`:

```rust
//! What image a container is made from, and who supplies it.

use serde::{Deserialize, Serialize};

/// The image, named the way whoever supplies it can be asked for it.
///
/// One question — can the provider get these bytes, and if not, who
/// can — and the answer decides how the image is named, which is why
/// this is one field rather than a source beside a string that means
/// something different depending on it.
///
/// # Two of the three are pinned, and the third is a choice
///
/// [`Client`](Self::Client) and [`Server`](Self::Server) carry a
/// repository name and a manifest digest, the same pair
/// [`images::check`](crate::endpoints::images::check::client::request::Frame)
/// asks about — so a check that came back available names an image a
/// run can ask for, with nothing to translate between them.
///
/// A digest cannot be repointed at different content, so those two
/// mean the same bytes every time. [`Registry`](Self::Registry) need
/// not, and that is the point: a caller writing `ubuntu:22.04` is
/// asking to track it, exactly as a loose version constraint on a
/// Python requirement is. One that wants the guarantee writes a digest
/// into the reference and gets it.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Image {
    /// The caller holds it.
    ///
    /// For images that exist nowhere a provider can reach — built
    /// locally, never pushed, carrying a digest no registry has heard
    /// of.
    ///
    /// The provider runs a registry of its own and its runtime pulls
    /// from that; what the registry does not hold, the provider asks
    /// the caller for by digest — a manifest, a blob — over
    /// [`oci`](crate::shared::containers::oci). The caller needs no
    /// registry and no HTTP: it needs the image's manifest and blobs
    /// in a store keyed by digest, which is what an image is once it
    /// has been saved anywhere, and it answers two kinds of fetch.
    ///
    /// The runtime never learns the registry is a proxy, and every
    /// header it relies on — `Content-Type`, `Content-Length`,
    /// `Range`, `Docker-Content-Digest` — is the provider's registry
    /// answering from its store. A runtime already indexes layers by
    /// digest and skips the ones it holds, so letting it pull means
    /// that logic is USED rather than reimplemented beside it.
    Client {
        /// The repository path — `library/nginx`, `myorg/myimage`.
        ///
        /// # It lands in a URL path
        ///
        /// The provider builds its pull reference by concatenation —
        /// its registry's address, a repository segment, then this —
        /// with no parsing. Which makes it a path fragment wearing the costume
        /// of a name: a `..` in it walks out of the scope segment and
        /// into another caller's namespace, so a provider normalizes
        /// or refuses before concatenating. The field cannot enforce
        /// that and does not pretend to.
        name: String,
        /// The manifest digest, `<algorithm>:<hex>`.
        ///
        /// What actually identifies the image, and the reason nothing
        /// here has to trust the name: the provider hashes what the
        /// caller sent before storing it, and the runtime hashes again
        /// on pull, so a caller holding the wrong bytes under the
        /// right digest fails before anything runs.
        digest: String,
    },
    /// The provider produces it, however it likes.
    ///
    /// Its own mirror, a pull-through cache, a private registry it
    /// holds credentials for, or something already on disk. A caller
    /// does not know and is not told.
    ///
    /// Which is what makes proprietary images expressible: a provider
    /// serves an image no public registry carries, and a caller asks
    /// for it, without the caller ever being able to fetch it itself.
    ///
    /// Ask [`images::check`](crate::endpoints::images::check) first if
    /// the answer matters before the container does. It takes this
    /// same pair, so what a check said yes to is what a run names.
    Server {
        /// The repository path — `library/nginx`, `myorg/myimage`.
        ///
        /// Kept alongside the digest because a digest alone is not
        /// resolvable: every registry API is repository-scoped, and
        /// there is no lookup from a digest to wherever it lives.
        ///
        /// No host. Where a provider gets the image is the provider's
        /// business, and a caller naming a source it cannot reach
        /// would be asserting something it has no standing to assert.
        name: String,
        /// The manifest digest, `<algorithm>:<hex>`.
        ///
        /// What actually identifies the image. Any registry serving
        /// these bytes serves the same image, which is what lets the
        /// provider choose where to get them.
        digest: String,
    },
    /// The provider pulls from where the caller says.
    ///
    /// The one case where the CALLER chooses the source, for public
    /// images where it knows what it wants and the provider has no
    /// opinion.
    ///
    /// Which makes the reference a host a caller picked, and the
    /// provider connects there and runs what it finds. Which
    /// registries are reachable is a provider's policy to set and
    /// enforce, and nothing here can express that policy — a caller
    /// learns it by being refused.
    Registry {
        /// Whatever a container runtime accepts —
        /// `ghcr.io/org/image@sha256:…`, `docker.io/library/ubuntu:22.04`.
        ///
        /// One string rather than a name and a digest, because the
        /// other two variants split them for a reason that does not
        /// apply here. There the pair exists so a provider can go to a
        /// source the caller did not name; here the caller named the
        /// source, and a reference is how a source is named — host,
        /// repository, and tag or digest, in the form the runtime
        /// already parses.
        reference: String,
    },
}
```

and by `diverge-provider-sdk/src/shared/containers/request/volume_mount.rs`:

```rust
//! One volume made visible inside a container.

use serde::{Deserialize, Serialize};

/// A volume from a listing, mounted into the container.
///
/// Host side first, then the container side — source before
/// destination, the order a mount reads in everywhere else.
///
/// # Why the host side is a name and an offset
///
/// Because a host path is not something a caller is allowed to state.
/// [`host_name`](Self::host_name) is a
/// [`Volume::name`](crate::endpoints::volumes::list::server::response::Volume::name)
/// the provider published, and
/// [`host_relative_path`](Self::host_relative_path) descends from
/// wherever that maps to — so a caller reaches a subdirectory of
/// something it was offered, and nothing else.
///
/// That is the same access model as
/// [`watch`](crate::endpoints::volumes::watch), and it holds for the same
/// reason: a provider never validates a path, it resolves a name it
/// chose and then descends. A caller cannot escape upward, because
/// there is no component it can write that means "up" — the offset is
/// components, and `..` is a name, not an instruction.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub struct VolumeMount {
    /// Which offered volume, by the name a listing gave it.
    ///
    /// Names come from
    /// [`Volume::name`](crate::endpoints::volumes::list::server::response::Volume::name)
    /// and mean nothing outside the provider that published them.
    pub host_name: String,
    /// How far into that volume to start, as path components
    /// relative to it.
    ///
    /// Empty mounts the volume itself, which is the common case;
    /// anything else mounts a subdirectory of it.
    pub host_relative_path: Vec<String>,
    /// Where it appears inside the container, as path components from
    /// the container's root.
    ///
    /// Empty means the root itself, which a provider will almost
    /// certainly refuse — the image's own filesystem is there.
    pub container_path: Vec<String>,
}
```

and by `diverge-provider-sdk/src/shared/containers/request/identity_mount.rs`:

```rust
//! One thing the caller mounts into the container.

use serde::{Deserialize, Serialize};

/// Content the caller wants present in the container's filesystem:
/// where, and what.
///
/// One shape for a file and for a directory — which it is, the field
/// it sits in says ([`identity_file_mounts`](super::Container::identity_file_mounts) or
/// [`identity_directory_mounts`](super::Container::identity_directory_mounts)), and the
/// [`identity`](Self::identity) grammar agrees. The server MUST mount every
/// one — read-only — before the container starts: the request
/// naming it IS the requirement. What the server does not hold it
/// MAY fetch from the client, by the hash, over the fetch exchanges.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct IdentityMount {
    /// Where it appears inside the container, as path components
    /// from the container's root — the shape every path in this
    /// crate takes, as
    /// [`container_path`](super::VolumeMount::container_path)
    /// does for a volume.
    ///
    /// No component is empty, `.` or `..` — an offset is components,
    /// and `..` is a name, not an instruction — and no mount's path
    /// is a prefix of another mount's, file or directory. Mounting
    /// INTO a directory the image owns is the point; mounts stacking
    /// on each other is not. Empty would name the root, which a
    /// provider refuses: the image's own filesystem is there.
    pub container_path: Vec<String>,
    /// The content's size-bearing identity:
    /// `<size>:<base64url sha256>` — the size in bytes, then the
    /// hash. For a file the hash is of its bytes; for a directory it
    /// is of its manifest — one `<hash> <size> <path>` line per file,
    /// paths relative and `/`-separated, sorted bytewise — and the
    /// size is the total. Which of the two it is, the field it sits in
    /// says, as does the fetch that asks for it; the value does not
    /// need to.
    ///
    /// Because the size rides the identity, a server can refuse an
    /// oversized request up front, as a request error, with nothing
    /// fetched.
    pub identity: String,
}
```

and by `diverge-provider-sdk/src/shared/containers/request/fuse_mount.rs`:

```rust
//! One file, or one directory, the caller serves live into the
//! container.

use serde::{Deserialize, Serialize};

/// A file or a directory the caller keeps, mounted into the container
/// over FUSE: where, under what id, and whether the container may
/// change it. Which of the two it is, is which list of the
/// [`Container`](super::Container) it is on.
///
/// The provider MUST mount every one before it answers the run's id
/// — one request to the proxy inside the container for each, on
/// [`/fuse/mount`](crate::container_proxy::fuse::mount), each
/// complete before the agent is registered and before any filetree
/// is opened — as a filesystem the caller serves: the mount point is the file or the
/// directory itself, made if absent with every missing parent
/// directory made too, and the directory around it stays whatever
/// the image or another mount made it. Nothing in the container or
/// the provider copies the contents in or reads them back: every
/// read, write, listing, removal, rename and new directory is one
/// exchange in [`fuse`](super::super::fuse), carrying the id, and the
/// caller serves it from wherever it keeps the thing. It is for the
/// credential files vendor CLIs rewrite when they refresh a login.
///
/// A FILE mount is one regular file that can be read and, unless
/// [`readonly`](Self::readonly), overwritten in place — opened,
/// truncated, written, closed — but never deleted or moved, and
/// never replaced by a rename: the mount point is the file itself,
/// and the kernel refuses to unlink or rename a mount point, so a
/// program that saves by writing a temporary beside the file and
/// renaming it over the file fails at the rename. A DIRECTORY mount
/// is for that program: a whole tree the caller serves, whose every
/// entry can be created, overwritten by either method, renamed and
/// deleted, and whose root alone is fixed.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct FuseMount {
    /// Where the mount appears inside the container, as path
    /// components from the container's root — the shape every path in
    /// this crate takes, as
    /// [`container_path`](super::IdentityMount::container_path) does
    /// for content.
    ///
    /// No component is empty, `.` or `..`. A path inside a directory
    /// another mount provides is allowed — a file over a directory a
    /// volume brought is the ordinary case — but a path equal to
    /// another mount's, or inside another FUSE directory mount, is
    /// not, and neither is the root.
    pub container_path: Vec<String>,
    /// The caller's own id for the mount: opaque, minted by the caller
    /// when it named the mount, and echoed back on every ask the
    /// provider sends for it. Two mounts on one request, on either
    /// list, may not share an id.
    pub id: String,
    /// Whether the container may change it. `true`: every open for
    /// writing, truncate, write, create, rename, removal and new
    /// directory inside the container fails, and the provider never
    /// sends a mutation for this id. `false`: the file is overwritable
    /// in place, or the tree fully editable, every change stored with
    /// the caller as it lands.
    #[serde(default)]
    pub readonly: bool,
}
```
