Skip to main content

Memory System

Qwen Code’s memory system allows the AI to remember important facts, preferences, and context across sessions, providing personalized and context-aware assistance.

Overview

The memory system consists of:
  • Global Memory: User-level facts shared across all projects (~/.qwen/QWEN.md)
  • Project Memory: Project-specific context (.qwen/QWEN.md or QWEN.md in project root)
  • Hierarchical Loading: Automatic discovery from workspace root to current directory
  • Import System: Include external files and organize memory content

Memory Files

File Locations

From packages/core/src/tools/memoryTool.ts:77:
export const QWEN_CONFIG_DIR = '.qwen';
export const DEFAULT_CONTEXT_FILENAME = 'QWEN.md';
export const AGENT_CONTEXT_FILENAME = 'AGENTS.md';
export const MEMORY_SECTION_HEADER = '## Qwen Added Memories';
Global memory:
~/.qwen/QWEN.md
~/.qwen/AGENTS.md
Project memory:
/path/to/project/QWEN.md
/path/to/project/.qwen/QWEN.md
/path/to/project/AGENTS.md

File Format

Memory files are Markdown documents with optional structure:
# Project Context

This is a Next.js application using TypeScript and Tailwind CSS.

## Architecture

- Frontend: React components in `src/components/`
- Backend: API routes in `src/pages/api/`
- Database: PostgreSQL with Prisma ORM

## Qwen Added Memories

- User prefers functional components over class components
- Always use `const` instead of `let` when possible
- API endpoints should return JSON with snake_case keys
- Tests use Vitest instead of Jest

Memory Tool

The save_memory tool allows Qwen Code to store facts for future reference.

Tool Schema

From memoryTool.ts:31:
interface SaveMemoryParams {
  fact: string;                // Required: The fact to remember
  scope?: 'global' | 'project'; // Optional: Where to save
  modified_by_user?: boolean;   // Internal: User edited content
  modified_content?: string;    // Internal: Modified content
}

Usage Examples

Save to Global Memory

{
  fact: "User prefers TypeScript for new projects",
  scope: "global"
}
This appends to ~/.qwen/QWEN.md:
## Qwen Added Memories
- User prefers TypeScript for new projects

Save to Project Memory

{
  fact: "API uses OAuth2 with client credentials flow",
  scope: "project"
}
This appends to project’s QWEN.md:
## Qwen Added Memories
- API uses OAuth2 with client credentials flow

Interactive Scope Selection

If scope is omitted, user is prompted to choose:
{ fact: "Database credentials in 1Password vault" }
User sees:
Choose where to save this memory:

"Database credentials in 1Password vault"

Options:
- Global: ~/.qwen/QWEN.md (shared across all projects)
- Project: /path/to/project/QWEN.md (current project only)

Tool Behavior

From memoryTool.ts:165:
/**
 * Computes the new content that would result from adding a memory entry
 */
function computeNewContent(currentContent: string, fact: string): string {
  let processedText = fact.trim();
  processedText = processedText.replace(/^(-+\s*)+/, '').trim();
  const newMemoryItem = `- ${processedText}`;

  const headerIndex = currentContent.indexOf(MEMORY_SECTION_HEADER);

  if (headerIndex === -1) {
    // Header not found, append header and then the entry
    const separator = ensureNewlineSeparation(currentContent);
    return (
      currentContent +
      `${separator}${MEMORY_SECTION_HEADER}\n${newMemoryItem}\n`
    );
  } else {
    // Header found, append to section
    const startOfSectionContent = headerIndex + MEMORY_SECTION_HEADER.length;
    let endOfSectionIndex = currentContent.indexOf(
      '\n## ',
      startOfSectionContent,
    );
    if (endOfSectionIndex === -1) {
      endOfSectionIndex = currentContent.length;
    }

    const beforeSectionMarker = currentContent
      .substring(0, startOfSectionContent)
      .trimEnd();
    let sectionContent = currentContent
      .substring(startOfSectionContent, endOfSectionIndex)
      .trimEnd();
    const afterSectionMarker = currentContent.substring(endOfSectionIndex);

    sectionContent += `\n${newMemoryItem}`;
    return (
      `${beforeSectionMarker}\n${sectionContent.trimStart()}\n${afterSectionMarker}`.trimEnd() +
      '\n'
    );
  }
}
Key behaviors:
  • Creates ## Qwen Added Memories section if it doesn’t exist
  • Appends new facts as bullet points
  • Strips leading dashes from fact text
  • Maintains proper spacing

Hierarchical Memory Loading

Qwen Code automatically discovers and loads memory files in a hierarchical manner.

Discovery Process

From packages/core/src/utils/memoryDiscovery.ts:24:
async function findProjectRoot(startDir: string): Promise<string | null> {
  let currentDir = path.resolve(startDir);
  while (true) {
    const gitPath = path.join(currentDir, '.git');
    try {
      const stats = await fs.lstat(gitPath);
      if (stats.isDirectory()) {
        return currentDir;
      }
    } catch (error) {
      // Continue searching upward
    }
    const parentDir = path.dirname(currentDir);
    if (parentDir === currentDir) {
      return null; // Reached root
    }
    currentDir = parentDir;
  }
}

Load Order

From memoryDiscovery.ts:68:
export async function getGeminiMdFilePathsInternal(
  currentWorkingDirectory: string,
  includeDirectoriesToReadGemini: readonly string[],
  userHomePath: string,
  fileService: FileDiscoveryService,
  extensionContextFilePaths: string[] = [],
  folderTrust: boolean,
): Promise<string[]> {
  const dirs = new Set<string>([
    ...includeDirectoriesToReadGemini,
    currentWorkingDirectory,
  ]);
  
  // Process directories in parallel with concurrency limit
  const CONCURRENT_LIMIT = 10;
  // ...
}
Memory files are loaded in this order:
  1. Global memory (~/.qwen/QWEN.md)
  2. Project root (found via .git directory)
  3. Intermediate directories (between project root and current directory)
  4. Current directory
  5. Extension context files (if any)
Example for /home/user/projects/myapp/src/components/:
~/.qwen/QWEN.md                           # Global
/home/user/projects/myapp/QWEN.md         # Project root
/home/user/projects/myapp/src/QWEN.md     # Subdirectory
/home/user/projects/myapp/src/components/QWEN.md  # Current dir

Content Concatenation

From memoryDiscovery.ts:282:
function concatenateInstructions(
  instructionContents: GeminiFileContent[],
  currentWorkingDirectoryForDisplay: string,
): string {
  return instructionContents
    .filter((item) => typeof item.content === 'string')
    .map((item) => {
      const trimmedContent = (item.content as string).trim();
      if (trimmedContent.length === 0) {
        return null;
      }
      const displayPath = path.isAbsolute(item.filePath)
        ? path.relative(currentWorkingDirectoryForDisplay, item.filePath)
        : item.filePath;
      return `--- Context from: ${displayPath} ---\n${trimmedContent}\n--- End of Context from: ${displayPath} ---`;
    })
    .filter((block): block is string => block !== null)
    .join('\n\n');
}
Final memory content:
--- Context from: ../.qwen/QWEN.md ---
# Global Context
- User works with TypeScript
--- End of Context from: ../.qwen/QWEN.md ---

--- Context from: QWEN.md ---
# Project Context
- Next.js application
--- End of Context from: QWEN.md ---

--- Context from: src/QWEN.md ---
# Source Directory
- Components use Tailwind
--- End of Context from: src/QWEN.md ---

Import System

Memory files support importing other files.

Import Syntax

From packages/core/src/utils/memoryImportProcessor.ts:
<!-- @import: path/to/file.md -->
<!-- @import: ../docs/api-guide.md -->
<!-- @import: ~/.config/coding-standards.md -->

Import Formats

Tree Format (default):
<imported from="docs/setup.md">
# Setup Guide
...
</imported>
Flat Format:
# Setup Guide
...
Configuration:
import { loadServerHierarchicalMemory } from '@qwen-code/qwen-code-core';

const { memoryContent } = await loadServerHierarchicalMemory(
  cwd,
  includeDirs,
  fileService,
  extensionContextFilePaths,
  folderTrust,
  'tree'  // or 'flat'
);

Recursive Imports

Imports can be nested:
<!-- File: QWEN.md -->
# Project Context

<!-- @import: docs/architecture.md -->

<!-- File: docs/architecture.md -->
# Architecture

<!-- @import: api/endpoints.md -->

<!-- File: api/endpoints.md -->
# API Endpoints
...

Configuration

Custom Filenames

From memoryTool.ts:90:
export function setGeminiMdFilename(newFilename: string | string[]): void {
  if (Array.isArray(newFilename)) {
    if (newFilename.length > 0) {
      currentGeminiMdFilename = newFilename.map((name) => name.trim());
    }
  } else if (newFilename && newFilename.trim() !== '') {
    currentGeminiMdFilename = newFilename.trim();
  }
}
Usage:
import { setGeminiMdFilename } from '@qwen-code/qwen-code-core';

// Single filename
setGeminiMdFilename('CONTEXT.md');

// Multiple filenames (checked in order)
setGeminiMdFilename(['QWEN.md', 'AGENTS.md', 'CONTEXT.md']);

Folder Trust

Memory loading respects folder trust settings:
const { memoryContent } = await loadServerHierarchicalMemory(
  cwd,
  includeDirs,
  fileService,
  extensionContextFilePaths,
  folderTrust: true  // Enable workspace scanning
);
When folderTrust is false, only global memory and explicitly included directories are loaded.

Best Practices

When to Use Global vs Project Memory

Global Memory (~/.qwen/QWEN.md):
  • Personal preferences and coding style
  • Tool configurations
  • Common patterns across all projects
  • Credential locations (not the credentials themselves!)
Example:
## Qwen Added Memories
- User prefers functional programming style
- Always use `const` instead of `let` when possible
- Database credentials stored in 1Password
- Prefer explicit error handling over try-catch
Project Memory (project’s QWEN.md):
  • Project architecture and structure
  • Technology stack and versions
  • Build and deployment processes
  • Team conventions
  • API endpoints and schemas
Example:
## Project Context

Next.js 14 application with App Router.

### Architecture
- `/app`: Next.js App Router pages
- `/components`: Reusable React components
- `/lib`: Utility functions and API clients
- `/prisma`: Database schema and migrations

### Tech Stack
- TypeScript 5.3
- React 18
- Tailwind CSS 3.4
- Prisma 5.x
- PostgreSQL 16

## Qwen Added Memories
- API uses camelCase for request/response
- All components must have PropTypes
- Use React Query for data fetching
- Prefer server components over client components

Memory File Organization

Structure memory files for clarity:
# Project Title

Brief project description.

## Architecture

High-level architecture overview.

## Technology Stack

List of technologies and versions.

## Development Workflow

Build, test, and deployment processes.

## Coding Standards

Team conventions and preferences.

## Qwen Added Memories

- Dynamic facts added by Qwen Code
- User preferences specific to this project

Import Organization

Use imports to keep files manageable:
<!-- Main QWEN.md -->
# Project Context

<!-- @import: docs/architecture.md -->
<!-- @import: docs/api-reference.md -->
<!-- @import: docs/deployment.md -->

## Qwen Added Memories
- (Dynamic content here)
This keeps the main file clean while providing comprehensive context.

Avoid Sensitive Information

Never store sensitive data in memory files: Bad:
- Database password: super_secret_123
- API key: sk-1234567890abcdef
Good:
- Database credentials stored in 1Password vault "Production"
- API keys managed via environment variables in `.env.local`
- Secrets rotation handled by DevOps team quarterly

Advanced Usage

Programmatic Memory Access

import { 
  loadServerHierarchicalMemory,
  FileDiscoveryService 
} from '@qwen-code/qwen-code-core';

const fileService = new FileDiscoveryService();

const { memoryContent, fileCount } = await loadServerHierarchicalMemory(
  process.cwd(),
  ['/additional/path'],
  fileService,
  [],
  true,  // folderTrust
  'tree' // importFormat
);

console.log(`Loaded ${fileCount} memory files`);
console.log(memoryContent);

Memory in Resumable Sessions

Memory content is included in session checkpoints:
// Memory loaded at session start
const initialMemory = await loadServerHierarchicalMemory(...);

// Included in system prompt
const systemPrompt = `
${baseInstructions}

${initialMemory.memoryContent}
`;

// Memory persists across resume
qwen --resume <session-id>
// Same memory context restored

Extension Context Files

Extensions can provide additional context:
const extensionContextPaths = [
  '/path/to/extension/context.md',
  '/another/extension/docs.md'
];

const { memoryContent } = await loadServerHierarchicalMemory(
  cwd,
  includeDirs,
  fileService,
  extensionContextPaths,  // Added to memory
  true
);

Troubleshooting

Memory Not Loading

Problem: Qwen Code doesn’t seem to remember context. Check:
# Verify files exist
ls -la ~/.qwen/QWEN.md
ls -la QWEN.md
ls -la .qwen/QWEN.md

# Check folder trust
qwen config get folderTrust

# Enable if disabled
qwen config set folderTrust true

Import Loops

Problem: Circular imports cause issues. Solution: Imports track visited files to prevent loops:
// From memoryImportProcessor.ts
const visitedFiles = new Set<string>();

if (visitedFiles.has(resolvedPath)) {
  // Skip already imported file
  continue;
}
visitedFiles.add(resolvedPath);
Avoid circular references:
<!-- A.md -->
<!-- @import: B.md -->  ❌ Don't create loops

<!-- B.md -->
<!-- @import: A.md -->  ❌ Don't create loops

Large Memory Files

Problem: Memory files too large, wasting tokens. Solution:
  1. Use imports to split content
  2. Keep only essential information
  3. Archive outdated context
# Move old content to archive
mkdir -p .qwen/archive
mv QWEN.md .qwen/archive/QWEN-$(date +%Y%m%d).md

# Start fresh
cat > QWEN.md << 'EOF'
# Project Context

<!-- @import: .qwen/archive/QWEN-20240315.md -->

## Current Context
(New, relevant information)
EOF

Permission Issues

Problem: Cannot write to global memory file. Solution:
# Check permissions
ls -la ~/.qwen/

# Fix if needed
chmod 755 ~/.qwen
chmod 644 ~/.qwen/QWEN.md

# Create if missing
mkdir -p ~/.qwen
touch ~/.qwen/QWEN.md

Source Code References

  • Memory tool: packages/core/src/tools/memoryTool.ts
  • Memory discovery: packages/core/src/utils/memoryDiscovery.ts
  • Import processor: packages/core/src/utils/memoryImportProcessor.ts
  • Configuration: packages/core/src/config/config.ts:52