コンテンツにスキップ

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. Persona-scoped nicknames and affixed labels (“Master Sparrow”) resolve too; see docs/en/architecture/pipelines/context-build/02-native-assembly/06-participants.md for the stage ladder.
  3. Sanitize content: sanitizeUnknownTemplatePlaceholders() strips brace-wrapped tokens that don’t match {user} or {bot} (e.g. the LLM writing {obonya} instead of the correct template token).
  4. Guard lineage: blocks if persona_lineage_id === 0 (reserved; signals an un-run schema migration).
  5. Check limits: serverMemoryRepository.checkServerMemoryLimit() or personalMemoryRepository.checkPersonalMemoryLimit() before writing.
  6. DB write: serverMemoryRepository.add(...) or personalMemoryRepository.add(...).
  7. Notify: send a success embed to Discord (sendStandardEmbed).
  8. Invalidate cache: invalidateTomoriStateCache(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.tomoriState: server_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 entry: log.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_enabledtrueMaster feature flag for all LTM tools
TomoriState.configpersonal_memories_enabledtrueControls embed footer wording for personal memory notifications
Env varMAX_SERVER_MEMORIES100Max server memories per (server_id, persona_lineage_id)
Env varMAX_PERSONAL_MEMORIES100Max personal memories per (user_id, persona_lineage_id), counting lineage 0 globals
Env varMAX_MEMORY_LENGTH1000Max characters per memory, enforced by validateMemoryContent()

Limits live only in src/utils/misc/memoryLimits.ts; there are no per-server limit columns. Counting is scoped to a persona lineage, so a multi-persona server holds MAX_SERVER_MEMORIES per persona rather than in total.