Skip to main content

membrane

Membrane

A living entity-field memory for a resident AI seat — hypervectors with a readable geometry, a background mind that works while you're away, every write guarded and audited, shared across seats by construction. Local-first; it runs in front of Ollama (or inside Home Assistant).

A Python lab. It runs as a local HTTP server on 127.0.0.1 — technically a proxy, but that is the mechanism, not the point. The point is the field.

Why that name? The project began as one question — how do you measure "variety" for a memory that decides absorb-a-cached-answer vs escalate-to-the-model? — and the answer is what makes the memory above possible and is why it is called membrane: a selective, active boundary between lived memory (the inside) and the incoming world (the outside) that absorbs what it has the variety for and escalates the rest. The measured underpinnings are in The math findings.


What separates membrane from other agentic memory on GitHub

Most memory projects give your agent a store to query. Membrane is built on different ground rules; each one is checkable in the repo.

1. The memory has a shape, not just a bag of chunks

The field is not cosine-over-pasted-summaries. It is an entity field over hypervectors with a geometry you can read and measure: entities with positions and states, relations between them, tiers of life (nursery → live → dormant), effective rank, free energy, a conflict fraction. membrane state prints the geometry of a running field; experiments/variety_toy.py shows in one command why a low-variety store reads its outliers as novel and a high-variety store doesn't (the absorb-vs-escalate decision is measured, not vibes); docs/math-findings.md carries the experiments.

2. It is a seat, not a decorator

You don't bolt it onto an app — the memory is the resident process: its own REST/SSE serve surface, its own chat UI, voice, the Home Assistant integration. The turn loop runs inside the seat, so every client's turn goes through the same field path; conversation transcript and memory are the same store, not two systems reconciling each other (see membrane serve, below). While nobody is talking, a background mind keeps working the field — a thinker digesting event streams, a dreamer consolidating at night, watchers, entity scripts — and its minds are visible as live agents at GET /agent (counters served fresh from the running process, nothing cached, nothing faked). The repo dogfoods it: its own field holds ~14k live entities and learned the codebase while it was written.

3. Structure crystallizes; you curate nothing

No schema to define, no tags to assign, no knowledge graph to hand-build. Entities are born when experience names them, their states move as the world speaks, relations and claims accumulate, symbols crystallize through language (docs/symbol-birth.md). When two seats assert opposite things about the same entity, the divergence is surfaced, never silently overwritten — nothing in a teammate's field is destroyed by yours.

4. Every write stands trial

A memory that lets the model whisper to itself unsupervised drifts into self-flattery. Membrane's writes run the same guards a turn runs: dedup, variety gate, length limits, fenced-code refusal, junk cut — every write audited with its origin (agent / mcp / night / operator) in audit.jsonl, and every claim traces back to the events that earned it. A dedicated lane replays earlier gate-verdicts against what the model actually answered and judges agreement — shadow by default (--replay-ab P=0: recorded, never bypassing you), measured at 0.77 mean similarity on a live seat (docs/ab-critic-scorecard.md faces the six criticisms honestly).

5. Multi-seat sharing is a property of the data model, not a feature

Every event, state, relation and claim is namespaced by the seat (origin) that wrote it — writes are disjoint, reads are merged, so one team bucket is collision-free by construction: a foreign entity's effective state is the seed plus the mass-weighted mean of every seat's displacement, cross-seat agreement is promoted into the commons at night, and disputed claims get marked disputed, not overwritten. Live peer asks (LAN/VPN, bounded fan-out) let a seat consult a sibling's field mid-turn. The mechanics: docs/multi-dev-field.md; the team process: docs/shared-field-workflow.md.

6. Local-first, and it degrades honestly

No SaaS, no account, no telemetry, no cloud round-trip required: chat and embeddings run on your Ollama (cloud models are routable rungs you add with membrane auth); the substrate is your Couchbase bucket — and with no cluster reachable the seat drops to a RAM field and still boots (degradation, never failure). The whole field is a substrate you own and can move (membrane move / reembed). The memory is for your data: the private-use license covers personal use and keeps it out of paid products (LICENSE).

How it maps to the patterns you've already seen

The common pattern What membrane does instead
Vector store + retriever: paste docs, query chunks Events with provenance; recall returns nearest past situations-and-actions and the named entities' held evidence, not loose chunks
Transcript summarizers / rolling history The full transcript is never replayed: each turn carries a since-your-last-message diff of what actually moved
Hand-curated knowledge graphs No hand work: entities, relations, claims crystallize from experience; structure emerges, and every element is auditable back to events
In-framework memory (your agent runtime keeps it) The seat is the runtime — sessions, turns and tool parts live in membrane's store; memory and history can't fork
Hosted memory APIs (account, endpoint, egress) Your bucket or your disk; offline mode keeps the seat alive with no cloud at all

