Skip to content
brightjadePublic

About

Source code for "Membrane: A Self-Evolving Contrastive Safety Memory for LLM Agent Defense", EMNLP 2026

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Membrane

Membrane β€” a self-evolving safety guard

An external guard that learns from adversarial attacks at test time, protecting an unguarded target model from jailbreaks through its self-evolving CSM (Contrastive Safety Memory) store.

πŸ“„ Paper: arxiv.org/abs/2606.05743

Membrane system overview: the inference phase (retrieval critic β†’ guard β†’ SAFE/UNSAFE), paired self-evolution (evolution signal β†’ memory planner β†’ writer), and the contrastive safety memory cell structure

  • Model safety on HarmBench and agent safety on AgentHarm from a single CLI β€” two methods, vanilla (no guard) and membrane (the CSM guard).
  • Paired Self-Evolution (PSE) β€” the CSM evolves by pairing each harmful prompt with its safe counterpart and distilling the contrast into one memory cell. A recall-preserving retrieval critic β€” off during training, on at eval β€” keeps APPLIES/MAYBE candidates.
  • Agent-safety extras (AgentHarm only): a safe-side veto and a semantic action context derived from the agent's tool list (always on for agent safety; inert for model safety).
  • Test-time adaptation (--tta) β€” keep evolving the CSM during eval using the label-free DecisionReviewer, with no test-time labels.

Compute prerequisite. The backend is a self-hosted vLLM server (Qwen/Qwen3-8B), so you need a CUDA-capable GPU host. The CLI client runs anywhere (CPU only) and points at the server via VLLM_SERVER_URL.


1. How it runs: client + model servers

Membrane separates the client (mbr ..., CPU-only) from the model server(s) (vLLM, CUDA GPU on Linux). The client reaches the servers over HTTP via the URLs in .env, so you can drive the CLI from a laptop while a remote GPU box serves the models.

Server Role Required? Launch script Example port
Chat vLLM (Qwen/Qwen3-8B) guard Β· target Β· eval judge always scripts/setup/start_vllm.sh 8077
Embedding model (embeddinggemma-300m) CSM retrieval vectors always scripts/setup/start_vllm_pooling.sh 8078
Decision Reviewer (e.g. gemma-3-27b-it) label-free judge for --tta only for TTA scripts/setup/start_vllm.sh 8079

One GPU can host the chat server plus the low-memory embedding server together; multiple GPUs split with -g. For a quick CPU-only smoke test, set EMBEDDING_BACKEND=local and skip the embedding server.


2. Install

This project uses uv for environments and dependencies (Python β‰₯ 3.13, fetched automatically).

# Install uv (macOS / Linux)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows (PowerShell):  irm https://astral.sh/uv/install.ps1 | iex

Then sync the dependencies for what you'll run:

# (A) Client only β€” drive the CLI, models served on a remote GPU host
uv sync --extra agentharm

# (B) GPU host β€” also serve vLLM locally; add tests
uv sync --extra serve --extra agentharm --extra test
Extra Adds When you need it
(base) CLI Β· guard Β· memory always
serve vllm to start a vLLM server on this machine
agentharm inspect-ai, … to run mbr agent (AgentHarm) experiments
test pytest to run uv run pytest tests/
source .venv/bin/activate   # once per shell; to skip it, prefix commands with `uv run`

Activating puts the mbr CLI on your PATH. Every mbr ... command below assumes it β€” without activation, prefix them with uv run (uv run mbr model eval ...).


3. Configure (.env)

cp .env.example .env

The keys a first-time runner must check (this is the paper setup):

# Chat vLLM address β€” must match the port you start the server on (Β§4.1)
VLLM_SERVER_URL=http://localhost:8077/v1

# Roles: vLLM for target/guard; the eval judge is Gemini (a hosted model).
# Set EVAL_MODEL=vllm:Qwen/Qwen3-8B to stay fully local instead.
TARGET_MODEL=vllm:Qwen/Qwen3-8B
GUARD_MODEL=vllm:Qwen/Qwen3-8B
EVAL_MODEL=gemini-3-flash-preview

# Gateway for the hosted judge above (OpenAI-compatible; required for EVAL_MODEL=gemini-*)
OPENAI_API_KEY=your_api_key_here
OPENAI_BASE_URL=https://your-gateway.example.com/v1

