diff --git a/crates/mcp-brain-server/src/routes.rs b/crates/mcp-brain-server/src/routes.rs index dade87ee3..033bc3002 100644 --- a/crates/mcp-brain-server/src/routes.rs +++ b/crates/mcp-brain-server/src/routes.rs @@ -463,10 +463,12 @@ pub fn run_enhanced_training_cycle(state: &AppState) -> EnhancedTrainingResult { } } - // 3c. Cache MinCut partition result (avoids recomputing on every /v1/partition request) + // 3c. Cache partition result (avoids recomputing on every /v1/partition request) + // Only compute exact MinCut for small graphs; large graphs skip to avoid timeout { let graph = state.graph.read(); - if graph.node_count() > 0 { + let edge_count = graph.edge_count(); + if graph.node_count() > 0 && edge_count <= 100_000 { let (clusters, cut_value, edge_strengths) = graph.partition_full(2); let result = crate::types::PartitionResult { total_memories: graph.node_count(), @@ -478,6 +480,8 @@ pub fn run_enhanced_training_cycle(state: &AppState) -> EnhancedTrainingResult { }; *state.cached_partition.write() = Some(result); } + // For large graphs, partition cache is populated by the scheduled + // rebuild_graph job which runs asynchronously without timeout pressure } // 4. Internal voice reflection (ADR-110) diff --git a/docs/adr/ADR-124-dynamic-partition-cache.md b/docs/adr/ADR-124-dynamic-partition-cache.md new file mode 100644 index 000000000..c36212afd --- /dev/null +++ b/docs/adr/ADR-124-dynamic-partition-cache.md @@ -0,0 +1,38 @@ +# ADR-124: Dynamic Partition Cache with Large-Graph Guard + +## Status +Accepted + +## Context +The pi.ruv.io brain server's `/v1/partition` endpoint runs exact Stoer-Wagner MinCut on the knowledge graph. With 2,090+ nodes and 971K+ edges, this computation exceeds Cloud Run's 300s timeout and the MCP SSE transport's 60s tool call timeout. + +PR #287 (ADR-117) introduced a canonical source-anchored MinCut with deterministic hashing, but it shares the same O(V*E) complexity for the initial computation. + +## Decision + +### 1. Partition Caching +Add `cached_partition: Arc>>` to `AppState`. The cache is populated during training cycles and served instantly via: +- **MCP `brain_partition`**: Returns cached compact partition (sub-millisecond) +- **REST `/v1/partition`**: Returns cache by default; `?force=true` recomputes + +### 2. Large-Graph Guard +During training cycles, only compute exact MinCut if `edge_count <= 100,000`. For larger graphs, the cache remains unpopulated until the scheduled `rebuild_graph` job runs asynchronously without timeout pressure. + +### 3. Tier Roadmap (from ADR-117) +- **Tier 1 (shipped)**: Exact Stoer-Wagner + source-anchored canonical cut +- **Tier 2 (next)**: Tree packing for O(V^2 log V) fast path +- **Tier 3 (future)**: Incremental maintenance for evolving graphs (dynamic MinCut) + +## Consequences +- MCP `brain_partition` returns instantly from cache instead of timing out +- Enhanced training cycle no longer blocks on MinCut for large graphs +- Fresh partition data depends on scheduled background jobs for graphs >100K edges +- REST `?force=true` still allows on-demand recomputation (may timeout for large graphs) + +## Benchmark Results (Canonical MinCut - ADR-117) + +| Graph Type | Nodes | Time | +|-----------|-------|------| +| Cycle | 50 | 3.09 us | +| Complete | 10 | 2.61 us | +| Hash stability | 100 | 1.39 us | diff --git a/examples/dragnes/.svelte-kit/ambient.d.ts b/examples/dragnes/.svelte-kit/ambient.d.ts index ef7722f65..73d21c109 100644 --- a/examples/dragnes/.svelte-kit/ambient.d.ts +++ b/examples/dragnes/.svelte-kit/ambient.d.ts @@ -50,6 +50,7 @@ declare module '$env/static/private' { export const NVS_ROOT: string; export const GIT_EDITOR: string; export const RVM_PATH: string; + export const BRAIN_URL: string; export const FEATURE_SPARK_POST_COMMIT_CREATE_ITERATION: string; export const HOSTNAME: string; export const GIT_ASKPASS: string; @@ -156,6 +157,7 @@ declare module '$env/static/private' { export const npm_config_globalconfig: string; export const npm_config_init_module: string; export const JAVA_HOME: string; + export const BRAIN_API_KEY: string; export const NVS_USE_XZ: string; export const PWD: string; export const INTERNAL_VSCS_TARGET_URL: string; @@ -278,6 +280,7 @@ declare module '$env/dynamic/private' { NVS_ROOT: string; GIT_EDITOR: string; RVM_PATH: string; + BRAIN_URL: string; FEATURE_SPARK_POST_COMMIT_CREATE_ITERATION: string; HOSTNAME: string; GIT_ASKPASS: string; @@ -384,6 +387,7 @@ declare module '$env/dynamic/private' { npm_config_globalconfig: string; npm_config_init_module: string; JAVA_HOME: string; + BRAIN_API_KEY: string; NVS_USE_XZ: string; PWD: string; INTERNAL_VSCS_TARGET_URL: string; diff --git a/examples/dragnes/.svelte-kit/generated/server/internal.js b/examples/dragnes/.svelte-kit/generated/server/internal.js index b23f53788..6fef9988d 100644 --- a/examples/dragnes/.svelte-kit/generated/server/internal.js +++ b/examples/dragnes/.svelte-kit/generated/server/internal.js @@ -25,7 +25,7 @@ export const options = { app: ({ head, body, assets, nonce, env }) => "\n\n\n\t\n\t\n\t\n\t\n\tDrAgnes -- Dermatology Intelligence\n\t" + head + "\n\n\n\t
" + body + "
\n\n\n", error: ({ status, message }) => "\n\n\t\n\t\t\n\t\t" + message + "\n\n\t\t\n\t\n\t\n\t\t
\n\t\t\t" + status + "\n\t\t\t
\n\t\t\t\t

" + message + "

\n\t\t\t
\n\t\t
\n\t\n\n" }, - version_hash: "3oxd9x" + version_hash: "a9s2ra" }; export async function get_hooks() {