How to Set Up a Custom Letta Agent with a Different Memory Block Architecture

You can configure a custom memory block architecture by creating a new .af JSON file that defines your specific block labels, importing it via importDefaultAgent in scripts/agent_config.ts, and setting the resulting agent ID in your environment or config file—no hook modifications required.

The Claude-Subconscious system treats Letta agents as declarative JSON files (.af format) containing configurable memory blocks. When you need to track project health metrics, coding style preferences, or ticket statuses instead of the default memory layout, you can design a custom agent with a different memory block architecture by editing the agent definition and reusing the existing synchronization infrastructure.

Understanding the Memory Block Architecture

In the letta-ai/claude-subconscious codebase, an agent is simply a JSON-encoded .af file containing a blocks array. Each block is a label-value pair with optional description and limit fields, persisted in Letta’s git-backed memory filesystem (memfs).

The sync logic is completely generic. When Claude Code hooks run, fetchAgent in scripts/conversation_utils.ts (lines 441-447) loads whatever blocks exist, formatMemoryBlocksAsXml (lines 442-466) converts them to XML for injection into CLAUDE.md, and detectChangedBlocks in scripts/sync_letta_memory.ts (lines 108-119) calculates diffs between runs. Because these functions operate on the blocks array dynamically, you can freely redesign the memory-block layout without changing any TypeScript code.

Steps to Create a Custom Agent Memory Architecture

1. Define Your Custom Blocks in a New .af File

Create a new agent file (e.g., my-agent.af) following the schema demonstrated in Subconscious.af (lines 24-30). Define your custom memory blocks with unique labels, descriptions, and token limits.

{
  "agents": [
    {
      "name": "MyCustomAgent",
      "memory_blocks": [],
      "tools": [],
      "block_ids": [
        "block-0",
        "block-1",
        "block-2"
      ],
      "tags": [
        "origin:claude-subconcious"
      ],
      "system": "You are a custom agent that tracks project health, user-style preferences, and pending tickets.",
      "blocks": [
        {
          "label": "project_health",
          "description": "High-level health metrics (build status, test coverage, recent failures).",
          "value": "(No data yet)",
          "limit": 4000
        },
        {
          "label": "style_preferences",
          "description": "User-specific coding style choices (indentation, naming, lint rules).",
          "value": "(No data yet)",
          "limit": 3000
        },
        {
          "label": "pending_tickets",
          "description": "Open TODOs / tickets mentioned in the session.",
          "value": "(No pending tickets)",
          "limit": 3000
        }
      ]
    }
  ],
  "blocks": [],
  "tools": []
}

2. Import the Custom Agent

Use the importDefaultAgent helper in scripts/agent_config.ts (lines 78-86) to POST your .af file to the Letta server. This function reads the file, uploads it to the /agents/import endpoint, and returns the new agent ID.

import { importDefaultAgent } from './agent_config.js';
import * as path from 'path';

const CUSTOM_AGENT_PATH = path.resolve('my-agent.af');

async function importMyAgent(apiKey: string): Promise<string> {
  // Temporarily override the default path constant
  (global as any).DEFAULT_AGENT_FILE = CUSTOM_AGENT_PATH;
  
  const agentId = await importDefaultAgent(apiKey);
  console.log('✅ Imported custom agent:', agentId);
  return agentId;
}

3. Configure the Agent ID

Expose the new ID to the hooks by either:

  • Setting the LETTA_AGENT_ID environment variable, or
  • Saving it to ~/.letta/claude-subconscious/config.json

The validation logic in agent_config.ts (lines 33-42) checks both locations when resolving which agent to synchronize.

export LETTA_AGENT_ID=agent-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

# Optional: persist for future sessions

echo '{"agentId":"'"$LETTA_AGENT_ID"'"}' > ~/.letta/claude-subconscious/config.json

4. Ensure Required Tags

The sync scripts only act on agents carrying the git-memory-enabled and origin:claude-subconcious tags. Call ensureRequiredAgentTags in scripts/agent_config.ts (lines 155-162) to verify and add these automatically if missing.

5. Run Claude Code

The existing hooks—session_start.ts (line 185), sync_letta_memory.ts, and pretool_sync.ts—will automatically read your custom blocks and inject them into CLAUDE.md or stdout. Because formatMemoryBlocksAsXml processes the agent.blocks array generically, your custom architecture works immediately without code changes.

How the Runtime Handles Custom Architectures

The synchronization pipeline treats memory blocks as opaque data structures. When sync_letta_memory.ts executes, it:

  1. Fetches the agent via fetchAgent in conversation_utils.ts
  2. Iterates over agent.blocks regardless of label names
  3. Formats each block using formatMemoryBlocksAsXml into XML tags like <project_health>...</project_health>
  4. Detects changes via detectChangedBlocks by comparing current values against the previous state

This architecture means the only file you need to modify is your .af definition. The DEFAULT_AGENT_FILE constant in agent_config.ts (line 25) controls which file gets imported by default, but you can override this per-import as shown above.

Key Files for Custom Memory Architectures

  • Subconscious.af: The baseline agent definition demonstrating the JSON schema for blocks and tags (lines 24-30)
  • scripts/agent_config.ts: Contains importDefaultAgent, ensureRequiredAgentTags, and getAgentId for agent management
  • scripts/conversation_utils.ts: Defines the MemoryBlock and Agent types used throughout the sync pipeline (lines 324-334)
  • scripts/sync_letta_memory.ts: Handles full-mode memory synchronization and change detection (lines 108-119)
  • scripts/pretool_sync.ts: Manages mid-tool-use updates to ensure memory changes appear immediately (lines 84-95)

Summary

  • Custom architectures are defined by creating new .af JSON files with your specific block labels and limits
  • Import logic resides in importDefaultAgent within scripts/agent_config.ts, which POSTs your definition to Letta
  • Agent identification works via the LETTA_AGENT_ID environment variable or ~/.letta/claude-subconscious/config.json
  • Required tags (git-memory-enabled and origin:claude-subconcious) are enforced by ensureRequiredAgentTags
  • Hook compatibility is automatic—formatMemoryBlocksAsXml and detectChangedBlocks adapt to any block structure without modification

Frequently Asked Questions

How do I add a new memory block to an existing custom agent?

Edit your .af file to include a new block object with a unique label, description, value, and limit, then re-import the agent using importDefaultAgent. The detectChangedBlocks function in scripts/sync_letta_memory.ts will recognize the new block structure on the next synchronization cycle.

Can I use completely different block labels than the default Subconscious agent?

Yes. The formatMemoryBlocksAsXml function in scripts/conversation_utils.ts (lines 442-466) processes blocks dynamically based on their label property. The system imposes no restrictions on label names, provided each block includes the required label and value fields.

What happens if my agent is missing the required tags?

The ensureRequiredAgentTags function in scripts/agent_config.ts (lines 155-162) automatically appends git-memory-enabled and origin:claude-subconcious if they are absent. Without these tags, the sync scripts in sync_letta_memory.ts and pretool_sync.ts will skip the agent, treating it as ineligible for memory synchronization.

Is there a performance cost to having many memory blocks?

There is no hardcoded limit on block count in the synchronization logic. However, the limit field on each block controls token allocation, and the total formatted output from formatMemoryBlocksAsXml must fit within Claude’s available context window when injected into CLAUDE.md. Monitor the compiled XML size if defining numerous high-limit blocks.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →