← Back to DevBytes

Agent Memory Architectures with Claude Code: Complete Guide

Introduction to Agent Memory Architectures

Agent memory architectures define how autonomous AI agents store, retrieve, and use information across interactions. When building agents with Claude Code, memory is the foundation that separates a stateless chatbot from a truly autonomous system capable of learning, adapting, and maintaining context over extended workflows. Without memory, every agent invocation starts from scratch. With it, agents accumulate knowledge, track progress, and make increasingly informed decisions.

Claude Code, Anthropic's CLI-based agentic coding tool, provides a unique environment for building memory-aware agents. It operates with file system access, tool-use capabilities, and the ability to persist state across sessions. This makes it an ideal platform for implementing sophisticated memory architectures that go beyond simple conversation history.

Why Memory Matters for AI Agents

Memory is not a luxury feature for production agents — it is a necessity. Consider the difference between an agent that remembers a project's architecture decisions and one that rediscovers them every session. The first becomes more efficient over time; the second wastes tokens and time repeatedly.

Core Problems Memory Solves

Types of Agent Memory

Effective memory architectures draw from cognitive science, categorizing memory into distinct types that serve different purposes. Understanding these categories helps you design systems that use the right memory for the right task.

Short-Term (Working) Memory

Short-term memory holds the current context of an active task. In Claude Code, this is the conversation context within a single session — the messages, tool results, and reasoning that Claude maintains while working on your request. It is fast but volatile, lost when the session ends.

Long-Term Memory

Long-term memory persists across sessions. In Claude Code, this typically takes the form of files on disk — markdown notes, JSON databases, or structured logs that the agent can read at the start of a new session. This is where project knowledge, user preferences, and accumulated learnings live.

Episodic Memory

Episodic memory records specific events and experiences — what happened, when, and in what context. For an agent, this might be a log of actions taken, errors encountered, and outcomes observed. Episodic memory enables agents to recall "what happened last time I tried this approach."

Semantic Memory

Semantic memory stores general knowledge and facts, detached from specific events. For a coding agent, this includes understanding of the codebase architecture, API documentation, coding standards, and domain knowledge. It is the "what I know" memory rather than "what happened."

Procedural Memory

Procedural memory captures how to do things — workflows, patterns, and step-by-step procedures. For Claude Code agents, this manifests as saved prompts, workflow templates, and learned sequences of tool calls that reliably accomplish specific tasks.

Memory Architecture Patterns for Claude Code

Now let's explore practical architectures you can implement with Claude Code. Each pattern addresses different needs, and most production systems combine several.

Pattern 1: File-Based Knowledge Base

The simplest and most Claude Code-native approach uses markdown files as a persistent knowledge base. Claude Code can read these files at the start of a session to bootstrap its understanding of a project.

# project-memory/
# ā”œā”€ā”€ architecture.md       # Semantic: system design knowledge
# ā”œā”€ā”€ conventions.md        # Semantic: coding standards
# ā”œā”€ā”€ decisions.md          # Episodic: key decisions and rationale
# ā”œā”€ā”€ session-log.md        # Episodic: what happened in past sessions
# ā”œā”€ā”€ known-issues.md       # Episodic: bugs and workarounds
# └── user-preferences.md   # Semantic: how the user likes things done

Create a CLAUDE.md file at your project root that instructs Claude Code to load relevant memory files:

# CLAUDE.md

## Memory System

This project uses a file-based memory architecture. Before starting work:

1. Read `project-memory/architecture.md` to understand system design
2. Read `project-memory/decisions.md` to review past architectural decisions
3. Read `project-memory/known-issues.md` to avoid repeating past mistakes
4. Read `project-memory/conventions.md` to follow established patterns

After completing significant work:
1. Append a summary to `project-memory/session-log.md`
2. Update `project-memory/known-issues.md` if you encountered new problems
3. Update `project-memory/architecture.md` if the system design changed

Format for session-log.md entries:
## YYYY-MM-DD Session Summary
- **Task**: Brief description
- **Actions**: Key actions taken
- **Outcome**: Result and any follow-up needed
- **Learnings**: New insights worth remembering

Pattern 2: Structured Memory with JSON

For more queryable memory, use JSON files that Claude Code can parse and search programmatically. This works well for episodic memory where you need to filter by date, type, or outcome.

{
  "episodes": [
    {
      "id": "ep-001",
      "timestamp": "2024-12-15T10:30:00Z",
      "type": "debugging",
      "task": "Fix authentication middleware",
      "actions": [
        "Read middleware/auth.ts",
        "Identified missing token refresh logic",
        "Added refresh handler in line 45"
      ],
      "outcome": "success",
      "files_modified": ["middleware/auth.ts"],
      "learnings": [
        "Auth middleware needs explicit refresh token handling",
        "Token expiry errors surface as 401, not 403"
      ],
      "tags": ["auth", "middleware", "bugfix"]
    },
    {
      "id": "ep-002",
      "timestamp": "2024-12-15T14:00:00Z",
      "type": "feature",
      "task": "Add rate limiting to API endpoints",
      "actions": [
        "Installed express-rate-limit package",
        "Created middleware/rateLimit.ts",
        "Applied to /api routes"
      ],
      "outcome": "success",
      "files_modified": ["middleware/rateLimit.ts", "routes/api.ts"],
      "learnings": [
        "Rate limiting should be applied before auth middleware",
        "Default limit of 100 req/15min works for this API"
      ],
      "tags": ["rate-limiting", "middleware", "feature"]
    }
  ]
}

Configure Claude Code to query this memory effectively:

# CLAUDE.md

## Episodic Memory Query Protocol

When approaching a new task, query `project-memory/episodes.json`:

1. Search for episodes with matching tags
2. Review learnings from past similar tasks
3. Check if any known issues relate to files you'll touch
4. Note successful action sequences for procedural reuse

When logging a new episode after task completion:
1. Read the current episodes.json
2. Append new episode with all fields populated
3. Write the updated file back
4. Ensure JSON remains valid

Pattern 3: Hierarchical Memory with Summarization

For long-running projects, raw logs become unwieldy. A hierarchical approach maintains multiple levels of detail — raw logs at the bottom, summaries in the middle, and high-level insights at the top.

project-memory/
ā”œā”€ā”€ insights.md              # Top level: high-level learnings
ā”œā”€ā”€ weekly-summaries/
│   ā”œā”€ā”€ 2024-W50.md         # Mid level: weekly summaries
│   └── 2024-W51.md
ā”œā”€ā”€ daily-logs/
│   ā”œā”€ā”€ 2024-12-15.md       # Bottom level: daily detailed logs
│   └── 2024-12-16.md
└── raw-sessions/            # Optional: full session transcripts
    └── session-2024-12-15-001.md

Define the summarization protocol in your CLAUDE.md:

# CLAUDE.md

## Hierarchical Memory Management

### Daily Logging (every session)
Append to `project-memory/daily-logs/YYYY-MM-DD.md`:
- Tasks worked on
- Files modified
- Problems encountered and solutions
- Decisions made and rationale

### Weekly Summarization (every 7 days or on request)
Create `project-memory/weekly-summaries/YYYY-WXX.md`:
- Aggregate daily logs
- Identify patterns and recurring issues
- Note progress on long-term goals
- Highlight key learnings

### Insight Extraction (when significant patterns emerge)
Update `project-memory/insights.md`:
- Distill recurring patterns into principles
- Document architectural decisions that proved correct
- Record anti-patterns to avoid
- Note user preferences confirmed through experience

### Memory Retrieval Priority
When starting work, read in this order:
1. insights.md (always)
2. Current weekly summary (always)
3. Today's daily log if resuming (always)
4. Search daily logs for relevant past work (as needed)

Pattern 4: Vector-Indexed Memory

For projects with extensive memory, file-based retrieval becomes insufficient. You can build a vector-indexed memory system that Claude Code populates and queries through scripts.

# scripts/memory-index.py

import json
import hashlib
from datetime import datetime
from pathlib import Path

MEMORY_DIR = Path("project-memory")
INDEX_FILE = MEMORY_DIR / "memory-index.json"

def index_memory_file(filepath, category):
    """Index a memory file with metadata for retrieval."""
    content = filepath.read_text()
    file_hash = hashlib.md5(content.encode()).hexdigest()
    
    entry = {
        "path": str(filepath),
        "category": category,
        "hash": file_hash,
        "indexed_at": datetime.now().isoformat(),
        "size": len(content),
        "preview": content[:200]
    }
    return entry

