376 lines
16 KiB
Markdown
376 lines
16 KiB
Markdown
# Pipeline mechanics
|
|
|
|
Full step-by-step for `mem0-integrate`. Read this when you are executing a
|
|
step. The one-line-per-step overview and every non-negotiable rule live in
|
|
`../SKILL.md`, which is loaded on every run; this file is loaded on demand.
|
|
|
|
Verbatim subagent system prompts for steps 8 and 10 are in
|
|
[`subagent-prompts.md`](subagent-prompts.md).
|
|
|
|
## 1. Language detection
|
|
|
|
| Signal | Track |
|
|
|---|---|
|
|
| `package.json` + TypeScript config | Node / TypeScript |
|
|
| `package.json` (no TS config) | Node / JavaScript |
|
|
| `pyproject.toml` or `requirements.txt` | Python |
|
|
|
|
Monorepo with both, ask which subdirectory to operate in, then recurse.
|
|
|
|
## 2. Repo comprehension: what does this repo do, and where is the backend?
|
|
|
|
Before any decision (product, goal, plan), understand the repo enough to
|
|
locate *where in the backend* the integration belongs. This is not
|
|
fit-surveying, the user already decided Mem0 fits. This is mechanics: you
|
|
cannot write a plan without knowing what files matter.
|
|
|
|
Read, in order, with a token budget. Do not scan the whole tree.
|
|
|
|
1. `README.md` (root) plus the first page of any `README_*.md` variants.
|
|
2. `CONTRIBUTING.md` / `AGENTS.md` / `CLAUDE.md` at root if present. These
|
|
often spell out architecture and entry points.
|
|
3. `package.json` / `pyproject.toml` scripts and entry points.
|
|
4. The layout of the top two directory levels, not recursive.
|
|
5. Key config files: `docker-compose.yml`, `Dockerfile`, `Makefile`,
|
|
`langgraph.json`, `next.config.*`, `nuxt.config.*`.
|
|
|
|
Produce `.mem0-integration/repo-summary.md`:
|
|
|
|
# Repo comprehension
|
|
|
|
**What this repo does:** <one paragraph in plain English. Who is
|
|
the end user? What does the app do for them? What LLM / agent
|
|
behavior is central? Do not list dependencies, describe behavior.>
|
|
|
|
**Architecture at a glance:**
|
|
- Backend: <path(s), framework, primary entry point>
|
|
- Frontend: <path(s) if any, framework, for context only; no
|
|
integration here>
|
|
- Agent loop / orchestration: <LangGraph? custom? none?>
|
|
- Existing memory/session/state systems: <name them, these are
|
|
what step 6 Coexistence must preserve>
|
|
|
|
**Candidate backend integration surfaces** (ranked, best first):
|
|
1. `<backend-file>:<line_range>` <function> <one-sentence
|
|
reason this is where write/read could slot in without
|
|
replacing anything existing>
|
|
2. ...
|
|
3. ...
|
|
|
|
**Not a fit here:** <list anything the skill considered but ruled
|
|
out, e.g. "frontend chat component: client-side, excluded by
|
|
backend-only rule"; "existing memory subsystem X: would require
|
|
replacement, excluded by additive principle">
|
|
|
|
**Sources read:** <list the files actually opened, with line counts,
|
|
so reviewers can verify coverage.>
|
|
|
|
Show the user the rendered summary and ask: *"Is this understanding correct?
|
|
Which of the candidate surfaces (1, 2, 3 ...) should step 3 forward target?"*
|
|
|
|
Gate rules:
|
|
|
|
- No backend surface found, exit code 1. Preconditions should already have
|
|
caught frontend-only repos; reaching this point means a subtler miss (for
|
|
example the "backend" is actually just a static build). Do not force a fit.
|
|
- Every candidate surface would require replacing an existing memory or
|
|
session system, exit code 1 with the additive-principle rationale. The user
|
|
can point at a non-conflicting location manually and re-run.
|
|
- User corrections update `repo-summary.md` and re-confirm. Max 3 rounds,
|
|
beyond that exit code 1.
|
|
|
|
The user's chosen surface index is baked into `product.json` as
|
|
`preferred_site` and referenced by steps 5 and 6.
|
|
|
|
## 3. Product selection: Platform vs OSS
|
|
|
|
Read the `## Identify the User's Setup` block in
|
|
`https://docs.mem0.ai/llms.txt` for the Platform-first routing rules, then
|
|
apply the heuristics below. Ask, but never blank.
|
|
|
|
- Other managed-service SDKs present (`@clerk/*`, `stripe`, `@supabase/*`,
|
|
`openai`, `@upstash/*`, `posthog-*`), 3 or more, recommend **Platform**.
|
|
- Local-infra signals (`docker-compose.yml` with postgres / redis / qdrant /
|
|
neo4j, ollama configs, self-hosted auth), 2 or more, recommend **OSS**.
|
|
- No strong signal, default recommendation **Platform**: lower integration
|
|
cost, and migration later is supported.
|
|
|
|
Example:
|
|
|
|
> I see `stripe`, `@clerk/nextjs`, and `@supabase/supabase-js`, managed
|
|
> services throughout. I recommend **Mem0 Platform** (4-line integration).
|
|
> Override and use open source?
|
|
|
|
Bake the choice into the goal doc in step 5. Do not re-decide later.
|
|
|
|
## 4. API key check (env first, then ask)
|
|
|
|
| Track | Key | Where to find |
|
|
|---|---|---|
|
|
| Platform | `MEM0_API_KEY` | https://app.mem0.ai |
|
|
| OSS (default LLM) | `OPENAI_API_KEY` | https://platform.openai.com/api-keys |
|
|
|
|
Present in env, continue.
|
|
|
|
`MEM0_API_KEY` missing and the track is **Platform**, **default to Agent
|
|
Mode**: run `mem0 init --agent --agent-caller <your-name> --json` (after
|
|
`pip install mem0-cli` or `npm install -g @mem0/cli`), substituting your agent
|
|
identity such as `claude-code`, `cursor`, `codex`. If you forgot
|
|
`--agent-caller`, run `mem0 identify <your-name>` after init. Cache the key to
|
|
`.env` with user consent and continue. Tell the user to claim it later with
|
|
`mem0 init --email <their-email>`: same key, no agent disruption.
|
|
|
|
Missing and **CI mode** (`MEM0_INTEGRATE_CI=1`), exit code 2 with the name of
|
|
the missing key.
|
|
|
|
Never echo key values into `trace.jsonl`. Persist to `.env` only with explicit
|
|
user consent, and append `.env` to `.gitignore` if it is not there already.
|
|
|
|
If the user is on OSS and wants a non-OpenAI LLM, route them to the
|
|
`components/llms/*` docs and re-run this step with the chosen provider's key.
|
|
|
|
## 5. Goal doc, the hard gate
|
|
|
|
Write `.mem0-integration/goal.md` and **require user approval before step 6**.
|
|
|
|
# Mem0 Integration Goal
|
|
|
|
**What gets stored:** <one sentence. User utterances? Extracted
|
|
preferences? A specific domain fact like "dietary restrictions"?>
|
|
|
|
**When it gets retrieved:** <one sentence. On each user turn? Before a
|
|
specific tool call? At session start?>
|
|
|
|
**Why:** <one sentence, the user-visible behavior change. "Assistant
|
|
remembers previous orders across sessions," not "we added memory.">
|
|
|
|
**Product:** Platform | OSS (locked from step 3, do not change)
|
|
|
|
**Delegated skill:** <raw URL of the published skill being used
|
|
from the delegation table in SKILL.md, or "none, custom integration
|
|
against `skills/mem0`">.
|
|
|
|
**Out of scope:** <anything explicitly excluded: "no graph memory,"
|
|
"no multimodal," "no migration from existing store">
|
|
|
|
Rules:
|
|
|
|
- The user must approve explicitly. If they edit the doc, reload and
|
|
re-confirm.
|
|
- `goal.md` is the contract the test suite is written against. Never rewrite
|
|
it after step 6 starts.
|
|
- Max 3 rejection rounds. On the 4th, exit code 3 with the rejection notes:
|
|
the integration is not well-specified enough to proceed.
|
|
|
|
## 6. Integration plan, where and how (hard gate)
|
|
|
|
`goal.md` is what and why. This step produces where and how, and gets explicit
|
|
sign-off before any code is written.
|
|
|
|
Do a **scoped** read of the repo, no wide survey:
|
|
|
|
- Grep for the LLM call sites that match the goal (`openai.chat.`,
|
|
`anthropic.messages.`, `model.generateContent`, `ChatOpenAI`, `createLLM`).
|
|
- Grep for the user-identity source (`req.user`, `session.user`, `auth()`,
|
|
`ctx.userId`, cookies).
|
|
- Check `package.json` / `pyproject.toml` / `requirements.txt` for conflicts,
|
|
for example an existing `mem0ai` at a different version.
|
|
|
|
Then write `.mem0-integration/plan.md`:
|
|
|
|
# Mem0 Integration Plan
|
|
|
|
**Write pattern:** <one sentence, e.g. "After each assistant reply,
|
|
call client.add([user_msg, assistant_msg], user_id=<source>).">
|
|
|
|
**Read pattern:** <one sentence, e.g. "Before building the LLM prompt,
|
|
call client.search(query=latest_user_msg, user_id=<source>, limit=5)
|
|
and inject results as a system message.">
|
|
|
|
**User identifier source:** <code path, e.g. `req.auth.userId`,
|
|
`session.user.email`, `ctx.params.user_id`. If none, ask the user.>
|
|
|
|
**Session scoping:**
|
|
- user_id: <source>
|
|
- agent_id: <static slug | null>
|
|
- run_id: <source | null>
|
|
|
|
**Write call site:** `<file:line_range>` inside `<function>`
|
|
**Read call site:** `<file:line_range>` inside `<function>`
|
|
|
|
**Dependencies to add:**
|
|
- `<package>@<version pinned in frontmatter>`
|
|
|
|
**Preserved behavior:** <list the existing repo behaviors that must
|
|
keep working after this edit, e.g. "existing OpenAI streaming still
|
|
works," "existing Redis session store still used," "existing tests
|
|
still pass unchanged.">
|
|
|
|
**Coexistence:** <one bullet per existing system the integration sits
|
|
alongside. Name the files/classes. Example: "The existing
|
|
`agents/memory/storage.py` MemoryStorage class remains untouched and
|
|
keeps its LangGraph SummarizationEvent flow. Mem0 is added as a
|
|
parallel long-term-facts store, in a new file, invoked only when
|
|
MEM0_ENABLED=1 is set.">
|
|
|
|
**Feature flag:** <the exact mechanism and the default. Required.
|
|
Example: `env MEM0_ENABLED=1`, default unset / off; `config.mem0.enabled`,
|
|
default false. With the flag in its default state, the repo must
|
|
behave exactly like `main`.>
|
|
|
|
**Sources consulted:** <minimum 2 URLs from "Canonical sources" in
|
|
SKILL.md that informed this plan. At least one `docs.mem0.ai` URL and
|
|
one delegated-skill URL. Cite the specific section or heading.>
|
|
|
|
**E2E recipe:** <how the verification skill should drive the app
|
|
end-to-end. Omit only if the repo is a pure library with no runnable
|
|
entry point, in which case the E2E step skips with a warning.>
|
|
|
|
start: <shell command to launch the app locally,
|
|
using $PORT for any network port>
|
|
ready_probe: <one of: url=<URL> status=<code> /
|
|
log="<substring to wait for>" /
|
|
sleep=<seconds, last resort>>
|
|
compose_services: <optional: whitespace-separated service
|
|
names in docker-compose.yml to start first;
|
|
use label mem0-e2e: "true" to mark them>
|
|
write_call: <command that triggers the Mem0 write path
|
|
exactly once; 60s runtime or less>
|
|
write_async_wait_ms: <milliseconds to wait after write_call for
|
|
async memory flush; default 0>
|
|
read_call: <command that triggers the Mem0 read path,
|
|
typically a fresh session / new request>
|
|
read_assert: <substring, regex, or jsonpath=<expr>=<value>
|
|
that MUST appear in read_call's output for
|
|
the E2E to pass. Derived from goal.md's
|
|
"What gets stored.">
|
|
|
|
**Rejected alternatives:** <briefly, 1 or 2 bullets. Patterns the skill
|
|
considered but did not pick, and why. Helps the user decide.>
|
|
|
|
Rules:
|
|
|
|
- Show the user the proposed call sites with 10 lines of context around each
|
|
before asking for approval.
|
|
- If no plausible call site exists for either write or read, exit code 5 and
|
|
ask the user to name the files manually. That is the "no fit here" signal,
|
|
do not guess.
|
|
- Max 3 rejection rounds on the plan. On the 4th, exit code 5 with the last
|
|
plan and the user's notes.
|
|
- If the user edits `plan.md` by hand, reload and re-confirm.
|
|
|
|
`plan.md`, not `goal.md`, is the contract the subagent implements against in
|
|
step 8.
|
|
|
|
## 7. Tests first (TDD)
|
|
|
|
The main agent writes failing tests against `goal.md` in the repo's native
|
|
test framework:
|
|
|
|
| Track | Default framework |
|
|
|---|---|
|
|
| Python | `pytest` |
|
|
| TypeScript | `vitest` if detected, else `jest` |
|
|
| JavaScript | same |
|
|
|
|
Test assertion shapes must match the **canonical signatures**:
|
|
|
|
- Platform method signatures: `https://docs.mem0.ai/openapi.json`, the request
|
|
body schemas for `/v1/memories/` and `/v1/memories/search/`.
|
|
- OSS method signatures: the delegated skill named in `plan.md` (fetched from
|
|
its raw URL), or `skills/mem0/SKILL.md` as the default.
|
|
- Do not hand-roll request shapes. If the delegated skill has an example
|
|
block, lift it verbatim.
|
|
|
|
Minimum two test files, paths taken from `plan.md` call sites:
|
|
|
|
- `test_mem0_write.<ext>` asserts `add()` is called at the write call site
|
|
with the right payload shape (Platform messages-array vs OSS string) and the
|
|
right `user_id` source.
|
|
- `test_mem0_read.<ext>` asserts `search()` runs before the read call site and
|
|
the result is wired into the LLM prompt or response path.
|
|
|
|
Tests MUST be importable with `MEM0_API_KEY` unset. This is the design
|
|
pressure that forces step 8's lazy `MemoryClient()` / `Memory()` construction:
|
|
eager module-level init hits the API on import and breaks pre-existing test
|
|
collection when the key is missing.
|
|
|
|
Run the tests. They **must fail**. If they pass before any implementation, the
|
|
tests are wrong. Rewrite them.
|
|
|
|
## 8. Implementation (subagent, fresh context)
|
|
|
|
Spawn a subagent with:
|
|
|
|
- **Inputs**: the repo, `goal.md`, `plan.md`, the two test files, and direct
|
|
URLs to the delegated skill (from `plan.md`), the SDK source (pinned per
|
|
`mem0_tested_versions`), `https://docs.mem0.ai/llms.txt`, and
|
|
`https://docs.mem0.ai/openapi.json`.
|
|
- **No access** to the main agent's reasoning trace or scratchpad.
|
|
- **System prompt**: use the implementation prompt in
|
|
[`subagent-prompts.md`](subagent-prompts.md) verbatim.
|
|
|
|
The subagent returns a diff. The main agent reviews it against `plan.md` (the
|
|
mechanical contract) and `goal.md` (the intent):
|
|
|
|
- Approved, apply the diff and commit.
|
|
- Rejected, return with specific actionable feedback, not "try again."
|
|
- Max 3 review loops. Beyond that, exit code 4 with the last diff and the
|
|
reviewer feedback.
|
|
|
|
## 9. Commit and handoff
|
|
|
|
Create branch `mem0-integrate/<short-goal-slug>` and commit in **separate
|
|
commits** so reviewers can cherry-pick:
|
|
|
|
1. `mem0: add gated dependency`, just the `pyproject.toml` / `package.json`
|
|
change.
|
|
2. `mem0: add integration module`, the new files.
|
|
3. `mem0: wire into <call site>`, the call-site edits, still gated.
|
|
4. `mem0: add tests`, the new test files.
|
|
|
|
With `--no-heal`, print `Run /mem0-test-integration to verify.` and exit.
|
|
Otherwise proceed to step 10.
|
|
|
|
## 10. Self-healing loop (default ON, disable with `--no-heal`)
|
|
|
|
Run `/mem0-test-integration --ci` in a subprocess. If `scorecard.json` reports
|
|
`overall: pass`, done, exit 0.
|
|
|
|
Otherwise loop:
|
|
|
|
1. **Categorize the failing check** from `scorecard.json` and route:
|
|
- `install` / `static_checks`, dependency or import fix.
|
|
- `unit_tests`, wiring or assertion fix.
|
|
- `smoke_test`, API key or SDK call-shape fix.
|
|
- `e2e_test`, recipe, flag-wiring, or integration-point fix.
|
|
- **Pre-existing test failure** (test skill exit code 7,
|
|
`non_invasive: false` in the scorecard), **STOP**. This is a
|
|
non-invasiveness violation. Do NOT attempt to fix it, that breaks
|
|
principle 3. Exit code 6 with a rationale.
|
|
|
|
2. **Spawn a remediation subagent** with fresh context. Inputs: `plan.md`,
|
|
`goal.md`, `scorecard.md`, `scorecard.json`, the last committed diff, and
|
|
the relevant log for the failing category (`test-stdout.log` /
|
|
`smoke-stdout.log` / `e2e-app.log` / `e2e-calls.log`). Use the remediation
|
|
prompt in [`subagent-prompts.md`](subagent-prompts.md) verbatim.
|
|
|
|
3. **Apply the diff** and commit on the same branch as
|
|
`mem0-heal: <category> attempt <N>`. Do NOT amend earlier commits,
|
|
reviewers need the heal trail.
|
|
|
|
4. **Re-run `/mem0-test-integration --ci`**:
|
|
- `overall: pass`, done, exit 0.
|
|
- Same check still failing, increment the attempt counter and loop.
|
|
- A *different* check now failing, that is a regression. Revert the heal
|
|
commit (`git revert HEAD --no-edit`), record it in
|
|
`.mem0-integration/heal-trace.md`, exit code 6.
|
|
|
|
5. **Bounded iterations.** Default 3 attempts per failing category, override
|
|
with `--heal-max N` (hard cap 10). On exhaustion, exit code 6 with the full
|
|
attempt trace: each diff, each scorecard, final log tail.
|
|
|
|
6. **Post-loop summary** written to `.mem0-integration/heal-trace.md`: which
|
|
category failed, how many attempts, each diff's intent, final status, and
|
|
on success the delta from the initial scorecard to the final one.
|