@@ -99,18 +99,20 @@ func (w *EnrichWorker) resolveProviderForTenant(ctx context.Context, tenantID st
9999// Stop cancels in-flight enrichment for the given tenant.
100100// Safe to call even if no enrichment is running.
101101func (w * EnrichWorker ) Stop (tenantID string ) {
102- if cancel , ok := w .cancelFuncs .Load (tenantID ); ok {
102+ if cancel , ok := w .cancelFuncs .LoadAndDelete (tenantID ); ok {
103103 cancel .(context.CancelFunc )()
104- w .cancelFuncs .Delete (tenantID )
105- w .progress .Finish ()
106- slog .Info ("vault.enrich: stopped by user" , "tenant" , tenantID )
107104 }
105+ // Always finish progress — ensures UI resets even if cancelFuncs was empty.
106+ w .progress .Finish ()
107+ slog .Info ("vault.enrich: stopped by user" , "tenant" , tenantID )
108108}
109109
110110// IsRunning returns true if enrichment is in progress for the tenant.
111111func (w * EnrichWorker ) IsRunning (tenantID string ) bool {
112- _ , ok := w .cancelFuncs .Load (tenantID )
113- return ok
112+ if _ , ok := w .cancelFuncs .Load (tenantID ); ok {
113+ return true
114+ }
115+ return w .progress .Status ().Running
114116}
115117
116118// EnqueueUnenriched fetches documents with empty summary and emits enrichment events.
@@ -127,8 +129,8 @@ func (w *EnrichWorker) EnqueueUnenriched(ctx context.Context, tenantID, workspac
127129
128130 count := 0
129131 for _ , doc := range docs {
130- // Skip auto-generated media files — they create noise links.
131- if strings . HasPrefix (filepath .Base (doc .Path ), "goclaw_gen_" ) {
132+ // Skip meaningless filenames — they create noise links.
133+ if shouldSkipEnrichment (filepath .Base (doc .Path )) {
132134 continue
133135 }
134136 agentID := ""
@@ -178,10 +180,8 @@ func (w *EnrichWorker) Handle(ctx context.Context, event eventbus.DomainEvent) e
178180 return nil
179181 }
180182
181- // Skip auto-generated media files (goclaw_gen_*) — they create excessive
182- // noise links due to similar embeddings. These are typically image outputs
183- // that don't benefit from semantic linking.
184- if basename := filepath .Base (payload .Path ); strings .HasPrefix (basename , "goclaw_gen_" ) {
183+ // Skip meaningless filenames — they create noise links.
184+ if shouldSkipEnrichment (filepath .Base (payload .Path )) {
185185 return nil
186186 }
187187
@@ -201,7 +201,12 @@ func (w *EnrichWorker) Handle(ctx context.Context, event eventbus.DomainEvent) e
201201 return nil // another goroutine already processing this agent's queue
202202 }
203203
204- w .processBatch (ctx , key )
204+ // Create per-tenant cancel context for stop capability.
205+ cancelCtx , cancel := context .WithCancel (ctx )
206+ w .cancelFuncs .Store (payload .TenantID , cancel )
207+ w .processBatch (cancelCtx , key )
208+ // Clean up after batch completes naturally.
209+ w .cancelFuncs .Delete (payload .TenantID )
205210 return nil
206211}
207212
@@ -217,6 +222,10 @@ type enriched struct {
217222// overwhelm the LLM provider with hundreds of concurrent requests.
218223func (w * EnrichWorker ) processBatch (ctx context.Context , key string ) {
219224 for {
225+ if ctx .Err () != nil {
226+ w .queue .TryFinish (key )
227+ return
228+ }
220229 items := w .queue .Drain (key )
221230 if len (items ) == 0 {
222231 if w .queue .TryFinish (key ) {
0 commit comments