Files
clawmates/crates/cm-runtime/tests/warm_pool.rs
T
Omar SobhandClaude Fable 5 add4f79fed Rebrand: TeamClaw -> Clawmates (clawmates.work)
Full-depth rename per the approved plan; the 'claw' product vocabulary
(claws, /claws routes, clawId, Claw Chat) stays — it is now the brand.

- Display brand: Clawmates (manifest, titles, hero, login/rail logo
  'clawmates'); default host app.clawmates.work; registry
  ghcr.io/clawmates
- Crates tc-* -> cm-* (16 crates + all imports); binaries
  clawmates-server/broker/bundler; images clawmates/*; env prefix
  CLAWMATES_* (+ CM_TEST_DATABASE_URL / CM_LIVE_LLM); config
  clawmates.toml; helm chart deploy/helm/clawmates with clawmates-*
  resources; db names clawmates*; sockets /run/clawmates; cookie
  cm_session; kind cluster clawmates-test; seccomp node profile
  clawmates-agent-profile.json
- All 9 Playwright brand assertions updated in lockstep; historical
  spec document left untouched as the only remaining 'TeamClaw'
- Local env migrated: dev pg clawmates-dev-pg/clawmates_dev, shared
  test server clawmates-test-pg, kind cluster recreated with image +
  profile, compose images rebuilt under clawmates/*

Verified end to end: 161 Rust + 68 frontend tests, 29 Playwright
journeys, 4 live kind tests, helm/install/LOC/placeholder gates, and
the clean-room install rehearsal serving the clawmates login page from
a signed bundle of the rebuilt images.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-10 12:31:25 -05:00

81 lines
2.4 KiB
Rust

//! Warm sandbox pool: pre-provisioned containers absorb the first-exec
//! latency. Real Docker — the pool fills in the background, an exec
//! takes a sandbox from it, and the warmer restores the target.
use std::process::Command;
use std::sync::Arc;
use std::time::Duration;
use cm_domain::AgentId;
use cm_runtime::SandboxManager;
use cm_sandbox::DockerDriver;
const IMAGE: &str = "clawmates/agent-base:dev";
fn ensure_image() {
let exists = Command::new("docker")
.args(["image", "inspect", IMAGE])
.output()
.expect("docker available")
.status
.success();
if !exists {
let root = env!("CARGO_MANIFEST_DIR");
let status = Command::new("docker")
.args([
"build",
"-t",
IMAGE,
"-f",
&format!("{root}/../../images/agent-base/Dockerfile"),
&format!("{root}/../../images/agent-base"),
])
.status()
.expect("docker build runs");
assert!(status.success());
}
}
async fn pool_reaches(manager: &SandboxManager, target: usize) {
for _ in 0..120 {
if manager.pool_size().await == target {
return;
}
tokio::time::sleep(Duration::from_millis(250)).await;
}
panic!(
"pool never reached {target} (now {})",
manager.pool_size().await
);
}
#[tokio::test]
async fn the_pool_prefills_assigns_and_refills() {
ensure_image();
let driver: Arc<dyn cm_sandbox::SandboxDriver> =
Arc::new(DockerDriver::connect().expect("docker reachable"));
let manager = Arc::new(SandboxManager::new(driver, IMAGE)).warm(2);
// The warmer fills the pool without any exec happening.
pool_reaches(&manager, 2).await;
// An exec is served from the pool — and works.
let agent = AgentId::new();
let result = manager.exec(agent, "id -u").await.unwrap();
assert_eq!(result.stdout.trim(), "10001");
// The warmer restores the target while the agent keeps its sandbox.
pool_reaches(&manager, 2).await;
let again = manager.exec(agent, "echo still-mine").await.unwrap();
assert_eq!(again.stdout.trim(), "still-mine");
assert_eq!(
manager.pool_size().await,
2,
"reuse must not drain the pool"
);
// Shutdown destroys assigned AND pooled sandboxes.
manager.shutdown().await;
assert_eq!(manager.pool_size().await, 0);
}