# Embedding β€” served on its own vLLM pooling server (Β§4.2)
EMBEDDING_MODEL=google/embeddinggemma-300m
EMBEDDING_BACKEND=vllm
EMBEDDING_VLLM_URL=http://localhost:8078/v1

# TTA only: the label-free reviewer model + its server (Β§4.3)
DECISION_REVIEWER_MODEL=vllm:google/gemma-3-27b-it
DECISION_REVIEWER_VLLM_URL=http://localhost:8079/v1

OPENAI_API_KEY / OPENAI_BASE_URL are only consulted for hosted models β€” leave them unset if every role is vLLM-served. Each *_VLLM_URL lets a component sit on its own port and falls back to VLLM_SERVER_URL if left empty. Full reference: docs/configuration.md.


4. Start the vLLM server(s)

Run these on a GPU Linux host with --extra serve installed. Skip this section if you point VLLM_SERVER_URL at a server someone else is running.

4.1 Chat server (required) β€” Qwen3-8B

One server handles all three chat roles (guard Β· target Β· eval judge).

./scripts/setup/start_vllm.sh                          # Qwen3-8B on :8077
./scripts/setup/start_vllm.sh -g 0,1 -t 2              # multi-GPU, tensor parallel

The launch port must match VLLM_SERVER_URL. Sampling is applied server-side by this script (Qwen3 model-card presets); the client never passes sampling params.

4.2 Embedding server (required)

The embedder runs on a vLLM pooling server so parallel workers share one GPU-resident copy:

./scripts/setup/start_vllm_pooling.sh google/embeddinggemma-300m -p 8078

It only needs ~20% of a GPU, so it can co-locate with the chat server (start_vllm.sh --gpu-mem 0.7). For a quick CPU-only smoke you can instead set EMBEDDING_BACKEND=local and skip this server.

4.3 Decision Reviewer server (required for TTA only β€” Β§6.4)

TTA judges without labels using a separate reviewer model β€” kept off the guard's model to avoid correlated blind spots.

./scripts/setup/start_vllm.sh google/gemma-3-27b-it -p 8079

Setting the reviewer to the guard model (vllm:Qwen/Qwen3-8B) on the chat server is allowed (then leave DECISION_REVIEWER_VLLM_URL empty). There is no silent fallback: if the configured reviewer isn't served, a --tta run aborts.


5. Build the dataset splits

The tracked data/<bench>/{warmup,eval}_*.jsonl splits are bundled, but regenerate them once to be safe (idempotent):

uv run python scripts/data/harmbench_splits.py     # HarmBench (model safety)
uv run python scripts/data/agentharm_splits.py     # AgentHarm (agent safety)

Each split interleaves harmful/benign on disk (h0, b0, h1, b1, …), so --sample-size N selects N per label (N harmful + N benign) from the prefix. Layout and rationale: docs/splits.md.


6. Reproduce the results

Each experiment is shown as (1) a quick single run to confirm the pipeline works, then (2) the full sweep that produces the paper-scale numbers. Every sweep accepts --dry-run (or DRY_RUN=1) to print the planned commands without executing.

  • train evolves memory only (no metrics); eval runs inference on a frozen store and reports metrics.
  • membrane = guard + CSM; vanilla = the no-defense reference.
  • Each eval prints a headline table and writes …/<dataset>/summary.json (aggregate metrics + run metadata) plus per-sample sample_*.json. Results land under outputs/{model_safety|agent_safety}/{namespace}/... β€” main for headline sweeps, tta/reviewer for --tta runs.

6.1 Model safety β€” HarmBench

(1) Quick single run

mbr model train --method membrane --memory-dir shared/memory --sample-size 7
mbr model eval  --method membrane --memory-dir shared/memory --sample-size 10

--method membrane eval reads the store built by train via --memory-dir. The no-defense reference is --method vanilla (no --memory-dir).

(2) Full sweep β€” membrane + vanilla Γ— 7 attacks Γ— warmup sizes {7, 14, 28, 80}:

./scripts/sweeps/harmbench.sh --port 8077

# Subset: only membrane, specific attacks/sizes
TARGETS=membrane METHODS="original pap" SIZES="28 80" ./scripts/sweeps/harmbench.sh

6.2 Agent safety β€” AgentHarm

Unlike HarmBench, the tool-list-driven safe-side veto and semantic action context turn on automatically. Needs --extra agentharm.

(1) Quick single run (--sample-size is per label)

mbr agent train --method membrane --memory-dir shared/memory --sample-size 8
mbr agent eval  --method membrane --memory-dir shared/memory --sample-size 10

(2) Full sweep

./scripts/sweeps/agentharm.sh --port 8077

# Subset
TARGETS=membrane SIZES="8 16 24 32" ./scripts/sweeps/agentharm.sh

AgentHarm selects the jailbreak variant with --attack-method (not --dataset): default original, plus autodan-turbo tap pair pap renellm flipattack. Eval concurrency is set by VLLM_MAX_CONNECTIONS (default 32).

6.3 Warmup-size learning curve

The learning curve is the SIZES axis of the sweeps above β€” one trained store + eval per warmup size (the defaults do 7 14 28 80 for HarmBench, 8 16 24 32 for AgentHarm). Plot defense / over-refusal vs warmup size from each size's summary.json.

6.4 Test-time adaptation (TTA)

Keep adapting the CSM on the eval stream itself, with no test-time labels β€” the label-free DecisionReviewer supplies the verdict. Prereqs: chat + embedding servers and the Decision Reviewer server (Β§4.3).

(1) Single run β€” add --tta to a membrane eval (auto-routes to the tta/reviewer namespace):

mbr model eval --method membrane --tta --memory-dir shared/memory
mbr agent eval --method membrane --tta --memory-dir shared/memory

(2) Sweep β€” add the reviewer leg to the headline sweep with TTA=1:

TTA=1 ./scripts/sweeps/harmbench.sh --port 8077
TTA=1 ./scripts/sweeps/agentharm.sh --port 8077

The frozen baseline stays in main/; the adaptive leg lands in outputs/<regime>/tta/reviewer/..., so the two sit side-by-side. TTA eval is sequential (memory writes are racy). Design details: docs/memory-system.md.


How it works

A user query first reaches the guard, which retrieves matching cells from the CSM store, optionally filters them through the retrieval critic, and emits SAFE or UNSAFE. SAFE queries fall through to the vanilla target model; UNSAFE queries are refused with a generated explanation. During training the loop continues β€” this is Paired Self-Evolution: each harmful prompt is paired with its safe counterpart, a judge labels each verdict (JAILBROKEN / OVER_REFUSAL / CORRECT), a memory planner picks an action (CREATE / UPDATE / DELETE / SKIP), and the writer distills the pair into one memory cell. Eval reuses the same pipeline with read-only memory (or keeps evolving under --tta).

Pipeline diagrams and the memory design rationale live in docs/architecture.md and docs/memory-system.md.

Repository layout

src/                  CLI, config, schema, LLM client, top-level utilities
  frameworks/         inference (guard) Β· evolution (learning) Β· evaluation (judge)
  memory/             ChromaDB-backed safety memory store
  benchmarks/         AgentHarm benchmark adapter
  evaluation/         orchestrator, data loader, metrics, results I/O
prompts/              YAML / Jinja2 templates for every LLM role
policies/             Safety taxonomies and attack-mechanism definitions
data/                 Jailbreak datasets and generated splits
scripts/{setup,data,sweeps}/   vLLM launchers Β· split generation Β· sweep runners
tests/                pytest suite
docs/                 Long-form architecture, memory, configuration, splits

Documentation

File Contents
docs/architecture.md Full pipeline diagrams for vanilla and membrane, training and eval.
docs/memory-system.md CSM design β€” memory cells, retrieval, retrieval critic, evolution, TTA.
docs/configuration.md All .env and CLI knobs with defaults and rationale.
docs/splits.md Dataset partitioning rationale and reproducibility notes.

Citation

If you use Membrane, please cite the paper:

@article{choi2026membrane,
  title={Membrane: A Self-Evolving Contrastive Safety Memory for LLM Agent Defense},
  author={Choi, Minseok and Yang, Seungbin and Kim, Dongjin and Kim, Subin and Son, Jungmin and Lee, Yunseung and Choo, Jaegul and Kwak, Youngjun},
  journal={arXiv preprint arXiv:2606.05743},
  year={2026}
}

About

Source code for "Membrane: A Self-Evolving Contrastive Safety Memory for LLM Agent Defense", EMNLP 2026

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages