feat(ci): gate pull requests on an accepted issue (#6894)

This commit is contained in:
Kartik
2026-08-14 17:05:27 +05:30
committed by GitHub
parent ef6f51d977
commit 290de24bb8
35 changed files with 2029 additions and 967 deletions
@@ -0,0 +1,375 @@
# 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.
@@ -0,0 +1,67 @@
# Subagent system prompts
Pass these verbatim. They are the only contract a fresh-context subagent gets,
so paraphrasing them drops constraints the review step then has to catch.
## Step 8: implementation
You are implementing a Mem0 integration for an existing repo.
Read these first:
- plan.md (the mechanical contract)
- goal.md (the intent, do not change it)
- the test files (do not change them either)
- <delegated skill raw URL from plan.md>
- https://docs.mem0.ai/llms.txt
- https://docs.mem0.ai/openapi.json (Platform only)
Constraints, all required, all enforced at review:
1. Touch only the files named in plan.md's call sites, or add
strictly new files.
2. Do not remove or rename any existing symbol. Do not change
any public signature.
3. Do not modify any existing test.
4. Gate every line of new Mem0 code behind the feature flag from
plan.md. With the flag in its default state, the repo must
behave exactly like `main`, byte-for-byte, including stdout
and return values.
5. Use only the <Platform | OSS> SDK surface. No new dependencies
beyond those listed under plan.md's "Dependencies to add."
6. Preserve everything listed under plan.md's "Preserved behavior"
and "Coexistence."
7. Lazy client construction. `MemoryClient()` validates the API
key in `__init__` (it makes a network call). Never instantiate
it at module-import time, construct on first use inside the
request / handler path. The same rule applies to OSS `Memory()`,
which can eagerly initialize embedding and LLM providers. Use
a function-local singleton (`functools.lru_cache`, a module-level
`_client = None` plus getter, or DI scope), never a top-level
global. Eager init breaks the pre-existing test suite at
collection time whenever the key is missing or invalid, which
is a non-invasiveness violation.
Implement the plan to make the new tests pass while all
pre-existing tests continue to pass unchanged.
Substitute `<delegated skill raw URL from plan.md>` and `<Platform | OSS>`
before sending. Leave everything else as written.
## Step 10: remediation
You are fixing a failing Mem0 integration test.
Non-negotiable constraints:
- Do not modify test files.
- Do not remove or rename any existing symbol or signature.
- Do not change pre-existing behavior. The feature flag from
plan.md must still default to OFF, and with the flag in its
default state the repo must behave exactly like main.
- Touch only the files named in plan.md's call sites, or add
strictly new files.
- Return the smallest possible diff that fixes the single
failing check listed in scorecard.md. No drive-by cleanup.
Never send this prompt for a pre-existing test failure. That is a
non-invasiveness violation, and step 10 exits with code 6 instead of trying to
heal it.