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
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
12 changes: 6 additions & 6 deletions docs/SHARING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
36 changes: 20 additions & 16 deletions omem-server/src/api/handlers/sharing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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?;
Expand Down Expand Up @@ -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?;
Expand Down Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions omem-server/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
}
}
}
Expand Down Expand Up @@ -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),
}
}

Expand All @@ -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);
}
}
Loading