Skip to content

feat(ai): add persistent conversation store backends - #1540

Open
beatenevo wants to merge 9 commits into
apache:aifrom
beatenevo:feat/ai-gorm-store
Open

feat(ai): add persistent conversation store backends#1540
beatenevo wants to merge 9 commits into
apache:aifrom
beatenevo:feat/ai-gorm-store

Conversation

@beatenevo

@beatenevo beatenevo commented Aug 22, 2026

Copy link
Copy Markdown

Please provide a description of this PR:

This PR adds pluggable persistence for AI conversation data.

Related to #1502.

Changes

  • Define SessionStore, MessageStore, and the combined Store interfaces.
  • Migrate in-memory Session, Turn, and Genkit ai.Message handling to MemoryStore.
  • Inject one shared Store instance into the Session Manager, Agent, and Memory Tool.
  • Add GORM models for Sessions, Turns, and Messages.
  • Implement GormStore for persistent conversation history.
  • Add Memory/GORM backend configuration and database connection initialization.
  • Add schema migration support.
  • Use max_turns as the single Turn-limit configuration for both backends.
  • Return ErrTurnLimitReached when a Session attempts to create a Turn beyond the configured limit.
  • Add transactional writes, Turn finalization, and Session-associated data deletion.
  • Enforce message ordering with a unique (turn_id, sequence) constraint.
  • Delete Messages and Turns explicitly without database foreign keys.
  • Prevent expiration cleanup from deleting a Session refreshed by another instance.
  • Use a cancellation-independent persistence context for generated message writes and final Turn finalization, so client disconnects do not interrupt already-generated data persistence.
  • Keep HistoryMemory only as a lazily initialized compatibility layer; production Agent, Memory Tool, and Session Manager paths use Store interfaces exclusively.
  • Add contract, rollback, restart recovery, multi-instance sharing, JSON codec, max-turn, persistence-context, and component regression tests.

Testing

The following checks pass in the ai/ Go module:

  • go test ./...
  • go vet ./...
  • go test -race ./store/... ./component/agent/react ./component/tools/test ./component/server/engine ./component/server/engine/sse

SQLite is used for local persistence and behavioral tests. MySQL and PostgreSQL drivers are included and compile successfully. Service-backed MySQL/PostgreSQL integration tests are outside the current PR because CI database services are not configured.

To help us figure out who should review this PR, please put an X in all the areas that this PR affects.

  • Docs
  • Installation
  • User Experience
  • Dubboctl
  • Console
  • Core Component

Please check any characteristics that apply to this pull request.

@robocanic

Copy link
Copy Markdown
Contributor

@larry-zy

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds pluggable, persistent AI conversation storage shared across agents, tools, sessions, and server components.

Changes:

  • Introduces memory and GORM store backends with migrations and limits.
  • Injects one shared store throughout AI runtime components.
  • Adds configuration schemas and comprehensive backend tests.

Reviewed changes

Copilot reviewed 39 out of 40 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
ai/store/types.go Defines persisted session and turn types.
ai/store/store.go Defines conversation store interfaces.
ai/store/errors.go Adds store sentinel errors.
ai/store/memory/store.go Implements in-memory storage.
ai/store/gorm/store.go Implements relational storage.
ai/store/gorm/models.go Defines GORM database models.
ai/store/gorm/dialector.go Opens supported databases.
ai/store/gorm/codec.go Serializes Genkit messages.
ai/store/gorm/codec_test.go Tests message serialization.
ai/store/gorm/store_test.go Tests persistence and transactions.
ai/store/test/contract.go Defines shared backend contract tests.
ai/store/test/memory_store_test.go Tests memory-store behavior.
ai/component/memory/config.go Adds backend/database configuration.
ai/component/memory/factory.go Applies configuration defaults.
ai/component/memory/component.go Owns and initializes the shared store.
ai/component/memory/config_test.go Tests configuration and lifecycle.
ai/component/agent/agent.go Adds request context to agent interactions.
ai/component/agent/react/react.go Persists interaction history and turns.
ai/component/agent/react/steps.go Reads and writes through MessageStore.
ai/component/agent/react/orchestrator.go Carries persistence context.
ai/component/agent/react/component.go Injects the shared store.
ai/component/agent/react/component_store_test.go Verifies store injection.
ai/component/agent/react/persistence_context_test.go Tests detached persistence contexts.
ai/component/agent/react/page_context_test.go Updates page-context tests.
ai/component/agent/react/step_test.go Updates step tests for stores.
ai/component/tools/engine/tools.go Injects MessageStore into tools.
ai/component/tools/engine/memory_tools.go Reads persisted conversation history.
ai/component/tools/test/engine_tools_test.go Tests shared-store tool access.
ai/component/server/component.go Injects stores and manages router cleanup.
ai/component/server/engine/router.go Constructs store-backed session routing.
ai/component/server/engine/handlers.go Uses persistent session operations.
ai/component/server/engine/handlers_test.go Updates handler tests.
ai/component/server/engine/session/session.go Replaces session maps with SessionStore.
ai/component/server/engine/session/session_test.go Tests shared session storage.
ai/component/server/engine/sse/sse_test.go Adds SSE behavior tests.
ai/config/test/loader_test.go Tests GORM configuration loading.
ai/schema/json/memory.schema.json Defines backend configuration schema.
ai/schema/json/REQUIRED_FIELDS.md Documents new configuration fields.
ai/go.mod Adds GORM database dependencies.
ai/go.sum Records dependency checksums.
Suppressed comments (2)

ai/component/server/engine/handlers.go:212

  • SessionStore.Get can now return arbitrary GORM/database failures, but this maps every error to 404 and labels it "Session not found". Return 404 only for ErrSessionNotFound/ErrSessionExpired; operational failures should produce a sanitized 5xx response.
	sessionObj, err := h.sessionMgr.GetSession(c.Request.Context(), sessionID)
	if err != nil {
		c.JSON(http.StatusNotFound, NewErrorResponse("Session not found: "+err.Error()))
		return

ai/component/server/engine/handlers.go:245

  • A failed database transaction is also mapped to 404 here, even when the session exists. Check specifically for ErrSessionNotFound; return a sanitized 5xx response for other persistence errors so clients do not treat an outage as a missing resource.
	if err := h.sessionMgr.DeleteSession(c.Request.Context(), sessionID); err != nil {
		c.JSON(http.StatusNotFound, NewErrorResponse("Session not found: "+err.Error()))
		return

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread ai/store/store.go Outdated
Comment thread ai/store/gorm/store.go Outdated
Comment thread ai/store/memory/store.go Outdated
Comment thread ai/component/agent/react/react.go Outdated
Comment thread ai/store/gorm/store.go Outdated
Comment thread ai/component/server/engine/handlers.go Outdated
Comment thread ai/component/server/engine/handlers.go Outdated
// context, so there is no need to wrap the input in a
// JSON envelope the model would otherwise have to read through.
history.AddHistory(sessionID, ai.NewUserMessage(ai.NewTextPart(input.Content)))
turnID, err := ra.messageStore.BeginTurn(parent, sessionID)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This creates the persisted Turn before the interaction loop starts. Any later cancellation path must either finalize or explicitly abort this Turn; otherwise it remains active.

if err != nil {
return nil, nil, fmt.Errorf("failed to begin turn: %w", err)
}
if err := ra.messageStore.AddHistoryToTurn(parent, sessionID, turnID, ai.NewUserMessage(ai.NewTextPart(input.Content))); err != nil {

@larry-zy larry-zy Sep 2, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The user message is persisted immediately after BeginTurn. Therefore, a client disconnect after this point leaves durable conversation state even if the remaining interaction is canceled.

}
defer s.cancelPersistence()

if err := runLoop(ctx, s, ra.maxIterations, ra.buildSteps(chans)...); err != nil {

@larry-zy larry-zy Sep 2, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The context passed to runLoop is derived from the HTTP request context in newInteraction, so runLoop remains cancellation-sensitive even though persistCtx is detached.

defer s.cancelPersistence()

if err := runLoop(ctx, s, ra.maxIterations, ra.buildSteps(chans)...); err != nil {
chans.ErrorChan <- err

@larry-zy larry-zy Sep 2, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a stage returns context.Canceled after a client disconnect, this immediate return has no cleanup or abort operation for the Turn created above.

return
}

if err := ra.messageStore.NextTurnForTurn(s.persistenceContext(ctx), sessionID, s.TurnID); err != nil {

@larry-zy larry-zy Sep 2, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NextTurnForTurn is only reached after runLoop succeeds. It is skipped by the cancellation or error return above, leaving the persisted Turn active.

Comment thread ai/store/gorm/store.go
@sonarqubecloud

sonarqubecloud Bot commented Sep 2, 2026

Copy link
Copy Markdown

@larry-zy

larry-zy commented Sep 2, 2026

Copy link
Copy Markdown

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants