Skip to main content
doc0blade47/mem0documentation home
Sign in

Plugin Conformance Testing

Last updated Oct 11, 2026100% grounded
Relevant source files

The following files were used as context for generating this wiki page:

Overview

Plugin conformance testing provides a unified verification framework to validate coding-agent memory plugins, ensuring bundle integrity, contract adherence, and cross-platform runtime stability across Python and TypeScript implementations. The suite systematically exercises both the shared core logic and individual plugin wrappers to guarantee that interactions with host environments remain predictable and consistent.

Sources: integrations/agent-plugin-core/conformance/run.py:1-30, integrations/agent-plugin-core/conformance/artifacts.py:1-24

The framework coordinates automated test suites, command execution checks, and artifact inspections for hosts such as Claude Code Plugin, Cursor, Codex, Kimi, and Antigravity, alongside portable plugins and platform integrations. It prevents subtle behavioral divergence across the diverse host environments by establishing strict runtime and build constraints that each implementation must satisfy.

Sources: integrations/agent-plugin-core/conformance/run.py:19-29, integrations/agent-plugin-core/conformance/artifacts.py:15-23

By centralizing test orchestration, artifact validation, hook normalization contracts, and harness segregation checks into a single runner architecture, the test suite catches regression issues and verifies distribution readiness prior to deployment. This guarantees that built plugins remain self-contained, free of monorepo path leaks, and fully capable of operating within host-specific sandbox limitations.

Sources: integrations/agent-plugin-core/conformance/run.py:38-133, integrations/agent-plugin-core/conformance/artifacts.py:29-44

Conformance Suite Test Runner Architecture

Suite Orchestration and Python Hosts

The conformance runner uses Python path resolution via CORE_ROOT and REPOSITORY_ROOT to locate plugin directories, importing redact from memory_core, build from build.build, and artifact verifiers from conformance.artifacts. Python host integrations cover five coding-agent environments: claude-code, cursor, codex, kimi, and antigravity.

CORE_ROOT = Path(__file__).resolve().parents[1]
REPOSITORY_ROOT = CORE_ROOT.parents[1]
PYTHON_HOSTS = ("claude-code", "cursor", "codex", "kimi", "antigravity")
GROUPS = (
    "python-bundles",
    "python-tests",
    "typescript-core",
    "openclaw",
    "opencode",
    "pi-agent",
    "deepseek",
)
LIVE_GROUP = "live-platform"
 
sys.path.insert(0, str(CORE_ROOT))
sys.path.insert(0, str(CORE_ROOT / "python"))
from memory_core import redact  # noqa: E402
from build.build import build  # noqa: E402
from conformance.artifacts import TYPESCRIPT_ARTIFACTS, verify_artifact as _typescript_artifact_check  # noqa: E402

Sources: integrations/agent-plugin-core/conformance/run.py:17-36

Group execution encompasses seven static validation groups alongside a live platform integration target.

Group NameTarget Environment / ToolingAssociated Commands / ArtifactsSources
python-bundlesPython hosts & portable bundleNative host builds & mem0-agent-plugin portable buildintegrations/agent-plugin-core/conformance/run.py:19-21, integrations/agent-plugin-core/conformance/run.py:101-110
python-testsShared Python test suitepytest integrations/agent-plugin-core/tests (plus host tests, ignoring conformance and integration tests)integrations/agent-plugin-core/conformance/run.py:22, integrations/agent-plugin-core/conformance/run.py:50-65
typescript-coreTypeScript Agent Corepnpm test, pnpm typecheckintegrations/agent-plugin-core/conformance/run.py:23, integrations/agent-plugin-core/conformance/run.py:66
openclawOpenClaw integrationpnpm test, pnpm exec tsc --noEmit, pnpm build + required artifactsintegrations/agent-plugin-core/conformance/run.py:24, integrations/agent-plugin-core/conformance/run.py:67-71
opencodeOpenCode pluginbun test, bun run type-check, bun run build + required artifactsintegrations/agent-plugin-core/conformance/run.py:25, integrations/agent-plugin-core/conformance/run.py:72-76
pi-agentPi-Agent pluginpnpm test, pnpm typecheck, pnpm build + required artifactsintegrations/agent-plugin-core/conformance/run.py:26, integrations/agent-plugin-core/conformance/run.py:77-81
deepseekDeepSeek pluginpnpm test, pnpm typecheck, pnpm build + required artifactsintegrations/agent-plugin-core/conformance/run.py:27, integrations/agent-plugin-core/conformance/run.py:82-86
live-platformClaude Code integration testspytest integrations/claude-code-plugin/tests/integration -qintegrations/agent-plugin-core/conformance/run.py:29, integrations/agent-plugin-core/conformance/run.py:87-95

