From b5702dc25cf3cb8ce4b5b5282cd3b8bc88b93c39 Mon Sep 17 00:00:00 2001
From: Sascha Grunert <sgrunert@redhat.com>
Date: Mon, 18 Mar 2024 15:17:28 +0100
Subject: [PATCH] Add streaming server

Signed-off-by: Sascha Grunert <sgrunert@redhat.com>
---
 Cargo.lock                               | 329 +++++++-
 Makefile                                 |   2 +-
 conmon-rs/common/proto/conmon.capnp      |  18 +
 conmon-rs/server/Cargo.toml              |   3 +
 conmon-rs/server/src/attach.rs           |   5 +
 conmon-rs/server/src/bounded_hashmap.rs  | 131 ++++
 conmon-rs/server/src/config.rs           |   4 +-
 conmon-rs/server/src/container_io.rs     |  43 +-
 conmon-rs/server/src/lib.rs              |   2 +
 conmon-rs/server/src/rpc.rs              |  56 ++
 conmon-rs/server/src/server.rs           |  32 +-
 conmon-rs/server/src/streaming_server.rs | 422 ++++++++++
 conmon-rs/server/src/streams.rs          |  16 +-
 conmon-rs/server/src/terminal.rs         |   5 +-
 internal/proto/conmon.capnp.go           | 931 +++++++++++++++++------
 pkg/client/client.go                     |  92 +++
 pkg/client/suite_test.go                 |   2 +-
 17 files changed, 1827 insertions(+), 266 deletions(-)
 create mode 100644 conmon-rs/server/src/bounded_hashmap.rs
 create mode 100644 conmon-rs/server/src/streaming_server.rs

diff --git a/Cargo.lock b/Cargo.lock
index 31d068ea86..e1beb7449f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -90,6 +90,19 @@ version = "1.0.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
 
+[[package]]
+name = "async-channel"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3"
+dependencies = [
+ "concurrent-queue",
+ "event-listener",
+ "event-listener-strategy",
+ "futures-core",
+ "pin-project-lite",
+]
+
 [[package]]
 name = "async-stream"
 version = "0.3.5"
@@ -136,13 +149,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
 dependencies = [
  "async-trait",
- "axum-core",
+ "axum-core 0.3.4",
  "bitflags 1.3.2",
  "bytes",
  "futures-util",
- "http",
- "http-body",
- "hyper",
+ "http 0.2.12",
+ "http-body 0.4.6",
+ "hyper 0.14.28",
  "itoa",
  "matchit",
  "memchr",
@@ -157,6 +170,43 @@ dependencies = [
  "tower-service",
 ]
 
+[[package]]
+name = "axum"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1236b4b292f6c4d6dc34604bb5120d85c3fe1d1aa596bd5cc52ca054d13e7b9e"
+dependencies = [
+ "async-trait",
+ "axum-core 0.4.3",
+ "base64",
+ "bytes",
+ "futures-util",
+ "http 1.1.0",
+ "http-body 1.0.0",
+ "http-body-util",
+ "hyper 1.2.0",
+ "hyper-util",
+ "itoa",
+ "matchit",
+ "memchr",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustversion",
+ "serde",
+ "serde_json",
+ "serde_path_to_error",
+ "serde_urlencoded",
+ "sha1",
+ "sync_wrapper",
+ "tokio",
+ "tokio-tungstenite",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
 [[package]]
 name = "axum-core"
 version = "0.3.4"
@@ -166,12 +216,33 @@ dependencies = [
  "async-trait",
  "bytes",
  "futures-util",
- "http",
- "http-body",
+ "http 0.2.12",
+ "http-body 0.4.6",
+ "mime",
+ "rustversion",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "axum-core"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "futures-util",
+ "http 1.1.0",
+ "http-body 1.0.0",
+ "http-body-util",
  "mime",
+ "pin-project-lite",
  "rustversion",
+ "sync_wrapper",
  "tower-layer",
  "tower-service",
+ "tracing",
 ]
 
 [[package]]
@@ -222,6 +293,12 @@ version = "3.15.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"
 
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
 [[package]]
 name = "bytes"
 version = "1.5.0"
@@ -352,6 +429,15 @@ dependencies = [
  "tokio",
 ]
 
+[[package]]
+name = "concurrent-queue"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363"
+dependencies = [
+ "crossbeam-utils",
+]
+
 [[package]]
 name = "conmon-common"
 version = "0.6.1"
@@ -365,6 +451,8 @@ name = "conmonrs"
 version = "0.6.1"
 dependencies = [
  "anyhow",
+ "async-channel",
+ "axum 0.7.4",
  "capnp",
  "capnp-rpc",
  "clap",
@@ -400,6 +488,7 @@ dependencies = [
  "tokio-eventfd",
  "tokio-seqpacket",
  "tokio-util",
+ "tower-http",
  "tracing",
  "tracing-opentelemetry",
  "tracing-subscriber",
@@ -500,6 +589,12 @@ dependencies = [
  "parking_lot_core",
 ]
 
+[[package]]
+name = "data-encoding"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
+
 [[package]]
 name = "deranged"
 version = "0.3.11"
@@ -554,6 +649,27 @@ dependencies = [
  "windows-sys 0.52.0",
 ]
 
+[[package]]
+name = "event-listener"
+version = "5.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener-strategy"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291"
+dependencies = [
+ "event-listener",
+ "pin-project-lite",
+]
+
 [[package]]
 name = "fastrand"
 version = "1.9.0"
@@ -793,7 +909,26 @@ dependencies = [
  "futures-core",
  "futures-sink",
  "futures-util",
- "http",
+ "http 0.2.12",
+ "indexmap 2.0.1",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "h2"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51ee2dd2e4f378392eeff5d51618cd9a63166a2513846bbc55f21cfacd9199d4"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http 1.1.0",
  "indexmap 2.0.1",
  "slab",
  "tokio",
@@ -845,6 +980,17 @@ dependencies = [
  "itoa",
 ]
 
+[[package]]
+name = "http"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
 [[package]]
 name = "http-body"
 version = "0.4.6"
@@ -852,7 +998,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
 dependencies = [
  "bytes",
- "http",
+ "http 0.2.12",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
+dependencies = [
+ "bytes",
+ "http 1.1.0",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http 1.1.0",
+ "http-body 1.0.0",
  "pin-project-lite",
 ]
 
@@ -878,9 +1047,9 @@ dependencies = [
  "futures-channel",
  "futures-core",
  "futures-util",
- "h2",
- "http",
- "http-body",
+ "h2 0.3.25",
+ "http 0.2.12",
+ "http-body 0.4.6",
  "httparse",
  "httpdate",
  "itoa",
@@ -892,18 +1061,54 @@ dependencies = [
  "want",
 ]
 
+[[package]]
+name = "hyper"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "h2 0.4.3",
+ "http 1.1.0",
+ "http-body 1.0.0",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+]
+
 [[package]]
 name = "hyper-timeout"
 version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1"
 dependencies = [
- "hyper",
+ "hyper 0.14.28",
  "pin-project-lite",
  "tokio",
  "tokio-io-timeout",
 ]
 
+[[package]]
+name = "hyper-util"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa"
+dependencies = [
+ "bytes",
+ "futures-util",
+ "http 1.1.0",
+ "http-body 1.0.0",
+ "hyper 1.2.0",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+]
+
 [[package]]
 name = "iana-time-zone"
 version = "0.1.60"
@@ -1378,7 +1583,7 @@ checksum = "1a016b8d9495c639af2145ac22387dcb88e44118e45320d9238fbf4e7889abcb"
 dependencies = [
  "async-trait",
  "futures-core",
- "http",
+ "http 0.2.12",
  "opentelemetry",
  "opentelemetry-proto",
  "opentelemetry-semantic-conventions",
@@ -1789,6 +1994,39 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "serde_path_to_error"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6"
+dependencies = [
+ "itoa",
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha1"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
 [[package]]
 name = "sha2"
 version = "0.10.8"
@@ -2106,6 +2344,18 @@ dependencies = [
  "tokio",
 ]
 
+[[package]]
+name = "tokio-tungstenite"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38"
+dependencies = [
+ "futures-util",
+ "log",
+ "tokio",
+ "tungstenite",
+]
+
 [[package]]
 name = "tokio-util"
 version = "0.7.10"
@@ -2129,13 +2379,13 @@ checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13"
 dependencies = [
  "async-stream",
  "async-trait",
- "axum",
+ "axum 0.6.20",
  "base64",
  "bytes",
- "h2",
- "http",
- "http-body",
- "hyper",
+ "h2 0.3.25",
+ "http 0.2.12",
+ "http-body 0.4.6",
+ "hyper 0.14.28",
  "hyper-timeout",
  "percent-encoding",
  "pin-project",
@@ -2168,6 +2418,23 @@ dependencies = [
  "tracing",
 ]
 
+[[package]]
+name = "tower-http"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
+dependencies = [
+ "bitflags 2.4.1",
+ "bytes",
+ "http 1.1.0",
+ "http-body 1.0.0",
+ "http-body-util",
+ "pin-project-lite",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
 [[package]]
 name = "tower-layer"
 version = "0.3.2"
@@ -2186,6 +2453,7 @@ version = "0.1.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
 dependencies = [
+ "log",
  "pin-project-lite",
  "tracing-attributes",
  "tracing-core",
@@ -2261,6 +2529,25 @@ version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
 
+[[package]]
+name = "tungstenite"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1"
+dependencies = [
+ "byteorder",
+ "bytes",
+ "data-encoding",
+ "http 1.1.0",
+ "httparse",
+ "log",
+ "rand",
+ "sha1",
+ "thiserror",
+ "url",
+ "utf-8",
+]
+
 [[package]]
 name = "typenum"
 version = "1.17.0"
@@ -2355,6 +2642,12 @@ version = "2.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
 
+[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
 [[package]]
 name = "utf8parse"
 version = "0.2.1"
diff --git a/Makefile b/Makefile
index ae436d4ee9..d5db9e5481 100644
--- a/Makefile
+++ b/Makefile
@@ -56,7 +56,7 @@ integration-static: .install.ginkgo # It needs to be release so we correctly tes
 		$(MAKE) release-static; \
 	fi && \
 	export RUNTIME_BINARY="$(RUNTIME_PATH)" && \
-	export MAX_RSS_KB=7500 && \
+	export MAX_RSS_KB=9500 && \
 	sudo -E "$(GOTOOLS_BINDIR)/ginkgo" $(TEST_FLAGS) $(GINKGO_FLAGS)
 
 .install.ginkgo:
diff --git a/conmon-rs/common/proto/conmon.capnp b/conmon-rs/common/proto/conmon.capnp
index a5b3ded083..30ebc56c3d 100644
--- a/conmon-rs/common/proto/conmon.capnp
+++ b/conmon-rs/common/proto/conmon.capnp
@@ -194,4 +194,22 @@ interface Conmon {
         key @0 :Text;
         value @1 :Text;
     }
+
+    ###############################################
+    # ServeExecContainer
+    struct ServeExecContainerRequest {
+        metadata @0 :Metadata; # Standard metadata to carry.
+        id @1 :Text;
+        cmd @2 :List(Text);
+        tty @3 :Bool;
+        stdin @4 :Bool;
+        stdout @5 :Bool;
+        stderr @6 :Bool;
+    }
+
+    struct ServeExecContainerResponse {
+        url @0 :Text;
+    }
+
+    serveExecContainer @8 (request: ServeExecContainerRequest) -> (response: ServeExecContainerResponse);
 }
diff --git a/conmon-rs/server/Cargo.toml b/conmon-rs/server/Cargo.toml
index 960f0aafe1..14ee9870fb 100644
--- a/conmon-rs/server/Cargo.toml
+++ b/conmon-rs/server/Cargo.toml
@@ -9,6 +9,8 @@ path = "src/main.rs"
 
 [dependencies]
 anyhow = "1.0.81"
+async-channel = "2.2.0"
+axum = { version = "0.7.4", features = ["ws"]}
 capnp = "0.19.2"
 capnp-rpc = "0.19.0"
 clap = { version = "4.3.8", features = ["color", "cargo", "deprecated", "derive", "deprecated", "env", "string", "unicode", "wrap_help"] }
@@ -41,6 +43,7 @@ tokio = { version = "1.36.0", features = ["fs", "io-std", "io-util", "macros", "
 tokio-eventfd = "0.2.1"
 tokio-seqpacket = "0.7.1"
 tokio-util = { version = "0.7.10", features = ["compat"] }
+tower-http = { version = "0.5.2", features = ["trace"] }
 tracing = "0.1.40"
 tracing-opentelemetry = "0.23.0"
 tracing-subscriber = "0.3.18"
diff --git a/conmon-rs/server/src/attach.rs b/conmon-rs/server/src/attach.rs
index bc048551cf..815a33be9d 100644
--- a/conmon-rs/server/src/attach.rs
+++ b/conmon-rs/server/src/attach.rs
@@ -104,6 +104,11 @@ impl SharedContainerAttach {
         }
         Ok(())
     }
+
+    /// Retrieve a clone of the stdin sender.
+    pub fn stdin(&self) -> &Sender<Vec<u8>> {
+        &self.read_half_tx
+    }
 }
 
 #[derive(Clone, Debug)]
diff --git a/conmon-rs/server/src/bounded_hashmap.rs b/conmon-rs/server/src/bounded_hashmap.rs
new file mode 100644
index 0000000000..76eb41223e
--- /dev/null
+++ b/conmon-rs/server/src/bounded_hashmap.rs
@@ -0,0 +1,131 @@
+use std::{
+    collections::HashMap,
+    fmt::Debug,
+    hash::Hash,
+    time::{Duration, Instant},
+};
+use tracing::warn;
+
+#[derive(Debug)]
+/// A HashMap bounded by element age and maximum amount of items
+pub struct BoundedHashMap<K, V> {
+    map: HashMap<K, (Instant, V)>,
+    max_duration: Duration,
+    max_items: usize,
+}
+
+impl<K, V> BoundedHashMap<K, V>
+where
+    K: Eq + Hash + Clone + Debug,
+    V: Debug,
+{
+    /// Insert an element into the hashmap by:
+    /// - removing timed-out elements
+    /// - removing the oldest element if no space left
+    pub fn insert(&mut self, k: K, v: V) -> Option<V> {
+        let now = Instant::now();
+
+        // Remove timed-out items
+        let old_len = self.map.len();
+        self.map
+            .retain(|_, (inserted, _)| now - *inserted <= self.max_duration);
+        if old_len < self.map.len() {
+            warn!("Removed {} timed out elements", self.map.len() - old_len)
+        }
+
+        // Remove the oldest entry if still not enough space left
+        if self.map.len() >= self.max_items {
+            let mut key_to_remove = k.clone();
+
+            let mut oldest = now;
+            for (key, (inserted, _)) in self.map.iter() {
+                if *inserted < oldest {
+                    oldest = *inserted;
+                    key_to_remove = key.clone();
+                }
+            }
+
+            warn!("Removing oldest key: {:?}", key_to_remove);
+            self.map.remove(&key_to_remove);
+        }
+
+        self.map.insert(k, (Instant::now(), v)).map(|v| v.1)
+    }
+
+    /// Remove an element from the hashmap and return it if the element has not expired.
+    pub fn remove(&mut self, k: &K) -> Option<V> {
+        let now = Instant::now();
+
+        if let Some((key, (inserted, value))) = self.map.remove_entry(k) {
+            if now - inserted > self.max_duration {
+                warn!("Max duration expired for key: {:?}", key);
+                None
+            } else {
+                Some(value)
+            }
+        } else {
+            None
+        }
+    }
+}
+
+impl<K, V> Default for BoundedHashMap<K, V> {
+    fn default() -> Self {
+        Self {
+            map: Default::default(),
+            max_duration: Duration::new(60 * 60, 0), // 1 hour
+            max_items: 1000,
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::thread::sleep;
+
+    #[test]
+    fn bounded_hashmap_test() {
+        let mut sut = BoundedHashMap::default();
+        sut.max_items = 2;
+
+        assert_eq!(sut.map.len(), 0);
+
+        // Insert first item should be fine
+        assert!(sut.insert(0, 0).is_none());
+        assert_eq!(sut.map.len(), 1);
+
+        // Insert second item should be fine, removal should work as well
+        assert!(sut.insert(1, 0).is_none());
+        assert_eq!(sut.map.len(), 2);
+        assert!(sut.remove(&1).is_some());
+        assert_eq!(sut.map.len(), 1);
+        assert!(sut.insert(1, 0).is_none());
+
+        // Insert third item should be fine, but remove oldest
+        assert!(sut.insert(2, 0).is_none());
+        assert_eq!(sut.map.len(), 2);
+        assert!(sut.map.get(&0).is_none());
+        assert!(sut.map.get(&1).is_some());
+        assert!(sut.map.get(&2).is_some());
+
+        // Insert another item should be fine, but remove oldest
+        assert!(sut.insert(3, 0).is_none());
+        assert_eq!(sut.map.len(), 2);
+        assert!(sut.map.get(&1).is_none());
+        assert!(sut.map.get(&2).is_some());
+        assert!(sut.map.get(&3).is_some());
+
+        // Change the max age of the elements, all should be timed out
+        sut.max_duration = Duration::from_millis(100);
+        sleep(Duration::from_millis(200));
+        assert!(sut.insert(0, 0).is_none());
+        assert!(sut.map.get(&1).is_none());
+        assert!(sut.map.get(&2).is_none());
+        assert!(sut.map.get(&3).is_none());
+
+        // The last element should be also timed out if we wait
+        sleep(Duration::from_millis(200));
+        assert!(sut.remove(&0).is_none());
+    }
+}
diff --git a/conmon-rs/server/src/config.rs b/conmon-rs/server/src/config.rs
index 5165ebf0cb..2e41b4f09e 100644
--- a/conmon-rs/server/src/config.rs
+++ b/conmon-rs/server/src/config.rs
@@ -12,7 +12,9 @@ macro_rules! prefix {
     };
 }
 
-#[derive(CopyGetters, Debug, Deserialize, Eq, Getters, Parser, PartialEq, Serialize, Setters)]
+#[derive(
+    Clone, CopyGetters, Debug, Deserialize, Eq, Getters, Parser, PartialEq, Serialize, Setters,
+)]
 #[serde(rename_all = "kebab-case")]
 #[command(
     after_help("More info at: https://github.com/containers/conmon-rs"),
diff --git a/conmon-rs/server/src/container_io.rs b/conmon-rs/server/src/container_io.rs
index 19c555ad1e..6fb69d9702 100644
--- a/conmon-rs/server/src/container_io.rs
+++ b/conmon-rs/server/src/container_io.rs
@@ -3,6 +3,7 @@ use crate::{
     terminal::Terminal,
 };
 use anyhow::{bail, Context, Result};
+use async_channel::{Receiver, Sender};
 use getset::{Getters, MutGetters};
 use nix::errno::Errno;
 use std::{
@@ -15,10 +16,7 @@ use tempfile::Builder;
 use tokio::{
     io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt},
     select,
-    sync::{
-        mpsc::{UnboundedReceiver, UnboundedSender},
-        RwLock,
-    },
+    sync::RwLock,
     time::{self, Instant},
 };
 use tokio_util::sync::CancellationToken;
@@ -59,6 +57,11 @@ impl SharedContainerIO {
     pub async fn attach(&self) -> SharedContainerAttach {
         self.0.read().await.attach().clone()
     }
+
+    /// Retrieve the underlying stdout and stderr channels.
+    pub async fn channels(&mut self) -> Result<(Receiver<Message>, Receiver<Message>)> {
+        self.0.read().await.channels()
+    }
 }
 
 #[derive(Debug, Getters, MutGetters)]
@@ -155,6 +158,22 @@ impl ContainerIO {
         Ok(path)
     }
 
+    pub fn channels(&self) -> Result<(Receiver<Message>, Receiver<Message>)> {
+        match self.typ() {
+            ContainerIOType::Terminal(t) => {
+                if let Some(message_rx) = t.message_rx() {
+                    let (_, fake_rx) = async_channel::unbounded();
+                    Ok((message_rx.clone(), fake_rx))
+                } else {
+                    bail!("channels called before message_rx was registered");
+                }
+            }
+            ContainerIOType::Streams(s) => {
+                Ok((s.message_rx_stdout.clone(), s.message_rx_stderr.clone()))
+            }
+        }
+    }
+
     pub async fn read_all_with_timeout(
         &mut self,
         time_to_timeout: Option<Instant>,
@@ -184,7 +203,7 @@ impl ContainerIO {
 
     async fn read_stream_with_timeout(
         time_to_timeout: Option<Instant>,
-        receiver: &mut UnboundedReceiver<Message>,
+        receiver: &mut Receiver<Message>,
     ) -> (Vec<u8>, bool) {
         let mut stdio = vec![];
         let mut timed_out = false;
@@ -192,19 +211,18 @@ impl ContainerIO {
             let msg = if let Some(time_to_timeout) = time_to_timeout {
                 {
                     match time::timeout_at(time_to_timeout, receiver.recv()).await {
-                        Ok(Some(msg)) => msg,
-                        Err(_) => {
+                        Ok(Ok(msg)) => msg,
+                        _ => {
                             timed_out = true;
                             Message::Done
                         }
-                        Ok(None) => unreachable!(),
                     }
                 }
             } else {
                 {
                     match receiver.recv().await {
-                        Some(msg) => msg,
-                        None => Message::Done,
+                        Ok(msg) => msg,
+                        _ => Message::Done,
                     }
                 }
             };
@@ -230,7 +248,7 @@ impl ContainerIO {
         mut reader: T,
         pipe: Pipe,
         logger: SharedContainerLog,
-        message_tx: UnboundedSender<Message>,
+        message_tx: Sender<Message>,
         mut attach: SharedContainerAttach,
     ) -> Result<()>
     where
@@ -251,6 +269,7 @@ impl ContainerIO {
                     if !message_tx.is_closed() {
                         message_tx
                             .send(Message::Done)
+                            .await
                             .context("send done message")?;
                     }
 
@@ -275,6 +294,7 @@ impl ContainerIO {
                     if !message_tx.is_closed() {
                         message_tx
                             .send(Message::Data(data.into(), pipe))
+                            .await
                             .context("send data message")?;
                     }
                 }
@@ -290,6 +310,7 @@ impl ContainerIO {
                         if !message_tx.is_closed() {
                             message_tx
                                 .send(Message::Done)
+                                .await
                                 .context("send done message")?;
                         }
                         return Ok(());
diff --git a/conmon-rs/server/src/lib.rs b/conmon-rs/server/src/lib.rs
index c47ef04cc2..dd6b410583 100644
--- a/conmon-rs/server/src/lib.rs
+++ b/conmon-rs/server/src/lib.rs
@@ -8,6 +8,7 @@ pub use version::Version;
 mod macros;
 
 mod attach;
+mod bounded_hashmap;
 mod capnp_util;
 mod child;
 mod child_reaper;
@@ -24,6 +25,7 @@ mod oom_watcher;
 mod pause;
 mod rpc;
 mod server;
+mod streaming_server;
 mod streams;
 mod telemetry;
 mod terminal;
diff --git a/conmon-rs/server/src/rpc.rs b/conmon-rs/server/src/rpc.rs
index 6acc98aa62..96ee8dbe42 100644
--- a/conmon-rs/server/src/rpc.rs
+++ b/conmon-rs/server/src/rpc.rs
@@ -462,4 +462,60 @@ impl conmon::Server for Server {
             .instrument(debug_span!("promise")),
         )
     }
+
+    fn serve_exec_container(
+        &mut self,
+        params: conmon::ServeExecContainerParams,
+        mut results: conmon::ServeExecContainerResults,
+    ) -> Promise<(), capnp::Error> {
+        debug!("Got a serve exec container request");
+        let req = pry!(pry!(params.get()).get_request());
+
+        let span = debug_span!(
+            "serve_exec_container",
+            uuid = Uuid::new_v4().to_string().as_str()
+        );
+        let _enter = span.enter();
+        pry_err!(Telemetry::set_parent_context(pry!(req.get_metadata())));
+
+        let id = pry_err!(pry_err!(req.get_id()).to_string());
+        let cmd = capnp_vec_str!(req.get_cmd());
+        let (tty, stdin, stdout, stderr) = (
+            req.get_tty(),
+            req.get_stdin(),
+            req.get_stdout(),
+            req.get_stderr(),
+        );
+        let streaming_server = self.streaming_server().clone();
+        let child_reaper = self.reaper().clone();
+        let config = self.config().clone();
+
+        let logger = ContainerLog::new();
+        let container_io = pry_err!(ContainerIO::new(tty, logger)).into();
+
+        Promise::from_future(
+            async move {
+                let url = capnp_err!(
+                    streaming_server
+                        .read()
+                        .await
+                        .exec_url(
+                            child_reaper,
+                            container_io,
+                            config,
+                            id,
+                            cmd,
+                            stdin,
+                            stdout,
+                            stderr
+                        )
+                        .await
+                )?;
+
+                results.get().init_response().set_url(&url);
+                Ok(())
+            }
+            .instrument(debug_span!("promise")),
+        )
+    }
 }
diff --git a/conmon-rs/server/src/server.rs b/conmon-rs/server/src/server.rs
index 3d0a0adbff..20f7ed56eb 100644
--- a/conmon-rs/server/src/server.rs
+++ b/conmon-rs/server/src/server.rs
@@ -9,6 +9,7 @@ use crate::{
     journal::Journal,
     listener::{DefaultListener, Listener},
     pause::Pause,
+    streaming_server::StreamingServer,
     telemetry::Telemetry,
     version::Version,
 };
@@ -30,7 +31,7 @@ use tokio::{
     fs,
     runtime::{Builder, Handle},
     signal::unix::{signal, SignalKind},
-    sync::oneshot,
+    sync::{oneshot, RwLock},
     task::{self, LocalSet},
 };
 use tokio_util::compat::TokioAsyncReadCompatExt;
@@ -53,6 +54,9 @@ pub struct Server {
     /// Fd socket instance.
     #[getset(get = "pub(crate)")]
     fd_socket: Arc<FdSocket>,
+
+    #[getset(get = "pub(crate)")]
+    streaming_server: Arc<RwLock<StreamingServer>>,
 }
 
 impl Server {
@@ -62,6 +66,7 @@ impl Server {
             config: Default::default(),
             reaper: Default::default(),
             fd_socket: Default::default(),
+            streaming_server: Default::default(),
         };
 
         if let Some(v) = server.config().version() {
@@ -274,6 +279,13 @@ impl Server {
     }
 
     async fn start_backend(self, mut shutdown_rx: oneshot::Receiver<()>) -> Result<()> {
+        self.streaming_server
+            .write()
+            .await
+            .start()
+            .await
+            .context("start streaming server")?;
+
         let listener =
             Listener::<DefaultListener>::default().bind_long_path(self.config().socket())?;
         let client: conmon::Client = capnp_rpc::new_client(self);
@@ -357,6 +369,17 @@ impl GenerateRuntimeArgs<'_> {
 
     /// Generate the OCI runtime CLI arguments from the provided parameters.
     pub(crate) fn exec_sync_args(&self, command: Reader) -> Result<Vec<String>> {
+        let mut args = self.exec_sync_args_without_command();
+
+        for arg in command {
+            args.push(arg?.to_string()?);
+        }
+
+        debug!("Exec args {:?}", args.join(" "));
+        Ok(args)
+    }
+
+    pub(crate) fn exec_sync_args_without_command(&self) -> Vec<String> {
         let mut args = vec![];
 
         if let Some(rr) = self.config.runtime_root() {
@@ -378,11 +401,6 @@ impl GenerateRuntimeArgs<'_> {
         args.push(format!("--pid-file={}", self.pidfile.display()));
         args.push(self.id.into());
 
-        for arg in command {
-            args.push(arg?.to_string()?);
-        }
-
-        debug!("Exec args {:?}", args.join(" "));
-        Ok(args)
+        args
     }
 }
diff --git a/conmon-rs/server/src/streaming_server.rs b/conmon-rs/server/src/streaming_server.rs
new file mode 100644
index 0000000000..b0e1f21291
--- /dev/null
+++ b/conmon-rs/server/src/streaming_server.rs
@@ -0,0 +1,422 @@
+use crate::{
+    bounded_hashmap::BoundedHashMap,
+    child::Child,
+    child_reaper::ChildReaper,
+    config::Config,
+    container_io::{ContainerIO, Message as IOMessage, Pipe, SharedContainerIO},
+    server::GenerateRuntimeArgs,
+};
+use anyhow::{Context, Result};
+use axum::{
+    extract::{
+        ws::{close_code, CloseFrame, Message, WebSocket, WebSocketUpgrade},
+        Path, State as AxumState,
+    },
+    http::StatusCode,
+    response::IntoResponse,
+    routing::get,
+    Router,
+};
+use conmon_common::conmon_capnp::conmon::CgroupManager;
+use futures::{
+    sink::SinkExt,
+    stream::{SplitSink, StreamExt},
+};
+use serde::Deserialize;
+use serde_json::json;
+use std::{fmt::Debug, ops::ControlFlow, sync::Arc};
+use tokio::{
+    net::TcpListener,
+    sync::{
+        mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
+        RwLock,
+    },
+    task,
+};
+use tower_http::trace::{DefaultMakeSpan, TraceLayer};
+use tracing::{debug, debug_span, error, info, trace, warn};
+use uuid::Uuid;
+
+const ADDR: &str = "127.0.0.1";
+const PROTOCOL: &str = "v5.channel.k8s.io";
+
+const EXEC_PATH: &str = "exec";
+const ATTACH_PATH: &str = "attach";
+const PORTFORWARD_PATH: &str = "portforward";
+
+const STDIN_BYTE: u8 = 0;
+const STDOUT_BYTE: u8 = 1;
+const STDERR_BYTE: u8 = 2;
+const STREAM_ERR_BYTE: u8 = 3;
+const RESIZE_BYTE: u8 = 4;
+
+#[derive(Clone, Debug, Default)]
+pub struct StreamingServer {
+    port: u16,
+    state: Arc<RwLock<State>>,
+}
+
+#[derive(Debug, Default)]
+struct State {
+    exec: BoundedHashMap<Uuid, ExecSession>,
+}
+
+#[derive(Debug, Default)]
+struct ExecSession {
+    child_reaper: Arc<ChildReaper>,
+    container_io: Option<ContainerIO>,
+    server_config: Config,
+    container_id: String,
+    cmd: Vec<String>,
+    stdin: bool,
+    stdout: bool,
+    stderr: bool,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "PascalCase")]
+struct Resize {
+    width: u16,
+    height: u16,
+}
+
+impl StreamingServer {
+    pub async fn start(&mut self) -> Result<()> {
+        let listener = TcpListener::bind(ADDR.to_string() + ":0")
+            .await
+            .context("bind streaming server")?;
+
+        let local_addr = listener.local_addr()?;
+        let state: Arc<RwLock<State>> = Default::default();
+
+        self.port = local_addr.port();
+        self.state = state.clone();
+
+        info!("Starting streaming server on {}", local_addr);
+        task::spawn_local(Self::serve(listener, state));
+
+        Ok(())
+    }
+
+    async fn serve(listener: TcpListener, state: Arc<RwLock<State>>) -> Result<()> {
+        let router = Router::new()
+            .route(&Self::path_for(EXEC_PATH), get(Self::handle_exec))
+            .route(&Self::path_for(ATTACH_PATH), get(Self::handle_attach))
+            .route(
+                &Self::path_for(PORTFORWARD_PATH),
+                get(Self::handle_portforward),
+            )
+            .fallback(Self::fallback)
+            .with_state(state)
+            .layer(
+                TraceLayer::new_for_http()
+                    .make_span_with(DefaultMakeSpan::default().include_headers(true)),
+            );
+        axum::serve(listener, router)
+            .await
+            .context("start streaming server")
+    }
+
+    fn path_for(p: &str) -> String {
+        format!("/{}/:token", p)
+    }
+
+    pub async fn fallback() -> impl IntoResponse {
+        "not found"
+    }
+
+    #[allow(clippy::too_many_arguments)]
+    /// Returns the URL used for the provided exec parameters
+    pub async fn exec_url(
+        &self,
+        child_reaper: Arc<ChildReaper>,
+        container_io: Option<ContainerIO>,
+        server_config: Config,
+        container_id: String,
+        cmd: Vec<String>,
+        stdin: bool,
+        stdout: bool,
+        stderr: bool,
+    ) -> Result<String> {
+        let mut state_lock = self.state.write().await;
+        let uuid = Uuid::new_v4();
+        state_lock.exec.insert(
+            uuid,
+            ExecSession {
+                child_reaper,
+                container_io,
+                server_config,
+                container_id,
+                cmd,
+                stdin,
+                stdout,
+                stderr,
+            },
+        );
+        Ok(format!(
+            "http://{}:{}/{}/{}",
+            ADDR, self.port, EXEC_PATH, uuid
+        ))
+    }
+
+    async fn handle_attach(
+        _ws: WebSocketUpgrade,
+        Path(token): Path<Uuid>,
+        AxumState(_state): AxumState<Arc<RwLock<State>>>,
+    ) -> impl IntoResponse {
+        let span = debug_span!("handle_attach", token = token.to_string().as_str());
+        let _enter = span.enter();
+
+        unimplemented!()
+    }
+
+    async fn handle_portforward(
+        _ws: WebSocketUpgrade,
+        Path(token): Path<Uuid>,
+        AxumState(_state): AxumState<Arc<RwLock<State>>>,
+    ) -> impl IntoResponse {
+        let span = debug_span!("handle_portforward", token = token.to_string().as_str());
+        let _enter = span.enter();
+
+        unimplemented!()
+    }
+
+    async fn handle_exec(
+        ws: WebSocketUpgrade,
+        Path(token): Path<Uuid>,
+        AxumState(state): AxumState<Arc<RwLock<State>>>,
+    ) -> impl IntoResponse {
+        let span = debug_span!("handle_exec", token = token.to_string().as_str());
+        let _enter = span.enter();
+
+        info!("Got exec request for token: {}", token);
+        let mut state_lock = state.write().await;
+
+        match state_lock.exec.remove(&token) {
+            Some(session) => {
+                info!("Got valid exec session: {:?}", session);
+                ws.protocols([PROTOCOL])
+                    .on_upgrade(move |socket| Self::handle_exec_socket(socket, session))
+            }
+            None => (
+                StatusCode::NOT_FOUND,
+                format!("unable to find exec session for token: {}", token),
+            )
+                .into_response(),
+        }
+    }
+
+    async fn handle_exec_socket(socket: WebSocket, session: ExecSession) {
+        let (mut sender, mut receiver) = socket.split();
+        let (mut stdin_tx, stdin_rx) = unbounded_channel();
+
+        let mut send_task = tokio::spawn(async move {
+            if let Err(e) = Self::exec_in_container(session, &mut sender, stdin_rx).await {
+                error!("Unable to exec in container: {}", e);
+            }
+
+            if let Err(e) = sender
+                .send(Message::Close(
+                    CloseFrame {
+                        code: close_code::NORMAL,
+                        reason: "exec done".into(),
+                    }
+                    .into(),
+                ))
+                .await
+            {
+                error!("Unable to send close message: {}", e)
+            }
+        });
+
+        let mut recv_task = tokio::spawn(async move {
+            while let Some(Ok(msg)) = receiver.next().await {
+                if Self::process_message(msg, &mut stdin_tx).is_break() {
+                    break;
+                }
+            }
+        });
+
+        tokio::select! {
+            rv_a = (&mut send_task) => {
+                match rv_a {
+                    Ok(_) => info!("All messages sent"),
+                    Err(a) => error!("Error sending messages: {a:?}")
+                }
+                recv_task.abort();
+            },
+            rv_b = (&mut recv_task) => {
+                match rv_b {
+                    Ok(_) => info!("All messages received"),
+                    Err(b) => error!("Error receiving messages: {b:?}")
+                }
+                send_task.abort();
+            }
+        }
+
+        info!("Closing websocket connection");
+    }
+
+    fn process_message(
+        msg: Message,
+        stdin_tx: &mut UnboundedSender<Vec<u8>>,
+    ) -> ControlFlow<(), ()> {
+        match msg {
+            Message::Binary(data) if !data.is_empty() => {
+                debug!("Got {} binary bytes", data.len());
+                if let Err(e) = stdin_tx.send(data) {
+                    error!("Unableto send stdin data: {}", e);
+                }
+            }
+            Message::Close(c) => {
+                if let Some(cf) = c {
+                    info!(
+                        "Got websocket close with code {} and reason `{}`",
+                        cf.code, cf.reason
+                    );
+                } else {
+                    warn!("Got close message without CloseFrame");
+                }
+                return ControlFlow::Break(());
+            }
+            Message::Text(t) => trace!("Got text message: {t:?}"),
+            Message::Pong(_) => trace!("Got pong"),
+            Message::Ping(_) => trace!("Got ping"),
+            Message::Binary(_) => trace!("Got unknown binary data"),
+        }
+
+        ControlFlow::Continue(())
+    }
+
+    async fn exec_in_container(
+        session: ExecSession,
+        sender: &mut SplitSink<WebSocket, Message>,
+        mut stdin_rx: UnboundedReceiver<Vec<u8>>,
+    ) -> Result<()> {
+        let config = session.server_config.clone();
+        let pidfile = ContainerIO::temp_file_name(Some(config.runtime_dir()), "serve_exec", "pid")
+            .context("build pidfile path")?;
+
+        let mut container_io = session.container_io.context("no container IO provided")?;
+
+        let args = GenerateRuntimeArgs {
+            config: &config,
+            id: &session.container_id,
+            container_io: &container_io,
+            pidfile: &pidfile,
+            cgroup_manager: CgroupManager::Systemd,
+        };
+        let mut args = args.exec_sync_args_without_command();
+        let mut cmd_clone = session.cmd.clone();
+        args.append(&mut cmd_clone);
+
+        let (grandchild_pid, token) = session
+            .child_reaper
+            .create_child(
+                config.runtime(),
+                &args,
+                session.stdin,
+                &mut container_io,
+                &pidfile,
+                vec![],
+                vec![],
+            )
+            .await
+            .context("create new child process")?;
+
+        let mut io = SharedContainerIO::new(container_io);
+        let io_clone = io.clone();
+        let child = Child::new(
+            session.container_id.clone(),
+            grandchild_pid,
+            vec![],
+            vec![],
+            None,
+            io_clone,
+            vec![],
+            token.clone(),
+        );
+
+        let mut exit_rx = session
+            .child_reaper
+            .watch_grandchild(child, vec![])
+            .context("watch grandchild for pid")?;
+
+        let (stdout_rx, stderr_rx) = io.channels().await?;
+
+        loop {
+            tokio::select! {
+                Some(mut data) = stdin_rx.recv() => {
+                    if !session.stdin {
+                        continue
+                    }
+
+                    // First element is the message type indicator
+                    match data.remove(0) {
+                        STDIN_BYTE => {
+                            trace!("Got stdin message of len {}", data.len());
+                            io.attach().await.stdin().send(data).context("send to attach session")?;
+                        },
+                        RESIZE_BYTE => {
+                            let e = serde_json::from_slice::<Resize>(&data).context("unmarshal resize event")?;
+                            trace!("Got resize message: {e:?}");
+                            io.resize(e.width, e.height).await.context("resize terminal")?;
+                        },
+                        x => warn!("Unknown start byte for stdin: {x}"),
+                    };
+                },
+
+                Ok(IOMessage::Data(mut data, pipe))= stdout_rx.recv() => {
+                    if !session.stdout {
+                        continue
+                    }
+
+                    let byte = match pipe {
+                        Pipe::StdOut => STDOUT_BYTE,
+                        Pipe::StdErr => STDERR_BYTE,
+                    };
+                    data.insert(0, byte);
+                    sender.send(Message::Binary(data)).await.context("send to stdout")?;
+                },
+
+                Ok(IOMessage::Data(mut data, pipe)) = stderr_rx.recv() => {
+                    if !session.stderr {
+                        continue
+                    }
+
+                    let byte = match pipe {
+                        Pipe::StdOut => STDOUT_BYTE,
+                        Pipe::StdErr => STDERR_BYTE,
+                    };
+                    data.insert(0, byte);
+                    sender.send(Message::Binary(data)).await.context("send to stderr")?;
+                },
+
+                Ok(exit_data) = exit_rx.recv() => {
+                    if exit_data.exit_code != 0 {
+                        let mut err = vec![STREAM_ERR_BYTE];
+                        err.extend(
+                            json!({
+                                "status": "Failure",
+                                "reason": "NonZeroExitCode",
+                                "details": {
+                                    "causes": [{
+                                        "reason": "ExitCode",
+                                        "message": exit_data.exit_code.to_string(),
+                                    }],
+                                },
+                                "message": "command terminated with non-zero exit code",
+                            })
+                            .to_string()
+                            .as_bytes(),
+                        );
+                        sender.send(Message::Binary(err)).await.context("send exit failure message")?;
+                    }
+                    break
+                },
+            }
+        }
+
+        Ok(())
+    }
+}
diff --git a/conmon-rs/server/src/streams.rs b/conmon-rs/server/src/streams.rs
index f6f8be5100..fadcc3544a 100644
--- a/conmon-rs/server/src/streams.rs
+++ b/conmon-rs/server/src/streams.rs
@@ -6,10 +6,10 @@ use crate::{
     container_log::SharedContainerLog,
 };
 use anyhow::Result;
+use async_channel::{Receiver, Sender};
 use getset::Getters;
 use tokio::{
     process::{ChildStderr, ChildStdin, ChildStdout},
-    sync::mpsc::{self, UnboundedReceiver, UnboundedSender},
     task,
 };
 use tokio_util::sync::CancellationToken;
@@ -23,15 +23,15 @@ pub struct Streams {
     #[getset(get = "pub")]
     attach: SharedContainerAttach,
 
-    pub message_rx_stdout: UnboundedReceiver<Message>,
+    pub message_rx_stdout: Receiver<Message>,
 
     #[getset(get = "pub")]
-    message_tx_stdout: UnboundedSender<Message>,
+    message_tx_stdout: Sender<Message>,
 
-    pub message_rx_stderr: UnboundedReceiver<Message>,
+    pub message_rx_stderr: Receiver<Message>,
 
     #[getset(get = "pub")]
-    message_tx_stderr: UnboundedSender<Message>,
+    message_tx_stderr: Sender<Message>,
 }
 
 impl Streams {
@@ -39,8 +39,8 @@ impl Streams {
     pub fn new(logger: SharedContainerLog, attach: SharedContainerAttach) -> Result<Self> {
         debug!("Creating new IO streams");
 
-        let (message_tx_stdout, message_rx_stdout) = mpsc::unbounded_channel();
-        let (message_tx_stderr, message_rx_stderr) = mpsc::unbounded_channel();
+        let (message_tx_stdout, message_rx_stdout) = async_channel::unbounded();
+        let (message_tx_stderr, message_rx_stderr) = async_channel::unbounded();
 
         Ok(Self {
             logger,
@@ -130,7 +130,7 @@ mod tests {
         let attach = SharedContainerAttach::default();
         let token = CancellationToken::new();
 
-        let mut sut = Streams::new(logger, attach)?;
+        let sut = Streams::new(logger, attach)?;
 
         let expected = "hello world";
         let mut child = Command::new("echo")
diff --git a/conmon-rs/server/src/terminal.rs b/conmon-rs/server/src/terminal.rs
index 3b7e99dc0e..2068dd29e4 100644
--- a/conmon-rs/server/src/terminal.rs
+++ b/conmon-rs/server/src/terminal.rs
@@ -7,6 +7,7 @@ use crate::{
     listener::{DefaultListener, Listener},
 };
 use anyhow::{format_err, Context as _, Result};
+use async_channel::Receiver as UnboundedReceiver;
 use getset::{Getters, MutGetters, Setters};
 use libc::{winsize, TIOCSWINSZ};
 use nix::{
@@ -29,7 +30,7 @@ use tokio::{
     fs,
     io::{unix::AsyncFd, AsyncRead, AsyncWrite, AsyncWriteExt, Interest, ReadBuf},
     net::UnixStream,
-    sync::mpsc::{self, Receiver, Sender, UnboundedReceiver},
+    sync::mpsc::{self, Receiver, Sender},
     task,
 };
 use tokio_util::sync::CancellationToken;
@@ -118,7 +119,7 @@ impl Terminal {
 
         let attach_clone = self.attach.clone();
         let logger_clone = self.logger.clone();
-        let (message_tx, message_rx) = mpsc::unbounded_channel();
+        let (message_tx, message_rx) = async_channel::unbounded();
         self.message_rx = Some(message_rx);
 
         task::spawn({
diff --git a/internal/proto/conmon.capnp.go b/internal/proto/conmon.capnp.go
index 1e636b4229..d29d7bbede 100644
--- a/internal/proto/conmon.capnp.go
+++ b/internal/proto/conmon.capnp.go
@@ -176,6 +176,26 @@ func (c Conmon) StartFdSocket(ctx context.Context, params func(Conmon_startFdSoc
 
 }
 
+func (c Conmon) ServeExecContainer(ctx context.Context, params func(Conmon_serveExecContainer_Params) error) (Conmon_serveExecContainer_Results_Future, capnp.ReleaseFunc) {
+
+	s := capnp.Send{
+		Method: capnp.Method{
+			InterfaceID:   0xb737e899dd6633f1,
+			MethodID:      8,
+			InterfaceName: "internal/proto/conmon.capnp:Conmon",
+			MethodName:    "serveExecContainer",
+		},
+	}
+	if params != nil {
+		s.ArgsSize = capnp.ObjectSize{DataSize: 0, PointerCount: 1}
+		s.PlaceArgs = func(s capnp.Struct) error { return params(Conmon_serveExecContainer_Params(s)) }
+	}
+
+	ans, release := capnp.Client(c).SendCall(ctx, s)
+	return Conmon_serveExecContainer_Results_Future{Future: ans.Future()}, release
+
+}
+
 func (c Conmon) WaitStreaming() error {
 	return capnp.Client(c).WaitStreaming()
 }
@@ -264,6 +284,8 @@ type Conmon_Server interface {
 	CreateNamespaces(context.Context, Conmon_createNamespaces) error
 
 	StartFdSocket(context.Context, Conmon_startFdSocket) error
+
+	ServeExecContainer(context.Context, Conmon_serveExecContainer) error
 }
 
 // Conmon_NewServer creates a new Server from an implementation of Conmon_Server.
@@ -282,7 +304,7 @@ func Conmon_ServerToClient(s Conmon_Server) Conmon {
 // This can be used to create a more complicated Server.
 func Conmon_Methods(methods []server.Method, s Conmon_Server) []server.Method {
 	if cap(methods) == 0 {
-		methods = make([]server.Method, 0, 8)
+		methods = make([]server.Method, 0, 9)
 	}
 
 	methods = append(methods, server.Method{
@@ -381,6 +403,18 @@ func Conmon_Methods(methods []server.Method, s Conmon_Server) []server.Method {
 		},
 	})
 
+	methods = append(methods, server.Method{
+		Method: capnp.Method{
+			InterfaceID:   0xb737e899dd6633f1,
+			MethodID:      8,
+			InterfaceName: "internal/proto/conmon.capnp:Conmon",
+			MethodName:    "serveExecContainer",
+		},
+		Impl: func(ctx context.Context, call *server.Call) error {
+			return s.ServeExecContainer(ctx, Conmon_serveExecContainer{call})
+		},
+	})
+
 	return methods
 }
 
@@ -520,6 +554,23 @@ func (c Conmon_startFdSocket) AllocResults() (Conmon_startFdSocket_Results, erro
 	return Conmon_startFdSocket_Results(r), err
 }
 
+// Conmon_serveExecContainer holds the state for a server call to Conmon.serveExecContainer.
+// See server.Call for documentation.
+type Conmon_serveExecContainer struct {
+	*server.Call
+}
+
+// Args returns the call's arguments.
+func (c Conmon_serveExecContainer) Args() Conmon_serveExecContainer_Params {
+	return Conmon_serveExecContainer_Params(c.Call.Args())
+}
+
+// AllocResults allocates the results struct.
+func (c Conmon_serveExecContainer) AllocResults() (Conmon_serveExecContainer_Results, error) {
+	r, err := c.Call.AllocResults(capnp.ObjectSize{DataSize: 0, PointerCount: 1})
+	return Conmon_serveExecContainer_Results(r), err
+}
+
 // Conmon_List is a list of Conmon.
 type Conmon_List = capnp.CapList[Conmon]
 
@@ -3112,6 +3163,248 @@ func (f Conmon_TextTextMapEntry_Future) Struct() (Conmon_TextTextMapEntry, error
 	return Conmon_TextTextMapEntry(p.Struct()), err
 }
 
+type Conmon_ServeExecContainerRequest capnp.Struct
+
+// Conmon_ServeExecContainerRequest_TypeID is the unique identifier for the type Conmon_ServeExecContainerRequest.
+const Conmon_ServeExecContainerRequest_TypeID = 0xd01c697281e61c21
+
+func NewConmon_ServeExecContainerRequest(s *capnp.Segment) (Conmon_ServeExecContainerRequest, error) {
+	st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 3})
+	return Conmon_ServeExecContainerRequest(st), err
+}
+
+func NewRootConmon_ServeExecContainerRequest(s *capnp.Segment) (Conmon_ServeExecContainerRequest, error) {
+	st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 3})
+	return Conmon_ServeExecContainerRequest(st), err
+}
+
+func ReadRootConmon_ServeExecContainerRequest(msg *capnp.Message) (Conmon_ServeExecContainerRequest, error) {
+	root, err := msg.Root()
+	return Conmon_ServeExecContainerRequest(root.Struct()), err
+}
+
+func (s Conmon_ServeExecContainerRequest) String() string {
+	str, _ := text.Marshal(0xd01c697281e61c21, capnp.Struct(s))
+	return str
+}
+
+func (s Conmon_ServeExecContainerRequest) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr {
+	return capnp.Struct(s).EncodeAsPtr(seg)
+}
+
+func (Conmon_ServeExecContainerRequest) DecodeFromPtr(p capnp.Ptr) Conmon_ServeExecContainerRequest {
+	return Conmon_ServeExecContainerRequest(capnp.Struct{}.DecodeFromPtr(p))
+}
+
+func (s Conmon_ServeExecContainerRequest) ToPtr() capnp.Ptr {
+	return capnp.Struct(s).ToPtr()
+}
+func (s Conmon_ServeExecContainerRequest) IsValid() bool {
+	return capnp.Struct(s).IsValid()
+}
+
+func (s Conmon_ServeExecContainerRequest) Message() *capnp.Message {
+	return capnp.Struct(s).Message()
+}
+
+func (s Conmon_ServeExecContainerRequest) Segment() *capnp.Segment {
+	return capnp.Struct(s).Segment()
+}
+func (s Conmon_ServeExecContainerRequest) Metadata() (Conmon_TextTextMapEntry_List, error) {
+	p, err := capnp.Struct(s).Ptr(0)
+	return Conmon_TextTextMapEntry_List(p.List()), err
+}
+
+func (s Conmon_ServeExecContainerRequest) HasMetadata() bool {
+	return capnp.Struct(s).HasPtr(0)
+}
+
+func (s Conmon_ServeExecContainerRequest) SetMetadata(v Conmon_TextTextMapEntry_List) error {
+	return capnp.Struct(s).SetPtr(0, v.ToPtr())
+}
+
+// NewMetadata sets the metadata field to a newly
+// allocated Conmon_TextTextMapEntry_List, preferring placement in s's segment.
+func (s Conmon_ServeExecContainerRequest) NewMetadata(n int32) (Conmon_TextTextMapEntry_List, error) {
+	l, err := NewConmon_TextTextMapEntry_List(capnp.Struct(s).Segment(), n)
+	if err != nil {
+		return Conmon_TextTextMapEntry_List{}, err
+	}
+	err = capnp.Struct(s).SetPtr(0, l.ToPtr())
+	return l, err
+}
+func (s Conmon_ServeExecContainerRequest) Id() (string, error) {
+	p, err := capnp.Struct(s).Ptr(1)
+	return p.Text(), err
+}
+
+func (s Conmon_ServeExecContainerRequest) HasId() bool {
+	return capnp.Struct(s).HasPtr(1)
+}
+
+func (s Conmon_ServeExecContainerRequest) IdBytes() ([]byte, error) {
+	p, err := capnp.Struct(s).Ptr(1)
+	return p.TextBytes(), err
+}
+
+func (s Conmon_ServeExecContainerRequest) SetId(v string) error {
+	return capnp.Struct(s).SetText(1, v)
+}
+
+func (s Conmon_ServeExecContainerRequest) Cmd() (capnp.TextList, error) {
+	p, err := capnp.Struct(s).Ptr(2)
+	return capnp.TextList(p.List()), err
+}
+
+func (s Conmon_ServeExecContainerRequest) HasCmd() bool {
+	return capnp.Struct(s).HasPtr(2)
+}
+
+func (s Conmon_ServeExecContainerRequest) SetCmd(v capnp.TextList) error {
+	return capnp.Struct(s).SetPtr(2, v.ToPtr())
+}
+
+// NewCmd sets the cmd field to a newly
+// allocated capnp.TextList, preferring placement in s's segment.
+func (s Conmon_ServeExecContainerRequest) NewCmd(n int32) (capnp.TextList, error) {
+	l, err := capnp.NewTextList(capnp.Struct(s).Segment(), n)
+	if err != nil {
+		return capnp.TextList{}, err
+	}
+	err = capnp.Struct(s).SetPtr(2, l.ToPtr())
+	return l, err
+}
+func (s Conmon_ServeExecContainerRequest) Tty() bool {
+	return capnp.Struct(s).Bit(0)
+}
+
+func (s Conmon_ServeExecContainerRequest) SetTty(v bool) {
+	capnp.Struct(s).SetBit(0, v)
+}
+
+func (s Conmon_ServeExecContainerRequest) Stdin() bool {
+	return capnp.Struct(s).Bit(1)
+}
+
+func (s Conmon_ServeExecContainerRequest) SetStdin(v bool) {
+	capnp.Struct(s).SetBit(1, v)
+}
+
+func (s Conmon_ServeExecContainerRequest) Stdout() bool {
+	return capnp.Struct(s).Bit(2)
+}
+
+func (s Conmon_ServeExecContainerRequest) SetStdout(v bool) {
+	capnp.Struct(s).SetBit(2, v)
+}
+
+func (s Conmon_ServeExecContainerRequest) Stderr() bool {
+	return capnp.Struct(s).Bit(3)
+}
+
+func (s Conmon_ServeExecContainerRequest) SetStderr(v bool) {
+	capnp.Struct(s).SetBit(3, v)
+}
+
+// Conmon_ServeExecContainerRequest_List is a list of Conmon_ServeExecContainerRequest.
+type Conmon_ServeExecContainerRequest_List = capnp.StructList[Conmon_ServeExecContainerRequest]
+
+// NewConmon_ServeExecContainerRequest creates a new list of Conmon_ServeExecContainerRequest.
+func NewConmon_ServeExecContainerRequest_List(s *capnp.Segment, sz int32) (Conmon_ServeExecContainerRequest_List, error) {
+	l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 8, PointerCount: 3}, sz)
+	return capnp.StructList[Conmon_ServeExecContainerRequest](l), err
+}
+
+// Conmon_ServeExecContainerRequest_Future is a wrapper for a Conmon_ServeExecContainerRequest promised by a client call.
+type Conmon_ServeExecContainerRequest_Future struct{ *capnp.Future }
+
+func (f Conmon_ServeExecContainerRequest_Future) Struct() (Conmon_ServeExecContainerRequest, error) {
+	p, err := f.Future.Ptr()
+	return Conmon_ServeExecContainerRequest(p.Struct()), err
+}
+
+type Conmon_ServeExecContainerResponse capnp.Struct
+
+// Conmon_ServeExecContainerResponse_TypeID is the unique identifier for the type Conmon_ServeExecContainerResponse.
+const Conmon_ServeExecContainerResponse_TypeID = 0xa9e93cf268b17735
+
+func NewConmon_ServeExecContainerResponse(s *capnp.Segment) (Conmon_ServeExecContainerResponse, error) {
+	st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
+	return Conmon_ServeExecContainerResponse(st), err
+}
+
+func NewRootConmon_ServeExecContainerResponse(s *capnp.Segment) (Conmon_ServeExecContainerResponse, error) {
+	st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
+	return Conmon_ServeExecContainerResponse(st), err
+}
+
+func ReadRootConmon_ServeExecContainerResponse(msg *capnp.Message) (Conmon_ServeExecContainerResponse, error) {
+	root, err := msg.Root()
+	return Conmon_ServeExecContainerResponse(root.Struct()), err
+}
+
+func (s Conmon_ServeExecContainerResponse) String() string {
+	str, _ := text.Marshal(0xa9e93cf268b17735, capnp.Struct(s))
+	return str
+}
+
+func (s Conmon_ServeExecContainerResponse) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr {
+	return capnp.Struct(s).EncodeAsPtr(seg)
+}
+
+func (Conmon_ServeExecContainerResponse) DecodeFromPtr(p capnp.Ptr) Conmon_ServeExecContainerResponse {
+	return Conmon_ServeExecContainerResponse(capnp.Struct{}.DecodeFromPtr(p))
+}
+
+func (s Conmon_ServeExecContainerResponse) ToPtr() capnp.Ptr {
+	return capnp.Struct(s).ToPtr()
+}
+func (s Conmon_ServeExecContainerResponse) IsValid() bool {
+	return capnp.Struct(s).IsValid()
+}
+
+func (s Conmon_ServeExecContainerResponse) Message() *capnp.Message {
+	return capnp.Struct(s).Message()
+}
+
+func (s Conmon_ServeExecContainerResponse) Segment() *capnp.Segment {
+	return capnp.Struct(s).Segment()
+}
+func (s Conmon_ServeExecContainerResponse) Url() (string, error) {
+	p, err := capnp.Struct(s).Ptr(0)
+	return p.Text(), err
+}
+
+func (s Conmon_ServeExecContainerResponse) HasUrl() bool {
+	return capnp.Struct(s).HasPtr(0)
+}
+
+func (s Conmon_ServeExecContainerResponse) UrlBytes() ([]byte, error) {
+	p, err := capnp.Struct(s).Ptr(0)
+	return p.TextBytes(), err
+}
+
+func (s Conmon_ServeExecContainerResponse) SetUrl(v string) error {
+	return capnp.Struct(s).SetText(0, v)
+}
+
+// Conmon_ServeExecContainerResponse_List is a list of Conmon_ServeExecContainerResponse.
+type Conmon_ServeExecContainerResponse_List = capnp.StructList[Conmon_ServeExecContainerResponse]
+
+// NewConmon_ServeExecContainerResponse creates a new list of Conmon_ServeExecContainerResponse.
+func NewConmon_ServeExecContainerResponse_List(s *capnp.Segment, sz int32) (Conmon_ServeExecContainerResponse_List, error) {
+	l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz)
+	return capnp.StructList[Conmon_ServeExecContainerResponse](l), err
+}
+
+// Conmon_ServeExecContainerResponse_Future is a wrapper for a Conmon_ServeExecContainerResponse promised by a client call.
+type Conmon_ServeExecContainerResponse_Future struct{ *capnp.Future }
+
+func (f Conmon_ServeExecContainerResponse_Future) Struct() (Conmon_ServeExecContainerResponse, error) {
+	p, err := f.Future.Ptr()
+	return Conmon_ServeExecContainerResponse(p.Struct()), err
+}
+
 type Conmon_version_Params capnp.Struct
 
 // Conmon_version_Params_TypeID is the unique identifier for the type Conmon_version_Params.
@@ -4568,222 +4861,422 @@ func (p Conmon_startFdSocket_Results_Future) Response() Conmon_StartFdSocketResp
 	return Conmon_StartFdSocketResponse_Future{Future: p.Future.Field(0, nil)}
 }
 
-const schema_ffaaf7385bc4adad = "x\xda\xc4Y}p\x14\xe7y\x7f\x9e\xdd\x93V\xa7\x0f" +
-	"N\xdb=\x09\x10\xa8\xd2\x10HA\x0d\xe1C\xb8&\x0c" +
-	"\x19I\x80B!\xe0hu`ZH\\\x96\xbbE:" +
-	"\xb8\xdb=v\xf7\x00\x11\xa72I=v\x94\xd81*" +
-	"\x9e\xd8L\x99A\xb6\xa1\x80\xa1\xc6\x1fP\x83\xc1\x03\x18" +
-	"\xc6\x80M]h\xa1\xc6c\\0\xa6\xb6\x19\x7fQ\xbb" +
-	"S\xec\x81n\xe7y\xef\xf6C'\x19\x9fTw\xf2\x07" +
-	"\x83\xee\xd9\xdf>\xef\xc7\xf3\xf9{v\xe2\xe7\xc1\xc6\xc0" +
-	"\xa4\xb27\x86\x00'\xef*(\xb4\xcd\xcb\x1d\xc6\xb6\xcd" +
-	"\xb3\x7f\x05\xe2w\x10\xa0\x00\x05\x80\xfa\xf9\xc2\xbb\x08(" +
-	")B\x03\xa0\xfd_O\x9d\xf8\xe1\xef7|\xda\xe5\x07" +
-	"\xac\xcf\x00\x1ee\x80\xb7\xa7\xd6-\xdf\xc2\xcf\xfb\x8d\x1f" +
-	"\xb0_x\x8b\x00\xa7\x19\xe0\xaf\x97\x866\xfem\xe5\x12" +
-	"\x06\xb0\xaf\xd7/\xbf\xf8\xf8\x07w\xfe#\x14\x08\x04\xfc" +
-	"Xx\x0b\xa5`\x11\xfdYP\xf4;\x04\xb4_\xfb\xe3" +
-	"u\x8bB\xdb\x1ex,\x07\xcd\xd4^\x0d\xbe\x8b\x12\x16" +
-	"\x0b\x00\xd2\xad \xa9\xbe\xff\xc3\xbb\xf6-\xfc\xd5\xa7[" +
-	"\xfckO*\xfeOZ\xbb\xb9\x98\x00\x8f/\xf9`e" +
-	"\xf3\x9c\xd0\x13\xbd\xb5\x05\x08\x17/\xde\x89\xd2\xfab\x01" +
-	"x\xbb\xf2\xab+O\x9d\x8fL\xdd\x01\xf2w\xb0\xcf\xa2" +
-	"?#\\\x9a-\xba\xaax\x0d\xa0=\xfa\x99W\xcet" +
-	"M\x9f\xb0\xd3\xbf\xe8\xe9\xe2\xb3\xb4\xe8e\xb6h\xd7\xdf" +
-	"\xab\x7fr\xe4\xc0\x8f\x09\xc0y\xda\x00\xeb\xb1\xa4\x0b\xa5" +
-	"\xe1%\xa4\xaa\xa2\xe4N@{\xcd\xd2\x13\xcf\xac\x93\xaf" +
-	"\xee\xeag{\xd5%\xdd(\xddQB\xdb\x93fM\xdc" +
-	"\x7f\xbe\xbenw\xee\xf68\xc2\x89\x84\x1b\xc7t\x8e)" +
-	"y\x06\xd0\x9e\xd2\xf3\xfc\xbe\x87?Y\xfb\x0f\x84\xe6r" +
-	"\x0fs\xaed\x05J\x1f\x96\x0c\x05\x90\xae3\xf4\xcf\xcf" +
-	"\\\xdb\xfe\xf0o\x9a\xf6\xe6\xea\xe6\x09\xfd\xeb\xd2\xa3(" +
-	"\xf5\x94\xd2\x9f\x9bKk\xc8:\xfc\xa5\xf7\x8a\x1fn\x9a" +
-	"\xb0\xaf?\xeb\x1c/;\x85\xd2\xe52\xda\xc9\xc52\xba" +
-	"\x07\xf7\xb98\x92\xb7w\xef>\xb6d\xea\x7f\xef\xb4\xe9" +
-	"\x1en\x95Ua}\xd9\x90EH\x8aC\x02'\x05E" +
-	"\x01\xc0\x1e\xf7\xd1\x83\xdb\x1ex:r\xa0?\xe5\xd7\xcb" +
-	"\x8fb\x06&\x15\x88\xa4\xfc\xd4\xbe\x1d\xd3\xbe\xba\xb2\xe6" +
-	"@\xee\xc6i\xfd\xfa\x1f\x88gQZH\xe8zY|" +
-	"\x80\x07\xb4\xcb\x97\xbc\xf1\xc3\x8f\xee\xf9\x8f\xe3~\xa3\x1d" +
-	"\xaf`^z\xa1\x82\xf4\xbd\xaf\xbc\xc45\x9fN\xbc\xea" +
-	"\x07\xdc\xaa(\xe6\x00\xa5\x8aJ\x06x\xef\x7fV\xb4\xa5" +
-	"&\xbc\xee\x07\xdcQ\xd9\xcd|\x8d\x01V\x96\x9c\x08\x07" +
-	"\x1b\xcc\x7f\xf2\x03\xd4\xca\xa3\x04H3\xc0\x8d\x8a\x97\x7f" +
-	"_5\xfd@/\xc0\xa3\x95l\x0f;\x18\xa0\xaa\xe9\xcc" +
-	"\x94\x906\xfb\x9fs.\x80\xd9\xe2t\xe5\x13(]\xad" +
-	"\xa4\x0b\xb8\\I\x96{\xf2\xf3\xedK\xf7n\x08\x9f\xef" +
-	"\xe3e\x1b\x86\xae@i\xebPB\xf6\x0c\xed\x04\xb4o" +
-	"\xde?\xfd\xbe\xea\xea\xf3\x17\xfa\xf5\x9fsC\xaf\xa1t" +
-	"\x9d\xa1?\x1e\xfa>\xa0\xbd\xe9O\xd7\xa4\xeeY6\xed" +
-	"\x9d\x1c4s\xca\x93\xc3\xdeB\xe9\xea0\xb6\x89a\xb4" +
-	"\xe3\x9b\xd3n\xbe\xbcez\xea\xdfsU\x17\xb0\x88\x1e" +
-	"\xde\x85R\xf5p\xfas\xf8\xf0E\xe4>\x0bS\xb3\xc5" +
-	"\xef\xb6\x0e\xb9\xe4\xbf\x81\x87\xaa\xfe\x88.yk\x15\xe9" +
-	"\x9b\xf8\xf3\xd9;\xee\x89KW\xfc\x80\x93U\x9b\x98\x99" +
-	"\x18\xe0\xcf\xa4W\xf6h\x1b\xae]\xede\xa6\xaak\x04" +
-	"\x10G\x10\xe0\xc8\x92\xfa\x96\x7f\xbb\xf2\xdd\xcf@\x1c\xcf" +
-	"y\xb1\x00X?iD7JsF\xd0\xde\x9bGP" +
-	"\xf0\x9d\xf9\xa4f\xd7kW\x7f\xfcy\xee\xde\x83\xa4s" +
-	"\xe1\x88M(%G\xb0D1\xe2N\x0e\xd0\xde\xb6\xea" +
-	"\xc9Gn\x8c\x12\xbf\xc8\x8d+v\xd4C\xd5\xef\xa2t" +
-	"\xa1\x9a]h\xf5\xabt\xd4\x177m\xfc\xdd\xb1\xc9\xb3" +
-	"\xbf\xf0o\xf4b\x0d\xcb\x12\xd7kh\xa3\x15\x7f\xb5\xfe" +
-	"R\xdd\x87Wz\x01\xc4\xdaS\x04\x18SK\x80\x83\xb8" +
-	"\xb3\xe4\xa7+>\xb8\xe1\x07\xcc\xa9eG\xfd\x19\x03\xdc" +
-	"\xe8y\xba\xfe\xbe\xd3\xcf\x7f\xd9O\xf6X_{\x0a\xa5" +
-	"\xcd\xb5\x94=\xba\xffen\xf2\x9d[/}\x95\xe3T" +
-	"\xcc\xf8\x1d\xb5O\xa0\xb4\xa1\x96\xee\xe4\xa1\xda50\xde" +
-	"\x8ek\x96jhJ\xa2pB\xca\xd0-}BT\xd7" +
-	"\x92\xba\xf6\xfd\xa8\x92\xd2R\xd3ff~\xa8k\xd5h" +
-	"\xa4C\x8b\xce\xd45K\x89k\xaa1\xbaE1\x04%" +
-	"i\xca\x01>\x00\x10@\x00\xb1l\x06\x80\\\xc4\xa3\x1c" +
-	"\xe6\xb0\xd3PW\xa5U\xd3\xc2r\xef\x0e\x01\xb1\x1c0" +
-	"\xaf\xe5\xa2\x86\xaaX\xea]JR5SJT5G" +
-	"\xb7\xaafZHX\xbd\x96\x9b\x0b \x97\xf2(\x0f\xe3" +
-	"\xd06T3\xa5k\xa6\x0a\x00X\xee\xd5\x93\xff\xcb\x92" +
-	"-\x8a\xa1\xf0\xf9\x1c\xd0\xadu\x03Xmf\xcej\xad" +
-	"\xa4\x8d7\xad\x16Dy\xa4\xbb\xe0\xdee\x00\xf2\x0b<" +
-	"\xca\x879\x14\x11\xc3H\xc2C\x8b\x01\xe4\x83<\xcao" +
-	"r(r\\\x189\x00\xf1\x1c!\xff\x95G\xf93\x0e" +
-	"E\x9e\x0f#\x0f ~L\xc2\x8fx\x8c\x14!\x87b" +
-	" \x10\xc6\x00\xa5R\x9c\x0b\x10\x09 \x8f\x91r\x92\x17" +
-	"\x14\x84\xb1\x00@*\xc3\xc9\x00\x91\"\x92\x87I^X" +
-	"\x18\xc6B\x00Id\xf8r\x92\x7f\x0f9\xb4\x93\xaa\xa5" +
-	"\xc4\x14K\x01\xe1'\x89\x18\x96\x01\x87e\x80\xb6\x96=" +
-	"\x0a\xf0\xaa\x89C\x00[x\xc4\x90\x97\xaf\x00Ih\xa7" +
-	"\xe3\xb1\xf9J*\x15\x07Aksa\xa5\xc0\xb1\x87m" +
-	"\xb7{\xb8L1\xd5\x16\xc5j'\x03\x93\xac\x14\xb0&" +
-	"\xa5\xc7\xe6\xc4\x9c_\xde\xbe\x00\x9c\x97\xcb\xbd@\xc8n" +
-	"`p\xb61S\xba\xa0\x99*\x19\xc7\xe7\x0d\x8b\xb3\xfe" +
-	"7\x96\xeb\xff\xf8\xe5^\x8f1\x80\xd5\x0dUO\xa9\xda" +
-	"<\xbd\xcd\x0b\xb5V\xb5\xc6L\xe7\xed\xfcn\xfb\x93\xe3" +
-	"\x8e\x05\xb7Y\xb4\xd5Y\x94\xce\x1a\xd23g\xcd\xebM" +
-	"\xf7\x9a\xfco\xcaE\xeeF\xc7\xd5\x01\xc8\xa3y\x94'" +
-	"r\xe8x\xf0x\x92\x8d\xe5Q\x9e\xc2a\xc8\xeaH\xa9" +
-	"9\x9e\x12\x02\x0c\xa5\x14\xab\xdd5m>\xf7\xa6X\x96" +
-	"\x12m\xef\x95\x9f\x94$\xe6\x11\xben9\x1b\xc0}\xcd" +
-	"l3\xf4tj\xbe\xa2)m\xaa\x01\xc0\x8e\xcc\xe2P" +
-	"\x9cAj\xc4\xe0\\\x80N\xb3\xc3\xb4\xd4d\xcc\x8e2" +
-	"\xf0r\x13\x00\xf2R\xde\xc4N\xd2\x9a1*\xe6m\x89" +
-	"\xbbU\xc3\x8c\xeb\x1a\xcb$&\xb2LR\xea\x9e\xbd\x99" +
-	"\xce\xde\xc8\xa3<\xcf3\xc3\x1cJ\x0f\x7f\xce\xa3\xbc\x80" +
-	"\x12\x09f\x12\x89L\x8e\xd5\xc2\xa3\x9c\xe0\xb0s\xb5j" +
-	",\xd3M\x15\x118D\xf8\xba\xd0\x1fP\xe0\x05ns" +
-	"\x82yz\xdb,#\x14_\xad\x1ar\x00\xfdU\x1d\xeb" +
-	"B\x0b:R\xaa\xff<u\xfd\x9c\x87d\xb3x\x94[" +
-	"|\xe7\x99?\xc3;\xa4\xe3k\xae\xe2~|\xad3\xa9" +
-	"\xac\x8d\xc4\xd7\xa9\x18\x04\x0e\x83y\xfa^D\xb5\x16\xc5" +
-	"\xb5\x98\xbe\x86\xde\xcc\x18\xc0bN\x11v7\xfc\x8b*" +
-	"\x00y-\x8f\xf2\xdfx\x1b^?\x19@\xbe\x97G\xf9" +
-	"A\xdf\x86\xef\x9f\x06 \xdf\xc7\xa3\xfc[\xca\xe4\x98\xc9" +
-	"\xe4\xbf&S=\xc8\xa3\xbc\x91\x129\xc7\x12\xb9\xb8\x81" +
-	"L\xf5\x08\x8f\xf2.\x0e\xf9\xb8\x9b\x08k\xd6\xc4cV" +
-	";\x0a\xc0\xa1\x00\xd8\xd0\xae\xc6\xdb\xda-\xe7\xe7\xb7a" +
-	"\xc2\xdb\xde\x84\xa5\x18\xd6\x8fb\x11=\xbaR\xb5Z\xdd" +
-	"\xbc\x94\x939\xeb\xbc@\xec?\xd0\xf9\xaf[\x82\xd75" +
-	"y\x01\xa2G\x8f\xc4\x9eu^S'\xf6\xfc\xd2\xe3\x08" +
-	"b\xcf\x01\xaf\x17\x14\xb7\xb6\xfa(\xdaV\xc3\xebx\xc5" +
-	"\xadG\xbd\x16E\xdcq\xca\xeb\x9c\xc5g\xcfz\xc9A" +
-	"\xdco\xf8\x98\xdb\xfeu\xbe\xbe}\x7f\x97\x8fr\x1e\xea" +
-	"\xf6\xe8\x95xd\xa7\xaf_;\xfe\x9c\x8f\x15\x9f<\xea" +
-	"\xeb\xe5O\xb7\xfa\x18\xf0\xe9S^\xd9\x10\xcfu\xfb\x08" +
-	"\xd2\x85\x9d>*v\xf19_\x93w\xb9\xcbv\xa2\x1f" +
-	"\x1a2\xee\xe7\x0ax\xc7\x08\x99\xb2\xe6&\xc8V\x07\xc8" +
-	"\xa2.\xbeZ\x054l'\xafA\x0d\xcbl\xb6\xf3N" +
-	"\x81\xf3\x92\xa3\xac9\xb7\x1ft\xbc\x1el\xe7\x11\xe7{" +
-	"\x96Me\xb6\x93\xda\xa0&\xb3\xb6\xfb\xbb!\xa3\xd7v" +
-	"\x0a\x11\xb6y\x0a\xfd2G\x91\x13q\xe8\x84\\\x88\xe9" +
-	"\xcb\x15\x9b5\x19\xb5NI\xe7{\xf5[\xa6\x05N\xf9" +
-	"B\x0f\xc3\xf5\xaa\xfb\xcc}m\x0f\xe6\xdbB\xd6\xd51" +
-	"\xeb\xeb\xce\x16r\xc4\xce\x16\x16\xa8k-\xfa\x87\xf3\x95" +
-	"T\xb3f\x19\x1d\x00r-_\x00\xe0\x92Lt\x88\x90" +
-	"x}\x06p\xe2U\x01=F\x81\x0e\x8f\x14/\xfc\x12" +
-	"8\xf1\x8c\x80\x9c;\xc6A\x874\x88\xc7\xbb\x81\x13\x8f" +
-	"\x08\xc8\xbb\xf3\x0at8\xb0\xb8\x97\xde\xdb-`\xc0\xa5" +
-	"S\xe8LR\xc4\x9eM\xc0\x89\x9b\x05,p\x191:" +
-	"\xacM\xdcp\x008\xf1!\x01\x0b\xdd\xa1\x0f:\xe3!" +
-	"q}\x17p\xe2/\x04\x14\\\x1e\x8c\x0e\xc3\x11W\x19" +
-	"\xc0\x89q\x81j\x08\xf9a#\xda\xd1\xac3a\xd6-" +
-	"\xa0\x11m\x87W\xa0\xe3,h4\xa2\xed\xd4r?\xd2" +
-	"p\xbd \x0b\xe5U\x82\x9a\xbd,>S\xd7\x1a2\xaf" +
-	"\xb8\xeb\xdd\xa5\xa0cP =f\xd6>P\xc3\x0c\xd4" +
-	"\x88\xfe\xfa:\x80\xd4\xe6%\xf9~\xba\xb2\xb1\xdc\xb7\xde" +
-	"\x8f\xe6DZ\xa6\xc07:KK\xcfb\x15@d\x17" +
-	"u\xe9/\xa2\xc7\x16\xa4\xbd\xb8\x18 \xf2\x02\xc9\x0f#" +
-	"\x87\x98\xe1\x0b\xd2!\xd6\xd4\x1f$\xf1\x09\xf4\x0a\x8dt" +
-	"\x9c\x91\x80\xc3$\x7f\x1d\xbdZ#\x9d\xc4V\x80\xc8\x09" +
-	"\x92\xbf\xc7H\x03\x9f!\x0d\x97q\x05@\xe4\x12\xc9o" +
-	"2\xd2\x10\xc8\x90\x86/\xd9\xb27\x18\x99\xe08\x14\x85" +
-	"\x820qYI\xe4H^\xce\x11\x99 yQa\x18" +
-	"\x8b\x00\xa4qL>\x96\xe4\xb3H\x1e\x14\xc2\x18\x04\x90" +
-	"\x9a\xb8e\x00\x91F\x92\xff\x94\xe4\xc5Ea,\x06\x90" +
-	"\xfe\x92\xc9\xff\x82\xe41\x92\x97\x04\xc3X\x02 )\x1c" +
-	"\x9dk)\xc9\xef%yiq\x18K\x01\xa4\x0en\x06" +
-	"@\xc4\"\xf9#$/\xc30\x96\x11\x03\xe6\x0c\x80\xc8" +
-	"oI\xfe\x18\xc9\x87\x94\x84q\x08\x80\xf4(\x93o$" +
-	"\xf9\x1e\x92\x87J\xc3\x18\x02\x90v3=\xdbI~\x8c" +
-	"\xebUv\xedei-\x96P[\x14\xe0}\x05\xcdR" +
-	"\x8dd\\S\x12\xe4\x04\xd9.\xaa\xc6\xb4bq\xcd\xed" +
-	"\xa9\xd4\xb5q\x8b\x11\x1b\xec\xc3yt=\xd9LO!" +
-	"\xa4X\xed}\x9e&\x9c\xbc\xcd\x1b>\xca\xe1\x9b~0" +
-	"T4\xa1*Z:5\x13\xf8d\xac\x0f\xe1J\xe8\xcb" +
-	"\x94D\x93\x01|_\xbe\x15\xd5\x93IE\x8b5\x81`" +
-	"\xf4}8\xf8&\xa2S\xd5V\xdf\xad\xf87\x9c\x1b\x11" +
-	"\xd1\xde%\x08C^\xdd\xce\xb4k\xb6\x12\x8b\xc5\xad\xb8" +
-	"\xaeA\x8d\x92\xf8Q\xccU\x15\xccl\xae3\xa1*+" +
-	"\xfb\x8a\x07\xc5!ZU3\x9d\xe0\xf3%^n{\x90" +
-	"C$\x84\xdb\xach\xfa;\xc7\x1c\xf2b\x02|3{" +
-	"q\x9b\x8d\x01\xb0\x97lJ\xce\x9f\"\xb9\xdd\xd6\x00&" +
-	"\x1c\xa6?]:\x07\xfa\xe6\xa5\xdcvg\xc0\xa3\x9b\xc1" +
-	"\x9a\xcd\xed\x04\x07\xc7\x97W\xa5\x05\xd5\xcce[U\x1e" +
-	";\x11\xfb\xa7[\\_\xba\xe5O&\xff\xcfL\x8b\xf5" +
-	"3!*\x8a\x8c\xa5\xb0\xad\xdc1\x8aQ\xd7\xf1\xf4\x1f" +
-	"'\x8e\xa1\xffx\xb1\xba\x0e\x00\x03b\xc5(\x00!\x9e" +
-	"\x8a\x0a\x9aj\x09\xa9x,\x946UCH[f^" +
-	"\xf6\xe9\xa7Y\xf4\xcd\x0a\xca\xddkS\xe82\x96f." +
-	"\xc3\xb9\xb58\xd1\xa1\x18\x8fr\xca\xc7\x91\x92$l\xe7" +
-	"Q\xb6\xa8t\xd5f8\xd2*z;\xc5\xa3|/\x97" +
-	"\xc9\xaa3\xf5\x183q\x008\x0c\x006\x98VLO" +
-	"[\xcee\xd2O\xd50\xdc\xbb\xb5\xe2I5\xf6\x93\xb4" +
-	"\xe5\xcb\xd4\x83\xab\xce\xe4[|\x9fa\xd1\x0a\x9f\xffE" +
-	"\xb3`\x08\x19-\xf1\x18\x16\x01\x87Ey:\x9e\xd30" +
-	"g{cZd\x98\xbb\xc8\xe3\xe4v\x1by\x94\xb7\xf8" +
-	"\xdcn\xf3b\x00\xf9\xefx\x94\xb7\xfb\xdcn\xab\x01 " +
-	"?\xc5\xa3\xbc\x87C\xccN\x0bww\x03\xc8{x\x94" +
-	"\x0fR\xdd\xe73\x1cs?9\xed\x8b<\xca\xc7\xa8\xe8" +
-	"\x07X\xd1\x17\x8f\xd0M\x1f\xe6Q~\xbb\xb7\xd3\x9a," +
-	"\xd6s* \xeb\xf0T\xd3\x84\x9a\xb8\xae\xf9\xc6u\xa6" +
-	"\xa5\xa7\x9a\x96[*\x1a\x11\xaa\x87\xcd:.\xff6\xe7" +
-	"\x0c\x83H\xba,mX\x98g\xdap\x89\xdd \x12\xef" +
-	"\xc0\x12\x94Ko\x07\x90\x12\xfb\x99\"\xb6(!#\xaf" +
-	"\x81\xbd\xcbl\x07p2\x87D\x1a\xdf_\xd0\x91B\xd5" +
-	"7\x10;\xcb\xb2JE\x1d\x80\xeb\xf7\x9c\xd1\x9a\xd6(" +
-	"\xde\xe6\x90\xe6\xe5D\xabB+L]\x1b\xd8\x98\xcb\x17" +
-	"gc\xdd68\xc8\xfaTw\x88\xedt\xc1\"R\xdb" +
-	"VJ\xe2a\xe8%\x12\xa9\x02G9\xb3\xed\x91\xac\x0d" +
-	"\xe62m\xf0p\x9c\x06\x10\x09\x93\xbc\x16\xbdp\x90\xaa" +
-	"\x99\xfa\x91$\x1f\x8b^DHc\x18\xbe\xd6\x99\x91\x8b" +
-	"\x85\x05\x996x\x1cR\x9b:\x96\xe4SH.\x14f" +
-	"\xda\xe0I\xacm\x9eH\xf2\xe9$/\x122m\xf0\x0f" +
-	"\x98\xfe\xa9$\x9fE\xf2`Q\xb6\x0df\xedz#\xc9" +
-	"\xe7!\x87v\xca\xd0\xa3\xaai\xce\x01t\xd3\x87C\xb4" +
-	"\x9c\x00\x13,\xa5\xcd\xf9\xbb\x81\xba\xb9\xb8\xe5kU\xe3" +
-	"\x89\xd8,\xc5\x02T]\x88\xa5\x18m\xaa\x071\xd2\xa6" +
-	"EW\x0d\x82O\xa7\x1dU\x8c6\xfdn\xd5\x80\x90\xd9" +
-	"G\xbc\xc0P}\xfazE\xab\x13\xc1\x83,\x16^\x89" +
-	"\xadu}\xf7\x0c\xe5\xba\xd73_A\x9cTwnq" +
-	"\xf6#\xc8%_\xad\xb8HN\xfe&\x8f\xf2\x17d\xdf" +
-	"\xc6L\xae\xbbN\xd1\xf6\x19\x8f\xf2M\xdf<\xedK\xca" +
-	"u7x\x8c\x04\xfc\x0c\x07\xe9\xea[]\x0fq\x08\xce" +
-	"p\xe6Q\xccC&\xa2\x8f\xe0\x8cG\"\x0e\xdf#\xf9" +
-	"T\xec\x9d\x1e\xc9\xe3\xf5\xb4\x15\x01^\x8d:\xe3\xc5\xce" +
-	"l\x9f\x9d\xdba\xf7\xc3\x1b\xfe\xc0]\xf7`\x1a\xb1\xbc" +
-	"\x1bLwh7\xe8\x063\x93\xbcsZ\xe6\xaf\xcf\xa9" +
-	"\xee m\x00\x0b\xf6\xfd\x06\xda\xaa\x9a\xa1\xfc?\xcc\xb8" +
-	"\x83\xc5\x01\xac\x993Y\xf6\xcdS\xf3J\x96\xce\xd0\x89" +
-	"\xcd\x9c\x04\xcb\xe8\xc8\xf983\xca\xfb8\xe36\x0c\xe3" +
-	"'{_g\x84\x95j\x87;W^\xad$\xd2n|" +
-	"\xffo\x00\x00\x00\xff\xff\xfe\x11AT"
+type Conmon_serveExecContainer_Params capnp.Struct
+
+// Conmon_serveExecContainer_Params_TypeID is the unique identifier for the type Conmon_serveExecContainer_Params.
+const Conmon_serveExecContainer_Params_TypeID = 0x90a3950a51412b8b
+
+func NewConmon_serveExecContainer_Params(s *capnp.Segment) (Conmon_serveExecContainer_Params, error) {
+	st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
+	return Conmon_serveExecContainer_Params(st), err
+}
+
+func NewRootConmon_serveExecContainer_Params(s *capnp.Segment) (Conmon_serveExecContainer_Params, error) {
+	st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
+	return Conmon_serveExecContainer_Params(st), err
+}
+
+func ReadRootConmon_serveExecContainer_Params(msg *capnp.Message) (Conmon_serveExecContainer_Params, error) {
+	root, err := msg.Root()
+	return Conmon_serveExecContainer_Params(root.Struct()), err
+}
+
+func (s Conmon_serveExecContainer_Params) String() string {
+	str, _ := text.Marshal(0x90a3950a51412b8b, capnp.Struct(s))
+	return str
+}
+
+func (s Conmon_serveExecContainer_Params) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr {
+	return capnp.Struct(s).EncodeAsPtr(seg)
+}
+
+func (Conmon_serveExecContainer_Params) DecodeFromPtr(p capnp.Ptr) Conmon_serveExecContainer_Params {
+	return Conmon_serveExecContainer_Params(capnp.Struct{}.DecodeFromPtr(p))
+}
+
+func (s Conmon_serveExecContainer_Params) ToPtr() capnp.Ptr {
+	return capnp.Struct(s).ToPtr()
+}
+func (s Conmon_serveExecContainer_Params) IsValid() bool {
+	return capnp.Struct(s).IsValid()
+}
+
+func (s Conmon_serveExecContainer_Params) Message() *capnp.Message {
+	return capnp.Struct(s).Message()
+}
+
+func (s Conmon_serveExecContainer_Params) Segment() *capnp.Segment {
+	return capnp.Struct(s).Segment()
+}
+func (s Conmon_serveExecContainer_Params) Request() (Conmon_ServeExecContainerRequest, error) {
+	p, err := capnp.Struct(s).Ptr(0)
+	return Conmon_ServeExecContainerRequest(p.Struct()), err
+}
+
+func (s Conmon_serveExecContainer_Params) HasRequest() bool {
+	return capnp.Struct(s).HasPtr(0)
+}
+
+func (s Conmon_serveExecContainer_Params) SetRequest(v Conmon_ServeExecContainerRequest) error {
+	return capnp.Struct(s).SetPtr(0, capnp.Struct(v).ToPtr())
+}
+
+// NewRequest sets the request field to a newly
+// allocated Conmon_ServeExecContainerRequest struct, preferring placement in s's segment.
+func (s Conmon_serveExecContainer_Params) NewRequest() (Conmon_ServeExecContainerRequest, error) {
+	ss, err := NewConmon_ServeExecContainerRequest(capnp.Struct(s).Segment())
+	if err != nil {
+		return Conmon_ServeExecContainerRequest{}, err
+	}
+	err = capnp.Struct(s).SetPtr(0, capnp.Struct(ss).ToPtr())
+	return ss, err
+}
+
+// Conmon_serveExecContainer_Params_List is a list of Conmon_serveExecContainer_Params.
+type Conmon_serveExecContainer_Params_List = capnp.StructList[Conmon_serveExecContainer_Params]
+
+// NewConmon_serveExecContainer_Params creates a new list of Conmon_serveExecContainer_Params.
+func NewConmon_serveExecContainer_Params_List(s *capnp.Segment, sz int32) (Conmon_serveExecContainer_Params_List, error) {
+	l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz)
+	return capnp.StructList[Conmon_serveExecContainer_Params](l), err
+}
+
+// Conmon_serveExecContainer_Params_Future is a wrapper for a Conmon_serveExecContainer_Params promised by a client call.
+type Conmon_serveExecContainer_Params_Future struct{ *capnp.Future }
+
+func (f Conmon_serveExecContainer_Params_Future) Struct() (Conmon_serveExecContainer_Params, error) {
+	p, err := f.Future.Ptr()
+	return Conmon_serveExecContainer_Params(p.Struct()), err
+}
+func (p Conmon_serveExecContainer_Params_Future) Request() Conmon_ServeExecContainerRequest_Future {
+	return Conmon_ServeExecContainerRequest_Future{Future: p.Future.Field(0, nil)}
+}
+
+type Conmon_serveExecContainer_Results capnp.Struct
+
+// Conmon_serveExecContainer_Results_TypeID is the unique identifier for the type Conmon_serveExecContainer_Results.
+const Conmon_serveExecContainer_Results_TypeID = 0xdebaeed2a782ac80
+
+func NewConmon_serveExecContainer_Results(s *capnp.Segment) (Conmon_serveExecContainer_Results, error) {
+	st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
+	return Conmon_serveExecContainer_Results(st), err
+}
+
+func NewRootConmon_serveExecContainer_Results(s *capnp.Segment) (Conmon_serveExecContainer_Results, error) {
+	st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
+	return Conmon_serveExecContainer_Results(st), err
+}
+
+func ReadRootConmon_serveExecContainer_Results(msg *capnp.Message) (Conmon_serveExecContainer_Results, error) {
+	root, err := msg.Root()
+	return Conmon_serveExecContainer_Results(root.Struct()), err
+}
+
+func (s Conmon_serveExecContainer_Results) String() string {
+	str, _ := text.Marshal(0xdebaeed2a782ac80, capnp.Struct(s))
+	return str
+}
+
+func (s Conmon_serveExecContainer_Results) EncodeAsPtr(seg *capnp.Segment) capnp.Ptr {
+	return capnp.Struct(s).EncodeAsPtr(seg)
+}
+
+func (Conmon_serveExecContainer_Results) DecodeFromPtr(p capnp.Ptr) Conmon_serveExecContainer_Results {
+	return Conmon_serveExecContainer_Results(capnp.Struct{}.DecodeFromPtr(p))
+}
+
+func (s Conmon_serveExecContainer_Results) ToPtr() capnp.Ptr {
+	return capnp.Struct(s).ToPtr()
+}
+func (s Conmon_serveExecContainer_Results) IsValid() bool {
+	return capnp.Struct(s).IsValid()
+}
+
+func (s Conmon_serveExecContainer_Results) Message() *capnp.Message {
+	return capnp.Struct(s).Message()
+}
+
+func (s Conmon_serveExecContainer_Results) Segment() *capnp.Segment {
+	return capnp.Struct(s).Segment()
+}
+func (s Conmon_serveExecContainer_Results) Response() (Conmon_ServeExecContainerResponse, error) {
+	p, err := capnp.Struct(s).Ptr(0)
+	return Conmon_ServeExecContainerResponse(p.Struct()), err
+}
+
+func (s Conmon_serveExecContainer_Results) HasResponse() bool {
+	return capnp.Struct(s).HasPtr(0)
+}
+
+func (s Conmon_serveExecContainer_Results) SetResponse(v Conmon_ServeExecContainerResponse) error {
+	return capnp.Struct(s).SetPtr(0, capnp.Struct(v).ToPtr())
+}
+
+// NewResponse sets the response field to a newly
+// allocated Conmon_ServeExecContainerResponse struct, preferring placement in s's segment.
+func (s Conmon_serveExecContainer_Results) NewResponse() (Conmon_ServeExecContainerResponse, error) {
+	ss, err := NewConmon_ServeExecContainerResponse(capnp.Struct(s).Segment())
+	if err != nil {
+		return Conmon_ServeExecContainerResponse{}, err
+	}
+	err = capnp.Struct(s).SetPtr(0, capnp.Struct(ss).ToPtr())
+	return ss, err
+}
+
+// Conmon_serveExecContainer_Results_List is a list of Conmon_serveExecContainer_Results.
+type Conmon_serveExecContainer_Results_List = capnp.StructList[Conmon_serveExecContainer_Results]
+
+// NewConmon_serveExecContainer_Results creates a new list of Conmon_serveExecContainer_Results.
+func NewConmon_serveExecContainer_Results_List(s *capnp.Segment, sz int32) (Conmon_serveExecContainer_Results_List, error) {
+	l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz)
+	return capnp.StructList[Conmon_serveExecContainer_Results](l), err
+}
+
+// Conmon_serveExecContainer_Results_Future is a wrapper for a Conmon_serveExecContainer_Results promised by a client call.
+type Conmon_serveExecContainer_Results_Future struct{ *capnp.Future }
+
+func (f Conmon_serveExecContainer_Results_Future) Struct() (Conmon_serveExecContainer_Results, error) {
+	p, err := f.Future.Ptr()
+	return Conmon_serveExecContainer_Results(p.Struct()), err
+}
+func (p Conmon_serveExecContainer_Results_Future) Response() Conmon_ServeExecContainerResponse_Future {
+	return Conmon_ServeExecContainerResponse_Future{Future: p.Future.Field(0, nil)}
+}
+
+const schema_ffaaf7385bc4adad = "x\xda\xc4Z}\x90\x14\xe5\x99\x7f\x9e\xee\x19z\xf6\x8b" +
+	"\xd9\xa6\x87\x80\xeb\xb2+_9\xd8\x84\x00\xae\x9e\x84\"" +
+	"\xc5.\xb0\xe1 `\xb6w@O\xb0<\x9a\x99fw" +
+	"`vz\xec\xeeaY\xa2\x87\x9a\xb3T\xcc\x19\xe1\xb0" +
+	"\x12\xa9\xa3\xca\xaf\x18!pJ\x92\xf5\x84@J\x88\xa9" +
+	"\x00\x91;\x97\x0b\x9e\xb1\xa2\x07\xe2&@\xa9\x11c\xea" +
+	"\xc0\x82\xeb\xab\xe7\xed\xe9\xeewgG2\xb3zu\x7f" +
+	"X\xec<\xfd\xeb\xe7}\x9f\xf7}\xbe~O;\xe3J" +
+	"eKhf\xcd\xf4Z\x10\xd4\x03\xe1\x11\x8eu\xba\xd7" +
+	"|v\xc7\xc2o\x83<\x11\x01\xc2(\x014_\x91\xde" +
+	"A@E\x8e\xcc\x05t\xfe\xfc\xcc\x91\xaf}o\xcb\x1f" +
+	"7\xf3\x80\x99\x11\x06hc\x80\xdf\xcdjZ\xf3\x84\xb8" +
+	"\xe4a\x1e\x90\x8a\xbcI\x80\xbb\x19\xe0\xefWE\xb7\xfd" +
+	"\xd3\x17V2\x80s\xa1y\xcd[\x8f\x9f\xbd\xe9_!" +
+	",\x11pG\xe4MT\xf6E\xe8\xcf\xbe\xc8w\x11\xd0" +
+	"y\xf8K\xadj\xe5cO?\xca\xab{\xac\xf2<\xa9" +
+	"\xdbYI\xea~\xdd\xb0\xf1\xd6\xe8\xb3\x0f|\xbf@\x1d" +
+	"\x03\x1e\xad|\x07\x95\x81J\x09@9\xcd\xc0\xf7\x9f\xbb" +
+	"\xf9\xc5\xe5\xdf\xfe\xe3\x13\xbc\xb6\xd1U\x1f\x91\xb6\xa9U" +
+	"\x04x|\xe5\xd9um\x8b\xa2O\x0d\xd6\x16\"\xdc\xd2" +
+	"\xaa]\xa8\xe8U\x12\x88\xce\x17>9\xf3\xcc\xeb\xf1Y" +
+	";A\x9d\x88C\x16m%\xdcmU\xb4\xe8\xf2\xaa\x1e" +
+	"@\xe7\xc6\x9e\xbd]\x1f\xcd9\xb7\xb3\xd8\x0e\xf7V}" +
+	"\x84\xcaq\x06>\xca60\xe9\xf9_\xf4o\x9e3}" +
+	"\x17\xbf\xc3sU'h\x87W\x18`\xf3\x0f\xf5\xbf:" +
+	"\xb4\xff\x1b\x04\x10\x02m\x80\xcd\xe3\xaa7\xa3rc5" +
+	"\xa9\x9aY}\x13\xa0\xd3\xb3\xea\xc8\xf3\x1b\xd5\x81\xddE" +
+	"l\xf9j\xf5VT\xd4j\xb2EY0c\xdf\xeb\xcd" +
+	"M{\x0am\x11\x087\x8dpmLgk\xf5\xf3\x80" +
+	"\xce\x0dO\xfe\xe4\xc5G>\xd8\xf0/\x84\x16\x0a\x8d\xb9" +
+	"P\xbd\x16\x95p\xcd\x18\x00\xa5\xa6\x86\xd0\xdf\xea?\xff" +
+	"\xdc#\x0f\xb7\xf6\x15\xea\x16\x09\xfd\x83\x9a\xc3\xa8\x1c\xac" +
+	"\xa1?\xf7\xd54\xd2]\x8b\xa7\xde\xad|\xa4u\xfa\x8b" +
+	"\xc5\x0e\xea\xf4\xc8c\xa8\\\x19I;\xb94\x92\xce\xc1" +
+	"\x7f.\xd7\x8b\xce\x9e=\xaf\xac\x9c\xf5\xdf\xbb\x1c:\x87" +
+	"k\xa2u\xd8<5\xda\x83tr\xb5\x0b\x05\xe5\xfeQ" +
+	"\x12\x803\xf5\xbd\x07\x9f}\xe0G\xf1\xfd\xc5\x94w\x8f" +
+	":\x8c.L\xb9w\x14)?\xf6\xe2\xce\xd9\x9f\x9c\xe9" +
+	"\xd9_\xb8q\xb6\xdb\xbeQ'P\xe9't\xf3\xf1Q" +
+	"\x0f\x88\x80N\xed\xca\x7f\xff\xda{w\xfc\xfe\x97\xfc\xa5" +
+	"\xb5\x8ea>\xbf|\x0c\xe9\xfb\x83\xf63\xa1\xedx\xfa" +
+	"W<\xa0wL\xa5\x00\xa8lq\x01\xef\xfe\xcf\xda\xce" +
+	"\xec\xf4Wy\xc0\xde1[I\xc3!\x06XWu$" +
+	"V1\xd7\xfa7\x1epz\xcca\x02\\`\x80\x8b\xa3" +
+	"\x7f\xfe\xbd\xba9\xfb\x07\x01\xe4\xb1l\x0f\x93\xc7\x12`" +
+	"|\xfd\xef\xef5S\xf5\xaf\x15\xbd\x8cEc\xcf\xa3\xa2" +
+	"\x8f\xa5?\xb5\xb1,\xf0\xeaZ\xfbo\x88f\x16\xbeV" +
+	"p^\x0c]S\xf7\x14*\x93\xeb\xe8\xbc\xc6\xd7\xd1E" +
+	"?\xfd\xa7\xe7V\xf5m\x89\xbd>\xc4)\xfb\xeb\xd6\xa2" +
+	"2\xc0\x90\xa7\xeb6\x01:\x97\xef\x9fs\xcf\xb8q\xaf" +
+	"\xff\xb6\xa8\xbb\x8d\xbe\xf6<*3\xaf%\xf4\xb4k\xff" +
+	"\x00\xe8l\xffRO\xf6\x8e\xd5\xb3\xdf.@3\x1f\xae" +
+	"\xa8\x7f\x13\x95\xc9\xf5l\x13\xf5d\xe0=\xbb\xef\xfb\xe1" +
+	"\x89\x0f\xf6\xbf\xcd\x9f@[=\x0b\xee\xdb\x18\xe0\xf2\xec" +
+	"\xcb?\x7fbN\xf6\xbf\x0a\xd7\x0e\x13\xf2\xee\xfa\xcd\xa8" +
+	"<F\xea\x9a\xb7\xd4\xdfJ'\xb0<\xbbP\xfeb\xc7" +
+	"\xc8S\xbc\xbep\xc3(\xba\xb4q\x0d\xa4o\xc6\xb7\x16" +
+	"\xee\xbc#\xa5\x9c\x19t\xed\x0d\xdbiA\x95\x01\xfeZ" +
+	"\xf9\xc5\x0b\x99-\xe7\x07x@\xae\x81%\xaf\x87\x18\xe0" +
+	"\xd0\xca\xe6\xf6\xff<\xf3\xc5\x0fA\x9e&\x04\xb1\x05\xd8" +
+	"\xbc\xb3a+*\x87\x1a\xc8\xb8\x83\x0d\x14\xcc\xfd\x1f4" +
+	"\xee\xfe\xf5\xc07\xfeT\xb8\xf7\x0a\xe6\x86\x0d\xdbQ\x19" +
+	"h`>\xd1p\x93\x00\xe8<{\xe7\xd3\x8f^\x9c " +
+	"\x7f\\\x18\xa7\xcc\xd4\x1b\xc7\xbf\x83\x8a:\x9e%\xb5\xf1" +
+	"\xbf\"S_\xda\xbe\xed\xbb\xaf\\\xbf\xf0c~\xa3\xcb" +
+	"'\xb2\xac\x93\x9aH\x1b\x1d\xfdw\xf7\x9ej:wf" +
+	"\x10\xe0\xa1\x89\xc7\x08\xb0\x83\x01\x0e\xe0\xae\xaa\xdb\xd7\x9e" +
+	"\xbd\xc8\x03\x0eMd\xa6\x9ed\x80\x8bO\xfe\xa8\xf9\x9e" +
+	"\xe3?\xb9T$\x1b]\x9ax\x0c\x95\xd1\x93(\x1bm" +
+	"\xfd\x8f\xc5\xddo_\xf9\xd9'\x05^\xc7\xbc\xe3\xc2\xc4" +
+	"\xa7P\xa9\x98Dg\x12\x9e\xd4\x03\xd3\x9cT\xc6\xd6\xcd" +
+	"\x8c\x96\x1e1=k\x1a\xb61=ad\xba\x8d\xccW" +
+	"\x12Z6\x93\x9d=\xdf\xfd\xa1o\xd0\x13\xf1\xdeLb" +
+	"\xbe\x91\xb1\xb5TF7'\xb5k\xa6\xa4u[jH" +
+	"\x0c\x01\x84\x10@\xae\x99\x07\xa0FDTc\x02n2" +
+	"\xf5;s\xbaecmp\x86\x80X\x0bX\xd2r\x09" +
+	"S\xd7l\xfdf\xad[\xb7\xb2ZB\xb7&u\xe8V" +
+	"NJ\xdb\x83\x96[\x0c\xa0V\x8b\xa8\x8e\x15\xd01u" +
+	"+kd,\x1d\x00\xb06(f\x9fe\xc9v\xcd\xd4" +
+	"\xc4R\x0c\xf4+q\x19\xab\xcd/X\xad\x83\xb4\x89\x96" +
+	"\xdd\x8e\xa8\xd6\xfb\x0b\xf6\xad\x06P\x7f*\xa2\xfa\xb2\x80" +
+	"2b\x0cIxp\x05\x80z@D\xf5\x0d\x01eA" +
+	"\x88\xa1\x00 \x9f$\xe4oDT?\x14P\x16\xc5\x18" +
+	"\x8a\x00\xf2\xfb$|O\xc4x\x04\x05\x94C\xa1\x18\x86" +
+	"\xe8\xd2q1@<\x84\"\xc6kI\x1e\x0e\xc70L" +
+	"\xb5\x06\xaf\x07\x88GH\x1e#\xf9\x88\x111\x1c\x01\xa0" +
+	"\xc8\x0c_K\xf2/\xa3\x80N\xb7nkI\xcd\xd6@" +
+	"\xfaf:\x895 `\x0d\xa0\x93\xc9\x9b\x02\xa2n\xe1" +
+	"H\xc0v\x111\x1a$4@\x12:\xb9Tr\xa9\x96" +
+	"\xcd\xa6@\xcat\xfa\xb0j\x10\xd8\xc3\xce\xab=\\\xad" +
+	"Yz\xbbfw\xd1\x05\x93\xac\x1a\xb01k$\x17%" +
+	"\xbd_\xc1\xbe\x00\xbc\x97k\x83@\xc8o\xa0\x94\xbb\xb1" +
+	"ts\xbd\xde\xb6A\x1f\xe4\xecQ\xb3$g\xf7\xab\xc3" +
+	"g\xf2\x05+kH\x19K'g\xe0V\\\x91\xf7\xf7" +
+	")B\xf1\xe3\xae\x0d\x1a\xaa2\xac5u#\xabg\x96" +
+	"\x18\x9d\x81\xb5\x1dz\xa3\x95+9\xd8\xfc^\xaf\xc0\xe4" +
+	"\xf0U\x16\xed\xf0\x16%[\xa3\x86kkIo\xfa\xc7" +
+	"\xc4\xbf\xa9F\xfc\x8dNm\x02P'\x89\xa8\xce\x10\xd0" +
+	"\x8b\x98i$\x9b\"\xa2z\x83\x80Q\xbb7\xab\x17x" +
+	"f\x140\x9a\xd5\xec.\xdf\x95J9\xb7\xf8\x10/\xe9" +
+	"\xd0\xad\xc6\xac1\xf4\xde&\x04\x9e\"\xe5\xcctY\xab" +
+	"h\xb6\xad%\xba\x06e]\xad\x1bKpD\xbfH\x97" +
+	"q+\xf3;M#\x97]\xaae\xb4N\xdd\x04`\x07" +
+	"\xcb\xb2\x8b<\x8f\xd4\xc8\x15\x8b\x016Y\xbd\x96\xadw" +
+	"'\x9d\x04\x03\xaf\xb1\x00\xa0$\xe5\xad\xcc\x92\x0e\xd7u" +
+	"\xb0\xe4\xfb\xbeE7\xad\x94\x91a\xf9\xd1B\x96\x1f\xab" +
+	"}\xdb\xdb\xc8\xf6\x16\x11\xd5%\xc1e/\xa2\xa4\xf77" +
+	"\"\xaa\xcb(=\xa2\x9b\x1eUr\xdfv\x11\xd5\xb4\x80" +
+	"\x9b\xd6\xeb\xe6j\xc3\xd2\x11A@\x84OKhe\xa5" +
+	"\x93\xd0U,Xbt.0\xa3\xa9\xf5\xba\xa9\x86\x90" +
+	"\xefU\xb0)\xba\xac7\xab\xf3\xf64\x15\xb1\x87d\x0b" +
+	"DT\xdb9{\x96\xce\x0b\x8c\xf4<\xdaW\\\xc4\xa3" +
+	"7uk\x1b\xe2\xa9\x8d:V\x80\x80\x15%{\xb8}" +
+	"k*\x934z\xe8M\xf7\x02l\xe6\x141\x7f\xc3w" +
+	"\xd7\x01\xa8\x1bDT\xff!\xd8\xf0\xbd\xd7\x03\xa8w\x89" +
+	"\xa8>\xc8m\xf8\xfe\xd9\x00\xea=\"\xaa\xdf\xa1\xfa\x84" +
+	"n}z\x88\xae\xeaA\x11\xd5mT\x9e\x04V\x9e\xe4" +
+	"-tU\x8f\x8a\xa8\xee\x16PL\xf9\xe9\xbd\xb1'\x95" +
+	"\xb4\xbbP\x02\x01%\xc0\xb9]z\xaa\xb3\xcb\xf6~~" +
+	"\x1eWx\xd5\x93\xb05\xd3\xfez2n$\xd6\xe9v" +
+	"\x87\x9f\xfd\x0a\xe2\xbc)\x08\xc4\xe2\xe9D\xfc\xb4%D" +
+	"#\xa3\xde\x8e\x18\x90Hy\xef\xc6\xa0U\x95\xf7\xde\x17" +
+	"0)y\xef\xfe\xa0\xc3\x95\xfb:8\"\xdbg\x06\x8d" +
+	"\xbe\xdcw8h\xbc\xe4}\xc7\x02\xc2 \x1f:\x11$" +
+	"\x07\xf9\xa8\xc9\xf1\xdb\xa3\x1b9\xbart3\xc7\xe2\x8f" +
+	"o\x0dH\xa8\xdc\xbf\x8b\xebBO\xfe\x98\x9bD\xfc\xf6" +
+	"0Ga\xde\xea\xe0\x86\x0ao\x1d\x0b\x8a\x93<\xb0\x95" +
+	"\xa3\x91\xe7vq\x84\xf5\xfd\x1fs\xad\xeb\x85\xcdA9" +
+	"\x95\xff|\x82\x1b\x00\\y\xd3\xf1\xf2\x02\xccu\x1d\xd3" +
+	"\x17\x88\xde\xf5\xb8e5\xc8\xce\x1e\x90\xc5cj\xbd\x0e" +
+	"h:^\xc6\x83F\x96\xf3\x1c\xef\x9dp\x90\xd2]e" +
+	"m\x85\xfd\xaf\x17\x0f\xe0x\x8f\x04\xbe\x10\xb8I\xce\xf1" +
+	"\x92\x1e4\xbak\xfb\xbf\xe7\xbaz\x1d\xaf\x10bg\xa0" +
+	"\x90\x97y\x8a\xbcXD/\x18\xa3L_\xa18_\x80" +
+	"\x1c\xaf\xa5\x10\x07\xf5\x97\x96\x0d^\xf9\xc4\x00#\x0c\xea" +
+	";\x98c;\x01\x8c\xdbB>\x080\x1f\x05\xde\x16\x0a" +
+	"\xc4\xde\x16\x96\xe9\x1bl\xfa\x0f\x97j\xd9\xb6\x8cm\xf6" +
+	"\x028^\xcd\x14\x0a\xcf\x11\xed\xe2\xcfhm\xd1\xd2\xd5" +
+	"Ib\x18\xc0g\xf7\xe81FE\xc6y (a\x94" +
+	"0 _\xe8Qx\xf9\xd2} \xc8\x17$\x14\xfcy" +
+	"\x1cz\xfcJ\x1e\xd8\x0a\x82|ZB\xd1\x1f\x15\xa17" +
+	"~\x90O\xd2{\xc7%\x0c\xf9\xcc\x13\xbd\x89\x97|h" +
+	";\x08\xf2A\x09\xc3\xfe0\x02=\x82+\xef\xdd\x0f\x82" +
+	"\xbcG\xc2\x11\xfe\xf4\x0e\xbd9\x9f\xfc\xe4f\x10\xe4\x1d" +
+	"\x12J\xfe\x08\x02=2(o1A\x90\x1f\x920\xe2" +
+	"\x8f\xe9\xd0#\xe1\xf2\xdd\xb4^\xafDE\x8b\xdc\xbb\x05" +
+	"\x9dD\xdeG1\x7fJ\xd0\x82\x8eG\xcf\xd0;;4" +
+	"[\xd0\xf1\x9a\x07\x1ei\xfa\xce\x95\x87\x8a:A\xadA" +
+	"\x8e4\xdf\xc8\xccu_\xf1\xd7\xbbYC\xcfO\x80\xf4" +
+	"X\xf9k\x87Fv\xefL\x85{\x81\x98\xe05\xf3u" +
+	"\xbe\x8c\x14\x1b\x14\x9b\"=\xe8\x14\xe1s\xcc\xed\x85i" +
+	"\x82k4Z\xbc\xa5\x95\xbdX\x07\x10\xdfM\x1c\xe8%" +
+	"\x0c\xb8\x98\xd2\x87+\x00\xe2?%\xf9\xcb( \xbal" +
+	"L9\xc8(\xd3\x01\x12\x1f\xc1\xa0\xe0)\xbfd\x14\xeb" +
+	"e\x92\xbf\x8aA\xcdS\x8eb\x07@\xfc\x08\xc9\xdfe" +
+	"\x94Lt)\xd9i\\\x0b\x10?E\xf2\xcb\x8c\x92\x85" +
+	"\\Jv\x89-{\x91Q5A@Y\x0a\xc7\x90\xf8" +
+	"\xbc,\x90\xbcV \xaaF\xf2\xc8\x88\x18F\x00\x94\xa9" +
+	"L>\x85\xe4\x0bH^!\xc5\xb0\x02@i\x15V\x03" +
+	"\xc4[H~;\xc9+#1\xac\x04Pnc\xf2\xbf" +
+	"%y\x92\xe4U\x151\xac\x02P4\x81\xecZE\xf2" +
+	"\xbbH^]\x19\xc3j\x00\xa5W\x98\x07\x10\xb7I\xfe" +
+	"(\xc9k0\x865\x00\xca?\x0a&@\xfc;$\xff" +
+	">\xc9GV\xc5p$\x80\xf2\x18\x93o#\xf9\x0b$" +
+	"\x8fV\xc70\x0a\xa0\xecaz\x9e#\xf9+\xc2\xa0\xf2" +
+	"\xef\xac\xcee\x92i\xbd]\x03\x91+\xac\xb6nv\xa7" +
+	"2Z\x9a\x9c \xdf\xcd5Zv2\x95\xf1{;}" +
+	"C\xcaf\xb4\x11\x870J\xc3\xe8n\xa3\xa7\x10\xd5\xec" +
+	"\xae!O\xd3^\x95\x10M\x8e`q\xb3%\x86J\xa4" +
+	"u-\x93\xcb\xce\x07\xb1;9\x84\xce\xa6\x8d\xd5Z\xba" +
+	"\xd5\x04q(\x9bM\x18\xdd\xddZ&\xd9\x0a\x929\xf4" +
+	"\xe1\xf0\x9b\x99Mzf\xfd-\x1a\xbf\xe1\xc2\x88H\x0c" +
+	".x\x18\x0d\xfa\x07\xb7mt\xb4d2e\xa7\x8c\x0c" +
+	"4j\xe9\xaf'}U\x15\xee\xe66\xa5um\xddP" +
+	"\xf1\xb0\xb8L\x87n\xe5\xd2b\xa94\xd3oS\x0a\x08" +
+	"\x8dtU&\xcfu\xb0\x05$\xca\x02\xf8\xcb,\xcao" +
+	"z\xca`Q\xf9L]:U\xf3\xbb\xbe2f\x06\x16" +
+	"\x9f.=\x83\xfe\xf2R~\xdbU\xf6`l\xb8\xd7\xe6" +
+	"w\xa4e\xacX\x8cZ\xbb\xddF\xc1xlq~<" +
+	"\xf6\x1a7\x1e;N\x94\xe4\x88\x88\xeao\xb8\xf1X?" +
+	"q\xf0WET\xcf\x0a\x88\xf9\xe9\xd8\x00\xc9N\x89\xa8" +
+	"\xbeG\x99\x18]\xf6q\x8e\xc8\xcb\xbb\xf99ZX`" +
+	"iX~\x9f\xc8\xcbY\x11\xd5\x8f)\x07\x8b,\x07\xcb" +
+	"\x17f\xbb\xc35\xf5b\x89\xd5\x88\xcbfRbH\xba" +
+	"\x90l\xbb\xb7x\x0e\x9bk\xd9I#g\xf3?u\xd3" +
+	"\xf43\\\x99\xe3\x96;sR\xfe\x1c9\xdaY\x17\xd0" +
+	"N\xb98\x8f\x16\x86\xf2h>;\xff\x1fSh\xd6\x8e" +
+	"F\xa9\xf9`\xf4\x93m\xe5\xc6\x09l&1\x8d\xfe\x11" +
+	"\xe4\xc9\xf4\x8f(\x8fk\x02\xc0\x90<z\x02\x80\x94\xca" +
+	"&\xa4\x8cnK\xd9T2\x9a\xb3tS\xca\xd9VI" +
+	"\xeeW\xa4\xd7\xe7FM\xb5\xfe\xb1it\x18\xab\xdc\xc3" +
+	"\xf0N-E^\x91\x14Q\xcdr\xe4\xb7\x9b\x84]\"" +
+	"\xaa6\xf5\x02\xd7\xb9\xeew'\xbd\x9d\x15Q\xbdKp" +
+	"\xcb\xd4|#\xc9b&\x04\x02\x86\x82k\xcf\x1f\xa6w" +
+	"\xed\xde\xd9\xda\xa9n=\xf9\xcd\x9c\xcd\x95\xbe\xe1\xb5;" +
+	"\xf9\x16\xbb\xa0\xd5Z\xcb\x05t\"\x0f\x86\xa8\xd9\x9eJ" +
+	"b\x04\x04\x8c\x0c\x7f\x94Z\xdep\xd1\xe7|e$`" +
+	"\x8fd\xe5\xf9\x14Y6\xd6_\xebq\xf2\xf5m\"\xaa" +
+	"Op\xbe\xbec\x05\x80\xfa\xcf\"\xaa\xcfq\xbe\xfe\x03" +
+	"\x13@}FD\xf5\x85 g\xec\xd9\x0a\xa0\xbe \xa2" +
+	"z\x80r\x86\xe8\xe6\x8c}\x14)/\x89\xa8\xbeB9" +
+	"#\xe4\xe6\x8cCd\xd3\xcb\"\xaa\xbf\x1b\x1c)\x16\xcb" +
+	"\xd8\x05}\x0ck\xdfu\xcb\x82\xc6\x94\x91\xe1F\xda\x96" +
+	"md[\xd7\xd8:\x9aq\xca\x08m\x06\xae\xf9<\xa7" +
+	"V\xc3(\x9d,\xf9\xdbX\xe2\xed\xf9c\x82a\x94\xcf" +
+	"\xf2\xca\x8c?,)\xa3\xcc\x14\x99|\x97<\xe7\xf7\xe7" +
+	"$eX\xe6\x0d\x1e\xcc\xaf,\xeb\xcd\xa2\xce\x8dWO" +
+	"\xb0T6\xba\x09\xc0\x0f6\xc1\xec\xc8e(\xc8\x17\x91" +
+	"\xe65D\xc5\xa3k-#S\xde\xd0\x94\x0b\xee)>" +
+	"\x99\xa9`l\xc3\xff\xd0\xe3q\x19\xe2\xd2\x10\xaf&\xf1" +
+	"X\x0c\xb2\x972\x1a'x\xdf\x7f\xea\x19\x99\x11\\2" +
+	"s\x0d\xce\x06\x88\xc7H~\x1d\x06\xe1\xa0\x8cc\xea\xeb" +
+	"I>\x05\x83\x88P&3\xfcu\xdew$yD\xd8" +
+	"%3S\x91\xc8\xc6\x14\x92\xdf@ri\x84Kff" +
+	"2\xf23\x83\xe4sH\x1e\x91\\2\xf3U\xa6\x7f\x16" +
+	"\xc9\x17\x90\xbc\"\x92'3\x8ct\xb5\x90|\x09\x0a\xe8" +
+	"dM#\xa1[\xd6\"@?gy,\xda\xaf\xc3\xb6" +
+	"\xd6\xe9\xfd=\x97z\xf2\x94\xcd\x11\x8eT:\xb9@\xb3" +
+	"\x01u\x1fbkf\xa7\x1e@\xcc\x9ce\xd3Q\x83\xc4" +
+	"\xe9t\x12\x9a\xd9i\xdc\xa2\x9b\x10\xb5\x86\x88\x97\x99:" +
+	"\xa7oP\xb4z\x11<\xcc\x0a\x15\xd4\xf5\xeb|\xdf\xed" +
+	"\xaf\xcbw=o\x04\x05\xea\xe4\x8a\xfc\x87\xc2S\\\x81" +
+	"z\x8b\x9c\xfc\x8d|\x83#\xb6\xb8\xb9\xee\x02E\xdb\x87" +
+	"\"\xaa\x97\xb9\xe9\xec%\xcau\x17E\x8c\x87x\x9e\x8a" +
+	"t\xf4\x1d\xbe\x87x4\xf5\x1a\xe6Q\xccCf G" +
+	"S\xa7!\xd1\xbf/\x93|\x16\x0eN\x8f\xe4\xf1F\xce" +
+	"\x8e\x83\xa8'\xbca\xf5\xa6<[*\xe4IE\xd8\xdf" +
+	"\xff3w\x1aN;]2M\xf0G\xc0\xc3\xa6\x09n" +
+	"\xf2. >\x9f\x9eS\xfd\xb1l\x19\x0b\x0e\xfd\xff\x04" +
+	":t+Zz\xbd\xf7\xc7\xd4e\xd1\x85A\xdf)\xb8" +
+	"\xe9|I\xc9\xd2\x1bT\xb29\xa5d\x9b\xbd\x05\x1f\x14" +
+	"'\x04\x1f\x14\xfd\x86a\xda\xf5\xc1\x17Ei\x9d\xde\xeb" +
+	"\x7f\xa5X\xaf\xa5s~|\xffo\x00\x00\x00\xff\xff2" +
+	"\xa3T>"
 
 func RegisterSchema(reg *schemas.Registry) {
 	reg.Register(&schemas.Schema{
@@ -4793,10 +5286,12 @@ func RegisterSchema(reg *schemas.Registry) {
 			0x8aef91973dc8a4f5,
 			0x8b4c03a0662a38dc,
 			0x8b5b1693940f607e,
+			0x90a3950a51412b8b,
 			0x9887a60f577a1ecb,
 			0xa0ef8355b64ee985,
 			0xa20f49456be85b99,
 			0xa93853d6a4e3fa16,
+			0xa9e93cf268b17735,
 			0xaa2f3c8ad1c3af24,
 			0xaa4bbac12765a78a,
 			0xace5517aafc86077,
@@ -4812,10 +5307,12 @@ func RegisterSchema(reg *schemas.Registry) {
 			0xcc2f70676afee4e7,
 			0xce733f0914c80b6b,
 			0xceba3c1a97be15f8,
+			0xd01c697281e61c21,
 			0xd0476e0f34d1411a,
 			0xd61491b560a8f3a3,
 			0xd9d61d1d803c85fc,
 			0xde3a625e70772b9a,
+			0xdebaeed2a782ac80,
 			0xdf703ca0befc3afc,
 			0xe00e522611477055,
 			0xe313695ea9477b30,
diff --git a/pkg/client/client.go b/pkg/client/client.go
index 0927d423ec..fe463cc606 100644
--- a/pkg/client/client.go
+++ b/pkg/client/client.go
@@ -1274,3 +1274,95 @@ func mappingsToSlice(mappings []idtools.IDMap) (res []string) {
 
 	return res
 }
+
+// ExecSyncConfig is the configuration for calling the ServeExecContainer method.
+type ServeExecContainerConfig struct {
+	// ID is the container identifier.
+	ID string
+
+	// Cmd is the command to be run.
+	Cmd []string
+
+	// Tty indicates if a tty should be used or not.
+	Tty bool
+
+	// Stdin indicates if stdin should be available or not.
+	Stdin bool
+
+	// Stdout indicates if stdout should be available or not.
+	Stdout bool
+
+	// Stderr indicates if stderr should be available or not.
+	Stderr bool
+}
+
+// ServeExecContainerResult is the result for calling the ServeExecContainer method.
+type ServeExecContainerResult struct {
+	// URL specifies the returned URL.
+	URL string
+}
+
+// ExecSyncContainer can be used to execute a command within a running
+// container.
+func (c *ConmonClient) ServeExecContainer(
+	ctx context.Context,
+	cfg *ServeExecContainerConfig,
+) (*ServeExecContainerResult, error) {
+	ctx, span := c.startSpan(ctx, "ServeExecContainer")
+	if span != nil {
+		defer span.End()
+	}
+
+	conn, err := c.newRPCConn()
+	if err != nil {
+		return nil, fmt.Errorf("create RPC connection: %w", err)
+	}
+	defer conn.Close()
+
+	client := proto.Conmon(conn.Bootstrap(ctx))
+	future, free := client.ServeExecContainer(ctx, func(p proto.Conmon_serveExecContainer_Params) error {
+		req, err := p.NewRequest()
+		if err != nil {
+			return fmt.Errorf("create request: %w", err)
+		}
+		if err := c.setMetadata(ctx, req); err != nil {
+			return err
+		}
+		if err := req.SetId(cfg.ID); err != nil {
+			return fmt.Errorf("set ID: %w", err)
+		}
+
+		if err := stringSliceToTextList(cfg.Cmd, req.NewCmd); err != nil {
+			return fmt.Errorf("convert command to text list: %w", err)
+		}
+
+		req.SetTty(cfg.Tty)
+		req.SetStdin(cfg.Stdin)
+		req.SetStdout(cfg.Stdout)
+		req.SetStderr(cfg.Stderr)
+
+		return nil
+	})
+	defer free()
+
+	result, err := future.Struct()
+	if err != nil {
+		return nil, fmt.Errorf("create result: %w", err)
+	}
+
+	resp, err := result.Response()
+	if err != nil {
+		return nil, fmt.Errorf("set response: %w", err)
+	}
+
+	url, err := resp.Url()
+	if err != nil {
+		return nil, fmt.Errorf("get url: %w", err)
+	}
+
+	serveExecContainerResult := &ServeExecContainerResult{
+		URL: url,
+	}
+
+	return serveExecContainerResult, nil
+}
diff --git a/pkg/client/suite_test.go b/pkg/client/suite_test.go
index 6d2d9746c9..f70bacca6f 100644
--- a/pkg/client/suite_test.go
+++ b/pkg/client/suite_test.go
@@ -38,7 +38,7 @@ var (
 	busyboxDest = filepath.Join(busyboxDestDir, "busybox")
 	runtimePath = os.Getenv("RUNTIME_BINARY")
 	conmonPath  = os.Getenv(conmonBinaryKey)
-	maxRSSKB    = 7500
+	maxRSSKB    = 9500
 )
 
 // TestConmonClient runs the created specs.