Quick start

Membrane runs two ways. Both need an Ollama server with one chat model and one embedding model (ollama pull qwen3:8b && ollama pull mxbai-embed-large, or any cloud model Ollama serves), and 1–3 GB of RAM for the seat.

Standalone: its own web UI

pip install membrane-seat          # or: uv tool install membrane-seat
mkdir ~/bob && cd ~/bob
membrane standalone                # opens http://localhost:4096/ in your browser

You get membrane home: the field in 3D, the chat, and voice ("Hey Bob" — a new membrane is Bob until you agree on a name together; it will ask). Other devices on your network reach it at http://<this machine>:4096/. No login on the local network; a public or proxied caller is refused. Voice needs a secure origin, so the microphone works on localhost (the first voice use downloads ~65 MB of speech models, checked against their checksums).

Inside Home Assistant (HACS)

  1. HACS → ⋮ → Custom repositories → https://github.com/chedim/membrane, category Integration.
  2. Install Membrane, restart Home Assistant.
  3. Settings → Devices & services → Add integration → Membrane → Inside Home Assistant. If Home Assistant already has the Ollama integration, it is used; otherwise give Ollama's address. Pick the models.

Home Assistant then runs the seat as its own child process (started and stopped with HA, restarted if it dies; its data in <config>/membrane/), opens on membrane home for everyone (HA's sidebar folds away; ☰ leads to HA's own pages — switch it off in the integration's options), adds the seat as an Assist conversation agent, and its field's readings as sensors.

What it can do in your house: by default the seat may use Home Assistant through a hidden, non-admin, local-only user ("Membrane") — read any state, call any service, and mirror the entities it chooses (they update live). It acts on what people ask, but its commands are written by a model: turn Let the seat use Home Assistant off in the integration's options if you'd rather it only talks. It can't change Home Assistant's configuration or users.

Connect to a seat running elsewhere is for a seat on another machine (the integration is then only its UI and agent).

The daily work: typed tools, guarded writes

A seat hands its model the membrane MCP surface — query, state, learn, forget, the entity-hand tools (entity_add/edit/remove/link/inherit, entity_query, entity_tracks, entities_related …), intend, peer_post, graph. The model reads the field with query/state, files an earned lesson with learn (same guards as any turn), cleans its own mistakes with forget — writes are audited with their origin. Meanwhile the ambient layer needs no command at all: every turn the model sees what the field holds about the entities it names, and on later turns only the diff — which entity states moved since the last message it processed, whether the change came from conversation, code, other seats or entity scripts. Two safety lanes run on every turn and stay advisory: the gate replays the nearest past ask against the live answer (A/B, shadow by default), and the [membrane] monologue in the thinking channel shows the gate verdict, field vitals and the per-turn memory flow (/monologue toggles it).

Idle background loops run on a LOCAL, cheaper model by default (--idle-cortex-model) so a stuck background seat can never burn cloud tokens. Token economics and routing: docs/token-economics.md.

Bugs and ideas

github.com/chedim/membrane/issues — your membrane knows this too: when it finds a bug in itself it drafts the issue and hands you the link to file.


Installation

Install the CLI (one line, no sudo)

curl -fsSL https://raw.githubusercontent.com/chedim/membrane/main/install.sh | bash

Installs the membrane CLI globally, user-local (~/.local/bin): uv tool install if you have uv, otherwise a managed venv at ~/.membrane/venv + a symlink on your PATH. Re-running it is safe (upgrade in place). To install from a local checkout instead (e.g. while hacking on membrane):

MEMBRANE_REPO=/path/to/membrane bash install.sh

Dependencies, honestly:

  • Python ≥ 3.10 + numpy — the only import-time dep (couchbase installed by default, imported lazily; a missing cluster degrades that feature, never the boot).
  • Ollama (brew install ollama / https://ollama.com) — NOT auto-installed; embeddings and local chat need it running on :11434.
  • opencode — optional; membrane opencode launches it if found on PATH, otherwise point any OpenAI-compatible client at http://127.0.0.1:11500/v1.
  • The viewer ships inside the package (three.js + both pixel fonts are vendored, served same-origin) — no CDN, no node, no browser install.

First run in a project

cd ~/projects/my-app
membrane opencode

On first run in a directory it bootstraps that project: auto-installs the agent skills into .opencode/skills/ there (project-scoped on purpose), opens an entity field namespaced by the project's origin, and backfills what it can without cost (the codegraph; with membrane backfill, the project's history). If no memory sharing is configured it offers (once, interactive shell only) to connect a Couchbase cluster — decline and it stays a private local field.

It starts the OpenAI-compatible proxy on :11500 (backend Ollama at http://localhost:11434, override --backend/--model) and opens the opencode TUI attached to membrane's own serve surface: sessions, turns, tool parts and their history live in membrane's stores beside the field — the TUI is a thin client. Every turn is captured into the field (discourse and observations as events), entities move as the conversation names them, and the model is grounded by what those entities hold — docs/entity-field.md is the design doc.

Surfaces

Command What it does
membrane opencode the daily driver: proxy on :11500 + membrane's own serve surface (loopback) with the opencode TUI attached as a thin client (MEMBRANE_TUI_DIRECT=1 restores the old direct-provider lane)
membrane serve the same seat, headless: membrane's OWN opencode-compatible REST/SSE server (opencode replaced, no child process)
membrane standalone the seat with its own web UI — membrane home (the field in 3D), chat, voice; see docs/standalone.md
membrane backfill rebuild the field from history (code first, then the audit log; idempotent, resumable)
membrane review print what the field learned (annotations, replay A/B with --ab)
membrane connect (re)run shared-field onboarding for THIS project
membrane reset kill the seat on :11500 (or --listen), relaunch membrane opencode fresh
membrane install install .opencode/skills/* explicitly (+ .gitignore entry)
membrane auth store a cloud API key in ~/.membrane/auth.json (600) — its models become routable rungs
membrane move / membrane migrate migrate the field between stores
membrane reembed move the field to another embedding model (re-embeds every live event, re-applies the log in order)
membrane update check the install source, show what changed, apply and re-exec
membrane ingest cold start: absorb docs/README into the field (DOC events; a second ingest is a no-op)
membrane codegraph stdlib-AST call graph (stats / impact / callers / callees)
membrane doctor what's installed/running + the field's vitals

MEMBRANE_EFIELD selects the field's mode: on (the default: capture, embed, apply), shadow (capture events only — a lab ablation), off.

Headless serving (membrane serve)

membrane serve runs the seat with no TUI and no opencode at all — sessions, messages, turns, tool parts, aborts, the SSE event stream, served by membrane itself on :4096 (basic auth, the pair in .membrane/serve-auth.json, credentials survive restarts). Any opencode client connects unchanged: the mobile companion app, IDE plugins, a script. The turn loop calls the seat's OWN /v1 on loopback — gate, injection, capture and routing run through the exact code path every other client's turn uses, so the serve surface adds no separate memory, and the model's tool invocations appear to the client as ordinary tool parts riding message.part.updated (pending → completed), so you can watch what it's doing from the room. --serve-port (default 4096, walks up if busy) and --listen (the proxy, never leaves 127.0.0.1) are independent. Exposure is LAN-only by design: reach the server only from a network you own.

  • The minds are visible. /agents lists the ambient minds as agents — the three thinking-loop workers (each with its live claim and the event queue's depth) plus thinker and dreamer with their real counters.
  • The loop queue is RAM-only and dies with the seat — a seat restart drops pending reactive work on purpose; stale reactions must not outlive their justification. What must survive a restart belongs in the field (learn), not in the queue.

Sharing a team field (Couchbase)

Point every seat at the same Couchbase bucket: one bucket per team, the membrane scope, the nine entity-field collections (events, sentences, terms, entity_states, entity_deltas, relations, claims, annotations, origin_stats), created and indexed on first boot. Projects distinguish themselves by the origin axis — a seat writes ONLY rows keyed by its own origin; do NOT add per-project buckets (that would defeat cross-seat agreement).

membrane connect        # conn string, user, password (hidden), bucket

The same offer appears automatically on the first run in a project with no store configured (interactive shell only; declining is remembered). Secrets never touch the repo — credentials are stored per-project OUTSIDE it, at ~/.membrane/projects/<slug>.env (mode 600), and a .env inside the repo is designed-out of the failure mode. Plain env config still works:

export MEMBRANE_STORE=couchbase
export COUCHBASE_CONN_STRING=couchbase://<team-host>
export COUCHBASE_USER=... COUCHBASE_PASSWORD=... COUCHBASE_BUCKET=membrane

A local cluster answering at the default endpoint is auto-detected at boot; an unreachable cluster transparently falls back to the RAM field. What sharing guarantees — disjoint writes / merged reads, divergence surfaced, the commons, live peer asks — is in docs/multi-dev-field.md, and the full team process in docs/shared-field-workflow.md.


Measured, not promised

  • Token economics, live seat (2026-10-12): 5,736 live entities over 4,949 streamed events; the standing per-turn header ~3 KB (entity tracks, one line each) instead of state dumps; per-turn injection is the state diff, not the transcript; a recall of a few hundred tokens replaces re-reading a 4,000-line log. docs/token-economics.md
  • The gate is calibrated by experiment: cosine-primary similarity with a novelty specialist as the escalation judge; the variety toy reproduces the decision in one command. docs/math-findings.md, docs/ab-critic-scorecard.md (replay A/B agreement 0.77 on a live seat, six criticisms faced)
  • The field's own state is readable: membrane state / membrane doctor print live events by stream, entities by tier, geometry (effective rank, free energy, conflict fraction), the replay ledger, the last night pass.

Lifecycle

  • Update check at boot (opt out MEMBRANE_NO_UPDATE_CHECK=1): the CLI compares against its install source (git or PyPI), prints the CHANGELOG delta, and asks before pulling (--ff-only; a dirty tree is refused). Inside Home Assistant, updates come through HACS.
  • Restart only from the shell (membrane reset / ./run.sh restart). Membrane never restarts itself while running, and a restart loses nothing: the field re-hydrates from its backend. The one way a restart CAN change results is membrane reembed (moving to another embedding model).
  • Field moves are first-class: membrane move between stores, membrane migrate as the interactive front door, membrane reembed --efield --embed-model M pins the rebuilt field equal to a fresh one.

Documentation map

Doc What's in it
docs/standalone.md Standalone mode: the web UI, the first run and the name, who can reach it, voice, settings.json, updates
docs/home-assistant.md Membrane in Home Assistant (HACS): setup, the UI takeover, the Assist agent and sensors, what the seat may do in the house
docs/entity-field.md THE FIELD. Events → entities → readers; the update rule; relations and claims; injection (full block / state diff); the gate + replay A/B; storage, sharing, the background mind; the operator/listener/plan tool lanes + the intent lane
docs/seat-first-2-tier.md The seat-first two-tier field: local authoritative store, a published global tier on your terms
docs/shared-field-workflow.md Team field: the process. Day-one setup, the async loop, watches, operator rules, go-live checklist
docs/multi-dev-field.md Team field: the substrate. The origin axis, disjoint writers / merged reads, divergence conflicts, the commons
docs/global-field.md The global commons: facts true beyond one repo; operator promotion, consensus, residency
docs/background-mind.md The background mind: thinker + dreamer, the NightClock, entity scripts and tracks, the intent steering wheel, the pause, kill switches
docs/vsa-token-lane.md Entity keys + relations (the VSA lane). Canonical ids, key binding, the relations composites, structure-awareness numbers
docs/token-economics.md Where the savings come from + the router: the ladder, the ceiling, the outage fallback
docs/monologue.md The [membrane] thinking-channel banner: every line explained, /monologue toggle
docs/symbol-birth.md The symbol crystallization: how the hierarchy is born through language
docs/math-findings.md The measured math: the variety/gate experiments and the e-series evidence
docs/ab-critic-scorecard.md The A/B scorecard vs a plain cosine cache — six criticisms faced honestly
docs/fork-port-notes.md Fork notes (what it took to port membrane's ideas into another project)

Development

Source layout: membrane/ (the field: streams → efield/efstore → entity_block/efgate/night; the seat's serve server (server.py, opencode-compatible); the MCP surface (mcp_server.py); the CLI), experiments/ (the e-series rigs behind math-findings), tests/ (unittest + pytest, plain-python runnable).

python -m unittest discover -s tests     # everything runs against in-process fakes, no server needed
python experiments/variety_toy.py        # the variety toy: the gate's calibration in one command

Status

Working lab, in daily use on its own development (dogfooded: this repo's own field holds ~14k live entities and its background mind digests, dreams, and speaks about its own work). Version 0.3.4; API and flag surfaces may still move. Licensing: private-use license (see LICENSE) — free for home and personal use; commercial use, redistribution and paid offerings need the author's written permission.

Metadata

Release files for membrane-seat 0.3.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for membrane-seat 0.3.4
File Size Uploaded
membrane_seat-0.3.4.tar.gz 3.9 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for membrane-seat 0.3.4
File Interpreter ABI Platform
membrane_seat-0.3.4-py3-none-any.whl Python 3 none any Details

Total release size: 7.5 MB

Release files / membrane_seat-0.3.4.tar.gz

Download URL membrane_seat-0.3.4.tar.gz
Size 3.9 MB
Tags Source
SHA-256 checksum
How to use checksums
bc8b85579d120afad2807abe84215acabfa917a910641b4509e5ff3211599b10
BLAKE2b-256 checksum
How to use checksums
4f2d9a39798c1472d4f64bbf8580fb8f47e46e412c7bc808e8e681b829161fe8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / membrane_seat-0.3.4-py3-none-any.whl

Download URL membrane_seat-0.3.4-py3-none-any.whl
Size 3.6 MB
Tags Python 3
SHA-256 checksum
How to use checksums
26abbe11f0ddc9d901564f83aa8db5b59d44928aedfcec939b3b7f96551ec00c
BLAKE2b-256 checksum
How to use checksums
af28c37e50940422ce0de6f64da9393fd33f9d92e53fec9d259620652dd3eaab
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

This release

0.3.4 This release

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page