Sources: integrations/agent-plugin-core/conformance/run.py:19-96

Check Planning and Execution

The check planning phase dynamically aggregates tasks based on selected groups. For python-bundles, it schedules native and portable bundle targets across all defined python hosts. For runtime command groups, it maps each command entry to a numbered check identifier and appends artifact verification checks if the group exists in TYPESCRIPT_ARTIFACTS.

def _planned_checks(groups: set[str]) -> list[dict[str, Any]]:
    checks: list[dict[str, Any]] = []
    if "python-bundles" in groups:
        checks.append(
            {
                "name": "python-bundles",
                "group": "python-bundles",
                "status": "planned",
                "hosts": list(PYTHON_HOSTS),
                "kinds": {"native": list(PYTHON_HOSTS), "portable": ["mem0-agent-plugin"]},
            }
        )
    for group, commands in _runtime_commands().items():
        if group not in groups:
            continue
        for index, command in enumerate(commands, start=1):
            checks.append(
                {
                    "name": f"{group}-{index}",
                    "group": group,
                    "status": "planned",
                    "command": command,
                }
            )
        if group in TYPESCRIPT_ARTIFACTS:
            _, required = TYPESCRIPT_ARTIFACTS[group]
            checks.append(
                {
                    "name": f"{group}-artifact",
                    "group": group,
                    "status": "planned",
                    "required": list(required),
                }
            )
    return checks

Sources: integrations/agent-plugin-core/conformance/run.py:99-133

Command execution is handled by _command_check, which records monotonic duration, merges environment overrides, and redacts sensitive data from both the command arguments and captured standard output and error streams. Output capture truncates the response text to the final 8,000 characters when populated.

def _command_check(
    name: str,
    group: str,
    command: list[str],
    *,
    cwd: Path,
    environment_overrides: dict[str, str] | None = None,
) -> dict[str, Any]:
    started = time.monotonic()
    environment = dict(os.environ)
    environment.update(environment_overrides or {})
    safe_command = [redact(part) for part in command]
    try:
        result = subprocess.run(
            command,
            cwd=cwd,
            env=environment,
            text=True,
            capture_output=True,
            check=False,
        )
        output = redact((result.stdout + result.stderr).strip())
        return {
            "name": name,
            "group": group,
            "status": "passed" if result.returncode == 0 else "failed",
            "duration_seconds": round(time.monotonic() - started, 3),
            "command": safe_command,
            "exit_code": result.returncode,
            **({"output": output[-8_000:]} if output else {}),
        }
    except OSError as exc:
        return {
            "name": name,
            "group": group,
            "status": "failed",
            "duration_seconds": round(time.monotonic() - started, 3),
            "command": safe_command,
            "exit_code": None,
            "output": redact(str(exc)),
        }

Sources: integrations/agent-plugin-core/conformance/run.py:136-177

Note

Command checks execute with check=False to capture non-zero exit codes as failed status outcomes without throwing exceptions prematurely during test suite execution.

Sources: integrations/agent-plugin-core/conformance/run.py:149-166

Artifact Validation and Build Inspection

TypeScript Artifact Definitions

The conformance framework verifies that built TypeScript packages contain all required distribution files and do not leak internal monorepo dependencies. The TYPESCRIPT_ARTIFACTS mapping defines the root paths and mandatory build outputs for each integration package.

Plugin GroupPackage PathRequired Artifacts
openclawintegrations/openclawdist/index.js, dist/index.d.ts
opencodeintegrations/opencode-plugindist/index.js, index.d.ts
pi-agentintegrations/pi-agent-plugindist/index.js, dist/index.d.ts, dist/entry.js, dist/entry.d.ts
deepseekintegrations/deepseek-plugindist/index.js, dist/index.d.ts
TYPESCRIPT_ARTIFACTS = {
    "openclaw": (INTEGRATIONS_ROOT / "openclaw", ("dist/index.js", "dist/index.d.ts")),
    "opencode": (INTEGRATIONS_ROOT / "opencode-plugin", ("dist/index.js", "index.d.ts")),
    "pi-agent": (
        INTEGRATIONS_ROOT / "pi-agent-plugin",
        ("dist/index.js", "dist/index.d.ts", "dist/entry.js", "dist/entry.d.ts"),
    ),
    "deepseek": (INTEGRATIONS_ROOT / "deepseek-plugin", ("dist/index.js", "dist/index.d.ts")),
}

Sources: integrations/agent-plugin-core/conformance/artifacts.py:15-23

Verification Logic and Monorepo Leak Detection

The verify_artifact function checks for the physical presence of every required file within the package directory. It then recursively scans compiled JavaScript and declaration files (*.js, *.mjs, *.cjs, *.d.ts) inside the dist directory using a regular expression pattern that detects unbundled monorepo imports referencing agent-plugin-core.

MONOREPO_IMPORT = re.compile(
    r"(?:from\s+|import\s*\(|require\s*\()\s*['\"][^'\"]*agent-plugin-core"
)
 
 
def verify_artifact(group: str, package: Path, required: tuple[str, ...]) -> dict[str, Any]:
    started = time.monotonic()
    errors = [f"missing package artifact: {name}" for name in required if not (package / name).is_file()]
    dist = package / "dist"
    for pattern in ("*.js", "*.mjs", "*.cjs", "*.d.ts"):
        for artifact in dist.rglob(pattern) if dist.is_dir() else ():
            if MONOREPO_IMPORT.search(artifact.read_text(encoding="utf-8")):
                errors.append(f"monorepo source import in package artifact: {artifact.relative_to(package)}")
    return {
        "name": f"{group}-artifact",
        "group": group,
        "status": "failed" if errors else "passed",
        "duration_seconds": round(time.monotonic() - started, 3),
        **({"output": "\n".join(errors)} if errors else {}),
    }

Sources: integrations/agent-plugin-core/conformance/artifacts.py:24-43

Warning

Any matching agent-plugin-core import found inside built distribution files causes the artifact verification check to fail, ensuring that distributed packages remain entirely self-contained.

Sources: integrations/agent-plugin-core/conformance/artifacts.py:33-36

Python Hook Runner Contract Conformance

Hook Input Handling and Session Orchestration

The shared Python hook runner implementations across agent variants orchestrate event hooks via standard input parsing and background worker handoffs (see Python Agent Core). The read_hook_input function parses JSON payloads from standard input, returning an empty dictionary upon encountering json.JSONDecodeError or OSError.

def read_hook_input() -> dict:
    try:
        value = json.load(sys.stdin)
        return value if isinstance(value, dict) else {}
    except (json.JSONDecodeError, OSError):
        return {}

Sources: integrations/claude-code-plugin/core/hook_runner.py:44-50

