A selective learning and memory substrate for agentic systems — typed, revisable, decayable memory with competence learning and trust-aware retrieval.
SaferSkills independently audited membrane (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Membrane is a selective learning and memory substrate for LLM agents. It gives agents typed, revisable memory instead of an append-only transcript or a flat vector store.
Use it when an agent needs to remember facts, retrieve prior context, revise stale knowledge, preserve task state, and cite the records that informed an answer.
competence, plan graph, and canonical entity nodes that connect them.
RetrieveGraph returns ranked records plusbounded graph neighborhoods, not only top-k chunks.
CaptureMemory accepts events, observations,tool outputs, working state, and facts, then creates linked records and edges.
with audit trails.
or penalized by outcomes.
authentication, and scope checks.
membraned over gRPC, use the embedded Go API, orcall it from TypeScript/Python SDKs.
Workers with Wrangler.
| Layer | Purpose | Typical contents |
|---|---|---|
| Episodic | Raw experience | Tool calls, incidents, observations, agent turns |
| Working | Active task state | Current goal, next actions, open questions |
| Semantic | Durable facts | Preferences, system facts, relationships |
| Competence | Learned procedures | Debugging playbooks, success rates, applicability |
| Plan graph | Reusable plans | Rollout DAGs, checkpoints, dependencies |
| Entity | Graph spine | Projects, services, tools, files, people, databases |
The entity layer is not a replacement for the five memory layers. It is the shared graph layer that lets facts, episodes, procedures, plans, and task state retrieve together.
protoc only when regenerating protobuf codegit clone https://github.com/BennettSchwartz/membrane.git
cd membrane
make build
./bin/membranedBy default, membraned uses local SQLite storage. To use Postgres:
./bin/membraned \
--postgres-dsn "postgres://membrane:membrane@localhost:5432/membrane?sslmode=disable"Useful commands:
make test # Go test suite
make proto # Regenerate Go protobuf bindings
make ts-build # Build the TypeScript SDK
make eval-all # Targeted evaluation suitenpm --prefix clients/typescript install
npm --prefix clients/typescript run buildimport {
MembraneClient,
MemoryType,
Sensitivity,
SourceKind,
} from "@bennettschwartz/membrane";
const client = new MembraneClient("localhost:9090", {
apiKey: process.env.MEMBRANE_API_KEY,
});
const capture = await client.captureMemory(
{
subject: "auth-service",
predicate: "uses_database",
object: "PostgreSQL",
},
{
source: "agent",
sourceKind: SourceKind.OBSERVATION,
summary: "auth-service uses PostgreSQL",
tags: ["auth-service", "postgres"],
scope: "project-orion",
sensitivity: Sensitivity.LOW,
},
);
const graph = await client.retrieveGraph("debug auth-service latency", {
trust: {
max_sensitivity: Sensitivity.MEDIUM,
authenticated: true,
actor_id: "debug-agent",
scopes: ["project-orion"],
},
memoryTypes: [
MemoryType.ENTITY,
MemoryType.EPISODIC,
MemoryType.WORKING,
MemoryType.SEMANTIC,
MemoryType.COMPETENCE,
MemoryType.PLAN_GRAPH,
],
rootLimit: 12,
nodeLimit: 64,
edgeLimit: 160,
maxHops: 2,
});
console.log(capture.primary_record.id, graph.nodes.length);
client.close();See clients/typescript for the full SDK.
python -m pip install -e "clients/python[dev]"
python -m pytest clients/python/testsfrom membrane import MembraneClient, MemoryType, Sensitivity, SourceKind
client = MembraneClient("localhost:9090")
capture = client.capture_memory(
{
"thread_id": "incident-42",
"state": "investigating auth-service latency",
"next_actions": ["check database wait", "hold canary"],
},
source="agent",
source_kind=SourceKind.WORKING_STATE,
summary="Incident state updated",
tags=["auth-service", "incident"],
sensitivity=Sensitivity.LOW,
)
graph = client.retrieve_graph(
"auth-service canary incident",
memory_types=[
MemoryType.ENTITY,
MemoryType.WORKING,
MemoryType.SEMANTIC,
MemoryType.COMPETENCE,
MemoryType.PLAN_GRAPH,
],
)
print(capture.primary_record.id, len(graph.nodes))
client.close()See clients/python for package details.
Membrane can also run embedded in a Go process:
package main
import (
"context"
"log"
"github.com/BennettSchwartz/membrane/pkg/ingestion"
"github.com/BennettSchwartz/membrane/pkg/membrane"
"github.com/BennettSchwartz/membrane/pkg/retrieval"
"github.com/BennettSchwartz/membrane/pkg/schema"
)
func main() {
m, err := membrane.New(membrane.DefaultConfig())
if err != nil {
log.Fatal(err)
}
defer m.Stop()
ctx := context.Background()
if err := m.Start(ctx); err != nil {
log.Fatal(err)
}
capture, err := m.CaptureMemory(ctx, ingestion.CaptureMemoryRequest{
Source: "agent",
SourceKind: "observation",
Content: map[string]any{
"subject": "auth-service",
"predicate": "uses_database",
"object": "PostgreSQL",
},
Summary: "auth-service uses PostgreSQL",
Tags: []string{"auth-service", "postgres"},
})
if err != nil {
log.Fatal(err)
}
graph, err := m.RetrieveGraph(ctx, &retrieval.RetrieveGraphRequest{
TaskDescriptor: "debug auth-service latency",
Trust: &retrieval.TrustContext{
MaxSensitivity: schema.SensitivityMedium,
Authenticated: true,
},
MemoryTypes: []schema.MemoryType{
schema.MemoryTypeEntity,
schema.MemoryTypeSemantic,
schema.MemoryTypeCompetence,
},
RootLimit: 10,
MaxHops: 2,
})
if err != nil {
log.Fatal(err)
}
log.Printf("captured=%s graph_nodes=%d", capture.PrimaryRecord.ID, len(graph.Nodes))
}The docs site lives in docs, is built with Docusaurus, and is deployed to Cloudflare Workers at <https://membrane.gustycube.com>.
npm install
npm run docs:dev
npm run docs:build
# Deploy to Cloudflare Workers when Wrangler is configured
npm run docs:deployCloudflare configuration is in wrangler.jsonc. The GitHub Actions deploy workflow runs when docs files change and deploys when CLOUDFLARE_API_TOKEN is present as a repository secret. The membrane.gustycube.com DNS record must be proxied through Cloudflare for the Worker route to receive production traffic. Sidebar and theme configuration live in sidebars.js and docusaurus.config.js.
The gRPC API is defined in api/proto/membrane/v1/membrane.proto. Core RPCs include:
CaptureMemoryRetrieveGraphRetrieveByIDSupersedeForkRetractMergeContestReinforcePenalizeGetMetricsGenerated Go, TypeScript, and Python bindings are committed so consumers do not need protobuf tooling for normal development.
Membrane supports SQLite for local and embedded use, and Postgres for concurrent deployments. Postgres can optionally use pgvector for embedding-backed ranking.
Common environment variables:
| Variable | Purpose |
|---|---|
MEMBRANE_API_KEY | Bearer token for gRPC requests |
MEMBRANE_ENCRYPTION_KEY | SQLCipher encryption key |
MEMBRANE_POSTGRES_DSN | Postgres connection string |
MEMBRANE_EMBEDDING_API_KEY | API key for embedding-backed retrieval |
MEMBRANE_LLM_API_KEY | API key for background semantic extraction |
MEMBRANE_INGEST_LLM_API_KEY | API key for ingest-time interpretation |
See docs/guides/configuration.mdx for full configuration details.
api/ Protobuf definitions and generated Go gRPC bindings
clients/typescript/ TypeScript SDK
clients/python/ Python SDK
cmd/membraned/ Daemon entrypoint
docs/ Docusaurus docs content
examples/ Reference examples and experiments
pkg/ Core Go packages
tests/ Evaluation and integration testsRun the checks most often used before committing:
make build
make test
npm --prefix clients/typescript run build
npm run docs:buildIssues and PRs are welcome. See CONTRIBUTING.md for local workflow and expectations.
MIT. See LICENSE.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.