def build_index():
    """Build complete memory index."""
    index = {"entries": []}
    
    # Index different memory types
    patterns = {
        "daily-logs/*.md": "episodic",
        "weekly-summaries/*.md": "episodic",
        "insights.md": "semantic",
        "architecture.md": "semantic",
        "conventions.md": "semantic",
        "decisions.md": "episodic",
        "known-issues.md": "episodic"
    }
    
    for pattern, category in patterns.items():
        for filepath in MEMORY_DIR.glob(pattern):
            entry = index_memory_file(filepath, category)
            index["entries"].append(entry)
    
    INDEX_FILE.write_text(json.dumps(index, indent=2))
    print(f"Indexed {len(index['entries'])} memory files")

if __name__ == "__main__":
    build_index()

Then instruct Claude Code to use this index:

# CLAUDE.md

## Vector-Indexed Memory System

This project uses an indexed memory system for efficient retrieval.

### Before starting complex tasks:
1. Run `python scripts/memory-index.py` to refresh the index
2. Read `project-memory/memory-index.json` to see available memories
3. Use the previews to identify relevant files
4. Read the full content of relevant memory files

### Memory categories:
- **semantic**: Architecture, conventions, insights (read for context)
- **episodic**: Logs, decisions, issues (search for specific past events)

### Query strategy:
- For "how does X work" questions → read semantic memories
- For "what happened when" questions → search episodic memories
- For "should I do X" questions → check decisions.md and known-issues.md

Implementing a Complete Memory System

Let's build a complete, production-ready memory system that combines all the patterns above. This system gives Claude Code structured memory across all five types.

Step 1: Create the Directory Structure

mkdir -p project-memory/{semantic,episodic,procedural,working}
touch project-memory/semantic/{architecture,conventions,insights}.md
touch project-memory/episodic/{decisions,known-issues,session-log}.md
touch project-memory/procedural/workflows.md
touch project-memory/working/current-task.md

Step 2: Create the Memory Manager Script

# scripts/memory-manager.py

import json
import os
from datetime import datetime
from pathlib import Path

MEMORY_DIR = Path("project-memory")

class AgentMemory:
    """Manages agent memory across all five memory types."""
    
    def __init__(self):
        self.semantic_dir = MEMORY_DIR / "semantic"
        self.episodic_dir = MEMORY_DIR / "episodic"
        self.procedural_dir = MEMORY_DIR / "procedural"
        self.working_dir = MEMORY_DIR / "working"
    
    def read(self, category, filename):
        """Read a memory file."""
        path = self._get_path(category, filename)
        if path.exists():
            return path.read_text()
        return f"Memory file not found: {path}"
    
    def append(self, category, filename, content, separator="\n\n---\n\n"):
        """Append to a memory file."""
        path = self._get_path(category, filename)
        existing = path.read_text() if path.exists() else ""
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        entry = f"## {timestamp}\n\n{content}"
        
        if existing:
            path.write_text(existing + separator + entry)
        else:
            path.write_text(entry)
        
        return f"Appended to {path}"
    
    def write(self, category, filename, content):
        """Overwrite a memory file."""
        path = self._get_path(category, filename)
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(content)
        return f"Wrote to {path}"
    
    def search(self, query, category=None):
        """Search memory files for a query string."""
        results = []
        search_dirs = [self._get_dir(category)] if category else [
            self.semantic_dir, self.episodic_dir, 
            self.procedural_dir, self.working_dir
        ]
        
        for search_dir in search_dirs:
            if not search_dir.exists():
                continue
            for filepath in search_dir.glob("*.md"):
                content = filepath.read_text().lower()
                if query.lower() in content:
                    # Find matching lines for context
                    lines = content.split("\n")
                    matches = [
                        line.strip() for line in lines 
                        if query.lower() in line.lower()
                    ]
                    results.append({
                        "file": str(filepath),
                        "matches": matches[:5]
                    })
        
        return json.dumps(results, indent=2)
    
    def log_episode(self, task, actions, outcome, learnings, files=None, tags=None):
        """Log an episodic memory entry."""
        entry = {
            "task": task,
            "actions": actions,
            "outcome": outcome,
            "learnings": learnings,
            "files_modified": files or [],
            "tags": tags or []
        }
        
        content = f"""**Task**: {entry['task']}
**Outcome**: {entry['outcome']}
**Actions**:
"""
        for action in entry['actions']:
            content += f"- {action}\n"
        
        content += "\n**Learnings**:\n"
        for learning in entry['learnings']:
            content += f"- {learning}\n"
        
        if entry['files_modified']:
            content += "\n**Files Modified**:\n"
            for f in entry['files_modified']:
                content += f"- {f}\n"
        
        if entry['tags']:
            content += f"\n**Tags**: {', '.join(entry['tags'])}\n"
        
        return self.append("episodic", "session-log.md", content)
    
    def _get_dir(self, category):
        dirs = {
            "semantic": self.semantic_dir,
            "episodic": self.episodic_dir,
            "procedural": self.procedural_dir,
            "working": self.working_dir
        }
        return dirs.get(category, self.working_dir)
    
    def _get_path(self, category, filename):
        return self._get_dir(category) / filename