ConstantValuePurpose
STALE_RUNNING_SECONDS300Threshold in seconds before a running handoff file is considered stale and reset.
PENDING_EXPIRY_SECONDS604800 (7 days)Maximum age for pending handoff files before unlinking.
PENDING_LAUNCH_LIMIT5Maximum number of pending handoffs launched per recovery cycle.
DEFAULT_IDLE_FLUSH_SECONDS300Default idle duration triggering flush execution.
STALE_RUNNING_SECONDS = 300
PENDING_EXPIRY_SECONDS = 7 * 24 * 60 * 60
PENDING_LAUNCH_LIMIT = 5
DEFAULT_IDLE_FLUSH_SECONDS = 300

Sources: integrations/claude-code-plugin/core/hook_runner.py:36-40

Note

Automatic flushing relies on MEM0_CODE_AUTO_FLUSH environment variable settings, matching values "1", "true", "yes", or "on" case-insensitively.

Sources: integrations/claude-code-plugin/core/hook_runner.py:193-196

First Prompt Memory Injection and Handoff Lifecycle

When evaluating initial user prompts, first_prompt_memory_output retrieves query parameters from environment variables and checks length bounds before querying vector stores.

def first_prompt_memory_output(store: EvidenceStore, hook_input: dict) -> dict:
    """Search once before the agent handles the first prompt in a session."""
    repo, session_id, prompt, is_first_prompt = record_user_prompt(store, hook_input)
    if not is_first_prompt:
        return {}
    try:
        minimum_query_chars = int(os.environ.get("MEM0_CODE_MIN_QUERY_CHARS", "20"))
    except ValueError:
        minimum_query_chars = 20
    if len(prompt.strip()) < max(minimum_query_chars, 1):
        return {}
    result = search_memories(
        store, repo, session_id, bounded(prompt, 6000),
        top_k=5, operation="first-prompt-search", timeout=2,
    )

Sources: integrations/claude-code-plugin/core/hook_runner.py:62-76

Sources: integrations/claude-code-plugin/core/hook_runner.py:64-94, integrations/claude-code-plugin/core/hook_runner.py:170-191

Warning

Handoff files are written atomically using a .tmp suffix before replacing the target .json file to prevent partial read states during concurrent worker launches.

Sources: integrations/claude-code-plugin/core/hook_runner.py:180-190

Plugin Adapter Hook Normalization Testing

Native Event Mapping and Payload Translation

Plugin adapters translate proprietary agent harness event streams into standardized arguments for the shared Mem0 runtime. Each adapter processes raw JSON inputs from standard input and maps harness-specific keys to common payload structures recognized by hook_runner and memory_core.

Adapter PluginNative Event / TriggerMapped Runtime ActionTarget Source / File
cursor-pluginsessionStartsession-startintegrations/cursor-plugin/hooks/adapter.py:26
cursor-pluginbeforeSubmitPromptuser-promptintegrations/cursor-plugin/hooks/adapter.py:27
cursor-pluginpostToolUsepost-toolintegrations/cursor-plugin/hooks/adapter.py:28
cursor-pluginpostToolUseFailurepost-tool-failureintegrations/cursor-plugin/hooks/adapter.py:29
cursor-pluginafterAgentResponseassistant-stopintegrations/cursor-plugin/hooks/adapter.py:30
cursor-pluginstopstopintegrations/cursor-plugin/hooks/adapter.py:31
cursor-pluginsessionEndflush --reason session-endintegrations/cursor-plugin/hooks/adapter.py:32
cursor-pluginpreCompactflush --reason pre-compactintegrations/cursor-plugin/hooks/adapter.py:33
kimi-pluginSessionStartsession-startintegrations/kimi-plugin/hooks/adapter.py:23
kimi-pluginUserPromptSubmituser-promptintegrations/kimi-plugin/hooks/adapter.py:24
kimi-pluginPostToolUsepost-toolintegrations/kimi-plugin/hooks/adapter.py:25
kimi-pluginPostToolUseFailurepost-tool-failureintegrations/kimi-plugin/hooks/adapter.py:26
kimi-pluginStopstopintegrations/kimi-plugin/hooks/adapter.py:27
kimi-pluginPreCompactflush --reason pre-compactintegrations/kimi-plugin/hooks/adapter.py:28
kimi-pluginSessionEndflush --reason session-endintegrations/kimi-plugin/hooks/adapter.py:29

Sources: integrations/cursor-plugin/hooks/adapter.py:25-34, integrations/kimi-plugin/hooks/adapter.py:22-30

Note

The antigravity-plugin adapter configures execution through hooks.json mapping PreInvocation, PostToolUse, and Stop commands directly to adapter.py entry points with strict timeout limits.

Sources: integrations/antigravity-plugin/hooks.json:3-16

Transcript Stream Parsing and Session Reconstruction

Adapters for harnesses like Antigravity and Kimi Code inspect local transcript file paths to extract structured conversation turns when direct message fields are missing from hook payloads. The Antigravity adapter reads newline-delimited JSON step objects, filtering for items where status == "DONE" and extracting redacted user requests enclosed within <USER_REQUEST> tags or planner response models.

def _read_transcript(path: str) -> list[dict[str, str]]:
    messages = []
    try:
        with open(path, encoding="utf-8") as transcript:
            for line in transcript:
                try:
                    step = json.loads(line)
                except json.JSONDecodeError:
                    continue
                content = step.get("content")
                if not isinstance(content, str) or step.get("status") != "DONE":
                    continue
                if step.get("type") == "USER_INPUT":
                    match = re.search(r"<USER_REQUEST>\s*(.*?)\s*</USER_REQUEST>", content, re.DOTALL)
                    messages.append({"role": "user", "content": redact(match.group(1) if match else content).strip()})
                elif step.get("source") == "MODEL" and step.get("type") == "PLANNER_RESPONSE":
                    messages.append({"role": "assistant", "content": redact(content).strip()})
    except (OSError, TypeError):
        pass
    return messages

Sources: integrations/antigravity-plugin/hooks/adapter.py:28-47

Similarly, the Kimi Code adapter resolves session directories through session_index.jsonl lookups or globbing under sessions/*/ to locate wire transcripts (wire.jsonl), aggregating text parts across loop events while skipping error or interrupted finish reasons.

def _session_dir(session_id: str) -> Path | None:
    home = Path(os.environ.get("KIMI_CODE_HOME", Path.home() / ".kimi-code")).expanduser()
    found = None
    try:
        with (home / "session_index.jsonl").open(encoding="utf-8") as index:
            for line in index:
                try:
                    entry = json.loads(line)
                except json.JSONDecodeError:
                    continue
                if entry.get("sessionId") == session_id and isinstance(entry.get("sessionDir"), str):
                    found = Path(entry["sessionDir"])
    except OSError:
        pass
    if found:
        return found
    if not session_id or Path(session_id).name != session_id:
        return None
    return next((path for path in (home / "sessions").glob(f"*/{session_id}") if path.is_dir()), None)
 
 
def _last_assistant_message(transcript: Path) -> str:
    message = ""
    step_id = None
    parts = []
    try:
        lines = transcript.read_text(encoding="utf-8").splitlines()
    except OSError:
        return ""
    for line in lines:
        try:
            record = json.loads(line)
        except json.JSONDecodeError:
            continue
        if record.get("type") != "context.append_loop_event" or not isinstance(record.get("event"), dict):
            continue
        event = record["event"]
        if event.get("type") == "step.begin":
            step_id = event.get("uuid")
            parts = []
        elif event.get("type") == "content.part" and event.get("stepUuid") == step_id:
            part = event.get("part")
            if isinstance(part, dict) and part.get("type") == "text" and isinstance(part.get("text"), str):
                parts.append(part["text"])
        elif event.get("type") == "step.end" and event.get("uuid") == step_id:
            if event.get("finishReason") not in {"error", "interrupted"} and parts:
                message = "".join(parts)
            step_id = None
            parts = []
    return message

Sources: integrations/kimi-plugin/hooks/adapter.py:33-82

Warning

Transcript parsing fails silently on OSError, TypeError, or json.JSONDecodeError, returning empty message lists or falling back to default runner implementations to prevent hook crashes.

Sources: integrations/antigravity-plugin/hooks/adapter.py:35-46, integrations/kimi-plugin/hooks/adapter.py:38-61

Harness Identity and Multi-Agent Environments

Harness Identity Configuration and Source Tagging

The plugin core initializes runtime behavior using harness-specific identity parameters. Through configure_harness, plugins define their unique execution context including name, env_prefix, data_dir_name, and source_tag. When left unconfigured, defaults fallback to generic with standard MEM0_PLUGIN environment variables.

Harness IdentifierPlatform SourcePlatform ApplicationSource Reference
claude-codeMEM0_PLUGINclaude-codeintegrations/claude-code-plugin/core/_harness_id.py:3-11
codexMEM0_PLUGINcodexintegrations/codex-plugin/core/_harness_id.py:3-11
coding-agentMEM0_PLUGINNoneintegrations/mem0-agent-plugin/core/_harness_id.py:3-11
antigravityMEM0_PLUGINantigravityintegrations/antigravity-plugin/core/_harness_id.py:3-11

Sources: integrations/claude-code-plugin/core/_harness_id.py:3-11, integrations/codex-plugin/core/_harness_id.py:3-11, integrations/mem0-agent-plugin/core/_harness_id.py:3-11, integrations/antigravity-plugin/core/_harness_id.py:3-11

Note

The platform-side vocabulary separates the overarching source family (MEM0_PLUGIN) from the specific editor application header (X-Application). If the application field is empty, memory_core omits the application header entirely rather than transmitting a placeholder string.

Sources: integrations/claude-code-plugin/core/_harness_id.py:6-11

Multi-Agent Environment Scoping and CLI Integration

Multi-agent runtimes configure telemetry initialization (see Telemetry and Metrics) and CLI parameter parsing dynamically based on command-line arguments. When executing diagnostics or control commands via memory_cli.py, operators can supply explicit --harness flags to override default source tags and bind the telemetry collector to the target environment.

def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--plugin-data-dir", default="")
    parser.add_argument("--harness", default="")
    subparsers = parser.add_subparsers(dest="command", required=True)
 
    status = subparsers.add_parser("status")
    status.add_argument("--json", action="store_true")
 
    doctor_parser = subparsers.add_parser("doctor")
    doctor_parser.add_argument("--json", action="store_true")
 
    subparsers.add_parser("pause")
    subparsers.add_parser("resume")
 
    forget = subparsers.add_parser("forget")
    forget.add_argument("--remote", action="store_true")
    forget.add_argument("--yes", action="store_true")
    forget.add_argument("--include-project-memory", action="store_true")
 
    args = parser.parse_args()
    if args.harness:
        source_tag = f"{args.harness.replace('-', '_')}_plugin"
        configure_harness(args.harness, source_tag=source_tag)
        telemetry.init(harness=args.harness, source_tag=source_tag.upper())
    if args.plugin_data_dir:

Sources: integrations/claude-code-plugin/core/memory_cli.py:61-86, integrations/mem0-agent-plugin/core/memory_cli.py:61-86

Warning

Failing to pass matching harness identifiers during concurrent multi-agent executions risks cross-contamination of local evidence stores when multiple plugins share an unsegmented root directory. Always set explicit MEM0_CODE_DATA_DIR overrides or plugin data path flags.

Sources: integrations/claude-code-plugin/core/memory_cli.py:86-88, integrations/mem0-agent-plugin/core/memory_cli.py:86-88

Was this page helpful?