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 and

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Check database connectivity and error logs around the failure time
  2. Verify the memory subject table exists and migrations ran
  3. Retry the write after transient DB issues — nothing was persisted, so it is idempotent
  4. 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

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


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/bb436fe3aec1945f. Report an issue: GitHub.