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]>
81 lines
2.4 KiB
Rust
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);
|
|
}
|