Request
The payload of the channel request: the tag byte 7 and the request of the channel.
[7][request JSON …]
key. A string the client chooses: what a dequeue names to withdraw the message, and every other message still waiting under the same key. Two messages may share one. The server relays it verbatim and does not read it.content. An array of MCP content blocks, in order, each as theContentBlockof the crate’s MCP dependency states one: text, an image, audio, an embedded resource, or a resource link. What the container makes of a block is its own, and a container that cannot take one refuses the whole message, and the fate is the error, in the container’s words. A message with no block is refused too. The server relays the message to the proxy verbatim.- Malformed. A channel request whose payload does not decode as stated is answered by a channel response finish that no channel response precedes, and the server does nothing else for it.
The channel request is defined by diverge-sdk-rs/src/provider/endpoints/containers/agents/run/client/channel_request/frame.rs:
//! What a client's channel request frame carries for an agent container
//! run.
use std::error::Error;
use std::fmt;
use crate::wire::decode::Decode;
use crate::wire::encode::{Encode, Writer};
use crate::shared::containers::secrets::Values;
use crate::shared::containers::{dequeue, enqueue};
use crate::shared::containers::{fuse_mount, postgres, read, transfer, write_path};
/// What a caller asks a provider for while an agent container runs.
///
/// A payload leads with one byte saying which, and the rest is that
/// variant's own bytes.
///
/// | tag | asks for |
/// |-----|----------|
/// | `0` | [`Stop`](Self::Stop) |
/// | `1` | [`Filetree`](Self::Filetree) |
/// | `2` | [`Read`](Self::Read) |
/// | `3` | [`Write`](Self::Write) |
/// | `4` | [`Transfer`](Self::Transfer) |
/// | `5` | [`Postgres`](Self::Postgres) |
/// | `6` | [`Info`](Self::Info) |
/// | `7` | [`Enqueue`](Self::Enqueue) |
/// | `8` | [`Dequeue`](Self::Dequeue) |
/// | `9` | [`FuseMount`](Self::FuseMount) |
/// | `10` | [`Secrets`](Self::Secrets) |
///
/// The first seven are the same in every container scope, in the same
/// order, so a reader of one is a reader of all; what follows is this
/// family's own exchange — no daemon connection among them, since an
/// agent container holds no account and asks the daemon nothing —
/// and then the mount and the feed, which every scope has again, each
/// under a tag of its own. All of them but the first reach INTO the
/// container, which is the thing a caller cannot dial: it runs on the
/// provider. That is the whole reason these channels open outward
/// from the client rather than the other way. What comes OUT of the
/// agent — its conversation — is no channel: it rides the scope's own
/// main stream.
#[derive(Debug, Clone, PartialEq)]
pub enum Frame {
/// Stop the container. Tag `0`.
///
/// # It has no answer, and does not need one
///
/// Nothing comes back on this channel. What comes back is the end
/// of the SCOPE — a
/// [`ResponseFinish`](crate::wire::frame::server::ServerFrame::ResponseFinish),
/// which already means nothing bearing this scope follows on any
/// channel. Finishing this one first would be a smaller way of
/// saying the same thing, moments earlier.
///
/// # What it adds over closing the connection
///
/// The scope IS the container's life, so dropping the connection
/// stops it too. The difference is that a provider cannot tell a
/// deliberate exit from a network that stopped answering, and has
/// to wait to find out. This is unambiguous and immediate: a
/// caller that says so is not gone, it is finished.
///
Stop,
/// The container's filesystem, watched. Tag `1`.
///
/// Carries nothing — the variant is bare — and the provider answers
/// with a snapshot and then every change, for as long as the
/// channel lives. See
/// [`filetree`](crate::shared::containers::filetree).
Filetree,
/// One file, read out of the container. Tag `2`.
///
/// See [`read`](crate::shared::containers::read) for why this is
/// one file and never a directory.
Read(read::request::Request),
/// One file, written into the container. Tag `3`.
///
/// Carries no content. The provider answers by opening a channel
/// of its own asking for it — see
/// [`write_path`](crate::shared::containers::write_path) for why
/// it travels that direction, and
/// [`write_bytes`](crate::shared::containers::write_bytes) for
/// what comes back.
Write(write_path::request::Request),
/// One file, copied into another container. Tag `4`.
///
/// The other container by its id, and the caller must be running
/// it. The provider reads the file out of this
/// container and writes it into that one on its own connections
/// to the two proxies, and nothing of the file comes back here —
/// one answer does. See
/// [`transfer`](crate::shared::containers::transfer) for the rule.
Transfer(transfer::request::Request),
/// The caller's half of a database connection. Tag `5`.
///
/// Opened once the caller has taken the provider's half, quoting
/// the same connection; what comes back is everything the
/// container wrote. See
/// [`postgres`](crate::shared::containers::postgres) for the pair.
Postgres(postgres::request::Postgres),
/// What the arguments may be. Tag `6`.
///
/// Carries nothing — the variant is bare — and the provider answers
/// with the image's DIVERGE.md, parsed, or that it has none. See
/// [`info`](crate::shared::containers::info).
Info,
/// A message for the agent. Tag `7`.
///
/// The one way into it: a message with no loop running starts one,
/// on that message, and a message while one runs joins its queue.
/// Answered once — by an
/// [`enqueue::response::Frame`](crate::shared::containers::enqueue::response::Frame)
/// naming the message's fate, whenever that is known — and then the
/// finish. What the agent says in reply is the scope's main stream.
/// See [`enqueue`](crate::shared::containers::enqueue).
Enqueue(enqueue::request::Request),
/// Withdraw every message still waiting under a key. Tag `8`.
///
/// Carries the key, as the enqueues gave it. Answered once — by a
/// [`dequeue::response::Frame`](crate::shared::containers::dequeue::response::Frame)
/// saying whether anything waited under it — and then the finish.
/// Each message it withdraws is ALSO answered, on its own enqueue
/// channel. See [`dequeue`](crate::shared::containers::dequeue).
Dequeue(dequeue::request::Request),
/// A FUSE mount made while the container runs. Tag `9`.
///
/// The caller serves one more file or directory into the
/// container at a path, under an id of its own, as the mounts of
/// the run's request are served. Answered once — by a
/// [`fuse_mount::response::Frame`](crate::shared::containers::fuse_mount::response::Frame)
/// saying the mount is made, or why not — and then the finish;
/// the answer precedes every ask of the id. See
/// [`fuse_mount`](crate::shared::containers::fuse_mount).
FuseMount(fuse_mount::request::Request),
/// The secrets' values, fed to the program at an activation. Tag
/// `10`.
///
/// Carries the [`Values`] as JSON — every name the program
/// declared, each to its value, a `fixed` one read and a
/// `rotating` one locked — which the provider relays to the proxy,
/// and the proxy hands the program before it delivers anything.
/// Opened by the caller when an idle container is about to
/// receive work and holds no values: after the registration's
/// own feed, that is after every `Inactive`. Answered once — by a
/// [`feed::response::Frame`](crate::shared::containers::secrets::feed::response::Frame),
/// `Fed` or the program's refusal — and then the finish. See
/// [`secrets`](crate::shared::containers::secrets).
Secrets(Values),
}
/// Tag for [`Frame::Stop`].
const STOP: u8 = 0;
/// Tag for [`Frame::Filetree`].
const FILETREE: u8 = 1;
/// Tag for [`Frame::Read`].
const READ: u8 = 2;
/// Tag for [`Frame::Write`].
const WRITE: u8 = 3;
/// Tag for [`Frame::Transfer`].
const TRANSFER: u8 = 4;
/// Tag for [`Frame::Postgres`].
const POSTGRES: u8 = 5;
/// Tag for [`Frame::Info`].
const INFO: u8 = 6;
/// Tag for [`Frame::Enqueue`].
const ENQUEUE: u8 = 7;
/// Tag for [`Frame::Dequeue`].
const DEQUEUE: u8 = 8;
/// Tag for [`Frame::FuseMount`].
const FUSE_MOUNT: u8 = 9;
/// Tag for [`Frame::Secrets`].
const SECRETS: u8 = 10;
impl Encode for Frame {
/// The ordinary JSON failure, from whichever half has one.
type Error = serde_json::Error;
fn encode(&self, out: &mut Writer<'_>) -> Result<(), Self::Error> {
match self {
Frame::Stop => {
out.extend_from_slice(&[STOP]);
Ok(())
}
Frame::Filetree => {
out.extend_from_slice(&[FILETREE]);
Ok(())
}
Frame::Read(request) => {
out.extend_from_slice(&[READ]);
request.encode(out)
}
Frame::Write(request) => {
out.extend_from_slice(&[WRITE]);
request.encode(out)
}
Frame::Transfer(request) => {
out.extend_from_slice(&[TRANSFER]);
request.encode(out)
}
Frame::Postgres(request) => {
out.extend_from_slice(&[POSTGRES]);
request.encode(out).map_err(|error| match error {})
}
Frame::Info => {
out.extend_from_slice(&[INFO]);
Ok(())
}
Frame::Enqueue(request) => {
out.extend_from_slice(&[ENQUEUE]);
request.encode(out)
}
Frame::Dequeue(request) => {
out.extend_from_slice(&[DEQUEUE]);
request.encode(out)
}
Frame::FuseMount(request) => {
out.extend_from_slice(&[FUSE_MOUNT]);
request.encode(out)
}
Frame::Secrets(values) => {
out.extend_from_slice(&[SECRETS]);
serde_json::to_writer(out, values)
}
}
}
}
impl Decode<'_> for Frame {
/// Several ways to fail, and the parses among them name which.
type Error = FrameError;
fn decode(bytes: &[u8]) -> Result<Self, Self::Error> {
let (tag, rest) = bytes.split_first().ok_or(FrameError::Empty)?;
match *tag {
STOP => Ok(Frame::Stop),
FILETREE => Ok(Frame::Filetree),
READ => read::request::Request::decode(rest)
.map(Frame::Read)
.map_err(FrameError::Read),
WRITE => write_path::request::Request::decode(rest)
.map(Frame::Write)
.map_err(FrameError::Write),
TRANSFER => transfer::request::Request::decode(rest)
.map(Frame::Transfer)
.map_err(FrameError::Transfer),
POSTGRES => postgres::request::Postgres::decode(rest)
.map(Frame::Postgres)
.map_err(FrameError::Postgres),
INFO => Ok(Frame::Info),
ENQUEUE => enqueue::request::Request::decode(rest)
.map(Frame::Enqueue)
.map_err(FrameError::Enqueue),
DEQUEUE => dequeue::request::Request::decode(rest)
.map(Frame::Dequeue)
.map_err(FrameError::Dequeue),
FUSE_MOUNT => fuse_mount::request::Request::decode(rest)
.map(Frame::FuseMount)
.map_err(FrameError::FuseMount),
SECRETS => serde_json::from_slice(rest)
.map(Frame::Secrets)
.map_err(FrameError::Secrets),
tag => Err(FrameError::UnknownTag(tag)),
}
}
}
/// An agent container run channel request that could not be read.
#[derive(Debug)]
pub enum FrameError {
/// No bytes at all, so not even a tag.
Empty,
/// A tag that is none of this frame's.
UnknownTag(u8),
/// The read request did not parse.
Read(serde_json::Error),
/// The write request did not parse.
Write(serde_json::Error),
/// The transfer request did not parse.
Transfer(serde_json::Error),
/// The connection id was not four bytes.
Postgres(postgres::request::PostgresError),
/// The enqueued message did not parse as JSON.
Enqueue(serde_json::Error),
/// The dequeue's key did not parse as JSON.
Dequeue(serde_json::Error),
/// The mount request did not parse.
FuseMount(serde_json::Error),
/// The secrets' values did not parse as JSON. The message is the
/// parser's, which quotes no value.
Secrets(serde_json::Error),
}
impl fmt::Display for FrameError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FrameError::Empty => {
f.write_str("agents run channel request frame is empty")
}
FrameError::UnknownTag(tag) => {
write!(f, "unknown agents run channel request tag {tag}")
}
FrameError::Read(error) => {
write!(f, "read request did not parse: {error}")
}
FrameError::Write(error) => {
write!(f, "write request did not parse: {error}")
}
FrameError::Transfer(error) => {
write!(f, "transfer request did not parse: {error}")
}
FrameError::Postgres(error) => write!(f, "{error}"),
FrameError::Enqueue(error) => {
write!(f, "enqueue request did not parse: {error}")
}
FrameError::Dequeue(error) => {
write!(f, "dequeue request did not parse: {error}")
}
FrameError::FuseMount(error) => {
write!(f, "fuse mount request did not parse: {error}")
}
FrameError::Secrets(error) => {
write!(f, "secrets feed did not parse: {error}")
}
}
}
}
impl Error for FrameError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
FrameError::Read(error)
| FrameError::Write(error)
| FrameError::Transfer(error)
| FrameError::Enqueue(error)
| FrameError::Dequeue(error)
| FrameError::FuseMount(error)
| FrameError::Secrets(error) => Some(error),
FrameError::Postgres(error) => Some(error),
FrameError::Empty | FrameError::UnknownTag(_) => None,
}
}
}
and by diverge-sdk-rs/src/shared/containers/enqueue/request/request.rs:
//! A message for the running loop's queue.
use rmcp::model::ContentBlock;
use serde::{Deserialize, Serialize};
use serde_json::Error;
use crate::wire::decode::Decode;
use crate::wire::encode::{Encode, Writer};
/// Send a message to the agent.
///
/// With no loop running, the message starts one, and is its input.
/// With one running, the verb is the mechanism's: the message is QUEUED, not injected —
/// the turn in flight always runs to completion, and the agent picks
/// the message up at a seam of its own choosing: folded in beside the
/// next tool results, or opening the next turn when the assistant has
/// already finished. Nothing about this interrupts anything, ever.
///
/// # The content is MCP's
///
/// A list of content blocks — text, an image, audio, an embedded
/// resource, a link to one — in order, each as MCP's own
/// [`ContentBlock`] states it: one vocabulary for what a caller says
/// to an agent, what a tool answers, and what the agent says back.
/// What an agent's image makes of a block is its own. An image that
/// cannot take one refuses the whole message, in its own words, and
/// the fate carries them; nothing is reduced to its text on the
/// caller's behalf. A message with no block is refused too. The
/// [user parts](crate::provider::endpoints::containers::agents::run::server::response::user_parts)
/// that mark this message's delivery carry the same blocks back, one
/// chunk each, verbatim, under this key, at the position it landed.
///
/// # The answer says what became of it
///
/// One frame, then the finish: `delivered` when the agent has taken
/// the message into the conversation, `dequeued` when the caller
/// withdrew it first by a dequeue of its key, and an error when the agent refused the
/// message or no run could start on it. A run ending with the
/// message still waiting does not lose it: the message starts the
/// next run. The two fates carry nothing — the fate is the answer.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Request {
/// The caller's handle on the message: what a
/// [`dequeue`](crate::shared::containers::dequeue) names to
/// withdraw it, and every other message still waiting under the
/// same key. Not unique — two messages may share one — and not
/// read: the provider and the proxy compare it, and nothing else.
pub key: String,
/// The message's content, in order.
pub content: Vec<ContentBlock>,
}
/// Its JSON, and nothing in front of it. The tag that says which
/// request this is belongs to whichever frame carries it.
impl Encode for Request {
/// The ordinary JSON failure.
type Error = Error;
fn encode(&self, out: &mut Writer<'_>) -> Result<(), Error> {
serde_json::to_writer(out, self)
}
}
impl Decode<'_> for Request {
/// The ordinary JSON failure. There is nothing else here to get
/// wrong — no tag to be unknown, and no empty case, since no bytes
/// at all is a JSON document that ended too early and is reported
/// as one.
type Error = Error;
fn decode(bytes: &[u8]) -> Result<Self, Error> {
serde_json::from_slice(bytes)
}
}