if __name__ == "__main__":
    import sys
    mem = AgentMemory()
    
    if len(sys.argv) < 2:
        print("Usage: python memory-manager.py  [args]")
        print("Commands: read, append, write, search, log-episode")
        sys.exit(1)
    
    cmd = sys.argv[1]
    
    if cmd == "read":
        print(mem.read(sys.argv[2], sys.argv[3]))
    elif cmd == "search":
        print(mem.search(sys.argv[2], sys.argv[3] if len(sys.argv) > 3 else None))
    elif cmd == "log-episode":
        print(mem.log_episode(
            task=sys.argv[2],
            actions=sys.argv[3].split("|"),
            outcome=sys.argv[4],
            learnings=sys.argv[5].split("|")
        ))

Step 3: Configure CLAUDE.md for the Memory System

# CLAUDE.md

## Agent Memory System

This project implements a five-type memory architecture. Use it systematically.

### Session Start Protocol
1. Read `project-memory/semantic/insights.md` for high-level learnings
2. Read `project-memory/semantic/architecture.md` for system understanding
3. Read `project-memory/semantic/conventions.md` for coding standards
4. Read `project-memory/working/current-task.md` for in-progress work
5. Run `python scripts/memory-manager.py search ` for task-specific history

### During Work
- Update `project-memory/working/current-task.md` with progress notes
- When you discover something important, append to the appropriate memory file
- If you encounter an error, check `project-memory/episodic/known-issues.md` first

### Session End Protocol
1. Log the episode using the memory manager:
   python scripts/memory-manager.py log-episode \
     "Task description" \
     "Action 1|Action 2|Action 3" \
     "success|failure|partial" \
     "Learning 1|Learning 2"
   2. Update `project-memory/working/current-task.md` with final status
3. If new issues were found, append to `project-memory/episodic/known-issues.md`
4. If architectural decisions were made, append to `project-memory/episodic/decisions.md`
5. If new conventions were established, update `project-memory/semantic/conventions.md`
6. If significant insights emerged, update `project-memory/semantic/insights.md`

### Memory File Guidelines
- **insights.md**: Distilled principles, max 1-2 sentences each
- **architecture.md**: Current system design, update when design changes
- **conventions.md**: Coding rules and patterns, with examples
- **decisions.md**: Decision + rationale + date + alternatives considered
- **known-issues.md**: Problem + workaround + status (open/resolved)
- **session-log.md**: Chronological record of work sessions
- **workflows.md**: Step-by-step procedures for recurring tasks
- **current-task.md**: Active task context, updated during work

Step 4: Seed Initial Memory

Populate your memory files with initial content so Claude Code has context from the start:

# project-memory/semantic/architecture.md

# System Architecture

## Overview
This is a Node.js/TypeScript application using Express for the API layer
and PostgreSQL for data persistence. The frontend is a React SPA.

## Key Components
- `src/api/` - Express route handlers and middleware
- `src/services/` - Business logic layer
- `src/models/` - Database models and queries
- `src/utils/` - Shared utilities
- `frontend/src/` - React application

## Data Flow
Request → Middleware → Route Handler → Service → Model → Database
Response ← Route Handler ← Service ← Model ← Database

## Key Decisions
- Services are stateless and dependency-injected
- All database access goes through the model layer
- API responses follow JSON:API specification
# project-memory/semantic/conventions.md

# Coding Conventions

## Naming
- Files: kebab-case (e.g., `user-service.ts`)
- Classes: PascalCase (e.g., `UserService`)
- Functions/variables: camelCase (e.g., `getUserById`)
- Constants: UPPER_SNAKE_CASE (e.g., `MAX_RETRIES`)
- Database tables: snake_case (e.g., `user_sessions`)

## Error Handling
- All async functions use try/catch with typed errors
- Custom error classes extend AppError in `src/utils/errors.ts`
- Never swallow errors — always log or rethrow
- API errors return consistent JSON structure

## Testing
- Unit tests: `*.test.ts` alongside source files
- Integration tests: `tests/integration/` directory
- Use Jest with supertest for API tests
- Minimum 80% coverage for services and models

## Git
- Commit messages: `type(scope): description`
- Types: feat, fix, refactor, docs, test, chore
- Always run tests before committing
# project-memory/episodic/known-issues.md

# Known Issues

## 2024-12-10: PostgreSQL connection pool exhaustion
**Problem**: Under high load, connections were not being released properly.
**Root cause**: Missing `client.release()` in error paths in `src/models/base.ts`.
**Workaround**: Added finally block to ensure release.
**Status**: Resolved
**Files**: `src/models/base.ts`

## 2024-12-12: JWT token validation fails intermittently
**Problem**: Some valid tokens are rejected with "invalid signature" error.
**Root cause**: Clock skew between auth server and API server.
**Workaround**: Added 30-second leeway in JWT verification options.
**Status**: Resolved
**Files**: `src/middleware/auth.ts`

Advanced Memory Patterns

Memory Compaction and Forgetting

Just as human memory forgets irrelevant details, agent memory systems need compaction strategies to prevent bloat. Without compaction, memory files grow until they exceed useful context windows or become too noisy to search effectively.

# scripts/memory-compact.py

"""Compacts episodic memory by summarizing old entries."""

from pathlib import Path
from datetime import datetime, timedelta

EPISODIC_DIR = Path("project-memory/episodic")
ARCHIVE_DIR = Path("project-memory/episodic/archive")
RETENTION_DAYS = 30

def compact_session_log():
    """Archive entries older than retention period."""
    log_file = EPISODIC_DIR / "session-log.md"
    if not log_file.exists():
        return
    
    content = log_file.read_text()
    sections = content.split("\n\n---\n\n")
    
    cutoff = datetime.now() - timedelta(days=RETENTION_DAYS)
    kept_sections = []
    archived_sections = []
    
    for section in sections:
        # Extract date from section header
        if section.startswith("## "):
            date_str = section.split("\n")[0].replace("## ", "").strip()
            try:
                section_date = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")
                if section_date < cutoff:
                    archived_sections.append(section)
                else:
                    kept_sections.append(section)
            except ValueError:
                kept_sections.append(section)
        else:
            kept_sections.append(section)
    
    # Write retained entries
    log_file.write_text("\n\n---\n\n".join(kept_sections))
    
    # Archive old entries
    if archived_sections:
        ARCHIVE_DIR.mkdir(parents=True, exist_ok=True)
        archive_file = ARCHIVE_DIR / f"session-log-{datetime.now().strftime('%Y%m%d')}.md"
        archive_file.write_text("\n\n---\n\n".join(archived_sections))
        print(f"Archived {len(archived_sections)} entries to {archive_file}")
    
    print(f"Retained {len(kept_sections)} entries in session log")

if __name__ == "__main__":
    compact_session_log()

Cross-Project Memory Sharing

Some knowledge transfers across projects — general coding patterns, tool configurations, common debugging approaches. A shared memory directory lets agents benefit from experience gained in other projects.

# Directory structure
~/.claude-memory/              # Global, cross-project memory
ā”œā”€ā”€ insights.md               # Universal learnings
ā”œā”€ā”€ tool-patterns.md          # How to use tools effectively
ā”œā”€ā”€ common-bugs.md            # Bugs seen across projects
└── best-practices.md         # General best practices

project-memory/               # Project-specific memory
ā”œā”€ā”€ semantic/
ā”œā”€ā”€ episodic/
ā”œā”€ā”€ procedural/
└── working/
# CLAUDE.md

