Skip to main content

Installation

npm install @gustycube/membrane
Requires Node.js 20+ and a running Membrane daemon (default: localhost:9090).

Quick start

import { MembraneClient, Sensitivity } from "@gustycube/membrane";

const client = new MembraneClient("localhost:9090", {
  apiKey: "your-api-key"
});

const record = await client.ingestEvent("file_edit", "src/main.ts", {
  summary: "Refactored auth middleware",
  sensitivity: Sensitivity.LOW,
  tags: ["auth", "typescript"]
});

const records = await client.retrieve("debug auth", {
  trust: {
    max_sensitivity: Sensitivity.MEDIUM,
    authenticated: true,
    actor_id: "agent-1",
    scopes: []
  },
  limit: 10
});

console.log(record.id, records.length);
client.close();

Exports

The package exports the following from the top-level @gustycube/membrane entry point:
ExportKindDescription
MembraneClientclassMain client class
MembraneErrorclassError thrown by failed RPCs
Sensitivityconst enumSensitivity level values
MemoryTypeconst enumMemory type values
OutcomeStatusconst enumOutcome status values
DecayCurveconst enumDecay curve values
DeletionPolicyconst enumDeletion policy values
RevisionStatusconst enumRevision status values
ValidityModeconst enumValidity mode values
TaskStateconst enumTask state values
AuditActionconst enumAudit action values
ProvenanceKindconst enumProvenance kind values
EdgeKindconst enumPlan graph edge kind values
createDefaultTrustContextfunctionCreates a default TrustContext
TrustContexttypeTrust context interface
MemoryRecordtypeMemory record interface
RetrieveResulttypeResult from retrieveWithSelection
SelectionResulttypeSelector metadata
MembraneClientOptionstypeConstructor options
IngestEventOptionstypeOptions for ingestEvent
IngestToolOutputOptionstypeOptions for ingestToolOutput
IngestObservationOptionstypeOptions for ingestObservation
IngestOutcomeOptionstypeOptions for ingestOutcome
IngestWorkingStateOptionstypeOptions for ingestWorkingState
RetrieveOptionstypeOptions for retrieve / retrieveWithSelection

MembraneClient

Constructor

new MembraneClient(addr?: string, options?: MembraneClientOptions)
addr
string
gRPC server address in host:port format. Defaults to "localhost:9090".
options
MembraneClientOptions
Optional configuration object.

MembraneClientOptions

tls
boolean
Enable TLS transport. Defaults to false.
tlsCaCertPath
string
Path to a PEM-encoded CA certificate for server verification. Implies TLS.
apiKey
string
Bearer token sent as authorization metadata on every RPC.
timeoutMs
number
Default RPC timeout in milliseconds. No timeout if omitted.

TLS and authentication example

const client = new MembraneClient("membrane.example.com:443", {
  tls: true,
  tlsCaCertPath: "/path/to/ca.pem",
  apiKey: "your-api-key",
  timeoutMs: 10_000
});

Ingestion methods

ingestEvent

Create an episodic record from an event.
async ingestEvent(
  eventKind: string,
  ref: string,
  options?: IngestEventOptions
): Promise<MemoryRecord>
eventKind
string
required
Kind of event, e.g. "file_edit", "tool_call", "error".
ref
string
required
Reference identifier for the event source.
options.summary
string
Human-readable summary of the event.
options.sensitivity
Sensitivity | string
Sensitivity classification. Defaults to "low".
options.source
string
Provenance source identifier. Defaults to "typescript-client".
options.tags
string[]
Tags for categorization and retrieval filtering.
options.scope
string
Visibility scope for the record.
options.timestamp
string
RFC 3339 timestamp. Defaults to now.
const record = await client.ingestEvent("tool_call", "task#1", {
  summary: "Ran database migration successfully",
  tags: ["db", "migration"]
});

ingestToolOutput

Create an episodic record from a tool invocation.
async ingestToolOutput(
  toolName: string,
  options?: IngestToolOutputOptions
): Promise<MemoryRecord>
toolName
string
required
Name of the tool that produced output.
options.args
Record<string, unknown>
Arguments passed to the tool, JSON-serializable.
options.result
unknown
Result returned by the tool, JSON-serializable.
options.dependsOn
string[]
IDs of records this output depends on.
options.sensitivity
Sensitivity | string
Sensitivity classification. Defaults to "low".
const record = await client.ingestToolOutput("run_tests", {
  args: { suite: "auth" },
  result: { passed: 42, failed: 0 },
  tags: ["tests", "auth"]
});

ingestObservation

Create a semantic record from a subject-predicate-object triple.
async ingestObservation(
  subject: string,
  predicate: string,
  obj: unknown,
  options?: IngestObservationOptions
): Promise<MemoryRecord>
subject
string
required
The subject of the observation.
predicate
string
required
The predicate relating subject to object.
obj
unknown
required
The object value (any JSON-serializable value).
const record = await client.ingestObservation(
  "user",
  "prefers",
  { language: "TypeScript" },
  { sensitivity: Sensitivity.LOW, tags: ["preferences"] }
);

