Skip to documentation
Docs navigation
Docs/Simple chatbot
Example

Simple chatbot

Build a complete server-side chatbot that remembers a user’s name and response preference during an active conversation.

Estimated time: 7 minutes
Level: Beginner

Overview

This example uses one MemoGrafterAgent to handle recall, model invocation, conversation history, and incremental ingestion. The agent learns a name and response preference, then recalls both on a later turn.

MemoGrafterAgent generates its own session ID. Reuse the same instance for the active conversation instead of constructing one per request.

What this demonstrates

Automatic memory recall through invoke().
Automatic ingestion after a completed user and assistant exchange.
A bounded recent-history window combined with graph-backed long-term memory.
Inspecting the generated session ID, chat history, topics, and memories.
Graceful resource shutdown.

Architecture

The first turn may have no graph memory to recall. Once ingestion creates topic and memory nodes, later turns can retrieve relevant facts before the LLM responds.

User turn
Recall active facts
LLM response
Incremental ingest
Later recall

Project structure

The example stays in one file because one agent owns one active conversation. Larger applications can separate agent construction, request handling, and shutdown.

files
.env
package.json
src/
  chatbot.ts

Environment

.env
DATABASE_URL=postgres://postgres:postgres@localhost:5432/memo_grafter
OPENAI_API_KEY=...
terminal
npx memo-grafter init
npx memo-grafter migrate

Step 1: Create the agent

Construct and initialize MemoGrafter only in server-side Node.js code. The database must already be migrated.

src/chatbot.ts
import "dotenv/config";

import {
  MemoGrafterAgent,
  OpenAIEmbedAdapter,
  OpenAILLMAdapter,
} from "memo-grafter";

const agent = new MemoGrafterAgent({
  db: { connectionString: process.env.DATABASE_URL! },
  llm: new OpenAILLMAdapter("gpt-4o"),
  embedder: new OpenAIEmbedAdapter("text-embedding-3-small"),
});

await agent.initialize();

Step 2: Run the conversation

Do not call another ingestion method for these messages. invoke() already records the completed exchange and schedules it for incremental ingestion.

src/chatbot.ts
console.log(await agent.invoke("My name is Alice."));
console.log(await agent.invoke("I prefer concise answers."));
console.log(
  await agent.invoke("What is my name, and how should you answer?"),
);

Step 3: Inspect and close

src/chatbot.ts
console.log("Session:", agent.getSessionId());
console.log("History:", agent.getHistory());
console.log("Topics:", await agent.getActiveNodes());

await agent.close();

Complete example

src/chatbot.ts
import "dotenv/config";

import {
  MemoGrafterAgent,
  OpenAIEmbedAdapter,
  OpenAILLMAdapter,
} from "memo-grafter";

const agent = new MemoGrafterAgent({
  db: { connectionString: process.env.DATABASE_URL! },
  llm: new OpenAILLMAdapter("gpt-4o"),
  embedder: new OpenAIEmbedAdapter("text-embedding-3-small"),
});

await agent.initialize();

try {
  console.log(await agent.invoke("My name is Alice."));
  console.log(await agent.invoke("I prefer concise answers."));
  console.log(
    await agent.invoke("What is my name, and how should you answer?"),
  );

  const snapshot = await agent.getGraphSnapshot();
  console.log({
    sessionId: snapshot.sessionId,
    topics: snapshot.nodes.length,
    memories: snapshot.memories.length,
  });
} finally {
  await agent.close();
}
terminal
npx tsx --env-file=.env src/chatbot.ts

Expected interaction

Exact model wording can vary. The important result is that the later answer uses facts extracted from earlier turns.

User: “My name is Alice.”
User: “I prefer concise answers.”
User: “What is my name, and how should you answer?”
Expected result: the response identifies Alice and follows or acknowledges the concise-answer preference.

What gets stored

Completed user and assistant messages in the session message buffer.
Topic segments produced by drift detection.
Topic nodes containing historical labels and summaries.
Atomic memory nodes such as the user’s name and response preference when extraction identifies them.
Temporal, semantic, or reentry graph edges when applicable.
The incremental ingest cursor used to avoid rebuilding unchanged message ranges.

Inspect it in Studio

Run npx memo-grafter studio against the same database.
Find the session ID printed by the example.
Open Graph View and select the identity or preference topic.
Inspect its attached atomic memories and source information.
Run “What is my name?” in Prompt Preview and compare the included facts.

Production considerations

Keep the agent and all provider credentials on the server.
Reuse the active agent rather than creating one per message.
Use the lower-level MemoGrafter API when the application must reopen explicit session IDs across process lifetimes.
Call close() during graceful shutdown.
Measure ingestion lag before assuming a newly completed exchange is available to recall.

Common mistakes

Creating a new agent on each HTTP request and unintentionally creating a new session.
Manually ingesting turns that already passed through invoke().
Expecting useful recall before graph ingestion has produced memory nodes.
Treating exact model output as deterministic.
Exposing MemoGrafter in browser code.

Extensions

Add session tags for project or domain filtering.
Use direct recall() to show the evidence behind a response.
Add a user-directed forgetting workflow.
Move ingestion to a queue when extraction affects response latency.