Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions crates/mcp-brain-server/src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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)
Expand Down
38 changes: 38 additions & 0 deletions docs/adr/ADR-124-dynamic-partition-cache.md
Original file line number Diff line number Diff line change
@@ -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<RwLock<Option<PartitionResult>>>` 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 |
4 changes: 4 additions & 0 deletions examples/dragnes/.svelte-kit/ambient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion examples/dragnes/.svelte-kit/generated/server/internal.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const options = {
app: ({ head, body, assets, nonce, env }) => "<!DOCTYPE html>\n<html lang=\"en\" class=\"dark\">\n<head>\n\t<meta charset=\"utf-8\" />\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n\t<link rel=\"manifest\" href=\"/manifest.json\" />\n\t<link rel=\"icon\" href=\"/dragnes-icon-192.svg\" />\n\t<title>DrAgnes -- Dermatology Intelligence</title>\n\t" + head + "\n</head>\n<body data-sveltekit-preload-data=\"hover\">\n\t<div style=\"display: contents\">" + body + "</div>\n</body>\n</html>\n",
error: ({ status, message }) => "<!doctype html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<title>" + message + "</title>\n\n\t\t<style>\n\t\t\tbody {\n\t\t\t\t--bg: white;\n\t\t\t\t--fg: #222;\n\t\t\t\t--divider: #ccc;\n\t\t\t\tbackground: var(--bg);\n\t\t\t\tcolor: var(--fg);\n\t\t\t\tfont-family:\n\t\t\t\t\tsystem-ui,\n\t\t\t\t\t-apple-system,\n\t\t\t\t\tBlinkMacSystemFont,\n\t\t\t\t\t'Segoe UI',\n\t\t\t\t\tRoboto,\n\t\t\t\t\tOxygen,\n\t\t\t\t\tUbuntu,\n\t\t\t\t\tCantarell,\n\t\t\t\t\t'Open Sans',\n\t\t\t\t\t'Helvetica Neue',\n\t\t\t\t\tsans-serif;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\theight: 100vh;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t.error {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tmax-width: 32rem;\n\t\t\t\tmargin: 0 1rem;\n\t\t\t}\n\n\t\t\t.status {\n\t\t\t\tfont-weight: 200;\n\t\t\t\tfont-size: 3rem;\n\t\t\t\tline-height: 1;\n\t\t\t\tposition: relative;\n\t\t\t\ttop: -0.05rem;\n\t\t\t}\n\n\t\t\t.message {\n\t\t\t\tborder-left: 1px solid var(--divider);\n\t\t\t\tpadding: 0 0 0 1rem;\n\t\t\t\tmargin: 0 0 0 1rem;\n\t\t\t\tmin-height: 2.5rem;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t}\n\n\t\t\t.message h1 {\n\t\t\t\tfont-weight: 400;\n\t\t\t\tfont-size: 1em;\n\t\t\t\tmargin: 0;\n\t\t\t}\n\n\t\t\t@media (prefers-color-scheme: dark) {\n\t\t\t\tbody {\n\t\t\t\t\t--bg: #222;\n\t\t\t\t\t--fg: #ddd;\n\t\t\t\t\t--divider: #666;\n\t\t\t\t}\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<div class=\"error\">\n\t\t\t<span class=\"status\">" + status + "</span>\n\t\t\t<div class=\"message\">\n\t\t\t\t<h1>" + message + "</h1>\n\t\t\t</div>\n\t\t</div>\n\t</body>\n</html>\n"
},
version_hash: "3oxd9x"
version_hash: "a9s2ra"
};

export async function get_hooks() {
Expand Down
Loading