Tencent/WeKnora · error
ensure memory subject: %w
Error message
ensure memory subject: %w
What it means
Before inserting a memory item, write() calls repo.EnsureSubject to guarantee the memory subject/scope row exists; a failure aborts the write with this wrapped error so no orphan items are stored without their parent subject.
Source
Thrown at internal/application/service/memory/service.go:345
if err != nil {
return nil, fmt.Errorf("check forgotten memory: %w", err)
}
if !forgotten && item.SourceMessageID != "" && item.Origin == types.MemoryOriginExtracted {
// Only the background path is gated this way. An explicit "remember
// this" is the user asking again, and must always win.
forgotten, err = s.repo.HasTombstoneForMessage(
ctx, scope, item.SourceMessageID, rejectedMessageWindow,
)
if err != nil {
return nil, fmt.Errorf("check forgotten source: %w", err)
}
}
if forgotten {
logger.Infof(ctx, "memory: skipped a statement the user previously deleted")
return nil, ErrPreviouslyForgotten
}
if _, err := s.repo.EnsureSubject(ctx, scope); err != nil {
return nil, fmt.Errorf("ensure memory subject: %w", err)
}
topic := types.SanitizeMemoryTopic(item.Topic)
normalizedKey := types.MemoryItemKey(topic, content)
existing, err := s.repo.FindActiveByKey(ctx, scope, normalizedKey)
if err != nil {
return nil, fmt.Errorf("find conflicting memory: %w", err)
}
if existing != nil && types.SanitizeMemoryContent(existing.Content) == content {
// Same statement about the same topic: nothing changed, so keep the
// original timestamps instead of churning the row on every turn.
return existing, nil
}
if existing == nil {
// The same fact often arrives twice: once because the user said
// "remember ..." and again from the background distillation, phrased
// slightly differently ("我们的生产库是 X" vs "生产库是 X"). They get
// different topic keys, so key matching alone lets both through andView on GitHub (pinned to 988cbb0330)
Solutions
- Check database connectivity and error logs around the failure time
- Verify the memory subject table exists and migrations ran
- Retry the write after transient DB issues — nothing was persisted, so it is idempotent
- Check for row-lock contention on the subject row from concurrent writers
Example fix
// before
_, err := memSvc.CreateItem(ctx, scope, item) // opaque wrapped failure
// after
err := withRetry(ctx, 3, func() error {
_, err := memSvc.CreateItem(ctx, scope, item)
return err
}) Defensive patterns
Strategy: retry
Type guard
func isEnsureSubjectErr(err error) bool {
return err != nil && strings.Contains(err.Error(), "ensure memory subject")
} Try / catch
_, err := memSvc.Remember(ctx, scope, item)
if err != nil {
if isEnsureSubjectErr(err) {
return retryWithBackoff(ctx, 3, call) // nothing persisted; idempotent retry
}
return err
} Prevention
- Verify memory subject table migrations run in every environment
- Use idempotent EnsureSubject (upsert) to avoid unique-constraint races
- Keep context deadlines generous enough for the EnsureSubject round-trip
- Watch for lock contention on subject rows with many concurrent writers
When it happens
Trigger: Any memory write path (Remember, PromoteTopic, CreateItem, applyDecisions, mergeRedundant, observeTopics) where repo.EnsureSubject fails — DB error, FK constraint trouble, or context cancellation.
Common situations: Database outage mid-write; subject table locked or migrated; context deadline exceeded during slow EnsureSubject; permission/ownership issue on the scope row.
Understand the failure class
Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.
Related errors
- check forgotten memory: %w
- check forgotten source: %w
- find conflicting memory: %w
- create memory item: %w
- scan for duplicate memory: %w
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/bb436fe3aec1945f.
Report an issue: GitHub.