Skip to main content
Glama

memory_journal

Search your conversation journal to recall past work or find discussions by topic. Use semantic queries or browse recent entries.

Instructions

Search or browse the conversation journal. Use to answer 'what did I work on?' or find past conversations by topic.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryNoSearch query (semantic). Omit to list recent entries.
daysNoHow many days back to look (default: 7)

Implementation Reference

  • The handler function `handleMemoryJournal` that executes the memory_journal tool logic. If a query is provided, it performs semantic search via `searchMemories` and filters for journal entries. If no query, it queries the `journal_entries` table directly for recent entries within the specified day range (default 7).
    export async function handleMemoryJournal(
      query?: string,
      days?: number,
    ): Promise<string> {
      const db = getDb();
    
      if (query) {
        const results = await searchMemories(query, {
          topK: 5,
          minScore: 0.25,
        });
        const journalResults = results.filter((r) => r.source === "journal");
    
        if (journalResults.length === 0) {
          return `No journal entries found matching "${query}".`;
        }
    
        const lines = journalResults.map((r) => {
          const date = new Date(r.created_at).toISOString().slice(0, 10);
          const score = (r.score * 100).toFixed(0);
          const project = r.project ? ` [${r.project}]` : "";
          return `**${score}% match**${project} — ${date}\n${r.content}`;
        });
    
        return `Found ${journalResults.length} journal entry(s):\n\n${lines.join("\n\n")}`;
      }
    
      const cutoff = Date.now() - (days ?? 7) * 24 * 60 * 60 * 1000;
      const entries = db
        .prepare(
          `SELECT id, content, project, created_at FROM journal_entries
           WHERE created_at >= ? ORDER BY created_at DESC LIMIT 20`,
        )
        .all(cutoff) as Array<{
        id: string;
        content: string;
        project: string | null;
        created_at: number;
      }>;
    
      if (entries.length === 0) {
        return `No journal entries in the last ${days ?? 7} day(s).`;
      }
    
      const lines = entries.map((e) => {
        const date = new Date(e.created_at).toISOString().slice(0, 16).replace("T", " ");
        const project = e.project ? ` [${e.project}]` : "";
        return `**${date}**${project}\n${e.content.slice(0, 200)}`;
      });
    
      return `${entries.length} journal entry(s) from the last ${days ?? 7} day(s):\n\n${lines.join("\n\n")}`;
    }
  • Registration of the 'memory_journal' tool on the MCP server with Zod schema for optional 'query' and 'days' parameters, wired to call `handleMemoryJournal`.
    server.tool(
      "memory_journal",
      "Search or browse the conversation journal. Use to answer 'what did I work on?' or find past conversations by topic.",
      {
        query: z.string().optional().describe("Search query (semantic). Omit to list recent entries."),
        days: z.number().optional().describe("How many days back to look (default: 7)"),
      },
      async ({ query, days }) => {
        try {
          const result = await handleMemoryJournal(query, days);
          return { content: [{ type: "text", text: result }] };
        } catch (err) {
          return {
            content: [{ type: "text", text: `Error reading journal: ${err}` }],
            isError: true,
          };
        }
      },
    );
  • Input schema for the memory_journal tool: optional 'query' (string) for semantic search, optional 'days' (number) for recency filter (default 7).
    {
      query: z.string().optional().describe("Search query (semantic). Omit to list recent entries."),
      days: z.number().optional().describe("How many days back to look (default: 7)"),
    },
  • The `searchMemories` helper used by the handler for semantic search across both memories and journal entries. The handler filters results to only those with `source === 'journal'`.
    export async function searchMemories(
      query: string,
      options: {
        topK?: number;
        category?: string;
        project?: string;
        includeArchived?: boolean;
        minScore?: number;
      } = {},
    ): Promise<SearchResult[]> {
      const {
        topK = 5,
        category,
        project,
        includeArchived = false,
        minScore = 0.3,
      } = options;
    
      // Fall back to keyword search when Ollama is unavailable
      if (isUsingFallback()) {
        return keywordSearch(query, { topK, category, project, includeArchived, minScore: 0.2 });
      }
    
      const db = getDb();
      const queryEmbedding = await embed(query);
    
      if (!queryEmbedding) {
        return keywordSearch(query, { topK, category, project, includeArchived, minScore: 0.2 });
      }
    
      let memorySql = `SELECT id, content, category, project, created_at, embedding FROM memories WHERE embedding IS NOT NULL`;
      const params: unknown[] = [];
    
      if (!includeArchived) {
        memorySql += ` AND archived = 0`;
      }
      if (category) {
        memorySql += ` AND category = ?`;
        params.push(category);
      }
      if (project) {
        memorySql += ` AND project = ?`;
        params.push(project);
      }
    
      const memories = db.prepare(memorySql).all(...params) as Array<{
        id: string;
        content: string;
        category: string;
        project: string | null;
        created_at: number;
        embedding: Buffer;
      }>;
    
      let journalSql = `SELECT id, content, project, created_at, embedding FROM journal_entries WHERE embedding IS NOT NULL`;
      const journalParams: unknown[] = [];
    
      if (project) {
        journalSql += ` AND project = ?`;
        journalParams.push(project);
      }
    
      const journals = db.prepare(journalSql).all(...journalParams) as Array<{
        id: string;
        content: string;
        project: string | null;
        created_at: number;
        embedding: Buffer;
      }>;
    
      const results: SearchResult[] = [];
    
      for (const mem of memories) {
        const memEmbedding = bufferToEmbedding(mem.embedding);
        const score = cosineSimilarity(queryEmbedding, memEmbedding);
        if (score >= minScore) {
          results.push({
            id: mem.id,
            content: mem.content,
            category: mem.category,
            project: mem.project,
            score,
            created_at: mem.created_at,
            source: "memory",
          });
        }
      }
    
      for (const entry of journals) {
        const entryEmbedding = bufferToEmbedding(entry.embedding);
        const score = cosineSimilarity(queryEmbedding, entryEmbedding);
        if (score >= minScore) {
          results.push({
            id: entry.id,
            content: entry.content,
            category: "journal",
            project: entry.project,
            score,
            created_at: entry.created_at,
            source: "journal",
          });
        }
      }
    
      results.sort((a, b) => b.score - a.score);
    
      const updateAccess = db.prepare(
        `UPDATE memories SET accessed_at = ?, access_count = access_count + 1 WHERE id = ?`,
      );
      const now = Date.now();
      for (const r of results.slice(0, topK)) {
        if (r.source === "memory") {
          updateAccess.run(now, r.id);
        }
      }
    
      db.prepare(`INSERT INTO search_log (query, results_count, created_at) VALUES (?, ?, ?)`).run(
        query,
        Math.min(results.length, topK),
        now,
      );
    
      return results.slice(0, topK);
    }
  • The `journal_entries` table schema in SQLite, which the handler queries directly when no search query is provided.
    CREATE TABLE IF NOT EXISTS journal_entries (
      id TEXT PRIMARY KEY,
      session_id TEXT,
      project TEXT,
      content TEXT NOT NULL,
      created_at INTEGER NOT NULL,
      embedding BLOB
    );
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description bears full responsibility but only states 'Search or browse'. It fails to disclose behavioral details such as default behavior (e.g., what happens when query is omitted), result limits, ordering, or any side effects.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely concise: two sentences that immediately state the purpose and then offer example use cases. No redundant or extraneous information is present.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the lack of output schema and annotations, the description does not inform the agent about expected return values, pagination, ordering, or limitations. For a search/browse tool, this missing context reduces completeness.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and both parameters are already well-described in the schema (e.g., 'Search query (semantic). Omit to list recent entries.'). The tool description adds no additional meaning beyond what the schema provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Search or browse the conversation journal' and provides concrete use cases (e.g., 'what did I work on?'). However, it does not differentiate from sibling tools like memory_search or memory_recent, which may have overlapping functionality.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives specific scenarios ('Use to answer...'), implying when to use the tool, but it lacks explicit guidance on when not to use it or how it compares to alternatives like memory_search or memory_recent.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/DomDemetz/claude-soul'

If you have feedback or need assistance with the MCP directory API, please join our Discord server