refactor(mem0-agent): stop requiring org/project in client config

An API key is already bound to one (org_id, project_id) server-side, so storing a
copy locally was redundant state that can go stale. Verified live that add,
get_all and feedback all succeed with no ids in the body at all.

- both ids are now purely an OVERRIDE, for pointing one key at a different
  project in the same org; half an override is ignored rather than half-applied
- identity resolution no longer persists scope as a side effect
- the project-config endpoints (the only ones needing ids in the URL) resolve
  them from /v1/ping/ and cache the result
- corrects an over-generalized finding: feedback does not require the ids; it
  404'd earlier only because that memory lived outside the key's project
- onboarding now recommends a key issued for a dedicated project over an override

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Deshraj Yadav
2026-07-29 17:13:52 -07:00
co-authored by Claude Fable 5
parent dda5f21332
commit f4949c55af
6 changed files with 120 additions and 45 deletions
+16 -8
View File
@@ -6,19 +6,27 @@ each disagreement is marked and was reproduced.
## Four rules that apply to every call
### 1. Pin the project in the request BODY
### 1. Scope is the API key's job; body ids are only an override
An API key is bound to exactly one `(org_id, project_id)` pair server-side. Send nothing and
every call resolves correctly — verified live: `add`, `get_all` and `feedback` all return 200
with no ids anywhere. **Do not require them in client config**; that is redundant state that
can go stale.
They exist for one purpose: pointing a key at a *different* project in the same org. When you
do that, both ids must travel in the **body** — as query params they are silently ignored:
```python
body = {..., "project_id": "proj_...", "org_id": "org_..."} # routes correctly
body = {..., "project_id": "proj_...", "org_id": "org_..."} # overrides the key
params = {"project_id": "proj_..."} # SILENTLY IGNORED
```
The API key carries a default project. Passing `project_id` as a query param does not
override it — the call lands in the key's default project with no error. Two identical
probe writes, one routed each way, landed in two different projects.
Two probe writes, one routed each way, landed in two different projects. Half an override
(one id without the other) is treated as no override.
This is almost certainly how v1's benchmark corpora (39% of the production project) got
there. `Api._pin()` applies this automatically; `strict=True` raises if it cannot.
The only endpoints that genuinely need the ids are the project-configuration ones, which
carry both in the URL path. They resolve them from `GET /v1/ping/` and cache the result, so
nothing has to be configured locally.
### 2. Every read passes `latest_only=True`
@@ -107,7 +115,7 @@ Identity comes from `GET /v1/ping/` → `user_email`, `org_id`, `project_id`.
|---|---|
| `DELETE /v1/memories/` | Takes **query params**; a body returns 400 "at least one filter required" |
| `GET .../projects/<id>/` | `fields` must be **repeated** params (`?fields=a&fields=b`), not comma-joined |
| `POST /v1/feedback/` | **404s without the project pin** in the body |
| `POST /v1/feedback/` | 404s when the memory is in a project the key is not bound to and no override is sent; fine unpinned otherwise |
| `POST /v3/memories/` | This is `get_all`; `page`/`page_size` are query params, filters go in the body |
## Metadata schema