ingestOutcome

Attach an outcome to an existing episodic record.
async ingestOutcome(
  targetRecordId: string,
  outcomeStatus: OutcomeStatus | string,
  options?: IngestOutcomeOptions
): Promise<MemoryRecord>
targetRecordId
string
required
ID of the record to attach the outcome to.
outcomeStatus
OutcomeStatus | string
required
One of "success", "failure", or "partial".
import { OutcomeStatus } from "@gustycube/membrane";

await client.ingestOutcome(record.id, OutcomeStatus.SUCCESS);

ingestWorkingState

Create a working memory snapshot for a task thread.
async ingestWorkingState(
  threadId: string,
  state: string,
  options?: IngestWorkingStateOptions
): Promise<MemoryRecord>
threadId
string
required
Identifier for the task thread.
state
string
required
Current task state: "planning", "executing", "blocked", "waiting", or "done".
options.nextActions
string[]
Planned next steps.
options.openQuestions
string[]
Unresolved questions.
options.contextSummary
string
Human-readable summary of current context.
options.activeConstraints
Record<string, unknown>[]
Active constraints as JSON-serializable objects.
import { TaskState } from "@gustycube/membrane";

await client.ingestWorkingState("session-001", TaskState.EXECUTING, {
  nextActions: ["run tests", "deploy"],
  contextSummary: "Backend initialized, frontend pending"
});

Retrieval methods

retrieve

Retrieve memory records relevant to a task descriptor.
async retrieve(
  taskDescriptor: string,
  options?: RetrieveOptions
): Promise<MemoryRecord[]>
taskDescriptor
string
required
Natural-language description of the current task.
options.trust
TrustContext
Trust context controlling record access. Defaults to a minimal context with Sensitivity.LOW.
options.memoryTypes
Array<MemoryType | string>
Filter by memory types: "episodic", "working", "semantic", "competence", "plan_graph".
options.minSalience
number
Minimum salience threshold. Defaults to 0.
options.limit
number
Maximum records to return. Defaults to 10.
const records = await client.retrieve("database operations", {
  trust: {
    max_sensitivity: Sensitivity.MEDIUM,
    authenticated: true,
    actor_id: "ts-agent",
    scopes: []
  },
  memoryTypes: ["semantic", "competence"],
  limit: 5
});

retrieveWithSelection

Retrieve records plus selector metadata (ranked candidates, confidence).
async retrieveWithSelection(
  taskDescriptor: string,
  options?: RetrieveOptions
): Promise<RetrieveResult>
Returns a RetrieveResult with records and an optional selection field:
interface RetrieveResult {
  records: MemoryRecord[];
  selection?: SelectionResult;
}

interface SelectionResult {
  selected: MemoryRecord[];
  confidence: number;
  needs_more: boolean;
}
const result = await client.retrieveWithSelection("fix build error", {
  trust: { max_sensitivity: Sensitivity.HIGH, authenticated: true, actor_id: "ci", scopes: [] },
  memoryTypes: ["competence", "episodic"]
});
console.log(result.records.length, result.selection?.confidence);

retrieveById

Fetch a single record by its ID.
async retrieveById(
  recordId: string,
  options?: { trust?: TrustContext }
): Promise<MemoryRecord>
const record = await client.retrieveById("rec-uuid-here", {
  trust: { max_sensitivity: Sensitivity.MEDIUM, authenticated: true, actor_id: "agent", scopes: [] }
});

Revision methods

supersede

Replace a record with a new version, creating an audit trail.
async supersede(
  oldId: string,
  newRecord: MemoryRecord | JsonObject,
  actor: string,
  rationale: string
): Promise<MemoryRecord>
const updated = await client.supersede(
  oldRecord.id,
  { ...oldRecord, payload: { ...oldRecord.payload, version: "2.0" } },
  "agent-1",
  "Updated version field"
);

fork

Create a conditional variant of a record.
async fork(
  sourceId: string,
  forkedRecord: MemoryRecord | JsonObject,
  actor: string,
  rationale: string
): Promise<MemoryRecord>
const variant = await client.fork(
  sourceRecord.id,
  { ...sourceRecord, scope: "dev" },
  "agent-1",
  "Different behavior for dev environment"
);

retract

Soft-delete a record. The record remains in the store but is marked as retracted.
async retract(
  recordId: string,
  actor: string,
  rationale: string
): Promise<void>
await client.retract(record.id, "agent-1", "No longer accurate");

merge

Combine multiple records into a single consolidated record.
async merge(
  recordIds: string[],
  mergedRecord: MemoryRecord | JsonObject,
  actor: string,
  rationale: string
): Promise<MemoryRecord>
const merged = await client.merge(
  [id1, id2, id3],
  { type: "semantic", payload: { fact: "combined knowledge" } },
  "agent-1",
  "Consolidating duplicate semantic records"
);

contest

