Skip to content

feat(data-warehouse): implement mem0 import source - #70716

Merged
talyn-app[bot] merged 3 commits into
masterfrom
posthog-code/mem0-warehouse-source
Jul 15, 2026
Merged

feat(data-warehouse): implement mem0 import source#70716
talyn-app[bot] merged 3 commits into
masterfrom
posthog-code/mem0-warehouse-source

Conversation

@Gilbert09

@Gilbert09 Gilbert09 commented Jul 14, 2026

Copy link
Copy Markdown
Member

Problem

Mem0 is an AI agent memory platform: it stores extracted facts per user/agent/app/run. Teams building AI products on it want that memory data queryable next to their product analytics. The mem0 source existed only as a scaffolded stub (unreleasedSource=True, no fields, no sync logic).

Why

Fill in the scaffolded Mem0 connector end-to-end so Mem0 memory data can be synced into the Data warehouse.

Changes

Implements the Mem0 source end-to-end following the source.py / settings.py / mem0.py architecture contract:

  • Tables: memories (POST /v3/memories/, incremental), entities (GET /v1/entities/, full refresh), and events (GET /v1/events/, full refresh, opt-in by default since it's a potentially high-volume audit stream).
  • Base class: ResumableSource — memories resume by page number (with the incremental cutoff pinned in the resume state so a resumed run paginates the same server-side result set), events resume by the envelope's next URL. State is saved after each yielded batch.
  • Incremental sync: only memories gets supports_incremental=True — the v3 filter DSL exposes genuine server-side created_at/updated_at comparison operators (gte). The filter body rides every page request, so pagination terminates at the watermark naturally. sort_mode="desc" because the endpoint has no sort parameter (undefined ordering), which defers the watermark commit to successful end of run.
  • Match-all filter: the memories list requires a filters body, and a bare {"user_id": "*"} would miss agent/app/run-scoped memories, so the request ORs the wildcard across all four entity ids.
  • Transport: plain requests through make_tracked_session(), tenacity retries on 429/5xx/transient network errors, get_non_retryable_errors for 401/403.
  • Extras: canonical_descriptions.py sourced from the official API docs, lists_tables_without_credentials = True (static catalog) so the public docs render the table list, official Mem0 SVG icon, SOURCES.md moved to the Implemented table.
  • Stays behind unreleasedSource=True with releaseStatus=ReleaseStatus.ALPHA for now.

Note

Mem0 offers memory:add/update/delete/categorize webhooks, but I deliberately did not wire a WebhookSource: deliveries carry only {event_details: {id, data.memory, event}} (no categories, metadata, timestamps, or entity ids) and Mem0 documents no signing secret. Since webhook mode replaces the poll once active, the memories table would permanently degrade to sparse text-only rows. The server-side incremental filter already makes scheduled pulls cheap; a code comment in source.py records this decision.

Verified against the live API where possible without credentials: /v1/ping/, /v3/memories/, /v1/entities/, and /v1/events/ all exist and return structured 401s for bad keys ( /v1/ping/ is the same probe the official mem0ai SDK uses for key validation). Filter/pagination semantics come from the current API reference; the incremental cutoff is conservatively sent as a date string (the only format the docs demonstrate), which can only over-fetch — never skip — rows, and the merge dedupes on id.

Docs: the companion posthog.com doc is written (attached in a PR comment below) but could not be pushed from this session — the credentials only cover this repo. It needs to land as contents/docs/cdp/sources/mem0.md in posthog.com. docsUrl matches the mem0 slug and audit_source_docs run against a local posthog.com checkout with the doc in place reports no mem0 mismatch.

How did you test this code?

  • pytest products/warehouse_sources/backend/temporal/data_imports/sources/mem0/tests/ — 41 tests pass. Transport tests catch: dropping agent/app/run-scoped memories from the filter, saving resume state before yield (skipped rows on crash), replaying one endpoint's cursor against another, recomputing the cutoff on resume (pagination misalignment), retrying auth errors / not retrying rate limits, and forwarding the incremental flag to full-refresh endpoints. Source tests cover schema catalog behavior, credential validation plumbing, resume manager binding, and the credential-free documented-tables path that feeds public docs.
  • pytest .../sources/tests/test_source_categories.py .../sources/tests/test_generated_configs.py — 1550 pass (registry/category/config-generation consistency).
  • ruff check / ruff format clean; hogli ci:preflight --fix reports 0 failures.
  • I could not run a live end-to-end sync: that needs a real Mem0 API key. The endpoint shapes are verified from the current API reference plus unauthenticated probes of the live API.

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

Docs update

Companion doc for posthog.com (contents/docs/cdp/sources/mem0.md) is written and attached in a PR comment below — it needs a separate PR in the posthog.com repo (this session's credentials are scoped to this repo only).

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Authored with PostHog Code (Claude). Skills invoked: /implementing-warehouse-sources, /documenting-warehouse-sources, /writing-tests. Key decisions: chose pull-only ResumableSource over the initially suggested ResumableSource+WebhookSource because Mem0's webhook payloads are too sparse to feed the merge path without degrading the table (details in the note above); modeled the operation-event stream as the pulled events table instead, which has real ids and timestamps. The generated Mem0SourceConfig came from generate:source-configs; an unrelated environment-dependent Stripe hunk the generator produced locally was excluded from the commit.


Created with PostHog Code

@Gilbert09 Gilbert09 self-assigned this Jul 14, 2026
@pr-assigner-resolver-posthog
pr-assigner-resolver-posthog Bot requested a review from a team July 14, 2026 13:06

Copy link
Copy Markdown
Member Author

Companion posthog.com doc — ready to land at contents/docs/cdp/sources/mem0.md (could not be pushed from this session; credentials are scoped to this repo):

contents/docs/cdp/sources/mem0.md
---
title: Linking Mem0 as a source
sidebar: Docs
showTitle: true
availability:
  free: full
  selfServe: full
  enterprise: full
sourceId: Mem0
---

import SourceSetupIntro from "../_snippets/source-setup-intro.mdx"
import SyncModes from "../_snippets/sync-modes.mdx"
import TroubleshootingLink from "../_snippets/dw-troubleshooting-link.mdx"
import AlphaRelease from "../_snippets/alpha-release.mdx"

<AlphaRelease />

The Mem0 connector syncs your AI agent memory data into the PostHog Data warehouse, so you can analyze what your agents remember about users alongside your product data.

## Prerequisites

You need a Mem0 platform account with an API key. Note that Mem0's free tier has monthly usage limits, and syncs count towards them.

## Adding a data source

<SourceSetupIntro />

When linking Mem0, you'll need:

- **API key** – find it in the [Mem0 dashboard](https://app.mem0.ai/dashboard/api-keys) under **API keys**.
- **Organization ID** and **Project ID** (optional) – scope the entities table to a specific Mem0 organization or project.

## Sync modes

<SyncModes />

The memories table supports incremental syncs on `updated_at` (recommended) or `created_at`. Entities and events don't expose a server-side timestamp filter, so they sync as full refreshes.

## Configuration

<SourceParameters />

## Supported tables

<SourceTables />

## Troubleshooting

- If you see an authentication error, your API key is invalid or has been revoked. Create a new key in the Mem0 dashboard, then reconnect.
- If syncs fail with rate-limit errors, you may have hit your Mem0 plan's monthly usage limits. Reduce sync frequency or upgrade your Mem0 plan.

<TroubleshootingLink />

@github-actions

Copy link
Copy Markdown
Contributor

Hey @Gilbert09! 👋

It looks like your git author email on this PR isn't your @posthog.com address (owerstom@gmail.com). Since you're on the PostHog team, it's worth pointing your local git author email at your @posthog.com address. Why it matters:

  • Consistent work identity in git history — internal tooling that attributes commits to team members keys off your @posthog.com address.
  • Keeps team contributions easy to tell apart from external community ones when scanning history.

You can fix it for this repo with:

git config user.email "you@posthog.com"

Or set it globally with git config --global user.email "you@posthog.com". No need to redo this PR — just a nudge for next time. 🙂

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

🦔 Hogbox preview · ✅ ready

▶ Open the preview

🔑 Login test@posthog.com / 12345678 (demo data)
🧩 Running this PR's backend and frontend, on the PostHog :master base
🔗 Link stable across rebuilds — a re-push swaps the box underneath, the URL stays
🔒 Access tailnet only (PostHog VPN)
🛠️ Admin inspect & debug state in hogland
💤 Idle sleeps after ~30 min idle (snapshot to S3, zero node cost) and wakes on your next visit in ~30s, behind a brief "waking up" screen

commit 26fe143 · box box-055e9d22313e · ready in 703s (push → usable) · build log · rebuilds on every push, torn down on close

Comment thread products/warehouse_sources/backend/temporal/data_imports/sources/mem0/mem0.py Outdated
@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

🤖 CI report

Bundle size — no change

Uncompressed size of every built .js bundle, compared against the base branch.

Total: 64.77 MiB · no change

No file changed by more than 1000 B.

Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report

Eager graph — within budget

How much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy import() / React.lazy chunks are not counted.

Root Eager (shipped) Δ vs base Budget
entry (logged-out pages, app bootstrap)
src/index.tsx
1.22 MiB · 22 files no change ███░░░░░░░ 28.4% of 4.29 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
8.13 MiB · 2,978 files no change █████████░ 87.9% of 9.25 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx

Largest files eagerly shipped from src/index.tsx
Size File
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
24.6 KiB ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js
6.3 KiB ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
4.5 KiB ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js
3.9 KiB ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js
1.4 KiB ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js
1.3 KiB src/RootErrorBoundary.tsx
912 B ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js
789 B src/scenes/ChunkLoadErrorBoundary.tsx
762 B src/index.tsx
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
281.3 KiB ../node_modules/.pnpm/posthog-js@1.402.3/node_modules/posthog-js/dist/rrweb.js
267.7 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
235.5 KiB src/taxonomy/core-filter-definitions-by-group.json
222.9 KiB ../node_modules/.pnpm/posthog-js@1.402.3/node_modules/posthog-js/dist/module.js
164.0 KiB src/queries/validators.js
154.3 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
105.8 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
92.7 KiB ../packages/quill/packages/quill/dist/index.js

Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479

Dist folder size — 🔺 +5.3 KiB (+0.0%)

Total size of the built frontend/dist folder (all assets), compared against the base branch.

Total: 1312.49 MiB · 🔺 +5.3 KiB (+0.0%)

Playwright — all passed

All tests passed.

View test results →

⚠️ Backend coverage — 99.0% of changed backend lines covered — 4 uncovered

🧪 Backend test coverage

Patch coverage — changed backend lines (products + core): ████████████████████ 99.0% (439 / 443)

File Patch Uncovered changed lines
products/warehouse_sources/backend/temporal/data_imports/sources/mem0/tests/test_mem0.py 98.2% 257, 272, 299
products/warehouse_sources/backend/temporal/data_imports/sources/mem0/mem0.py 99.1% 266

🤖 Agents: add a test covering the lines above, or note why under "How did you test this code?". Machine-readable gap list: the patch-coverage artifact on this run (gh run download 29457753842 -n patch-coverage), or the coverage-data block at the end of this comment.

Per-product line coverage (touched products)
Product Coverage Lines
demo ███████████░░░░░░░░░ 56.2% 1,497 / 2,663
tasks █████████████░░░░░░░ 67.4% 25,560 / 37,895
signals ████████████████░░░░ 79.1% 19,037 / 24,073
data_modeling ████████████████░░░░ 80.0% 4,834 / 6,045
cdp ████████████████░░░░ 80.7% 3,118 / 3,864
notebooks █████████████████░░░ 84.3% 6,343 / 7,520
agent_platform █████████████████░░░ 84.7% 3,273 / 3,862
cohorts █████████████████░░░ 86.1% 4,022 / 4,671
actions █████████████████░░░ 86.6% 717 / 828
product_tours █████████████████░░░ 87.5% 1,266 / 1,447
exports ██████████████████░░ 88.3% 6,891 / 7,800
conversations ██████████████████░░ 88.9% 16,129 / 18,133
dashboards ██████████████████░░ 89.1% 5,719 / 6,418
mcp_analytics ██████████████████░░ 89.1% 2,502 / 2,807
error_tracking ██████████████████░░ 89.6% 9,718 / 10,852
alerts ██████████████████░░ 89.9% 3,638 / 4,046
engineering_analytics ██████████████████░░ 90.1% 5,105 / 5,665
streamlit_apps ██████████████████░░ 90.4% 2,499 / 2,764
slack_app ██████████████████░░ 90.6% 9,511 / 10,503
marketing_analytics ██████████████████░░ 90.8% 11,514 / 12,684
product_analytics ██████████████████░░ 91.1% 5,599 / 6,143
data_warehouse ██████████████████░░ 92.1% 18,133 / 19,683
workflows ██████████████████░░ 92.4% 5,148 / 5,574
web_analytics ███████████████████░ 92.7% 13,624 / 14,691
ai_observability ███████████████████░ 92.8% 14,868 / 16,019
surveys ███████████████████░ 92.9% 5,687 / 6,120
posthog_ai ███████████████████░ 93.2% 1,322 / 1,418
approvals ███████████████████░ 93.3% 3,395 / 3,640
reminders ███████████████████░ 93.4% 468 / 501
early_access_features ███████████████████░ 93.8% 848 / 904
endpoints ███████████████████░ 94.1% 8,606 / 9,143
skills ███████████████████░ 94.4% 2,827 / 2,995
revenue_analytics ███████████████████░ 94.5% 3,598 / 3,809
review_hog ███████████████████░ 94.6% 6,532 / 6,905
logs ███████████████████░ 95.3% 9,528 / 9,994
experiments ███████████████████░ 95.6% 24,171 / 25,288
replay_vision ███████████████████░ 95.7% 13,354 / 13,952
annotations ███████████████████░ 96.2% 732 / 761
warehouse_sources ███████████████████░ 96.2% 230,687 / 239,721
feature_flags ███████████████████░ 96.3% 16,002 / 16,625
user_interviews ███████████████████░ 96.4% 2,242 / 2,325
data_catalog ███████████████████░ 97.1% 2,034 / 2,095
customer_analytics ███████████████████░ 97.2% 7,480 / 7,698

Report-only. Patch coverage = changed backend lines covered vs origin/master. Sorted lowest first.
Known gaps: lines covered only by Temporal tests show as uncovered; core line numbers may drift if master changed the same file.

Comment thread products/warehouse_sources/backend/temporal/data_imports/sources/mem0/mem0.py Outdated
@veria-ai

veria-ai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 1 · PR risk: 0/10

@trunk-io

trunk-io Bot commented Jul 14, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic BadgeStatic Badge

View Full Report ↗︎Docs

Copy link
Copy Markdown
Member Author

Addressed the security-bot finding in 345d2ba: event pagination and resume URLs are now validated against the exact https://api.mem0.ai origin before any credentialed request is made or resume state is saved, with parameterized tests covering absolute, scheme-relative, non-HTTPS, and look-alike-host next links plus a poisoned resume-state entry. The earlier "deploy preview" failure was an infra timeout while pulling the preview image (hog-exec dial timeout), unrelated to this change — this push will retrigger it.

Copy link
Copy Markdown
Member Author

CI status: every required check passes on 977fcbbb (including the warehouse-sources product tests and the full Django/Temporal shards). The only red check is the non-required "deploy preview", which has now failed three times on the same hogland infra timeout (box restore/docker pull dial and read timeouts) before any PR code runs — I retried it once and it hit the same timeout, so it looks like a preview-infra issue rather than anything in this diff. It retries automatically on the next push.

@danielcarletti danielcarletti left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Implements the Mem0 warehouse source as a ResumableSource pulling three
tables: memories (incremental via the v3 filter DSL's server-side
created_at/updated_at gte filters), entities, and events (full refresh —
no server-side timestamp filters). All HTTP goes through the tracked
session. Kept behind unreleasedSource with releaseStatus alpha.

Generated-By: PostHog Code
Task-Id: 728de8ca-e311-47fc-b79f-75c16df5840b
The events iterator followed the envelope's `next` URL (and any persisted
resume URL) with the credentialed session, so a tampered response or
poisoned resume state pointing off-origin would leak the API key. Both
paths now require the exact https://api.mem0.ai origin before any request
is made or state is saved. Flagged by security review bots on the PR.

Generated-By: PostHog Code
Task-Id: 728de8ca-e311-47fc-b79f-75c16df5840b
Remove unreleasedSource=True so the finished source is visible in the
connector catalog, and drop any test asserting the hidden state.
@Gilbert09
Gilbert09 force-pushed the posthog-code/mem0-warehouse-source branch from 977fcbb to 26fe143 Compare July 15, 2026 23:10
@talyn-app
talyn-app Bot merged commit cb91ba0 into master Jul 15, 2026
257 checks passed
@talyn-app
talyn-app Bot deleted the posthog-code/mem0-warehouse-source branch July 15, 2026 23:53
@deployment-status-posthog

deployment-status-posthog Bot commented Jul 16, 2026

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-07-16 00:21 UTC Run
prod-us ✅ Deployed 2026-07-16 00:40 UTC Run
prod-eu ✅ Deployed 2026-07-16 00:41 UTC Run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants