Skip to content

dwright-snhu/portable-memory

Repository files navigation

Portable Learner Memory

A standalone LangGraph experiment demonstrating portable learner memory with scoped access, version history, and interoperable export formats.

Created for: Global Portable Memory Workshop (Gates Foundation, March 2026)

Overview

This experiment implements the "minimal interoperable schema" question from the Pre-read document: What must an AI learning system remember about a learner to personalize effectively, and how should that be represented for portability across tools?

Key Features

  • Multi-agent memory sharing - Math Tutor and Writing Coach share memory with scoped access
  • Source type distinction - user_declared vs model_inferred prevents identity hardening
  • Version history - Corrections create audit trail, previous versions can be restored
  • Multi-dimensional scopes - Filter by subject, category, and purpose
  • Interoperable exports - CLR and Caliper formats with round-trip support

Quick Start

# 1. Copy environment file and add your API key
cp .env.example .env
# Edit .env with your OPENAI_API_KEY

# 2. Install dependencies
make sync

# 3. Run the demos
make demo-alice       # Cross-agent memory sharing (Alice story)
make demo-correction  # Version history and corrections

# Or start LangGraph Studio with both agents
make dev

Deployment Options

Local Development (Default)

Uses OpenAI + in-memory storage:

# .env
MODEL_PROVIDER=openai
STORAGE_BACKEND=memory
OPENAI_API_KEY=sk-...

Local with DynamoDB

Uses OpenAI + DynamoDB Local for persistence testing:

make dynamodb-start           # Start DynamoDB Local
make dynamodb-create-tables   # Create tables
STORAGE_BACKEND=dynamodb DYNAMODB_ENDPOINT=http://localhost:8000 make dev

AWS Deployment

Uses Bedrock + DynamoDB for production:

# Deploy infrastructure
./scripts/deploy-infrastructure.sh dev

# Build and push Docker image
make docker-build
# Push to ECR (see CloudFormation outputs for URI)

# Environment in AWS
MODEL_PROVIDER=bedrock
STORAGE_BACKEND=dynamodb
# No DYNAMODB_ENDPOINT = uses real DynamoDB
# Credentials via IAM role

Architecture

Implements the five-component architecture from Pre-read Section 2.3:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   PortableMemoryService                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Memory Store    β”‚  Policy/AuthZ  β”‚  Context Router             β”‚
β”‚  - MemoryItem    β”‚  - Grants      β”‚  - Multi-dimensional        β”‚
β”‚  - Categories    β”‚  - Scopes      β”‚    scope filtering          β”‚
β”‚  - Source types  β”‚  - Validation  β”‚  - Purpose-based access     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚       Audit Log                β”‚      Interoperability          β”‚
β”‚  - All operations logged       β”‚  - CLR export/import           β”‚
β”‚  - Evaluation metrics          β”‚  - Caliper export/import       β”‚
β”‚  - Minimization tracking       β”‚  - Scope-filtered exports      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Agents

Both agents share the same PortableMemoryService but see different memories based on their grants:

Agent Scope Can See
math_tutor Math subjects + general Math mastery, general preferences
writing_coach Writing subjects + general Writing mastery, general preferences

Demos

Alice Story (make demo-alice)

Demonstrates Pre-read Section 3 - cross-agent memory sharing:

  1. Alice works with Math Tutor, building a learner profile
  2. She switches to Writing Coach with scoped access
  3. Writing Coach sees general preferences but NOT math-specific mastery
  4. Exports show different content based on scope/purpose

Correction Flow (make demo-correction)

Demonstrates Pre-read Section 4.1/4.4 - correction pathways:

  1. Math Tutor infers "Struggles with word problems" (model_inferred, confidence: 0.48)
  2. Alice corrects: "I've been practicing, I'm better now" (user_declared, confidence: 0.65)
  3. Assessment confirms improvement (confidence: 0.78)
  4. Full version history preserved (v1 β†’ v2 β†’ v3)

Memory Categories

Category Description Example
fact Background about the learner "Works as a nurse with rotating shifts"
goal What they're trying to achieve "Wants to pass the algebra final"
preference How they like to learn "Prefers step-by-step examples"
mastery What they know/can do "Understands basic fractions"
constraint Limitations on learning "Only 30 minutes per day"
relationship Connections to others "Studies with her sister"

Source Types

Pre-read Section 2.4 - preventing identity hardening:

Source Type Meaning UI Indicator
user_declared Learner explicitly stated [U]
model_inferred Agent inferred from conversation [I]

User-declared memories have higher trust and are prioritized in exports (e.g., college applications exclude inferences).

Scopes and Grants

Scopes

Multi-dimensional filtering (Pre-read Section 2.5):

math_scope = Scope(
    agent_id="math_tutor",
    allowed_subjects=["math", "algebra", "geometry"],
    allowed_categories=["mastery", "preference", "goal"],
    allowed_purposes=["tutoring", "assessment"],
    include_inferred=True,
    can_write=True,
)

Grants

Session-scoped access control:

grant = Grant(
    grant_id="g-123",
    learner_id="alice",
    agent_id="math_tutor",
    scope=math_scope,
    created_at=datetime.now(UTC),
    expires_at=datetime.now(UTC) + timedelta(hours=1),
)

Export Formats

Both formats support scope-filtered exports:

CLR (Comprehensive Learner Record)

from memory.service import PortableMemoryService

service = PortableMemoryService()
clr = service.export_clr(learner_id="alice", grant=math_grant)
# Only includes memories visible to math_tutor scope

Caliper

caliper = service.export_caliper(learner_id="alice", grant=math_grant)
# AssessedEvent format with scope-filtered memories

Version History

All changes are tracked (Pre-read Section 4.1):

# Correct a memory
service.correct_memory(
    memory_id=mem_id,
    new_content="Improving on word problems",
    reason="Learner self-reported improvement",
    grant=grant,
    new_source_type="user_declared",
    new_confidence=0.65,
)

# View history
history = service.get_version_history(mem_id)
# Returns: [v1: created, v2: corrected, ...]

Audit Trail

All operations logged for learner inspection:

entries = service.audit_log.query(
    learner_id="alice",
    operation="search",  # Optional filter
)

# Each entry includes:
# - timestamp, operation, agent_id
# - memories accessed/modified
# - total_available vs returned_after_scope (minimization tracking)

Testing

# Run all tests (130 tests)
make test

# Run specific test file
uv run pytest tests/unit/test_scopes.py -v

# Run demos (requires API key)
make demo-all

Project Structure

portable_memory/
β”œβ”€β”€ graph/                      # LangGraph agents
β”‚   β”œβ”€β”€ server.py              # Exports math_tutor, writing_coach
β”‚   β”œβ”€β”€ models.py              # Model provider factory (OpenAI/Bedrock)
β”‚   └── agents/
β”‚       β”œβ”€β”€ base.py            # Shared agent builder
β”‚       β”œβ”€β”€ math_tutor.py      # Math Tutor graph
β”‚       └── writing_coach.py   # Writing Coach graph
β”‚
β”œβ”€β”€ memory/                     # Memory system
β”‚   β”œβ”€β”€ schema.py              # MemoryItem, LearnerProfile
β”‚   β”œβ”€β”€ service.py             # PortableMemoryService (central)
β”‚   β”œβ”€β”€ scopes.py              # Multi-dimensional scopes
β”‚   β”œβ”€β”€ grants.py              # Access grants
β”‚   β”œβ”€β”€ versioning.py          # Version history
β”‚   β”œβ”€β”€ audit.py               # Audit logging
β”‚   β”œβ”€β”€ security.py            # Security validation (seam)
β”‚   β”œβ”€β”€ conflicts.py           # Conflict detection (seam)
β”‚   β”œβ”€β”€ persistence/           # Storage backends
β”‚   β”‚   β”œβ”€β”€ __init__.py        # StorageBackend interface
β”‚   β”‚   β”œβ”€β”€ memory.py          # In-memory storage (default)
β”‚   β”‚   └── dynamodb.py        # DynamoDB storage (AWS)
β”‚   └── formats/               # Export/import
β”‚       β”œβ”€β”€ clr.py             # CLR format
β”‚       └── caliper.py         # Caliper format
β”‚
β”œβ”€β”€ api/                        # FastAPI demo endpoints
β”‚   └── main.py                # Demo API server
β”‚
β”œβ”€β”€ infrastructure/            # AWS deployment
β”‚   β”œβ”€β”€ cloudformation.yaml    # DynamoDB, S3, IAM resources
β”‚   └── agentcore-agent.yaml   # AgentCore config
β”‚
β”œβ”€β”€ scripts/                   # Deployment scripts
β”‚   β”œβ”€β”€ create_tables.py       # DynamoDB table creation
β”‚   └── deploy-infrastructure.sh
β”‚
β”œβ”€β”€ tests/                      # Test suite
β”‚   β”œβ”€β”€ unit/                  # Unit tests
β”‚   └── integration/           # Cross-agent tests
β”‚
β”œβ”€β”€ Dockerfile                 # Container image
β”œβ”€β”€ demo_alice.py              # Alice story demo
└── demo_correction.py         # Correction flow demo