Mark a record as contested due to conflicting evidence.
async contest(
  recordId: string,
  contestingRef: string,
  actor: string,
  rationale: string
): Promise<void>
await client.contest(
  record.id,
  conflictingRecord.id,
  "agent-1",
  "New evidence contradicts this"
);

Reinforcement methods

reinforce

Boost a record’s salience score.
async reinforce(
  recordId: string,
  actor: string,
  rationale: string
): Promise<void>
await client.reinforce(record.id, "agent-1", "Used successfully in task");

penalize

Reduce a record’s salience score.
async penalize(
  recordId: string,
  amount: number,
  actor: string,
  rationale: string
): Promise<void>
await client.penalize(record.id, 0.2, "agent-1", "Led to incorrect result");

getMetrics

Retrieve a point-in-time metrics snapshot from the daemon.
async getMetrics(): Promise<JsonObject>
const metrics = await client.getMetrics();
console.log(metrics.total_records, metrics.avg_salience);

close

Close the underlying gRPC channel. Always call this when done.
client.close(): void

Core types

Sensitivity

Controls access during retrieval. Records above the caller’s max_sensitivity are returned in redacted form (metadata only).
ValueString
Sensitivity.PUBLIC"public"
Sensitivity.LOW"low"
Sensitivity.MEDIUM"medium"
Sensitivity.HIGH"high"
Sensitivity.HYPER"hyper"

TrustContext

interface TrustContext {
  max_sensitivity: Sensitivity | string; // highest sensitivity the caller may access
  authenticated: boolean;               // whether the caller is authenticated
  actor_id: string;                     // identifier of the calling actor
  scopes: string[];                     // optional scope constraints
}
Use createDefaultTrustContext() to get a zero-value context (Sensitivity.LOW, unauthenticated).

MemoryRecord

interface MemoryRecord {
  id: string;
  type: MemoryType | string;           // "episodic" | "working" | "semantic" | "competence" | "plan_graph"
  sensitivity: Sensitivity | string;
  confidence: number;                  // 0–1 applicability score
  salience: number;                    // 0–1 current importance score
  scope?: string;
  tags?: string[];
  created_at?: string;
  updated_at?: string;
  lifecycle?: Lifecycle;
  provenance?: Provenance;
  relations?: Relation[];
  payload?: unknown;                   // type-specific structured payload
  audit_log?: AuditEntry[];
}

MemoryType

ValueString
MemoryType.EPISODIC"episodic"
MemoryType.WORKING"working"
MemoryType.SEMANTIC"semantic"
MemoryType.COMPETENCE"competence"
MemoryType.PLAN_GRAPH"plan_graph"

Error handling

Failed RPCs throw MembraneError. It exposes a stable shape regardless of the underlying gRPC status.
import { MembraneClient, MembraneError } from "@gustycube/membrane";

try {
  const record = await client.retrieveById("nonexistent-id");
} catch (err) {
  if (err instanceof MembraneError) {
    console.error(err.code);      // numeric gRPC status code
    console.error(err.codeName); // e.g. "NOT_FOUND", "UNAUTHENTICATED"
    console.error(err.details);  // gRPC details string
    console.error(err.metadata); // response metadata as string arrays
  }
}

LLM integration pattern

The common runtime pattern is: ingest execution traces, retrieve relevant memory, build a prompt, then reinforce useful records.
import OpenAI from "openai";
import { MembraneClient, Sensitivity } from "@gustycube/membrane";

const memory = new MembraneClient("localhost:9090", {
  apiKey: process.env.MEMBRANE_API_KEY
});

const llm = new OpenAI({
  apiKey: process.env.LLM_API_KEY,
  // For OpenRouter or other OpenAI-compatible providers:
  // baseURL: "https://openrouter.ai/api/v1",
});

const records = await memory.retrieve("how should I handle this incident?", {
  trust: {
    max_sensitivity: Sensitivity.MEDIUM,
    authenticated: true,
    actor_id: "incident-agent",
    scopes: ["prod"],
  },
  memoryTypes: ["semantic", "competence", "working"],
  limit: 10,
});

const memoryContext = records.map((r) => JSON.stringify(r)).join("\n");

const completion = await llm.chat.completions.create({
  model: "gpt-5.2",
  messages: [
    { role: "system", content: "Use the memory context as evidence. Cite record ids." },
    { role: "user", content: `Incident task:\n...\n\nMemory:\n${memoryContext}` },
  ],
});

console.log(completion.choices[0]?.message?.content);
memory.close();
After using a record successfully, call reinforce to boost its salience so it surfaces higher in future retrievals.

Snake_case aliases

Every camelCase method has a snake_case alias for ergonomics in mixed-language codebases:
camelCasesnake_case
ingestEventingest_event
ingestToolOutputingest_tool_output
ingestObservationingest_observation
ingestOutcomeingest_outcome
ingestWorkingStateingest_working_state
retrieveWithSelectionretrieve_with_selection
retrieveByIdretrieve_by_id
getMetricsget_metrics

Build docs developers (and LLMs) love