perf: speed up startup from ~15s to ~2s (#280)

Three high-impact changes eliminate most startup latency:

1. Enable wasmtime persistent compilation cache — call
   cache_config_load_default() so compiled native code is serialized to
   disk (~/.cache/wasmtime). Subsequent startups deserialize instead of
   recompiling, dropping the WASM phase from ~13s to <1s.

2. Cache compiled Component in PreparedModule — store the compiled
   wasmtime::component::Component directly instead of raw bytes.
   Eliminates ~2.6s recompilation on every first tool/channel execution.

3. Move blocking housekeeping to background tasks — embedding backfill
   (~1.3s of failing HTTP calls) and stale job cleanup are fire-and-forget
   work that no longer blocks the critical startup path.

Also: deduplicate Workspace creation in main.rs (two identical instances
reduced to one), and replace leftover println! in session validation with
tracing calls.

Co-authored-by: Claude Opus 4.6 <[email protected]>
This commit is contained in:
Illia Polosukhin
2026-02-21 02:30:57 +00:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 2cdd1acb1e
commit 98ee648fcb
8 changed files with 130 additions and 81 deletions
+31 -29
View File
@@ -487,9 +487,13 @@ async fn main() -> anyhow::Result<()> {
session.attach_store(Arc::clone(db), "default").await;
// Mark any jobs left in "running" or "creating" state as "interrupted".
if let Err(e) = db.cleanup_stale_sandbox_jobs().await {
tracing::warn!("Failed to cleanup stale sandbox jobs: {}", e);
}
// Fire-and-forget housekeeping — no need to block startup.
let db_cleanup = Arc::clone(db);
tokio::spawn(async move {
if let Err(e) = db_cleanup.cleanup_stale_sandbox_jobs().await {
tracing::warn!("Failed to cleanup stale sandbox jobs: {}", e);
}
});
}
// Create secrets store early: needed for injecting LLM API keys from encrypted
@@ -800,14 +804,20 @@ async fn main() -> anyhow::Result<()> {
);
}
// Register memory tools if database is available
if let Some(ref db) = db {
let mut workspace = Workspace::new_with_db("default", Arc::clone(db));
// Create workspace once, reused for memory tools and agent
let workspace: Option<Arc<Workspace>> = if let Some(ref db) = db {
let mut ws = Workspace::new_with_db("default", Arc::clone(db));
if let Some(ref emb) = embeddings {
workspace = workspace.with_embeddings(emb.clone());
ws = ws.with_embeddings(emb.clone());
}
let workspace = Arc::new(workspace);
tools.register_memory_tools(workspace);
Some(Arc::new(ws))
} else {
None
};
// Register memory tools if workspace is available
if let Some(ref ws) = workspace {
tools.register_memory_tools(Arc::clone(ws));
}
// Register builder tool if enabled.
@@ -1322,17 +1332,6 @@ async fn main() -> anyhow::Result<()> {
None
};
// Create workspace for agent (shared with memory tools)
let workspace = if let Some(ref db_ref) = db {
let mut ws = Workspace::new_with_db("default", Arc::clone(db_ref));
if let Some(ref emb) = embeddings {
ws = ws.with_embeddings(emb.clone());
}
Some(Arc::new(ws))
} else {
None
};
// Seed workspace with core identity files on first boot
if let Some(ref ws) = workspace {
match ws.seed_if_empty().await {
@@ -1343,17 +1342,20 @@ async fn main() -> anyhow::Result<()> {
}
}
// Backfill embeddings if we just enabled the provider
// Backfill embeddings in background (fire-and-forget housekeeping)
if let (Some(ws), Some(_)) = (&workspace, &embeddings) {
match ws.backfill_embeddings().await {
Ok(count) if count > 0 => {
tracing::info!("Backfilled embeddings for {} chunks", count);
let ws_bg = Arc::clone(ws);
tokio::spawn(async move {
match ws_bg.backfill_embeddings().await {
Ok(count) if count > 0 => {
tracing::info!("Backfilled embeddings for {} chunks", count);
}
Ok(_) => {}
Err(e) => {
tracing::warn!("Failed to backfill embeddings: {}", e);
}
}
Ok(_) => {}
Err(e) => {
tracing::warn!("Failed to backfill embeddings: {}", e);
}
}
});
}
// Create context manager (shared between job tools and agent)