Extensibility Seams

The following are designed for future extension (placeholder implementations):

Seam Location Future Work
OAuth integration memory/grants.py Real OAuth flow
Preview mode memory/service.py "What would agent see?"
Revocation propagation memory/grants.py Downstream notification
On-device models memory/service.py Local extraction
Evaluation metrics memory/audit.py Pre-read 4.2 metrics
Adversarial robustness memory/security.py Prompt injection defense
Conflict resolution memory/conflicts.py Semantic contradiction detection

Pre-read References

This implementation addresses:

  • Section 2.3: Five-component architecture
  • Section 2.4: Source type distinction, identity hardening prevention
  • Section 2.5: Two invariants (no unconstrained access, enforceable operations)
  • Section 3: Alice story (Table 1 learner profile structure)
  • Section 4.1: Memory lifecycle with versioning
  • Section 4.4: Correction pathways, provenance metadata

Workshop Context

This experiment supports the Global Portable Memory Workshop's exploration of:

  1. Minimal Schema (Section 4.1): What's the smallest interoperable format?
  2. Evaluation (Section 4.2): How do we measure if portable memory works?
  3. Governance (Section 4.4): Who controls memory, and how?

Related Standards (Represented at Workshop)

  • 1EdTech (CLR, Caliper, Open Badges) - Tim Couper, Chief Architect
  • Learning Economy Foundation (Verifiable Credentials) - Chris Puriofy, CEO
  • Inrupt (Solid pods) - Max Leonard, Principal Technologist
  • WGU Labs (Achievement Wallet) - Taylor Hansen

Standalone Design

This experiment is designed to be extractable to a separate repository:

  • Pluggable storage: In-memory (default) or DynamoDB via STORAGE_BACKEND env var
  • Pluggable models: OpenAI (default) or Bedrock via MODEL_PROVIDER env var
  • No platform dependencies: No le-exp-platform imports
  • Container-ready: Dockerfile for AWS AgentCore deployment
  • Infrastructure as code: CloudFormation template for AWS resources

To extract: copy the portable_memory/ folder, update imports if needed.

Environment Variables

Variable Values Default Description
MODEL_PROVIDER openai, bedrock openai LLM provider
STORAGE_BACKEND memory, dynamodb memory Storage backend
OPENAI_API_KEY - - Required for OpenAI
AWS_REGION - us-east-1 AWS region for Bedrock/DynamoDB
DYNAMODB_ENDPOINT URL - For DynamoDB Local
TUTOR_MODEL model name gpt-4o Main tutor model
EXTRACTION_MODEL model name gpt-4o-mini Memory extraction model

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages