Memory

Custom Memory Layers

How to build your own memory layer by implementing MemoryHooks, choosing a slot and scope, and configuring budgets and timeouts.

Overview

Every built-in memory layer is just a MemoryLayer object. You can build your own by implementing the same interface. This guide walks through each decision point.

Step 1: Define Your State Type

Your layer manages a single state object of type TState. Define what you need to track:

interface MyLayerState {
  entries: string[];
  lastUpdated: number;
}

Step 2: Choose a Slot

The slot determines where your layer's output appears in the assembled prompt. Lower slots appear first.

import { Slot } from '@noetic/core';

// Use a built-in constant...
const slot = Slot.PROCEDURAL; // 250

// ...or pick your own number
const slot = 275; // between PROCEDURAL and EPISODIC

Built-in slot constants for reference:

ConstantValue
WORKING_MEMORY100
ENTITY150
OBSERVATIONS200
PROCEDURAL250
EPISODIC300
RAG350
SEMANTIC_RECALL400

Step 3: Choose a Scope

Scope controls when state is shared or isolated:

ScopeUse When
'execution'State should not survive past the current run
'thread'State should persist per conversation thread
'resource'State should be shared across threads for the same resource
'global'State should be shared across everything

Step 4: Configure the Budget

The budget controls how many tokens your layer can inject during recall:

// Fixed budget
budget: 500

// Range budget -- allocator distributes spare tokens
budget: { min: 200, max: 1500 }

// Let the runtime decide
budget: 'auto'

Step 5: Implement Hooks

Implement only the hooks you need. All hooks are optional.

import type { MemoryLayer, MemoryHooks } from '@noetic/core';

function myCustomLayer(): MemoryLayer<MyLayerState> {
  return {
    id: 'my-custom-layer',
    name: 'My Custom Layer',
    slot: 275,
    scope: 'thread',
    budget: { min: 200, max: 1000 },
    hooks: {
      async init({ storage }) {
        const saved = await storage.get<MyLayerState>('state');
        return {
          state: saved ?? { entries: [], lastUpdated: 0 },
        };
      },

      async recall({ state, budget }) {
        if (!state.entries.length) return null;

        const text = state.entries.join('\n');
        const content = `<my_context>\n${text}\n</my_context>`;

        return {
          items: [{
            id: 'my-layer-recall',
            type: 'message' as const,
            role: 'developer' as const,
            status: 'completed' as const,
            content: [{ type: 'input_text' as const, text: content }],
          }],
          tokenCount: Math.ceil(content.length / 4),
        };
      },

      async store({ newItems, state }) {
        // Extract relevant data from the LLM response
        const texts = newItems
          .filter((i) => i.type === 'message')
          .flatMap((i) => i.content)
          .filter((c) => c.type === 'output_text')
          .map((c) => c.text);

        if (!texts.length) return;

        return {
          state: {
            entries: [...state.entries, ...texts],
            lastUpdated: Date.now(),
          },
        };
      },

      async onComplete({ state, outcome }) {
        // Optionally finalize state based on outcome
        return {
          state: {
            ...state,
            lastUpdated: Date.now(),
          },
        };
      },
    },
  };
}

Step 6: Set Timeouts (Optional)

If any hook makes network calls or runs LLM inference, set a timeout to prevent hangs:

timeouts: {
  store: 30_000,   // 30 seconds for store
  recall: 10_000,  // 10 seconds for recall
}

Step 7: Register with AgentConfig

import type { AgentConfig } from '@noetic/core';

const agent: AgentConfig = {
  name: 'my-agent',
  description: 'An agent with custom memory',
  model: 'gpt-4o',
  instructions: 'You are a helpful assistant.',
  memory: [
    workingMemory(),
    myCustomLayer(),
    observationalMemory(),
  ],
  storage: myStorageAdapter,
  projection: {
    tokenBudget: 8000,
    responseReserve: 4000,
    overflow: 'truncate',
  },
};

Layers are ordered by slot number regardless of array order. The runtime calls each layer's hooks in slot order.

Hook Parameter Reference

InitParams

FieldType
storageScopedStorage
scopeKeystring
ctxExecutionContext

RecallParams

FieldType
logItemLog
querystring
ctxExecutionContext
stateTState
budgetnumber

StoreParams

FieldType
newItemsItem[]
logItemLog
responseLLMResponse
ctxExecutionContext
stateTState

SpawnParams

FieldType
parentStateTState
childCtxExecutionContext
spawnOptsSpawnOptions

ReturnParams

FieldType
childStateTState
childLogItemLog
parentStateTState
resultunknown

CompleteParams

FieldType
logItemLog
ctxExecutionContext
stateTState
outcomeExecutionOutcome

DisposeParams

FieldType
stateTState

On this page