## Cross-Project Memory

In addition to project-specific memory, this agent uses a global memory
store at `~/.claude-memory/`.

### Session Start
1. Read `~/.claude-memory/insights.md` for universal learnings
2. Read `~/.claude-memory/best-practices.md` for general guidance
3. Then read project-specific memory as described above

### When you learn something universally applicable:
- Append to `~/.claude-memory/insights.md`
- This knowledge will be available in all future projects

### When you learn something project-specific:
- Keep it in `project-memory/`
- Do not pollute global memory with project-specific details

Memory Validation and Health Checks

Memory systems can degrade over time — files become outdated, contradictions emerge, and important information gets buried. Regular health checks keep memory reliable.

# scripts/memory-health.py

"""Validates memory system health and reports issues."""

import json
from pathlib import Path
from datetime import datetime, timedelta

MEMORY_DIR = Path("project-memory")

def check_memory_health():
    """Run health checks on the memory system."""
    report = {
        "timestamp": datetime.now().isoformat(),
        "issues": [],
        "stats": {}
    }
    
    # Check 1: All required files exist
    required_files = [
        "semantic/insights.md",
        "semantic/architecture.md",
        "semantic/conventions.md",
        "episodic/decisions.md",
        "episodic/known-issues.md",
        "episodic/session-log.md",
        "procedural/workflows.md",
        "working/current-task.md"
    ]
    
    for filepath in required_files:
        full_path = MEMORY_DIR / filepath
        if not full_path.exists():
            report["issues"].append(f"Missing required file: {filepath}")
    
    # Check 2: File sizes are reasonable
    for md_file in MEMORY_DIR.rglob("*.md"):
        size = md_file.stat().st_size
        if size > 100_000:  # 100KB
            report["issues"].append(
                f"File too large ({size} bytes): {md_file} — consider compaction"
            )
        report["stats"][str(md_file)] = size
    
    # Check 3: Session log has recent entries
    session_log = MEMORY_DIR / "episodic" / "session-log.md"
    if session_log.exists():
        content = session_log.read_text()
        today = datetime.now().strftime("%Y-%m-%d")
        week_ago = (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d")
        if today not in content and week_ago not in content:
            report["issues"].append(
                "Session log has no entries in the past week"
            )
    
    # Check 4: Known issues have status fields
    known_issues = MEMORY_DIR / "episodic" / "known-issues.md"
    if known_issues.exists():
        content = known_issues.read_text()
        if "**Status**:" not in content:
            report["issues"].append(
                "Known issues file missing status fields"
            )
    
    # Check 5: Current task file is not stale
    current_task = MEMORY_DIR / "working" / "current-task.md"
    if current_task.exists():
        mtime = datetime.fromtimestamp(current_task.stat().st_mtime)
        if datetime.now() - mtime > timedelta(days=7):
            report["issues"].append(
                "Current task file is stale (not updated in 7+ days)"
            )
    
    # Output report
    print(json.dumps(report, indent=2))
    
    if report["issues"]:
        print(f"\nāš ļø  {len(report['issues'])} issues found")
    else:
        print("\nāœ… Memory system is healthy")

if __name__ == "__main__":
    check_memory_health()

Best Practices for Agent Memory

Design Principles

Operational Practices

Common Pitfalls to Avoid

Conclusion

Agent memory architectures transform Claude Code from a capable but stateless assistant into a continuously improving collaborator. By implementing the five memory types — short-term, long-term, episodic, semantic, and procedural — you give your agent the ability to learn from experience, avoid repeating mistakes, and build on past successes. The file-based approach described here leverages Claude Code's native file system access, making it practical to implement without external infrastructure. Start with a simple CLAUDE.md and a few memory files, then gradually add scripts for indexing, compaction, and health checks as your needs grow. The investment in memory architecture pays dividends every session, as your agent becomes increasingly knowledgeable about your project, your preferences, and the patterns that lead to successful outcomes. Remember that memory is a living system — it requires maintenance, validation, and periodic pruning to remain accurate and useful. With disciplined memory management, your Claude Code agents will compound their effectiveness over time, turning each session's learnings into permanent capabilities.

— Ad —

Google AdSense will appear here after approval

← Back to all articles