import("trpc.group/trpc-go/trpc-agent-go/agent/llmagent""trpc.group/trpc-go/trpc-agent-go/memory"memoryinmemory"trpc.group/trpc-go/trpc-agent-go/memory/inmemory""trpc.group/trpc-go/trpc-agent-go/runner")// Step 1: Create memory servicememoryService:=memoryinmemory.NewMemoryService()// Step 2: Create Agent and register memory toolsllmAgent:=llmagent.New("memory-assistant",llmagent.WithModel(modelInstance),llmagent.WithDescription("An assistant with memory capabilities."),llmagent.WithTools(memoryService.Tools()),// Explicitly register tools)// Step 3: Create Runner and set memory serviceappRunner:=runner.NewRunner("memory-chat",llmAgent,runner.WithMemoryService(memoryService),// Set service at Runner level)
Memory Service
Configure the memory service in code. Nine built-in backends are supported:
in-memory, SQLite, SQLiteVec, Redis, MySQL, MySQL Vec, PostgreSQL, pgvector,
and ChromaDB.
import("os"memoryinmemory"trpc.group/trpc-go/trpc-agent-go/memory/inmemory"memoryredis"trpc.group/trpc-go/trpc-agent-go/memory/redis"memorymysql"trpc.group/trpc-go/trpc-agent-go/memory/mysql"memorypostgres"trpc.group/trpc-go/trpc-agent-go/memory/postgres")// In-memory implementation for development and testing.memService:=memoryinmemory.NewMemoryService()// Redis implementation for production.redisService,err:=memoryredis.NewService(memoryredis.WithRedisClientURL("redis://localhost:6379"),memoryredis.WithToolEnabled(memory.DeleteToolName,true),// Enable delete.)iferr!=nil{// Handle error.}// MySQL implementation for production (relational database).// Table is automatically created on service initialization (unless skipped). Returns error on failure.mysqlService,err:=memorymysql.NewService(memorymysql.WithMySQLClientDSN("user:password@tcp(localhost:3306)/dbname?parseTime=true"),memorymysql.WithToolEnabled(memory.DeleteToolName,true),// Enable delete.)iferr!=nil{// Handle error.}// PostgreSQL implementation for production (relational database).// Table is automatically created on service initialization (unless skipped). Returns error on failure.postgresService,err:=memorypostgres.NewService(memorypostgres.WithPostgresClientDSN(os.Getenv("POSTGRES_DSN")),memorypostgres.WithSoftDelete(true),// Enable soft delete.memorypostgres.WithToolEnabled(memory.DeleteToolName,true),// Enable delete.)iferr!=nil{// Handle error.}// Register memory tools with the Agent.llmAgent:=llmagent.New("memory-assistant",llmagent.WithTools(memService.Tools()),// Or redisService.Tools(), mysqlService.Tools(), or postgresService.Tools().)// Set memory service in the Runner.runner:=runner.NewRunner("app",llmAgent,runner.WithMemoryService(memService),// Or redisService, mysqlService, or postgresService.)
WithPostgresClientDSN is the recommended PostgreSQL connection form and has
priority over WithHost, WithPort, WithUser, WithPassword,
WithDatabase, and WithSSLMode. Supply POSTGRES_DSN through environment or
secret management. In production, use a certificate-validating DSN such as
postgres://<user>:<password>@db.example.com:5432/dbname?sslmode=verify-full&sslrootcert=<trusted-ca-path>;
the host must match the server certificate. sslmode=disable is appropriate
only for trusted local development.
Memory Tool Configuration
The memory service provides 6 tools. In Agentic mode, common tools are enabled
by default and dangerous operations require manual enabling. In Auto mode,
extractor operation availability and agent-facing tool exposure are controlled
separately.
Tool List
Tool
Function
Agentic Mode
Auto Extraction Mode
Description
memory_add
Add new memory
β Default
β Enabled for extractor; hidden from agent by default
Create new memory entry
memory_update
Update memory
β Default
β Enabled for extractor; hidden from agent by default
Modify existing memory
memory_search
Search memory
β Default
β Enabled and exposed by default
Find by keywords
memory_load
Load memories
β Default
βοΈ Disabled by default; exposed once enabled
Load recent memories
memory_delete
Delete memory
βοΈ Configurable
β Enabled for extractor; hidden from agent by default
Delete single memory
memory_clear
Clear memories
βοΈ Configurable
βοΈ Disabled by default
Delete all memories
Notes:
Agentic Mode: Agent actively calls tools to manage memory, all tools are configurable
Auto Mode: LLM extractor handles enabled write operations in background. Tools() exposes Search by default; Load is exposed once enabled; WithAutoMemoryExposedTools() can selectively expose enabled write tools for hybrid usage.
Enabled but not returned by Tools() by default: memory_add, memory_update, memory_delete
Default: Available immediately when service is created, no extra configuration needed
Configurable: Can be enabled/disabled via WithToolEnabled(); in Auto mode, enabled write tools can be exposed via WithAutoMemoryExposedTools()
Enable/Disable Tools
Note: WithToolEnabled() controls whether a memory operation is available at
all. WithAutoMemoryExposedTools() controls which enabled tools are returned
from Tools() for the Agent to call in Auto mode. Write tools remain hidden by
default unless you expose them explicitly.
// Scenario 1: User manageable (allow single deletion)memoryService:=memoryinmemory.NewMemoryService(memoryinmemory.WithToolEnabled(memory.DeleteToolName,true),)// Scenario 2: Admin privileges (allow clearing all)memoryService:=memoryinmemory.NewMemoryService(memoryinmemory.WithToolEnabled(memory.DeleteToolName,true),memoryinmemory.WithToolEnabled(memory.ClearToolName,true),)// Scenario 3: Read-only assistant (query only)memoryService:=memoryinmemory.NewMemoryService(memoryinmemory.WithToolEnabled(memory.AddToolName,false),memoryinmemory.WithToolEnabled(memory.UpdateToolName,false),)// Scenario 4: Hybrid auto memory + explicit agent writesmemoryService:=memoryinmemory.NewMemoryService(memoryinmemory.WithExtractor(memExtractor),memoryinmemory.WithAutoMemoryExposedTools(memory.AddToolName),)
Overwrite Semantics (IDs and duplicates)
Memory IDs are generated from memory content + appName + userID + canonical
episodic metadata. Topics are intentionally excluded, so changing tags does
not create a new memory. Adding the same content and identity metadata for the
same user is idempotent and overwrites the existing entry (not append).
The stored topics are replaced and UpdatedAt is refreshed. If that
canonical ID belongs to a soft-deleted row, AddMemory reactivates it.
If you need append semantics or different duplicate-handling strategies, you can
implement custom tools or extend the service with policy options (e.g. allow/overwrite/ignore).
Update Semantics and ID Rotation
UpdateMemory first applies the requested content, topics, and episodic
metadata, then recalculates the canonical memory ID. Topics are not part of the
ID, so a topics-only update stays on the same ID.
The operation follows this state machine:
State after applying the update
Result
Source is missing or soft-deleted
Return a not-found error without changing UpdateResult
Canonical ID is unchanged
Update the active source in place
New ID does not exist
Create the target and retire the source
New ID is soft-deleted
Reactivate the target; hard-delete mode replaces the stale tombstone
New ID is already active
Return a conflict error without modifying either record
For backends with soft deletion enabled, a successful ID rotation preserves
the old source as a tombstone. With hard deletion, the old source is removed.
SQL backends perform target preparation and source retirement atomically.
Timestamp behavior is also stable across SQL backends:
A newly inserted target inherits the source CreatedAt.
A reactivated target preserves its own CreatedAt.
A hard-delete replacement of a stale target inherits the source CreatedAt.
Every successful update refreshes UpdatedAt.
On success, UpdateResult.MemoryID receives the effective canonical ID. On
error, the caller-provided result remains unchanged.
Custom Tool Implementation
Note: In Auto mode, Tools() exposes memory_search by default, memory_load
when enabled, and any additional enabled tools you explicitly expose with
WithAutoMemoryExposedTools(). Dangerous operations like memory_clear should usually
stay application-controlled.
You can override default tools with custom implementations. See
memory/tool/tool.go for reference on how to implement custom tools.
import("context""fmt""trpc.group/trpc-go/trpc-agent-go/memory"memoryinmemory"trpc.group/trpc-go/trpc-agent-go/memory/inmemory"toolmemory"trpc.group/trpc-go/trpc-agent-go/memory/tool""trpc.group/trpc-go/trpc-agent-go/tool""trpc.group/trpc-go/trpc-agent-go/tool/function")// A custom clear tool with real logic using the invocation context.funccustomClearMemoryTool()tool.Tool{clearFunc:=func(ctxcontext.Context,_*toolmemory.ClearMemoryRequest)(*toolmemory.ClearMemoryResponse,error){// Get memory service and user info from invocation context.memSvc,err:=toolmemory.GetMemoryServiceFromContext(ctx)iferr!=nil{returnnil,fmt.Errorf("custom clear tool: %w",err)}appName,userID,err:=toolmemory.GetAppAndUserFromContext(ctx)iferr!=nil{returnnil,fmt.Errorf("custom clear tool: %w",err)}iferr:=memSvc.ClearMemories(ctx,memory.UserKey{AppName:appName,UserID:userID});err!=nil{returnnil,fmt.Errorf("custom clear tool: failed to clear memories: %w",err)}return&toolmemory.ClearMemoryResponse{Message:"π All memories cleared successfully!"},nil}returnfunction.NewFunctionTool(clearFunc,function.WithName(memory.ClearToolName),function.WithDescription("Clear all memories for the user."),)}// Register the custom tool with an InMemory service.memoryService:=memoryinmemory.NewMemoryService(memoryinmemory.WithCustomTool(memory.ClearToolName,customClearMemoryTool),)
Full Example
Below is a complete interactive chat example demonstrating memory capabilities in action.
$gorunmain.go
π§ SimpleMemoryChat
Model:deepseek-v4-flash
MemoryService:inmemory
In-memory
Streaming:trueAvailabletools:memory_add,memory_update,memory_search,memory_load
(memory_delete,memory_cleardisabledbydefault,andcanbeenabledorcustomized)==================================================β Memorychatready!Session:memory-session-1765504626
π‘Specialcommands:
/memory-Showusermemories
/new-Startanewsession
/exit-Endtheconversation
π€You:Hi,mynameisJohnandIlikecoffee.
π€Assistant:HiJohn!Nicetomeetyou.I've made a note that you like coffee. It'sgreattoknowyourpreferences-I'll remember this for our future conversations. Is there anything specific about coffee that you enjoy, or anything else you'dlikemetoknowaboutyou?
π§Memorytoolcallsinitiated:
β’memory_add(ID:call_00_wE9FAqaLEPtWcqgF3tQqRoLn)Args:{"memory":"John likes coffee.","topics":["preferences","food-drink"]}πExecutingmemorytools...
β Memorytoolresponse(ID:call_00_wE9FAqaLEPtWcqgF3tQqRoLn):{"message":"Memory added successfully","memory":"John likes coffee.","topics":["preferences","food-drink"]}Iseeyou're a coffee enthusiast! What brings you here today, John? Are you looking for coffee recommendations, or is there something else I can help you with?π€ You: /newπ Started new memory session! Previous: memory-session-1765504626 Current: memory-session-1765504664 (Conversation history has been reset, memories are preserved)π€ You: What do I like?π€ Assistant: I'llsearchthroughmymemoriestorecallwhatyoulike.LetmecheckwhatinformationIhavestoredaboutyourpreferences.
π§Memorytoolcallsinitiated:
β’memory_search(ID:call_00_CCn57ylCDDQ7iaL88d2JScvl)Args:{"query":"likes preferences favorite enjoy"}πExecutingmemorytools...
β Memorytoolresponse(ID:call_00_CCn57ylCDDQ7iaL88d2JScvl):{"query":"likes preferences favorite enjoy","results":[{"id":"47f1de6c1318d41001a17a46ebb9f9984b6e89e5ac549aedbf34d7744e8862e0","memory":"John likes coffee.","topics":["preferences","food-drink"],"created":"2025-12-12T09:57:12.456153047+08:00"}],"count":1}Basedonmymemories,Iknowthat**youlikecoffee**.That's the only preference I have recorded so far.To give you a more complete answer about your likes, I'dneedtolearnmoreaboutyou!Couldyoutellmeaboutsomeofyourotherinterests,hobbies,orpreferences?Forexample:
-Whatfoodsordrinksdoyouenjoy?
-Whathobbiesoractivitiesdoyoulike?
-Whatkindofmusic,movies,orbooksdoyouprefer?
-Arethereanyparticulartopicsorsubjectsyou're interested in?The more you share with me, the better I'llbeabletorememberandhelpyouinthefuture!
π€You:/exit
πGoodbye!
Code Example
For full code, see examples/memory.
The excerpt below is intentionally limited to InMemory, Redis, MySQL, and
PostgreSQL. The runnable example uses the shared
NewMemoryServiceByType
factory for all nine built-in backends, including MySQLVec and pgvector.
packagemainimport("flag""fmt""log""os""strconv""trpc.group/trpc-go/trpc-agent-go/agent/llmagent""trpc.group/trpc-go/trpc-agent-go/memory"memoryinmemory"trpc.group/trpc-go/trpc-agent-go/memory/inmemory"memoryredis"trpc.group/trpc-go/trpc-agent-go/memory/redis"memorymysql"trpc.group/trpc-go/trpc-agent-go/memory/mysql"memorypostgres"trpc.group/trpc-go/trpc-agent-go/memory/postgres""trpc.group/trpc-go/trpc-agent-go/model""trpc.group/trpc-go/trpc-agent-go/model/openai""trpc.group/trpc-go/trpc-agent-go/runner""trpc.group/trpc-go/trpc-agent-go/session/inmemory")funcmain(){var(memType=flag.String("memory","inmemory","Memory service type")streaming=flag.Bool("streaming",true,"Enable streaming")softDelete=flag.Bool("soft-delete",false,"Enable soft delete")modelName=flag.String("model","deepseek-v4-flash","Model name"))flag.Parse()// 1. Create memory servicememoryService,err:=createMemoryService(*memType,*softDelete)iferr!=nil{log.Fatalf("Failed to create memory service: %v",err)}// 2. Create modelmodelInstance:=openai.New(*modelName)// 3. Create AgentgenConfig:=model.GenerationConfig{MaxTokens:intPtr(2000),Temperature:floatPtr(0.7),Stream:*streaming,}llmAgent:=llmagent.New("memory-assistant",llmagent.WithModel(modelInstance),llmagent.WithDescription("A helpful AI assistant with memory capabilities. "+"I can remember important information about you and "+"recall it when needed.",),llmagent.WithGenerationConfig(genConfig),llmagent.WithTools(memoryService.Tools()),)// 4. Create RunnersessionService:=inmemory.NewSessionService()appRunner:=runner.NewRunner("memory-chat",llmAgent,runner.WithSessionService(sessionService),runner.WithMemoryService(memoryService),)deferappRunner.Close()// 5. Run chatlog.Println("π§ Starting memory-enabled chat...")// ... handle user input and responses}funccreateMemoryService(memTypestring,softDeletebool)(memory.Service,error){switchmemType{case"redis":redisAddr:=os.Getenv("REDIS_ADDR")ifredisAddr==""{redisAddr="localhost:6379"}returnmemoryredis.NewService(memoryredis.WithRedisClientURL(fmt.Sprintf("redis://%s",redisAddr),),memoryredis.WithToolEnabled(memory.DeleteToolName,false),)case"mysql":dsn:=buildMySQLDSN()returnmemorymysql.NewService(memorymysql.WithMySQLClientDSN(dsn),memorymysql.WithSoftDelete(softDelete),memorymysql.WithToolEnabled(memory.DeleteToolName,false),)case"postgres":port,err:=getEnvInt("PG_PORT",5432)iferr!=nil{returnnil,err}returnmemorypostgres.NewService(memorypostgres.WithHost(getEnv("PG_HOST","localhost")),memorypostgres.WithPort(port),memorypostgres.WithUser(getEnv("PG_USER","postgres")),memorypostgres.WithPassword(getEnv("PG_PASSWORD","")),memorypostgres.WithDatabase(getEnv("PG_DATABASE","trpc-agent-go-pgmemory")),memorypostgres.WithSoftDelete(softDelete),memorypostgres.WithToolEnabled(memory.DeleteToolName,false),)case"inmemory":returnmemoryinmemory.NewMemoryService(memoryinmemory.WithToolEnabled(memory.DeleteToolName,false),),nildefault:returnnil,fmt.Errorf("unsupported memory service type %q in abbreviated example",memType,)}}funcbuildMySQLDSN()string{host:=getEnv("MYSQL_HOST","localhost")port:=getEnv("MYSQL_PORT","3306")user:=getEnv("MYSQL_USER","root")password:=getEnv("MYSQL_PASSWORD","")database:=getEnv("MYSQL_DATABASE","trpc_agent_go")returnfmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true&charset=utf8mb4",user,password,host,port,database,)}funcgetEnv(key,defaultValstring)string{ifval:=os.Getenv(key);val!=""{returnval}returndefaultVal}funcgetEnvInt(keystring,defaultValint)(int,error){raw:=os.Getenv(key)ifraw==""{returndefaultVal,nil}value,err:=strconv.Atoi(raw)iferr!=nil||value<1||value>65535{return0,fmt.Errorf("invalid %s %q: must be an integer in [1, 65535]",key,raw,)}returnvalue,nil}funcintPtr(iint)*int{return&i}funcfloatPtr(ffloat64)*float64{return&f}
Advanced Configuration
Auto Mode Configuration Options
Option
Description
Default
WithExtractor(extractor)
Enable auto mode with LLM extractor
nil (disabled)
WithAsyncMemoryNum(n)
Number of background worker goroutines
1
WithMemoryQueueSize(n)
Size of memory job queue
10
WithMemoryJobTimeout(d)
Timeout for each extraction job
30s
Extraction Checkers
Checkers control when memory extraction should be triggered. By default, extraction happens on every conversation turn. Use checkers to optimize extraction frequency and reduce LLM costs.
Available Checkers
Checker
Description
Example
CheckMessageThreshold
Triggers when accumulated messages exceed threshold
CheckMessageThreshold(5) - when messages > 5
CheckTimeInterval
Triggers when time since last extraction exceeds interval
// Example 1: Extract when messages > 5 OR every 3 minutes (OR logic).memExtractor:=extractor.NewExtractor(extractorModel,extractor.WithCheckersAny(extractor.CheckMessageThreshold(5),extractor.CheckTimeInterval(3*time.Minute),),)// Example 2: Extract when messages > 10 AND every 5 minutes (AND logic).memExtractor:=extractor.NewExtractor(extractorModel,extractor.WithChecker(extractor.CheckMessageThreshold(10)),extractor.WithChecker(extractor.CheckTimeInterval(5*time.Minute)),)
Model callbacks (before/after)
The extractor also supports injecting before/after model callbacks via model.Callbacks (structured only). This is useful for tracing, request rewriting, or short-circuiting the model call in tests.
callbacks:=model.NewCallbacks().RegisterBeforeModel(func(ctxcontext.Context,args*model.BeforeModelArgs)(*model.BeforeModelResult,error){// You can modify args.Request or return CustomResponse.returnnil,nil},).RegisterAfterModel(func(ctxcontext.Context,args*model.AfterModelArgs)(*model.AfterModelResult,error){// You can inspect/override args.Response.returnnil,nil},)memExtractor:=extractor.NewExtractor(extractorModel,extractor.WithModelCallbacks(callbacks),)
ExtractionContext
The ExtractionContext provides information for checker decisions:
typeExtractionContextstruct{UserKeymemory.UserKey// User identifier.Messages[]model.Message// Filtered messages since last extraction.LastExtractAt*time.Time// Last extraction timestamp, nil if never extracted.}
Messages accumulates since the last successful extraction, but contains only
user/assistant messages with content. Tool calls, tool results, and empty
messages are excluded. When a checker returns false, the filtered messages
remain available to the next extraction check.
Tool Control
In auto extraction mode, WithToolEnabled controls whether each tool is
available. memory_search is exposed through Tools() by default,
memory_load is exposed once enabled, and WithAutoMemoryExposedTools
selectively exposes enabled write tools for hybrid usage.
Front-end Tools (agent-facing tools returned by Tools()):
Tool
Agent-facing default
Description
memory_search
β Exposed
Search memories by query
memory_load
β Not exposed
Load all or recent N memories; exposed once enabled
Back-end Operations (operation availability for the extractor):
memoryService:=memoryinmemory.NewMemoryService(memoryinmemory.WithExtractor(memExtractor),// Front-end: enable memory_load for agent to call.memoryinmemory.WithToolEnabled(memory.LoadToolName,true),// Hybrid: expose memory_add so the agent can store critical facts immediately.memoryinmemory.WithAutoMemoryExposedTools(memory.AddToolName),// Back-end: disable memory_delete so extractor cannot delete.memoryinmemory.WithToolEnabled(memory.DeleteToolName,false),// Back-end: enable memory_clear for extractor (use with caution).memoryinmemory.WithToolEnabled(memory.ClearToolName,true),)
Note: WithToolEnabled and WithAutoMemoryExposedTools can be called before or after
WithExtractor - the order does not matter.
Comparison: Agentic Mode vs Auto Mode
Tool
Agentic Mode (no extractor)
Auto Mode (with extractor)
memory_add
β Agent calls via Tools()
βοΈ Agent calls via Tools() if exposed; extractor uses in background
memory_update
β Agent calls via Tools()
βοΈ Agent calls via Tools() if exposed; extractor uses in background
memory_search
β Agent calls via Tools()
β Agent calls via Tools()
memory_load
β Agent calls via Tools()
βοΈ Agent calls via Tools() if enabled
memory_delete
βοΈ Agent calls via Tools() if enabled
βοΈ Agent calls via Tools() if exposed; extractor uses in background
memory_clear
βοΈ Agent calls via Tools() if enabled
βοΈ Agent calls via Tools() if exposed; extractor uses in background if enabled
Memory Preloading
Both modes support preloading memories into the system prompt:
llmAgent:=llmagent.New("assistant",llmagent.WithModel(model),llmagent.WithTools(memoryService.Tools()),// Preload options:// llmagent.WithPreloadMemory(0), // Disable preloading (default).// llmagent.WithPreloadMemory(10), // Adaptive preload budget 10.// // Loads all memories when count <= 10,// // otherwise injects top 10 search results.// llmagent.WithPreloadMemory(-1), // Load all.// // β οΈ WARNING: Loading all memories may significantly// // increase token usage and API costs, especially// // for users with many stored memories. Consider// // using a positive budget for production use.// llmagent.WithPreloadMemory(10), // Recommended production setting.)
When preloading is enabled, memories are automatically injected into the
system prompt, giving the Agent context about the user without explicit
tool calls.
When WithPreloadMemory(N) uses a positive value, the framework first probes
how many memories the user has. If the count is at most N, it injects all
memories. If the count is larger than N, it switches to query-aware
memory_search behavior internally and injects only the top N relevant
results for the current user message. If query extraction is empty, the
search fails, or the search returns no matches, it falls back to directly
loading up to N memories.
Injection Mechanism: Preloaded memories are merged into the existing
system prompt rather than inserted as a separate system message. This ensures
the request always contains a single system message, maintaining compatibility
with models that have limited support for multiple system messages (e.g.,
Qwen3.5 series may return "System message must be at the beginning" error).
β οΈ Important Note: Setting the configuration to -1 loads all memories,
which may significantly increase Token Usage and API Costs. By default,
preloading is disabled (0), and we recommend using positive budgets (e.g., 10-50)
to balance performance and cost.
Hybrid Approach
You can combine both approaches:
Use Auto mode for passive learning (background extraction)