From a8ad5d0a2e2f8f99a1aaeb3a0edfc09bd4ba7cfc Mon Sep 17 00:00:00 2001 From: DocTator Date: Thu, 21 May 2026 05:20:49 -0400 Subject: [PATCH] feat(sharing): make share-all / batch-share caps configurable The 5000 (share-all, share-all-to-user) and 500 (batch-share, org/publish) per-call caps were hardcoded hard rejects. Both paths already process via bounded-concurrency streams (buffer_unordered(10)), and share-all loads all candidates via list_all_active() regardless of the cap -- so the caps are arbitrary count limits, not load protection. Make them configurable: OMEM_SHARE_ALL_MAX (default 5000) and OMEM_BATCH_SHARE_MAX (default 500); 0 = unlimited. Defaults equal the previous hardcoded values, so there is no default behavior change -- operators can raise or disable the caps for larger spaces. Docs: .env.example + docs/SHARING.md updated. Co-Authored-By: Claude Opus 4.7 (1M context) --- .env.example | 6 +++++ docs/SHARING.md | 12 ++++----- omem-server/src/api/handlers/sharing.rs | 36 ++++++++++++++----------- omem-server/src/config.rs | 17 ++++++++++++ 4 files changed, 49 insertions(+), 22 deletions(-) diff --git a/.env.example b/.env.example index 2d8ccd6..7745a59 100644 --- a/.env.example +++ b/.env.example @@ -72,3 +72,9 @@ OMEM_EMBED_PROVIDER=noop # OMEM_LLM_PROVIDER=bedrock # OMEM_LLM_MODEL=anthropic.claude-3-haiku-20240307-v1:0 # AWS_REGION=us-east-1 + +# ─── Sharing limits ───────────────────────────────────────────────────────── +# Max memory IDs per batch-share / org-publish call (0 = unlimited). +OMEM_BATCH_SHARE_MAX=500 +# Max memories processed per share-all / share-all-to-user call (0 = unlimited). +OMEM_SHARE_ALL_MAX=5000 diff --git a/docs/SHARING.md b/docs/SHARING.md index b9c76a9..f3f05e6 100644 --- a/docs/SHARING.md +++ b/docs/SHARING.md @@ -237,7 +237,7 @@ A user pulls a memory from a shared space into their personal space. ### Batch Share -Share multiple memories at once. Runs up to 10 shares concurrently via `buffer_unordered(10)`. Hard limit: 500 memories per call. +Share multiple memories at once. Runs up to 10 shares concurrently via `buffer_unordered(10)`. Limit: `OMEM_BATCH_SHARE_MAX` per call (default 500, `0` = unlimited). ``` POST /v1/memories/batch-share @@ -428,7 +428,7 @@ This is opt-in because it requires extra I/O (reading source memories from other | POST | `/v1/memories/{id}/pull` | Pull memory to personal Space | | POST | `/v1/memories/{id}/unshare` | Remove shared copy from Space | | POST | `/v1/memories/{id}/reshare` | Refresh stale shared copy | -| POST | `/v1/memories/batch-share` | Share multiple memories (max 500) | +| POST | `/v1/memories/batch-share` | Share multiple memories (max `OMEM_BATCH_SHARE_MAX`, default 500) | | POST | `/v1/memories/share-all` | Share all matching memories | ### Convenience APIs @@ -820,13 +820,13 @@ The `require_approval` field exists on auto-share rules but has no effect. Rules LanceDB doesn't support cross-database vector queries. Each space has its own vector index. Cross-space search works by running independent searches per space and merging results. This means the same query might return slightly different results depending on each space's index state. -### Share-all hard limit +### Share-all limit (configurable) -`POST /v1/memories/share-all` processes at most 5000 memories per call. For larger spaces, multiple calls are needed. +`POST /v1/memories/share-all` and `share-all-to-user` process up to `OMEM_SHARE_ALL_MAX` memories per call (default 5000; set `0` to disable). Concurrency is bounded by `buffer_unordered(10)` regardless of the limit, so this is a count cap, not load protection — raise or disable it via the env var for larger spaces. -### Batch share hard limit +### Batch share limit (configurable) -`POST /v1/memories/batch-share` accepts at most 500 memory IDs per call. Requests exceeding this limit return 400 Bad Request. +`POST /v1/memories/batch-share` and `org/publish` accept up to `OMEM_BATCH_SHARE_MAX` memory IDs per call (default 500; set `0` to disable). Requests exceeding a non-zero limit return 400 Bad Request. ### No rate limiting on sharing diff --git a/omem-server/src/api/handlers/sharing.rs b/omem-server/src/api/handlers/sharing.rs index 11fea67..cb97dc0 100644 --- a/omem-server/src/api/handlers/sharing.rs +++ b/omem-server/src/api/handlers/sharing.rs @@ -484,10 +484,11 @@ pub async fn batch_share( "memory_ids cannot be empty".to_string(), )); } - if body.memory_ids.len() > 500 { - return Err(OmemError::Validation( - "batch_share limited to 500 memories".to_string(), - )); + if state.config.batch_share_max > 0 && body.memory_ids.len() > state.config.batch_share_max { + return Err(OmemError::Validation(format!( + "batch_share limited to {} memories per call (set OMEM_BATCH_SHARE_MAX=0 to disable)", + state.config.batch_share_max + ))); } if body.target_space.is_empty() { return Err(OmemError::Validation( @@ -909,10 +910,11 @@ pub async fn share_all( .collect(); let total = filtered_ids.len(); - if total > 5000 { - return Err(OmemError::Validation( - "share-all limited to 5000 memories. Apply stricter filters.".to_string(), - )); + if state.config.share_all_max > 0 && total > state.config.share_all_max { + return Err(OmemError::Validation(format!( + "share-all limited to {} memories per call (set OMEM_SHARE_ALL_MAX=0 to disable). Apply stricter filters.", + state.config.share_all_max + ))); } let target_store = state.store_manager.get_store(&target_space.id).await?; @@ -1075,10 +1077,11 @@ pub async fn share_all_to_user( .collect(); let total = filtered_ids.len(); - if total > 5000 { - return Err(OmemError::Validation( - "share-all-to-user limited to 5000 memories. Apply stricter filters.".to_string(), - )); + if state.config.share_all_max > 0 && total > state.config.share_all_max { + return Err(OmemError::Validation(format!( + "share-all-to-user limited to {} memories per call (set OMEM_SHARE_ALL_MAX=0 to disable). Apply stricter filters.", + state.config.share_all_max + ))); } let target_store = state.store_manager.get_store(&space_id).await?; @@ -1244,10 +1247,11 @@ pub async fn org_publish( let mut failed = 0; if let Some(memory_ids) = &body.memory_ids { - if memory_ids.len() > 500 { - return Err(OmemError::Validation( - "org/publish limited to 500 memories per call".to_string(), - )); + if state.config.batch_share_max > 0 && memory_ids.len() > state.config.batch_share_max { + return Err(OmemError::Validation(format!( + "org/publish limited to {} memories per call (set OMEM_BATCH_SHARE_MAX=0 to disable)", + state.config.batch_share_max + ))); } let source_store = state diff --git a/omem-server/src/config.rs b/omem-server/src/config.rs index fbcde0f..704b225 100644 --- a/omem-server/src/config.rs +++ b/omem-server/src/config.rs @@ -16,6 +16,10 @@ pub struct OmemConfig { pub embed_model: String, pub embed_dim: usize, pub embed_timeout_secs: u64, + /// Max memory IDs accepted per batch-share / org-publish call. 0 = unlimited. + pub batch_share_max: usize, + /// Max memories processed per share-all / share-all-to-user call. 0 = unlimited. + pub share_all_max: usize, } impl Default for OmemConfig { @@ -35,6 +39,8 @@ impl Default for OmemConfig { embed_model: String::new(), embed_dim: 1024, embed_timeout_secs: 10, + batch_share_max: 500, + share_all_max: 5000, } } } @@ -66,6 +72,14 @@ impl OmemConfig { .ok() .and_then(|v| v.parse().ok()) .unwrap_or(defaults.embed_timeout_secs), + batch_share_max: env::var("OMEM_BATCH_SHARE_MAX") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(defaults.batch_share_max), + share_all_max: env::var("OMEM_SHARE_ALL_MAX") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(defaults.share_all_max), } } @@ -91,5 +105,8 @@ mod tests { assert_eq!(config.embed_provider, "noop"); assert_eq!(config.llm_model, "gpt-4o-mini"); assert_eq!(config.log_level, "info"); + // Sharing caps default to the historical hardcoded values (0 = unlimited). + assert_eq!(config.batch_share_max, 500); + assert_eq!(config.share_all_max, 5000); } }