How the SAM In-App Agent Uses Project Memory and Context in OpenSEO

The SAM in-app agent stores its long-term context in a project-scoped memory table (sam_project_memory), allowing persistent, cross-session knowledge that any SAM chat for the same project can access and update.

The SAM (Search-Assisted-Metadata) agent in OpenSEO functions as a Durable Object bound to a single project, giving it a persistent "brain" that survives individual chat sessions. Unlike typical conversational AI where context evaporates when the session ends, SAM accumulates research, notes, and intermediate findings in a shared project workspace.

Project-Scoped Memory Architecture

SAM's memory system centers on a single database table with a simple key-value interface defined in src/server/features/sam/SamProjectMemoryRepository.ts.

Core Operations: getBlock and setBlock

The repository exposes two primary methods:

  • getBlock(projectId, label) – Retrieves a named memory block for a project (lines 10-24)
  • setBlock(projectId, label, content) – Upserts content, keyed by (projectId, label) composite (lines 27-38)

This design ensures that memory blocks like "memory" and "research_log" are shared across all chat sessions belonging to the same project, not isolated per conversation.

// src/server/features/sam/SamProjectMemoryRepository.ts
// Lines 10-24: Reading a persistent memory block
async function getBlock(projectId: string, label: string): Promise<string | null> {
  const row = await db
    .selectFrom("sam_project_memory")
    .select("content")
    .where("projectId", "=", projectId)
    .where("label", "=", label)
    .executeTakeFirst();
  
  return row?.content ?? null;
}

// Lines 27-38: Writing/upserting a block
async function setBlock(projectId: string, label: string, content: string): Promise<void> {
  await db
    .insertInto("sam_project_memory")
    .values({ projectId, label, content, updatedAt: new Date() })
    .onConflict((oc) => oc.columns(["projectId", "label"]).doUpdateSet({ content, updatedAt: new Date() }))
    .execute();
}

Durable Object Implementation in SamChatAgent.ts

The SamChatAgent.ts file implements the Durable Object that orchestrates memory access on every conversational turn.

Memory Lifecycle Per Turn

When processing a user message, SAM follows this sequence:

  1. Load the "memory" block via SamProjectMemoryRepository.getBlock to establish persisted context
  2. Execute reasoning and search operations using the combined system prompt + memory
  3. Persist new findings by updating "research_log" or "memory" via setBlock

This write-back pattern ensures that research progress, discovered entities, and analytical conclusions accumulate project-wide.

// Example: Loading project memory at turn start
import { SamProjectMemoryRepository } from "@/server/features/sam/SamProjectMemoryRepository";

async function loadProjectMemory(projectId: string) {
  const memory = await SamProjectMemoryRepository.getBlock(projectId, "memory");
  return memory ?? "";
}
// Example: Appending to research log after completing analysis
async function appendResearchLog(projectId: string, note: string) {
  const existing = await SamProjectMemoryRepository.getBlock(projectId, "research_log") ?? "";
  const updated = existing + "\n---\n" + note;
  await SamProjectMemoryRepository.setBlock(projectId, "research_log", updated);
}

System Prompt Integration

SAM's behavior is shaped by two concatenated components:

Component Source Mutability
System Prompt src/server/features/sam/samSystemPrompt.ts Immutable – defines SAM's core personality and capabilities
Memory Blocks sam_project_memory table Mutable – accumulates project-specific knowledge

On each LLM call, OpenSEO merges the static system prompt with the latest "memory" and "research_log" blocks, giving the model both its foundational instructions and current project context.

Database Schema

The underlying table structure is defined in src/db/sam.schema.ts:

Column Purpose
projectId Foreign key to the OpenSEO project
label Block identifier (e.g., "memory", "research_log")
content Text payload (typically JSON or markdown)
updatedAt Timestamp for cache invalidation and debugging

The composite primary key on (projectId, label) enforces the 1:1 relationship between projects and their named memory blocks.

Key Design Decisions

Why project-scoped instead of session-scoped? SEO workflows span days or weeks. A researcher might open multiple SAM chats while investigating competitors, keywords, or technical issues. Project memory eliminates redundant research and enables progressive depth—each conversation builds on prior work.

Why separate memory and research_log blocks? This separation allows structured retrieval: "memory" typically holds compact, synthesized knowledge, while "research_log" preserves raw chronological findings that can be summarized or queried on demand.

Summary

  • The SAM in-app agent persists context through sam_project_memory table operations in SamProjectMemoryRepository.ts
  • getBlock/setBlock provide simple read/write semantics keyed by (projectId, label)
  • SamChatAgent.ts loads memory at turn start and writes updates after reasoning completes
  • Memory is project-wide, enabling cross-session continuity for SEO research workflows
  • The immutable system prompt from samSystemPrompt.ts combines with mutable memory blocks to form complete LLM context

Frequently Asked Questions

How does SAM memory differ from typical chatbot memory?

Typical chatbots retain context only within a single conversation thread, often using limited token windows. SAM's memory is persistent and project-scoped—any chat opened for the same OpenSEO project accesses identical accumulated research, regardless of when previous sessions occurred.

Can multiple users access the same SAM memory?

Yes. Since memory keys on projectId rather than user or session identifiers, all team members with project access share the same "memory" and "research_log" blocks. This enables collaborative SEO workflows where findings compound across contributors.

What happens if two SAM chats write simultaneously?

The Durable Object architecture in SamChatAgent.ts serializes execution—only one turn processes at a time per project. The upsert logic in setBlock ensures last-write-wins consistency without complex conflict resolution.

How large can memory blocks grow?

According to the repository implementation, memory blocks are text columns without explicit size limits in the application layer. In practice, extremely large blocks would impact LLM context window usage. The code suggests future iterations may implement summarization or chunking strategies for long-running projects.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →