Skip to content

LTM 01: Memory Creation

LLM-initiated creation of a new persistent memory — server-wide or user-specific — written to the database.

File: src/tools/functionCalls/memoryTool.ts — class MemoryTool, tool name create_long_term_memory

When the LLM identifies a new, distinct fact or preference worth preserving across sessions, it calls create_long_term_memory with a memory_content string and a memory_scope of either server_wide or target_user.

MemoryTool.execute() runs the following sequence:

  1. Validate parameters (content non-empty, scope valid, feature flag on, critical state present).
  2. Resolve target user (scope target_user only) — resolveUserTarget() looks up the provided display name in the conversation/guild, disambiguating multiple matches and handling bridge-user and bot-self fallbacks.
  3. Sanitize contentsanitizeUnknownTemplatePlaceholders() strips brace-wrapped tokens that don’t match {user} or {bot} (e.g. the LLM writing {bredrumb} instead of the correct template token).
  4. Guard lineage — blocks if persona_lineage_id === 0 (reserved; signals an un-run schema migration).
  5. Check limitsserverMemoryRepository.checkServerMemoryLimit() or personalMemoryRepository.checkPersonalMemoryLimit() before writing.
  6. DB writeserverMemoryRepository.add(...) or personalMemoryRepository.add(...).
  7. Notify — send a success embed to Discord (sendStandardEmbed).
  8. Invalidate cacheinvalidateTomoriStateCache(serverId) or invalidateUserCache(userId).

Tool arguments (from LLM):

ArgTypeRequiredDescription
memory_contentstringyesThe fact to persist. Must use {user} / {bot} tokens instead of hardcoded names.
memory_scope"server_wide" | "target_user"yesDetermines the DB table and cache key.
target_userstringwhen scope = target_userDisplay name of the target user (not a Discord ID).

Context required:

  • context.tomoriStateserver_id, persona_id, persona_lineage_id, config.self_teaching_enabled, config.personal_memories_enabled.
  • context.userId / context.message.author.id — triggering user for audit and {user} resolution.
  • context.channel — for serverId extraction and embed delivery.

Promise<ToolResult> with data.status indicating outcome:

StatusMeaning
memory_saved_successfullyDB write succeeded; data.memory_id is the new row ID
memory_save_failed_disabledself_teaching_enabled is off
memory_save_failed_limit_exceededServer or personal memory limit reached
memory_save_failed_ambiguous_userMultiple users matched target_user
memory_save_failed_user_not_foundNo matching user found
memory_save_failed_privacy_restrictedTarget user has PrivacyLevel.PARTIAL or FULL
memory_save_failed_internal_errorMissing critical state or invalid lineage ID
memory_save_failed_db_errorDB operation failed
  • DB row inserted — one row in server_memories (server-wide) or personal_memories (target-user).
  • Discord embed sent — success notification in context.channel; routed through webhook if in alter-persona mode.
  • Cache invalidated:
    • Server-wide: invalidateTomoriStateCache(serverId)
    • Personal: invalidateUserCache(resolvedTargetUserId)
  • Log entrylog.success(...) on success with memory ID and content.

After a successful write:

  • The new memory row exists in the DB, scoped to (server_id, persona_lineage_id) for server memories or (user_id, persona_lineage_id) for personal memories.
  • The TomoriState or user cache for the affected scope has been invalidated — the next context-build will load from DB.
  • data.memory_id in the ToolResult matches the server_memory_id or personal_memory_id of the inserted row.
Input conditionEffective scopeReason
target_user resolves to the bot itselfserver_wideBot can’t have personal memories about itself
target_user is a Matrix bridge userserver_wideBridge users are not stored in users table with full identity
target_user has PrivacyLevel.PARTIAL/FULLError (no fallback)Privacy restriction; user must change setting
SurfacePlugin-relevance
serverMemoryRepository.add() / personalMemoryRepository.add()A plugin adding a new memory scope (e.g., channel-specific LTM) would add a repository method and a matching memory_scope enum value here. → plugin plan candidate
resolveUserTarget()src/utils/discord/targetResolver.ts. Internal — user resolution is a guild-lookup utility; no plugin seam.
Memory limit checks (checkServerMemoryLimit, checkPersonalMemoryLimit)Internal — limits are DB-column configured, not plugin-controlled.
convertMentions()src/utils/text/contextBuilder.ts. Internal — token replacement ({user} / {bot}) for embed display only; does not affect the stored content.
SourceKey / Env varDefaultPurpose
TomoriState.configself_teaching_enabledfalseMaster feature flag for all LTM tools
TomoriState.configpersonal_memories_enabledtrueControls embed footer wording for personal memory notifications
DB columnserver_memory_limit(schema default)Max server memories per (server_id, persona_lineage_id)
DB columnpersonal_memory_limit(schema default)Max personal memories per (user_id, persona_lineage_id)