NOETIC
Framework
API Reference

Memory Types

Type definitions for memory layers, hooks, storage adapters, and projection policies in Noetic.

MemoryLayer

The core memory layer interface.

declare interface MemoryLayerShape<TState = unknown> {
  id: string;
  name?: string;
  slot: number;
  scope: MemoryScope;
  budget?: BudgetConfig;
  hooks: MemoryHooks<TState>;
  timeouts?: Partial<LayerTimeouts>;
  onInitError?: 'throw' | 'disable';
  recallMode?: 'atomic' | 'eventual';
  provides?: unknown;
  itemSchemas?: unknown;
  rerenderTiming?: 'immediate' | 'batched';
}
FieldTypeRequiredDescription
idstringyesUnique layer identifier.
namestringnoHuman-readable name for debugging and trace output.
slotnumberyesOrdering slot (lower = recalled first). Use the Slot constants for well-known positions.
scopeMemoryScopeyesPersistence boundary.
budgetBudgetConfignoToken budget allocation: a fixed number, a { min, max } range, or 'auto'.
hooksMemoryHooks<TState>yesLifecycle callbacks.
timeoutsPartial<LayerTimeouts>noPer-hook timeout overrides (ms).
onInitError'throw' | 'disable'noWhat to do when init throws. 'throw' (default) surfaces the error and aborts the execution; 'disable' logs a diagnostic and runs the execution without this layer. Opt into 'disable' only for non-critical layers.
recallMode'atomic' | 'eventual'noWhether recall() blocks the model call. 'atomic' (default) runs in the hot path; 'eventual' serves recall from cache and refreshes after store(), so the next turn sees the update. A harness configured with forceAtomicRecall treats every layer as atomic.
providesLayerProvidesnoTyped data and function declarations exposed on ctx.memory['layerId']. Function entries are also auto-injected as LLM tools.
itemSchemasPick<ItemSchemaExtensions, 'developerMessages' | 'items'>noOptional item schemas contributed by this layer, primarily for developer-role memory items.
rerenderTiming'immediate' | 'batched'noDefault re-render timing when onItemAppend requests a re-render.

MemoryScope

type MemoryScope = 'thread' | 'resource' | 'global' | 'execution';

BudgetConfig

type BudgetConfig =
  | number
  | { min: number; max: number }
  | 'auto';

An omitted budget behaves like 'auto': the layer has infinite headroom and splits the proportional pool with the other auto layers after finite layers take their share.

Slot Constants

const SlotShape = {
  REMINDER: 80,
  STEERING: 90,
  WORKING_MEMORY: 100,
  ENTITY: 150,
  OBSERVATIONS: 200,
  PROCEDURAL: 250,
  EPISODIC: 300,
  RAG: 350,
  SEMANTIC_RECALL: 400,
} as const satisfies Record<string, number>;

The runtime exports the Slot constant from @noetic-tools/core with these values; pass any of them as a layer's slot field to position it consistently with built-in layers.

MemoryHooks

interface MemoryHooks<TState = unknown> {
  init?: (params: InitParams) => Promise<InitResult<TState>>;
  recall?: (params: RecallParams<TState>) => Promise<RecallResult<TState> | string | null>;
  store?: (params: StoreParams<TState>) => Promise<StoreResult<TState> | undefined>;
  onSpawn?: (params: SpawnParams<TState>) => Promise<SpawnResult<TState> | null>;
  onReturn?: (params: ReturnParams<TState>) => Promise<ReturnResult<TState> | undefined>;
  onComplete?: (params: CompleteParams<TState>) => Promise<void | { state: TState }>;
  dispose?: (params: DisposeParams<TState>) => Promise<void>;
  beforeToolCall?: (params: BeforeToolCallParams<TState>) => Promise<BeforeToolCallResult<TState>>;
  afterModelCall?: (params: AfterModelCallParams<TState>) => Promise<AfterModelCallResult<TState>>;
  onItemAppend?: (params: OnItemAppendParams<TState>) => Promise<OnItemAppendResult<TState>>;
  projectHistory?: (params: ProjectHistoryParams<TState>) => Promise<ProjectHistoryResult>;
}
  • recall may also return a bare string shorthand — the runtime wraps it in a developer message and estimates its token count — or null to contribute nothing this turn.
  • beforeToolCall / afterModelCall are the steering hooks: they return a SteeringDecision (allow / deny / guide) plus updated state. A deny surfaces as a NoeticError kind steering_denied.
  • onItemAppend runs when input items (user messages, tool outputs) are about to be appended — layers compose in slot order, each receiving the previous layer's output. It can transform, drop, or inject items, and may request a context re-render. Not called for LLM response items (use store).
  • projectHistory projects (caps, transforms) the history portion of the context window once per LLM step. Read-side only — itemLog storage is never mutated.

Hook Parameter Types

InitParams

FieldTypeDescription
storageScopedStorageScoped storage for this layer
scopeKeystringResolved scope key
ctxExecutionContextExecution context

InitResult

FieldTypeDescription
stateTStateInitial layer state

RecallParams

FieldTypeDescription
logItemLogCurrent conversation log
querystringCurrent user query
ctxExecutionContextExecution context
stateTStateCurrent layer state
budgetnumberAllocated token budget

RecallResult

FieldTypeDescription
itemsItem[]Items to inject into the prompt
tokenCountnumberTokens consumed by the items
stateTStateUpdated state (optional)

StoreParams

FieldTypeDescription
newItemsItem[]New items from the LLM response
logItemLogFull conversation log
responseLLMResponseComplete LLM response
ctxExecutionContextExecution context
stateTStateCurrent layer state

StoreResult

FieldTypeDescription
stateTStateUpdated state

SpawnParams

FieldTypeDescription
parentStateTStateParent layer state
childCtxExecutionContextChild execution context

SpawnResult

FieldTypeDescription
childStateTState | nullState for the child (null = don't propagate)
itemsItem[]Additional items for the child (optional)

ReturnParams

FieldTypeDescription
childStateTStateChild's final state
childLogItemLogChild's conversation log
parentStateTStateParent's current state
resultunknownChild's completion result

ReturnResult

FieldTypeDescription
parentStateTStateUpdated parent state
resultunknownTransformed result for pipeline (optional)

CompleteParams

FieldTypeDescription
logItemLogFull conversation log
ctxExecutionContextExecution context
stateTStateCurrent layer state
outcomeExecutionOutcomeHow the execution ended

DisposeParams

FieldTypeDescription
stateTStateFinal layer state

ExecutionOutcome

type ExecutionOutcome = 'success' | 'failure' | 'aborted';

ExecutionContext

Context available to memory layer hooks (different from the step Context).

interface ExecutionContext {
  executionId: string;
  threadId: string;
  resourceId?: string;
  depth: number;
  stepNumber: number;
  tokenUsage: { input: number; output: number };
  cost: number;
  fs: FsAdapter;
  shell: ShellAdapter;
  callModel?: (request: MemoryCallModelRequest) => Promise<LLMResponse>;
  tokenize(text: string): number;
  trace: {
    setAttribute(key: string, value: string | number | boolean): void;
    addEvent(name: string, attributes?: Record<string, string | number | boolean>): void;
  };
  readLayerState<T>(layerId: string): T | undefined;
}

fs is the FsAdapter from ctx.harness.fs, giving memory layer hooks access to the same filesystem backend as tools and skill discovery (real, in-memory, or remote). shell is the corresponding ShellAdapter. callModel is the minimal model-call surface ({ model, items, instructions? }) — present only when the harness has an LLM provider configured, so LLM-backed layers must handle its absence. readLayerState snapshots a sibling layer's state by its layer.id.

LayerTimeouts

FieldTypeDescription
initnumberTimeout for init hook (ms, optional)
recallnumberTimeout for recall hook (ms, optional)
storenumberTimeout for store hook (ms, optional)
onSpawnnumberTimeout for onSpawn hook (ms, optional)
onReturnnumberTimeout for onReturn hook (ms, optional)
onCompletenumberTimeout for onComplete hook (ms, optional)
disposenumberTimeout for dispose hook (ms, optional)
beforeToolCallnumberTimeout for beforeToolCall steering hook (ms, optional)
afterModelCallnumberTimeout for afterModelCall steering hook (ms, optional)
onItemAppendnumberTimeout for onItemAppend hook (ms, optional)
projectHistorynumberTimeout for projectHistory hook (ms, optional)

ProjectionPolicy

interface ProjectionPolicy {
  tokenBudget: number;
  responseReserve: number;
  overflow: 'truncate' | 'summarize' | 'sliding_window';
  overflowModel?: string;
  windowSize?: number;
}
FieldTypeRequiredDescription
tokenBudgetnumberyesTotal token budget for all layers
responseReservenumberyesTokens reserved for the model response
overflow'truncate' | 'summarize' | 'sliding_window'yesStrategy when total recall exceeds budget
overflowModelstringnoModel for summarization overflow
windowSizenumbernoItems to keep for sliding window overflow

StorageAdapter

interface StorageAdapter {
  get<T>(key: string): Promise<T | null>;
  set<T>(key: string, value: T): Promise<void>;
  delete(key: string): Promise<void>;
  list(prefix: string): Promise<string[]>;
}
MethodDescription
get(key)Retrieve a value by key (returns null if not found)
set(key, value)Store a value
delete(key)Delete a value
list(prefix)List all keys with the given prefix

ScopedStorage

Same interface as StorageAdapter but scoped to a specific layer and scope key.

interface ScopedStorage {
  get<T>(key: string): Promise<T | null>;
  set<T>(key: string, value: T): Promise<void>;
  delete(key: string): Promise<void>;
  list(prefix?: string): Promise<string[]>;
}

LayerStateStore

In-memory layer-state store with centralized durable write-through. Every set() for a registered (execution, layer) pair is asynchronously mirrored to the layer's ScopedStorage — state written by provides functions, lifecycle hooks, or store() all persists the same way. Available from @noetic-tools/core/unstable.

interface LayerStateStore {
  get<T>(executionId: string, layerId: string): T | undefined;
  set<T>(executionId: string, layerId: string, state: T): void;
  cleanup(executionId: string): void;
  diagnostic: (layerId: string, hook: string, error: unknown) => void;
  // Optional — implemented by createLayerStateStore(); custom stores may omit them.
  has?(executionId: string, layerId: string): boolean;
  registerDurable?(executionId: string, layerId: string, target: ScopedStorage): void;
  flush?(executionId: string): Promise<void>;
  disable?(executionId: string, layerId: string): void;
  isDisabled?(executionId: string, layerId: string): boolean;
}
MethodDescription
get(executionId, layerId)Read a layer's current in-memory state
set(executionId, layerId, state)Write state; mirrored durably when a target is registered (undefined deletes the durable key)
cleanup(executionId)Drop all state and registrations for an execution
diagnostic(layerId, hook, error)Sink for non-fatal layer errors (mirror failures report hook 'persist')
has(executionId, layerId)Whether any state entry exists — distinguishes "init never ran" (no entry → init-bearing layer skipped) from "state explicitly cleared" (entry present → hooks keep running with undefined state)
registerDurable(executionId, layerId, target)Register the durable write-through target for a layer (non-'execution' scopes; called by initLayers/spawnLayers)
flush(executionId)Await all in-flight durable mirror writes
disable(executionId, layerId) / isDisabled(...)Explicit disabled tracking for layers whose init failed with onInitError: 'disable'

Writes are coalesced per key (latest wins, one write in flight) and mirror failures never throw — they surface only through diagnostic.

MemoryTraceSpan

Observability data for a single memory hook execution.

interface MemoryTraceSpan {
  layerId: string;
  hook: 'init' | 'recall' | 'store' | 'onSpawn' | 'onReturn' | 'onComplete' | 'dispose';
  durationMs: number;
  status: 'ok' | 'error' | 'timeout' | 'skipped';
  budget?: { allocated: number; used: number; yielded: number };
  itemCount?: number;
  error?: { message: string; stack?: string };
}

ContextMemory

The default memory shape used when no typed memory config is provided.

type ContextMemory = Readonly<Record<string, Record<string, unknown>>>;

MemoryConfig

A typed memory configuration that carries a phantom _shape for compile-time memory access. Created via the memory() builder.

interface MemoryConfig<TLayers extends MemoryLayer[] = MemoryLayer[]> {
  layers: TLayers;
  _shape: InferMemoryShape<TLayers>;
}
FieldTypeDescription
layersTLayersArray of memory layers
_shapeInferMemoryShape<TLayers>Phantom field for compile-time shape inference (never set at runtime)

InferMemory

Extracts the memory shape type from a MemoryConfig.

type InferMemory<T extends { readonly _shape: unknown }> = T['_shape'];

InferMemoryShape

A mapped type that collects each layer's provides declarations into a record keyed by layer id. Each key maps to the resolved data/function types declared by that layer.

LayerProvides

type LayerProvides = Record<string, LayerDataDecl | LayerFunctionDecl>;

A record of named declarations that a layer exposes on ctx.memory[layerId].

LayerDataDecl

interface LayerDataDecl<T = unknown, TState = unknown> {
  kind: 'data';
  read: (state: TState) => T;
}
FieldTypeDescription
kind'data'Discriminant
read(state: TState) => TDerives the value from layer state

LayerFunctionDecl

interface LayerFunctionDecl<TInput = unknown, TOutput = unknown, TState = unknown> {
  kind: 'function';
  description: string;
  input: ZodType<TInput>;
  output: ZodType<TOutput>;
  execute(args: TInput, state: TState, ctx: ExecutionContext): Promise<{ result: TOutput; state?: TState }>;
}
FieldTypeDescription
kind'function'Discriminant
descriptionstringHuman-readable description for the LLM
inputZodType<TInput>Zod schema for input validation
outputZodType<TOutput>Zod schema for output validation
execute(args: TInput, state: TState, ctx: ExecutionContext) => Promise<{ result: TOutput; state?: TState }>Execution function. Returns the result plus an optional state update; omit state to leave the layer's state untouched.

Builder Functions

layerData

function layerData<T, TState>(opts: {
  read: (state: TState) => T;
}): LayerDataDecl<T, TState>;

Creates a LayerDataDecl that derives a value from layer state. Takes an options object with a single read function.

layerFn

function layerFn<TInput, TOutput, TState>(opts: {
  description: string;
  input: ZodType<TInput>;
  output: ZodType<TOutput>;
  execute: (
    args: TInput,
    state: TState,
    ctx: ExecutionContext,
  ) => Promise<{ result: TOutput; state?: TState }>;
}): LayerFunctionDecl<TInput, TOutput, TState>;

Creates a LayerFunctionDecl that exposes a callable function on the memory object. execute returns { result, state? }; include state to persist an updated layer state.

memory

function memory<const T extends readonly MemoryLayer[]>(layers: T): MemoryConfig<T>;

Collects an array of layers into a MemoryConfig with inferred _shape — e.g. memory([workingMemory(), planMemory()]). Pass the result to StepSpawn.memory or AgentConfig to get typed ctx.memory access.

On this page