Skip to content

ARlinklabs/permamemory

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Arweave Memory Graph

A local-first context graph memory for LLMs — backed by permanent Arweave storage.

Your LLM's entire knowledge graph lives in-memory for instant search, and syncs to Arweave as a single transaction. One txid = your entire memory. Import it anywhere, anytime.

How It Works

┌─────────────────────────────────────────────────────┐
│                   MemoryClient SDK                  │
│   addMemory() → search() → link() → sync() → txid │
├────────────────────┬────────────────────────────────┤
│  LocalEmbeddingService  │  ArweaveStorageProvider   │
│  (transformers.js)      │  ┌──────────────────────┐ │
│  384-dim vectors        │  │   In-Memory Graph    │ │
│  all-MiniLM-L6-v2       │  │ (cosine similarity)  │ │
│                         │  └────────┬─────────────┘ │
│                         │           │               │
│                         │    Turbo Bundler           │
│                         │   (free < 100 KiB)        │
│                         │           │               │
│                         │     arweave.net            │
└─────────────────────────┴───────────┴───────────────┘

Key features:

  • Local-first — everything runs in-memory, no external databases
  • Semantic search — 384-dim embeddings via transformers.js (no API keys)
  • Permanent storage — sync to Arweave, get a txid, reload anywhere
  • Free uploads — Turbo bundler subsidizes uploads under 100 KiB
  • Portable — one txid = your entire knowledge graph

Quick Start

1. Install

cd arweave-memory
npm install

2. Generate a Wallet

node -e "
const Arweave = require('arweave');
const fs = require('fs');
Arweave.init({}).wallets.generate().then(jwk => {
  fs.writeFileSync('wallet.json', JSON.stringify(jwk, null, 2));
  return Arweave.init({}).wallets.jwkToAddress(jwk);
}).then(addr => console.log('Wallet address:', addr));
"

3. Use the SDK

import { MemoryClient } from './src/index.js';

const client = new MemoryClient({
  arweave: {
    connection: { host: 'arweave.net', port: 443, protocol: 'https' },
    wallet: JSON.parse(fs.readFileSync('wallet.json', 'utf-8')),
  },
});

// Add memories
await client.addMemory('TypeScript adds static typing to JavaScript', 'language', 'typescript');
await client.addMemory('Arweave provides permanent storage', 'blockchain', 'arweave');

// Link them
await client.link('typescript', 'arweave', 'integrates_with');

// Search semantically
const results = await client.search('web programming');
// → [{ entity: { name: 'typescript', ... }, score: 0.82 }]

// Sync to Arweave (free if < 100 KiB)
const txid = await client.sync();
console.log(`Memory stored permanently: https://arweave.net/${txid}`);

// Later — reload from any machine
const client2 = new MemoryClient({ arweave: { connection, wallet } });
await client2.load(txid);
const graph = await client2.getGraph();
// → { entities: [...], relations: [...] }

API Reference

MemoryClient

Method Description Returns
addMemory(content, type?, name?, metadata?) Add a memory with auto-generated embedding Entity
search(query, limit?, minSimilarity?) Semantic search across all memories MemorySearchResult[]
link(from, to, relationType, strength?, confidence?) Create a relation between two memories Relation
sync() Upload current state to Arweave via Turbo string (txid)
load(txid) Restore memory from an Arweave transaction void
getEntity(name) Get a specific entity Entity | null
getGraph() Get the full knowledge graph KnowledgeGraph
addObservations(entityName, contents) Add facts to an existing memory string[]
deleteMemories(names) Delete memories (cascades to relations) void
getStats() Entity/relation/embedding counts + txid object
getTxId() Current Arweave transaction ID string | null

Types

interface Entity {
  name: string;
  entityType: string;
  observations: string[];
  createdAt?: number;
  updatedAt?: number;
}

interface Relation {
  from: string;
  to: string;
  relationType: string;
  strength?: number;
  confidence?: number;
}

interface MemorySearchResult {
  entity: Entity;
  score: number;  // cosine similarity (0–1)
}

Storage Model

Your memory graph is a JSON object:

{
  "version": 1,
  "timestamp": 1708200000000,
  "entities": [ ... ],
  "relations": [ ... ],
  "embeddings": { "entity_name": { "vector": [...], "model": "...", "lastUpdated": ... } }
}

This gets uploaded as a single Arweave transaction. The txid is your portable handle to the entire memory state.

Upload Flow

  1. client.sync() serializes the graph to JSON
  2. JSON is uploaded via Turbo bundlerfree if under 100 KiB
  3. Returns a txid (e.g. EVEjUngdu2BqbA5FvvhXbWKRLeyJVTt1MH_7jxsfEHo)
  4. Data is permanently available at https://arweave.net/{txid}

Read Flow

  1. client.load(txid) fetches JSON from arweave.net (or gateway.irys.xyz fallback)
  2. Deserializes entities, relations, and embeddings into memory
  3. All search/CRUD operations work instantly on the in-memory graph

Embeddings

Uses transformers.js with the Xenova/all-MiniLM-L6-v2 model:

  • 384 dimensions — compact but effective
  • Runs locally — no API keys, no network after first model download (~25MB)
  • Cosine similarity — search results ranked by vector similarity
  • Auto-embeddedaddMemory() and addObservations() generate embeddings automatically

Running Tests

# All tests (including embedding model load — ~25s first run)
npm test

# Fast tests only (skip embedding + live network)
SKIP_EMBEDDING_TESTS=true SKIP_LIVE_TESTS=true npm test

# Live Arweave roundtrip (requires wallet.json + internet)
npx vitest run src/live.test.ts

# Verbose output
npx vitest run --reporter verbose

Test Coverage

Suite Tests What It Covers
ArweaveStorageProvider.test.ts 26 Entity/relation CRUD, keyword search, cosine similarity, embeddings
MemoryClient.test.ts 20 SDK lifecycle, search, link, delete, complex graph scenario
LocalEmbeddingService.test.ts 6 Vector dims, normalization, semantic similarity ranking
live.test.ts 1 Real upload to arweave.net via Turbo → read back → verify

Environment Variables

Variable Default Description
ARWEAVE_HOST localhost Arweave gateway host
ARWEAVE_PORT 1984 Arweave gateway port
ARWEAVE_PROTOCOL http http or https
ARWEAVE_WALLET_PATH Path to JWK wallet file
ARWEAVE_TXID Existing memory txid to auto-load

For live arweave.net usage:

ARWEAVE_HOST=arweave.net
ARWEAVE_PORT=443
ARWEAVE_PROTOCOL=https
ARWEAVE_WALLET_PATH=./wallet.json

Project Structure

arweave-memory/
├── package.json              # standalone deps
├── tsconfig.json             # TypeScript config
├── vitest.config.ts          # test config
├── wallet.json               # your Arweave JWK (gitignored)
└── src/
    ├── index.ts              # barrel export
    ├── types.ts              # Entity, Relation, KnowledgeGraph, EntityEmbedding
    ├── ArweaveConfig.ts      # connection config + wallet loading
    ├── LocalEmbeddingService.ts  # transformers.js embeddings
    ├── ArweaveStorageProvider.ts  # in-memory graph + Turbo upload
    ├── MemoryClient.ts       # high-level SDK
    ├── ArweaveStorageProvider.test.ts
    ├── MemoryClient.test.ts
    ├── LocalEmbeddingService.test.ts
    └── live.test.ts          # live arweave.net roundtrip

License

MIT

About

a permanent memory layer for agents that dont forget

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors