Skip to Content
Telegram MCPPlan & Phases4 — Memory System

Phase 4 — Memory System

Goal

The agent has persistent per-user memory and can read recent chat history without keeping anything in its own context. After this phase the agent can be stateless across sessions — all relevant state lives in this server’s Postgres.

Scope / Deliverables

  • Migrations: memory_kv, chat_messages (full schemas from data-and-contracts.md)
  • Archive every inbound message into chat_messages (in the webhook handler, before forwarding to the agent)
  • Archive every outbound send into chat_messages (in telegram/outbound.ts)
  • Tool: update_memory (single key upsert)
  • Tool: store_memory (bulk upsert)
  • Tool: get_chat_history (paginated by before timestamp; default limit 50, max 500)
  • Tool: get_user_context (user row + full memory map + last 20 messages)
  • Index check: chat_messages_user_time_idx keeps get_chat_history fast at 100k+ rows

Out of scope for this phase: summarization, vector embeddings, semantic recall. Schema is forward-compatible — chat_summaries and memory_vectors tables can be added later without breaking the KV table.

Automated Unit Testing

  • memory.kv.test.ts
    • update_memory(userId, key, value) inserts when missing
    • second update_memory to the same (userId, key) overwrites value and bumps updated_at
    • reading returns the latest JSON value verbatim (including nested objects and arrays)
    • cross-user isolation: same key under two userIds does not collide
  • store_memory.test.ts
    • bulk write of N entries returns {count: N, updatedAt}
    • call with empty entries array → INVALID_INPUT (Zod minLength=1) — or returns count: 0 (document which behavior is shipped)
    • transactional behavior: one malformed entry causes the whole batch to roll back (document and assert the implementation choice)
  • chat_history.test.ts
    • default call returns the last 50 messages in chronological order (oldest → newest, or newest → oldest — assert and document)
    • limit=20 returns 20; limit=1000 is clamped to 500
    • before ISO timestamp pages backward correctly; combined with limit it produces stable pages with no overlap
    • returns inbound + outbound interleaved with correct direction field
  • get_user_context.test.ts
    • returns {user, memory, recentMessages} bundle
    • user block contains id, username, firstName, status, role, createdAt, approvedAt
    • memory is the full KV map for that user
    • recentMessages defaults to the last 20 (chronological); empty user has empty arrays/objects
  • chat_messages.archive.test.ts
    • inbound text message archived with direction='inbound', kind='text', populated text and payload columns
    • outbound send_image archived with kind='photo' and the image source in payload
    • archive write happens before forwarding to the agent (assert ordering with a probe)
  • chat_messages.index.test.tsEXPLAIN on the get_chat_history query confirms the planner uses chat_messages_user_time_idx (sanity test; skip in CI if Postgres is unavailable).

Definition of Done

  • Agent writes a memory key, reads it back across separate MCP sessions.
  • get_chat_history(userId, limit=20) returns the last 20 messages in chronological order, both inbound and outbound interleaved.
  • get_user_context returns coherent bundle (sub-100ms for typical users).