use serde::{Deserialize, Serialize}; /// Hardening parameters for one agent sandbox. The non-negotiable controls /// (uid 10001, cap-drop ALL, no-new-privileges, seccomp profile, read-only /// rootfs, no network) are enforced by the driver and are not configurable /// here by design — only resource limits vary per deployment. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SandboxSpec { /// Unique container name, e.g. `clawmates-sbx-{agent_id}`. pub name: String, /// Image reference; preloaded in air-gapped installs (never pulled). pub image: String, pub memory_bytes: i64, pub nano_cpus: i64, pub pids_limit: i64, /// Egress-enabled containers exist ONLY for the browser tool: they /// hold no credentials and have no broker route; their output is /// tainted `web`. Everything else runs with no network at all. pub egress: bool, /// What this sandbox is for — drives the reaper label and whether the /// rootfs/home is writable. pub kind: SandboxKind, /// Named-volume subpath mounts (Terminal drives). Empty for tool sandboxes. pub mounts: Vec, } /// A read-write mount of a per-agent subpath of a named Docker volume into the /// container — used to expose the Files drives inside the Terminal. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DriveMount { /// The engine's named volume (e.g. `clawmates_filedata`). pub volume: String, /// Subpath within the volume (per-agent isolation), e.g. `{ws}/documents/{agent}`. pub subpath: String, /// Mount target inside the container, e.g. `/home/agent/drives/documents`. pub target: String, pub read_only: bool, } /// The flavour of a sandbox container. Agent + Browser are hardened tool /// sandboxes; Terminal is the interactive themed dev shell. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum SandboxKind { /// Hardened, no-egress agent tool sandbox (read-only rootfs, tmpfs home). Agent, /// Egress-enabled browser sandbox (no credentials; output tainted `web`). Browser, /// Interactive themed terminal: a writable home so the baked zsh / /// oh-my-zsh / powerlevel10k config + history work. Still non-root, /// cap-drop ALL, seccomp, no-new-privileges and resource-limited. Terminal, } impl SandboxKind { /// The [`crate::SANDBOX_LABEL`] value, so each manager reaps only its own. pub fn label(self) -> &'static str { match self { SandboxKind::Agent => "agent", SandboxKind::Browser => "browser", SandboxKind::Terminal => "terminal", } } /// Terminal keeps a writable rootfs + home (baked dotfiles + history); /// the hardened tool sandboxes stay read-only with a tmpfs home. pub fn writable_home(self) -> bool { matches!(self, SandboxKind::Terminal) } /// The uid:gid the container runs as. Terminal matches the server's nonroot /// uid (65532) so it shares read-write ownership of the file-drive volume; /// the hardened tool sandboxes run as 10001. pub fn run_user(self) -> &'static str { match self { SandboxKind::Terminal => "65532:65532", _ => "10001:10001", } } } /// An attached interactive PTY exec (a `docker exec -it` session): combined /// TTY output as byte chunks + a writer for keystrokes, plus the exec id so /// the window size can be resized. pub struct PtySession { pub exec_id: String, pub output: std::pin::Pin, crate::SandboxError>> + Send>>, pub input: std::pin::Pin>, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SandboxHandle { /// Container id assigned by the engine. pub id: String, pub name: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ExecResult { pub exit_code: i64, pub stdout: String, pub stderr: String, } /// A sandbox the driver currently knows about (label-filtered), used by the /// reaper to find orphans — containers that outlived the process that made them. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ManagedSandbox { /// Engine container/pod id. pub id: String, /// Creation time, unix seconds (for TTL-based reaping). pub created_unix: i64, }