diff --git a/api/graphql/gqlgen.yml b/api/graphql/gqlgen.yml
index 63281efdfc27b14a6545a7507bc8014b0f1cb1e3..4bca6ec2dc996ee5fda0622b56c69b6dcaa9a98a 100644
--- a/api/graphql/gqlgen.yml
+++ b/api/graphql/gqlgen.yml
@@ -9,6 +9,7 @@ model:
 
 autobind:
   - "github.com/git-bug/git-bug/api/graphql/models"
+  - "github.com/git-bug/git-bug/cache"
   - "github.com/git-bug/git-bug/repository"
   - "github.com/git-bug/git-bug/entity"
   - "github.com/git-bug/git-bug/entities/common"
diff --git a/api/graphql/graph/operation.generated.go b/api/graphql/graph/operation.generated.go
index 9d3d4cdc1312871493a04d8001d4825ab76308c0..25e579443f0878225426df06740e21c2105b1d8c 100644
--- a/api/graphql/graph/operation.generated.go
+++ b/api/graphql/graph/operation.generated.go
@@ -107,9 +107,9 @@ func (ec *executionContext) _OperationConnection_nodes(ctx context.Context, fiel
 		}
 		return graphql.Null
 	}
-	res := resTmp.([]dag.OperationWithApply[*bug.Snapshot])
+	res := resTmp.([]dag.Operation)
 	fc.Result = res
-	return ec.marshalNOperation2ᚕgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperationWithApplyᚄ(ctx, field.Selections, res)
+	return ec.marshalNOperation2ᚕgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperationᚄ(ctx, field.Selections, res)
 }
 
 func (ec *executionContext) fieldContext_OperationConnection_nodes(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
@@ -293,9 +293,9 @@ func (ec *executionContext) _OperationEdge_node(ctx context.Context, field graph
 		}
 		return graphql.Null
 	}
-	res := resTmp.(dag.OperationWithApply[*bug.Snapshot])
+	res := resTmp.(dag.Operation)
 	fc.Result = res
-	return ec.marshalNOperation2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperationWithApply(ctx, field.Selections, res)
+	return ec.marshalNOperation2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperation(ctx, field.Selections, res)
 }
 
 func (ec *executionContext) fieldContext_OperationEdge_node(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
@@ -319,7 +319,7 @@ func (ec *executionContext) fieldContext_OperationEdge_node(_ context.Context, f
 
 // region    ************************** interface.gotpl ***************************
 
-func (ec *executionContext) _Operation(ctx context.Context, sel ast.SelectionSet, obj dag.OperationWithApply[*bug.Snapshot]) graphql.Marshaler {
+func (ec *executionContext) _Operation(ctx context.Context, sel ast.SelectionSet, obj dag.Operation) graphql.Marshaler {
 	switch obj := (obj).(type) {
 	case nil:
 		return graphql.Null
@@ -464,7 +464,7 @@ func (ec *executionContext) _OperationEdge(ctx context.Context, sel ast.Selectio
 
 // region    ***************************** type.gotpl *****************************
 
-func (ec *executionContext) marshalNOperation2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperationWithApply(ctx context.Context, sel ast.SelectionSet, v dag.OperationWithApply[*bug.Snapshot]) graphql.Marshaler {
+func (ec *executionContext) marshalNOperation2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperation(ctx context.Context, sel ast.SelectionSet, v dag.Operation) graphql.Marshaler {
 	if v == nil {
 		if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
 			ec.Errorf(ctx, "the requested element is null which the schema does not allow")
@@ -474,7 +474,7 @@ func (ec *executionContext) marshalNOperation2githubᚗcomᚋgitᚑbugᚋgitᚑb
 	return ec._Operation(ctx, sel, v)
 }
 
-func (ec *executionContext) marshalNOperation2ᚕgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperationWithApplyᚄ(ctx context.Context, sel ast.SelectionSet, v []dag.OperationWithApply[*bug.Snapshot]) graphql.Marshaler {
+func (ec *executionContext) marshalNOperation2ᚕgithubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperationᚄ(ctx context.Context, sel ast.SelectionSet, v []dag.Operation) graphql.Marshaler {
 	ret := make(graphql.Array, len(v))
 	var wg sync.WaitGroup
 	isLen1 := len(v) == 1
@@ -498,7 +498,7 @@ func (ec *executionContext) marshalNOperation2ᚕgithubᚗcomᚋgitᚑbugᚋgit
 			if !isLen1 {
 				defer wg.Done()
 			}
-			ret[i] = ec.marshalNOperation2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperationWithApply(ctx, sel, v[i])
+			ret[i] = ec.marshalNOperation2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋentityᚋdagᚐOperation(ctx, sel, v[i])
 		}
 		if isLen1 {
 			f(i)
diff --git a/api/graphql/graph/root_.generated.go b/api/graphql/graph/root_.generated.go
index 64520ccfa97f95335030e1060a76d8fe5242021d..5fed0daf75c51de15f387319ebd8724ceddf0d91 100644
--- a/api/graphql/graph/root_.generated.go
+++ b/api/graphql/graph/root_.generated.go
@@ -389,9 +389,9 @@ type ComplexityRoot struct {
 	}
 
 	Subscription struct {
-		AllEvents      func(childComplexity int, repoFilter *string) int
-		BugEvents      func(childComplexity int, repoFilter *string, query *string) int
-		IdentityEvents func(childComplexity int, repoFilter *string) int
+		AllEvents      func(childComplexity int, repoRef *string, typename *string) int
+		BugEvents      func(childComplexity int, repoRef *string) int
+		IdentityEvents func(childComplexity int, repoRef *string) int
 	}
 }
 
@@ -1854,7 +1854,7 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
 			return 0, false
 		}
 
-		return e.complexity.Subscription.AllEvents(childComplexity, args["repoFilter"].(*string)), true
+		return e.complexity.Subscription.AllEvents(childComplexity, args["repoRef"].(*string), args["typename"].(*string)), true
 
 	case "Subscription.bugEvents":
 		if e.complexity.Subscription.BugEvents == nil {
@@ -1866,7 +1866,7 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
 			return 0, false
 		}
 
-		return e.complexity.Subscription.BugEvents(childComplexity, args["repoFilter"].(*string), args["query"].(*string)), true
+		return e.complexity.Subscription.BugEvents(childComplexity, args["repoRef"].(*string)), true
 
 	case "Subscription.identityEvents":
 		if e.complexity.Subscription.IdentityEvents == nil {
@@ -1878,7 +1878,7 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
 			return 0, false
 		}
 
-		return e.complexity.Subscription.IdentityEvents(childComplexity, args["repoFilter"].(*string)), true
+		return e.complexity.Subscription.IdentityEvents(childComplexity, args["repoRef"].(*string)), true
 
 	}
 	return 0, false
@@ -2604,7 +2604,8 @@ type LabelChangeResult {
 }
 `, BuiltIn: false},
 	{Name: "../schema/operation.graphql", Input: `"""An operation applied to an entity."""
-interface Operation {
+interface Operation
+@goModel(model: "github.com/git-bug/git-bug/entity/dag.Operation") {
     """The identifier of the operation"""
     id: ID!
     """The operations author."""
@@ -2693,31 +2694,31 @@ type Mutation # See each entity mutations
 `, BuiltIn: false},
 	{Name: "../schema/subscription.graphql", Input: `type Subscription {
   """Subscribe to events on all entities. For events on a specific repo you can provide a repo reference. Without it, you get the unique default repo or all repo events."""
-  allEvents(repoFilter: String): EntityEvent!
+  allEvents(repoRef: String, typename: String): EntityEvent!
   """Subscribe to identity entity events. For events on a specific repo you can provide a repo reference. Without it, you get the unique default repo or all repo events."""
-  identityEvents(repoFilter: String): IdentityEvent!
+  identityEvents(repoRef: String): IdentityEvent!
   """Subscribe to bug entity events. For events on a specific repo you can provide a repo reference. Without it, you get the unique default repo or all repo events."""
-  bugEvents(repoFilter: String, query: String): BugEvent!
+  bugEvents(repoRef: String): BugEvent!
 }
 
-enum EventType {
+enum EntityEventType {
   CREATED
   UPDATED
   REMOVED
 }
 
 type EntityEvent {
-  type: EventType!
+  type: EntityEventType!
   entity: Entity
 }
 
 type IdentityEvent {
-  type: EventType!
+  type: EntityEventType!
   identity: Identity!
 }
 
 type BugEvent {
-  type: EventType!
+  type: EntityEventType!
   bug: Bug!
 }
 `, BuiltIn: false},
diff --git a/api/graphql/graph/subscription.generated.go b/api/graphql/graph/subscription.generated.go
index b906c92576d80f5ebbf091d83f88f4040692ebd9..0050bab3c40703a15f60521544227918ce6bbe10 100644
--- a/api/graphql/graph/subscription.generated.go
+++ b/api/graphql/graph/subscription.generated.go
@@ -12,15 +12,16 @@ import (
 
 	"github.com/99designs/gqlgen/graphql"
 	"github.com/git-bug/git-bug/api/graphql/models"
+	"github.com/git-bug/git-bug/cache"
 	"github.com/vektah/gqlparser/v2/ast"
 )
 
 // region    ************************** generated!.gotpl **************************
 
 type SubscriptionResolver interface {
-	AllEvents(ctx context.Context, repoFilter *string) (<-chan *models.EntityEvent, error)
-	IdentityEvents(ctx context.Context, repoFilter *string) (<-chan *models.IdentityEvent, error)
-	BugEvents(ctx context.Context, repoFilter *string, query *string) (<-chan *models.BugEvent, error)
+	AllEvents(ctx context.Context, repoRef *string, typename *string) (<-chan *models.EntityEvent, error)
+	IdentityEvents(ctx context.Context, repoRef *string) (<-chan *models.IdentityEvent, error)
+	BugEvents(ctx context.Context, repoRef *string) (<-chan *models.BugEvent, error)
 }
 
 // endregion ************************** generated!.gotpl **************************
@@ -30,24 +31,29 @@ type SubscriptionResolver interface {
 func (ec *executionContext) field_Subscription_allEvents_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
 	var err error
 	args := map[string]any{}
-	arg0, err := ec.field_Subscription_allEvents_argsRepoFilter(ctx, rawArgs)
+	arg0, err := ec.field_Subscription_allEvents_argsRepoRef(ctx, rawArgs)
 	if err != nil {
 		return nil, err
 	}
-	args["repoFilter"] = arg0
+	args["repoRef"] = arg0
+	arg1, err := ec.field_Subscription_allEvents_argsTypename(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["typename"] = arg1
 	return args, nil
 }
-func (ec *executionContext) field_Subscription_allEvents_argsRepoFilter(
+func (ec *executionContext) field_Subscription_allEvents_argsRepoRef(
 	ctx context.Context,
 	rawArgs map[string]any,
 ) (*string, error) {
-	if _, ok := rawArgs["repoFilter"]; !ok {
+	if _, ok := rawArgs["repoRef"]; !ok {
 		var zeroVal *string
 		return zeroVal, nil
 	}
 
-	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("repoFilter"))
-	if tmp, ok := rawArgs["repoFilter"]; ok {
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("repoRef"))
+	if tmp, ok := rawArgs["repoRef"]; ok {
 		return ec.unmarshalOString2ᚖstring(ctx, tmp)
 	}
 
@@ -55,32 +61,17 @@ func (ec *executionContext) field_Subscription_allEvents_argsRepoFilter(
 	return zeroVal, nil
 }
 
-func (ec *executionContext) field_Subscription_bugEvents_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
-	var err error
-	args := map[string]any{}
-	arg0, err := ec.field_Subscription_bugEvents_argsRepoFilter(ctx, rawArgs)
-	if err != nil {
-		return nil, err
-	}
-	args["repoFilter"] = arg0
-	arg1, err := ec.field_Subscription_bugEvents_argsQuery(ctx, rawArgs)
-	if err != nil {
-		return nil, err
-	}
-	args["query"] = arg1
-	return args, nil
-}
-func (ec *executionContext) field_Subscription_bugEvents_argsRepoFilter(
+func (ec *executionContext) field_Subscription_allEvents_argsTypename(
 	ctx context.Context,
 	rawArgs map[string]any,
 ) (*string, error) {
-	if _, ok := rawArgs["repoFilter"]; !ok {
+	if _, ok := rawArgs["typename"]; !ok {
 		var zeroVal *string
 		return zeroVal, nil
 	}
 
-	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("repoFilter"))
-	if tmp, ok := rawArgs["repoFilter"]; ok {
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("typename"))
+	if tmp, ok := rawArgs["typename"]; ok {
 		return ec.unmarshalOString2ᚖstring(ctx, tmp)
 	}
 
@@ -88,17 +79,27 @@ func (ec *executionContext) field_Subscription_bugEvents_argsRepoFilter(
 	return zeroVal, nil
 }
 
-func (ec *executionContext) field_Subscription_bugEvents_argsQuery(
+func (ec *executionContext) field_Subscription_bugEvents_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
+	var err error
+	args := map[string]any{}
+	arg0, err := ec.field_Subscription_bugEvents_argsRepoRef(ctx, rawArgs)
+	if err != nil {
+		return nil, err
+	}
+	args["repoRef"] = arg0
+	return args, nil
+}
+func (ec *executionContext) field_Subscription_bugEvents_argsRepoRef(
 	ctx context.Context,
 	rawArgs map[string]any,
 ) (*string, error) {
-	if _, ok := rawArgs["query"]; !ok {
+	if _, ok := rawArgs["repoRef"]; !ok {
 		var zeroVal *string
 		return zeroVal, nil
 	}
 
-	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("query"))
-	if tmp, ok := rawArgs["query"]; ok {
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("repoRef"))
+	if tmp, ok := rawArgs["repoRef"]; ok {
 		return ec.unmarshalOString2ᚖstring(ctx, tmp)
 	}
 
@@ -109,24 +110,24 @@ func (ec *executionContext) field_Subscription_bugEvents_argsQuery(
 func (ec *executionContext) field_Subscription_identityEvents_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
 	var err error
 	args := map[string]any{}
-	arg0, err := ec.field_Subscription_identityEvents_argsRepoFilter(ctx, rawArgs)
+	arg0, err := ec.field_Subscription_identityEvents_argsRepoRef(ctx, rawArgs)
 	if err != nil {
 		return nil, err
 	}
-	args["repoFilter"] = arg0
+	args["repoRef"] = arg0
 	return args, nil
 }
-func (ec *executionContext) field_Subscription_identityEvents_argsRepoFilter(
+func (ec *executionContext) field_Subscription_identityEvents_argsRepoRef(
 	ctx context.Context,
 	rawArgs map[string]any,
 ) (*string, error) {
-	if _, ok := rawArgs["repoFilter"]; !ok {
+	if _, ok := rawArgs["repoRef"]; !ok {
 		var zeroVal *string
 		return zeroVal, nil
 	}
 
-	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("repoFilter"))
-	if tmp, ok := rawArgs["repoFilter"]; ok {
+	ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("repoRef"))
+	if tmp, ok := rawArgs["repoRef"]; ok {
 		return ec.unmarshalOString2ᚖstring(ctx, tmp)
 	}
 
@@ -168,9 +169,9 @@ func (ec *executionContext) _BugEvent_type(ctx context.Context, field graphql.Co
 		}
 		return graphql.Null
 	}
-	res := resTmp.(models.EventType)
+	res := resTmp.(cache.EntityEventType)
 	fc.Result = res
-	return ec.marshalNEventType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐEventType(ctx, field.Selections, res)
+	return ec.marshalNEntityEventType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋcacheᚐEntityEventType(ctx, field.Selections, res)
 }
 
 func (ec *executionContext) fieldContext_BugEvent_type(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
@@ -180,7 +181,7 @@ func (ec *executionContext) fieldContext_BugEvent_type(_ context.Context, field
 		IsMethod:   false,
 		IsResolver: false,
 		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
-			return nil, errors.New("field of type EventType does not have child fields")
+			return nil, errors.New("field of type EntityEventType does not have child fields")
 		},
 	}
 	return fc, nil
@@ -284,9 +285,9 @@ func (ec *executionContext) _EntityEvent_type(ctx context.Context, field graphql
 		}
 		return graphql.Null
 	}
-	res := resTmp.(models.EventType)
+	res := resTmp.(cache.EntityEventType)
 	fc.Result = res
-	return ec.marshalNEventType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐEventType(ctx, field.Selections, res)
+	return ec.marshalNEntityEventType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋcacheᚐEntityEventType(ctx, field.Selections, res)
 }
 
 func (ec *executionContext) fieldContext_EntityEvent_type(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
@@ -296,7 +297,7 @@ func (ec *executionContext) fieldContext_EntityEvent_type(_ context.Context, fie
 		IsMethod:   false,
 		IsResolver: false,
 		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
-			return nil, errors.New("field of type EventType does not have child fields")
+			return nil, errors.New("field of type EntityEventType does not have child fields")
 		},
 	}
 	return fc, nil
@@ -369,9 +370,9 @@ func (ec *executionContext) _IdentityEvent_type(ctx context.Context, field graph
 		}
 		return graphql.Null
 	}
-	res := resTmp.(models.EventType)
+	res := resTmp.(cache.EntityEventType)
 	fc.Result = res
-	return ec.marshalNEventType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐEventType(ctx, field.Selections, res)
+	return ec.marshalNEntityEventType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋcacheᚐEntityEventType(ctx, field.Selections, res)
 }
 
 func (ec *executionContext) fieldContext_IdentityEvent_type(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
@@ -381,7 +382,7 @@ func (ec *executionContext) fieldContext_IdentityEvent_type(_ context.Context, f
 		IsMethod:   false,
 		IsResolver: false,
 		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
-			return nil, errors.New("field of type EventType does not have child fields")
+			return nil, errors.New("field of type EntityEventType does not have child fields")
 		},
 	}
 	return fc, nil
@@ -463,7 +464,7 @@ func (ec *executionContext) _Subscription_allEvents(ctx context.Context, field g
 	}()
 	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
 		ctx = rctx // use context from middleware stack in children
-		return ec.resolvers.Subscription().AllEvents(rctx, fc.Args["repoFilter"].(*string))
+		return ec.resolvers.Subscription().AllEvents(rctx, fc.Args["repoRef"].(*string), fc.Args["typename"].(*string))
 	})
 	if err != nil {
 		ec.Error(ctx, err)
@@ -538,7 +539,7 @@ func (ec *executionContext) _Subscription_identityEvents(ctx context.Context, fi
 	}()
 	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
 		ctx = rctx // use context from middleware stack in children
-		return ec.resolvers.Subscription().IdentityEvents(rctx, fc.Args["repoFilter"].(*string))
+		return ec.resolvers.Subscription().IdentityEvents(rctx, fc.Args["repoRef"].(*string))
 	})
 	if err != nil {
 		ec.Error(ctx, err)
@@ -613,7 +614,7 @@ func (ec *executionContext) _Subscription_bugEvents(ctx context.Context, field g
 	}()
 	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
 		ctx = rctx // use context from middleware stack in children
-		return ec.resolvers.Subscription().BugEvents(rctx, fc.Args["repoFilter"].(*string), fc.Args["query"].(*string))
+		return ec.resolvers.Subscription().BugEvents(rctx, fc.Args["repoRef"].(*string))
 	})
 	if err != nil {
 		ec.Error(ctx, err)
@@ -871,13 +872,13 @@ func (ec *executionContext) marshalNEntityEvent2ᚖgithubᚗcomᚋgitᚑbugᚋgi
 	return ec._EntityEvent(ctx, sel, v)
 }
 
-func (ec *executionContext) unmarshalNEventType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐEventType(ctx context.Context, v any) (models.EventType, error) {
-	var res models.EventType
+func (ec *executionContext) unmarshalNEntityEventType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋcacheᚐEntityEventType(ctx context.Context, v any) (cache.EntityEventType, error) {
+	var res cache.EntityEventType
 	err := res.UnmarshalGQL(v)
 	return res, graphql.ErrorOnPath(ctx, err)
 }
 
-func (ec *executionContext) marshalNEventType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋapiᚋgraphqlᚋmodelsᚐEventType(ctx context.Context, sel ast.SelectionSet, v models.EventType) graphql.Marshaler {
+func (ec *executionContext) marshalNEntityEventType2githubᚗcomᚋgitᚑbugᚋgitᚑbugᚋcacheᚐEntityEventType(ctx context.Context, sel ast.SelectionSet, v cache.EntityEventType) graphql.Marshaler {
 	return v
 }
 
diff --git a/api/graphql/models/gen_models.go b/api/graphql/models/gen_models.go
index 94ac43e88484203035c5f0b2b8bea16315b57d5b..5a9affaa72d30449118e6fffb2f5d57e75bde9c1 100644
--- a/api/graphql/models/gen_models.go
+++ b/api/graphql/models/gen_models.go
@@ -3,11 +3,7 @@
 package models
 
 import (
-	"bytes"
-	"fmt"
-	"io"
-	"strconv"
-
+	"github.com/git-bug/git-bug/cache"
 	"github.com/git-bug/git-bug/entities/bug"
 	"github.com/git-bug/git-bug/entities/common"
 	"github.com/git-bug/git-bug/entity/dag"
@@ -194,8 +190,8 @@ type BugEditCommentPayload struct {
 }
 
 type BugEvent struct {
-	Type EventType  `json:"type"`
-	Bug  BugWrapper `json:"bug"`
+	Type cache.EntityEventType `json:"type"`
+	Bug  BugWrapper            `json:"bug"`
 }
 
 type BugSetTitleInput struct {
@@ -269,8 +265,8 @@ type BugTimelineItemEdge struct {
 }
 
 type EntityEvent struct {
-	Type   EventType `json:"type"`
-	Entity Entity    `json:"entity,omitempty"`
+	Type   cache.EntityEventType `json:"type"`
+	Entity Entity                `json:"entity,omitempty"`
 }
 
 type IdentityConnection struct {
@@ -286,8 +282,8 @@ type IdentityEdge struct {
 }
 
 type IdentityEvent struct {
-	Type     EventType       `json:"type"`
-	Identity IdentityWrapper `json:"identity"`
+	Type     cache.EntityEventType `json:"type"`
+	Identity IdentityWrapper       `json:"identity"`
 }
 
 type LabelConnection struct {
@@ -307,16 +303,16 @@ type Mutation struct {
 
 // The connection type for an Operation
 type OperationConnection struct {
-	Edges      []*OperationEdge                        `json:"edges"`
-	Nodes      []dag.OperationWithApply[*bug.Snapshot] `json:"nodes"`
-	PageInfo   *PageInfo                               `json:"pageInfo"`
-	TotalCount int                                     `json:"totalCount"`
+	Edges      []*OperationEdge `json:"edges"`
+	Nodes      []dag.Operation  `json:"nodes"`
+	PageInfo   *PageInfo        `json:"pageInfo"`
+	TotalCount int              `json:"totalCount"`
 }
 
 // Represent an Operation
 type OperationEdge struct {
-	Cursor string                                `json:"cursor"`
-	Node   dag.OperationWithApply[*bug.Snapshot] `json:"node"`
+	Cursor string        `json:"cursor"`
+	Node   dag.Operation `json:"node"`
 }
 
 // Information about pagination in a connection.
@@ -336,60 +332,3 @@ type Query struct {
 
 type Subscription struct {
 }
-
-type EventType string
-
-const (
-	EventTypeCreated EventType = "CREATED"
-	EventTypeUpdated EventType = "UPDATED"
-	EventTypeRemoved EventType = "REMOVED"
-)
-
-var AllEventType = []EventType{
-	EventTypeCreated,
-	EventTypeUpdated,
-	EventTypeRemoved,
-}
-
-func (e EventType) IsValid() bool {
-	switch e {
-	case EventTypeCreated, EventTypeUpdated, EventTypeRemoved:
-		return true
-	}
-	return false
-}
-
-func (e EventType) String() string {
-	return string(e)
-}
-
-func (e *EventType) UnmarshalGQL(v any) error {
-	str, ok := v.(string)
-	if !ok {
-		return fmt.Errorf("enums must be strings")
-	}
-
-	*e = EventType(str)
-	if !e.IsValid() {
-		return fmt.Errorf("%s is not a valid EventType", str)
-	}
-	return nil
-}
-
-func (e EventType) MarshalGQL(w io.Writer) {
-	fmt.Fprint(w, strconv.Quote(e.String()))
-}
-
-func (e *EventType) UnmarshalJSON(b []byte) error {
-	s, err := strconv.Unquote(string(b))
-	if err != nil {
-		return err
-	}
-	return e.UnmarshalGQL(s)
-}
-
-func (e EventType) MarshalJSON() ([]byte, error) {
-	var buf bytes.Buffer
-	e.MarshalGQL(&buf)
-	return buf.Bytes(), nil
-}
diff --git a/api/graphql/resolvers/subscription.go b/api/graphql/resolvers/subscription.go
index a7422bf8cef781e5b0bf6458f2f3642b564aee59..ffcd764e7f167dda103b06a88c9fde50fd3e4c2c 100644
--- a/api/graphql/resolvers/subscription.go
+++ b/api/graphql/resolvers/subscription.go
@@ -8,6 +8,7 @@ import (
 	"github.com/git-bug/git-bug/api/graphql/models"
 	"github.com/git-bug/git-bug/cache"
 	"github.com/git-bug/git-bug/entities/bug"
+	"github.com/git-bug/git-bug/entities/identity"
 	"github.com/git-bug/git-bug/entity"
 )
 
@@ -17,91 +18,128 @@ type subscriptionResolver struct {
 	cache *cache.MultiRepoCache
 }
 
-func (s subscriptionResolver) AllEvents(ctx context.Context, repoFilter *string) (<-chan *models.EntityEvent, error) {
-	// TODO implement me
-	panic("implement me")
-}
+func (s subscriptionResolver) AllEvents(ctx context.Context, repoRef *string, typename *string) (<-chan *models.EntityEvent, error) {
+	out := make(chan *models.EntityEvent)
+	sub := &subscription[models.EntityEvent]{
+		cache: s.cache,
+		out:   out,
+		makeEvent: func(repo *cache.RepoCache, excerpt cache.Excerpt, eventType cache.EntityEventType) *models.EntityEvent {
+			switch excerpt := excerpt.(type) {
+			case *cache.BugExcerpt:
+				return &models.EntityEvent{Type: eventType, Entity: models.NewLazyBug(repo, excerpt)}
+			case *cache.IdentityExcerpt:
+				return &models.EntityEvent{Type: eventType, Entity: models.NewLazyIdentity(repo, excerpt)}
+			default:
+				panic(fmt.Sprintf("unknown excerpt type: %T", excerpt))
+			}
+		},
+	}
 
-var _ cache.Observer = &subscription[any]{}
+	var repoRefStr string
+	if repoRef != nil {
+		repoRefStr = *repoRef
+	}
 
-type subscription[T any] struct {
-	out             chan *T
-	filter          func(T) bool
-	excerptResolver func(id entity.Id) cache.Excerpt
-	repo            *cache.RepoCache
-}
+	var typenameStr string
+	if typename != nil {
+		typenameStr = *typename
+	}
 
-func (s subscription[T]) EntityEvent(event cache.EntityEventType, typename string, id entity.Id) {
+	err := s.cache.RegisterObserver(sub, repoRefStr, typenameStr)
+	if err != nil {
+		return nil, err
+	}
 
+	go func() {
+		<-ctx.Done()
+		s.cache.UnregisterObserver(sub)
+	}()
+
+	return out, nil
 }
 
-func (s subscriptionResolver) IdentityEvents(ctx context.Context, repoFilter *string) (<-chan *models.IdentityEvent, error) {
-	out := make(chan *models.IdentityEvent)
-	sub := &subscription[models.IdentityEvent]{
-		out: out,
-		filter: func(e models.IdentityEvent) bool { return true},
-		excerptResolver: s.cache.
+func (s subscriptionResolver) BugEvents(ctx context.Context, repoRef *string) (<-chan *models.BugEvent, error) {
+	out := make(chan *models.BugEvent)
+	sub := &subscription[models.BugEvent]{
+		cache: s.cache,
+		out:   out,
+		makeEvent: func(repo *cache.RepoCache, excerpt cache.Excerpt, event cache.EntityEventType) *models.BugEvent {
+			return &models.BugEvent{Type: event, Bug: models.NewLazyBug(repo, excerpt.(*cache.BugExcerpt))}
+		},
+	}
+
+	var repoRefStr string
+	if repoRef != nil {
+		repoRefStr = *repoRef
 	}
-}
 
-func (s subscriptionResolver) BugEvents(ctx context.Context, repoFilter *string, query *string) (<-chan *models.BugEvent, error) {
-	// TODO implement me
-	panic("implement me")
+	err := s.cache.RegisterObserver(sub, repoRefStr, bug.Typename)
+	if err != nil {
+		return nil, err
+	}
+
+	go func() {
+		<-ctx.Done()
+		s.cache.UnregisterObserver(sub)
+	}()
+
+	return out, nil
 }
 
-func (s subscriptionResolver) BugChanged(ctx context.Context, repoRef *string, query *string) (<-chan *models.BugChange, error) {
-	var repo *cache.RepoCache
-	var err error
+func (s subscriptionResolver) IdentityEvents(ctx context.Context, repoRef *string) (<-chan *models.IdentityEvent, error) {
+	out := make(chan *models.IdentityEvent)
+	sub := &subscription[models.IdentityEvent]{
+		cache: s.cache,
+		out:   out,
+		makeEvent: func(repo *cache.RepoCache, excerpt cache.Excerpt, event cache.EntityEventType) *models.IdentityEvent {
+			return &models.IdentityEvent{Type: event, Identity: models.NewLazyIdentity(repo, excerpt.(*cache.IdentityExcerpt))}
+		},
+	}
 
-	if repoRef == nil {
-		repo, err = s.cache.DefaultRepo()
-	} else {
-		repo, err = s.cache.ResolveRepo(*repoRef)
+	var repoRefStr string
+	if repoRef != nil {
+		repoRefStr = *repoRef
 	}
 
+	err := s.cache.RegisterObserver(sub, repoRefStr, identity.Typename)
 	if err != nil {
 		return nil, err
 	}
 
-	out := make(chan *models.BugChange)
-	sub := bugSubscription{out: out, repo: repo}
-	repo.RegisterObserver(bug.Typename, sub)
-
 	go func() {
 		<-ctx.Done()
-		repo.RegisterObserver(bug.Typename, sub)
+		s.cache.UnregisterObserver(sub)
 	}()
 
 	return out, nil
 }
 
-type bugSubscription struct {
-	out  chan *models.BugChange
-	repo *cache.RepoCache
+var _ cache.Observer = &subscription[any]{}
+
+type subscription[eventT any] struct {
+	cache     *cache.MultiRepoCache
+	out       chan *eventT
+	filter    func(cache.Excerpt) bool
+	makeEvent func(repo *cache.RepoCache, excerpt cache.Excerpt, event cache.EntityEventType) *eventT
 }
 
-func (bs bugSubscription) EntityCreated(_ string, id entity.Id) {
-	excerpt, err := bs.repo.Bugs().ResolveExcerpt(id)
+func (s subscription[eventT]) EntityEvent(event cache.EntityEventType, repoName string, typename string, id entity.Id) {
+	repo, err := s.cache.ResolveRepo(repoName)
 	if err != nil {
-		// Should never happen
-		fmt.Printf("bug in the cache: could not resolve excerpt for %s: %s\n", id, err)
+		// something terrible happened
 		return
 	}
-	bs.out <- &models.BugChange{
-		Type: models.ChangeTypeCreated,
-		Bug:  models.NewLazyBug(bs.repo, excerpt),
+	var excerpt cache.Excerpt
+	switch typename {
+	case bug.Typename:
+		excerpt, err = repo.Bugs().ResolveExcerpt(id)
+	case identity.Typename:
+		excerpt, err = repo.Identities().ResolveExcerpt(id)
+	default:
+		panic(fmt.Sprintf("unknown typename: %s", typename))
 	}
-}
-
-func (bs bugSubscription) EntityUpdated(_ string, id entity.Id) {
-	excerpt, err := bs.repo.Bugs().ResolveExcerpt(id)
-	if err != nil {
-		// Should never happen
-		fmt.Printf("bug in the cache: could not resolve excerpt for %s: %s\n", id, err)
+	if s.filter != nil && !s.filter(excerpt) {
 		return
 	}
-	bs.out <- &models.BugChange{
-		Type: models.ChangeTypeUpdated,
-		Bug:  models.NewLazyBug(bs.repo, excerpt),
-	}
+	s.out <- s.makeEvent(repo, excerpt, event)
 }
diff --git a/api/graphql/schema/operation.graphql b/api/graphql/schema/operation.graphql
index ed952c06d9280758d0491e713593e98d311a3a4b..46455ccd6e1f431ab54ee656286e28a4d300c99e 100644
--- a/api/graphql/schema/operation.graphql
+++ b/api/graphql/schema/operation.graphql
@@ -1,5 +1,6 @@
 """An operation applied to an entity."""
-interface Operation {
+interface Operation
+@goModel(model: "github.com/git-bug/git-bug/entity/dag.Operation") {
     """The identifier of the operation"""
     id: ID!
     """The operations author."""
diff --git a/api/graphql/schema/subscription.graphql b/api/graphql/schema/subscription.graphql
index 29e0106abcf0c88c3614c9bec6e8c5e450f1e4bf..7843afa268baa4a3b8bad53e0df5b25614a7c462 100644
--- a/api/graphql/schema/subscription.graphql
+++ b/api/graphql/schema/subscription.graphql
@@ -1,29 +1,29 @@
 type Subscription {
   """Subscribe to events on all entities. For events on a specific repo you can provide a repo reference. Without it, you get the unique default repo or all repo events."""
-  allEvents(repoFilter: String): EntityEvent!
+  allEvents(repoRef: String, typename: String): EntityEvent!
   """Subscribe to identity entity events. For events on a specific repo you can provide a repo reference. Without it, you get the unique default repo or all repo events."""
-  identityEvents(repoFilter: String): IdentityEvent!
+  identityEvents(repoRef: String): IdentityEvent!
   """Subscribe to bug entity events. For events on a specific repo you can provide a repo reference. Without it, you get the unique default repo or all repo events."""
-  bugEvents(repoFilter: String, query: String): BugEvent!
+  bugEvents(repoRef: String): BugEvent!
 }
 
-enum EventType {
+enum EntityEventType {
   CREATED
   UPDATED
   REMOVED
 }
 
 type EntityEvent {
-  type: EventType!
+  type: EntityEventType!
   entity: Entity
 }
 
 type IdentityEvent {
-  type: EventType!
+  type: EntityEventType!
   identity: Identity!
 }
 
 type BugEvent {
-  type: EventType!
+  type: EntityEventType!
   bug: Bug!
 }
diff --git a/cache/events.go b/cache/events.go
index 9d10b8ef3d20cf67f12c23f94a432e7a3241f236..3fb041fa2d6b486ff62eeffce89fcf0337f2aa0f 100644
--- a/cache/events.go
+++ b/cache/events.go
@@ -1,6 +1,12 @@
 package cache
 
-import "github.com/git-bug/git-bug/entity"
+import (
+	"fmt"
+	"io"
+	"strconv"
+
+	"github.com/git-bug/git-bug/entity"
+)
 
 type BuildEventType int
 
@@ -43,5 +49,36 @@ const (
 
 // Observer gets notified of changes in entities in the cache
 type Observer interface {
-	EntityEvent(event EntityEventType, repoRef string, typename string, id entity.Id)
+	EntityEvent(event EntityEventType, repoName string, typename string, id entity.Id)
+}
+
+func (e EntityEventType) MarshalGQL(w io.Writer) {
+	switch e {
+	case EntityEventCreated:
+		_, _ = w.Write([]byte(strconv.Quote("CREATED")))
+	case EntityEventUpdated:
+		_, _ = w.Write([]byte(strconv.Quote("UPDATED")))
+	case EntityEventRemoved:
+		_, _ = w.Write([]byte(strconv.Quote("REMOVED")))
+	default:
+		panic("missing case")
+	}
+}
+
+func (e *EntityEventType) UnmarshalGQL(v interface{}) error {
+	str, ok := v.(string)
+	if !ok {
+		return fmt.Errorf("enums must be strings")
+	}
+	switch str {
+	case "CREATED":
+		*e = EntityEventCreated
+	case "UPDATED":
+		*e = EntityEventUpdated
+	case "REMOVED":
+		*e = EntityEventRemoved
+	default:
+		return fmt.Errorf("%s is not a valid EntityEventType", str)
+	}
+	return nil
 }
diff --git a/cache/multi_repo_cache.go b/cache/multi_repo_cache.go
index 46852ca4ef1817fbcf0e3484acb5756d1b091b6f..333fb34008c94d44624126b4f526518864ee6765 100644
--- a/cache/multi_repo_cache.go
+++ b/cache/multi_repo_cache.go
@@ -20,7 +20,7 @@ func NewMultiRepoCache() *MultiRepoCache {
 	}
 }
 
-// RegisterRepository register a named repository. Use this for multi-repo setup
+// RegisterRepository registers a named repository. Use this for multi-repo setup
 func (c *MultiRepoCache) RegisterRepository(repo repository.ClockedRepo, name string) (*RepoCache, chan BuildEvent) {
 	r, events := NewNamedRepoCache(repo, name)
 
@@ -42,12 +42,12 @@ func (c *MultiRepoCache) RegisterRepository(repo repository.ClockedRepo, name st
 	return r, out
 }
 
-// RegisterDefaultRepository register an unnamed repository. Use this for single-repo setup
+// RegisterDefaultRepository registers an unnamed repository. Use this for single-repo setup
 func (c *MultiRepoCache) RegisterDefaultRepository(repo repository.ClockedRepo) (*RepoCache, chan BuildEvent) {
 	return c.RegisterRepository(repo, defaultRepoName)
 }
 
-// DefaultRepo retrieve the default repository
+// DefaultRepo retrieves the default repository
 func (c *MultiRepoCache) DefaultRepo() (*RepoCache, error) {
 	if len(c.repos) != 1 {
 		return nil, fmt.Errorf("repository is not unique")
@@ -60,7 +60,7 @@ func (c *MultiRepoCache) DefaultRepo() (*RepoCache, error) {
 	panic("unreachable")
 }
 
-// ResolveRepo retrieve a repository by name
+// ResolveRepo retrieves a repository by name
 func (c *MultiRepoCache) ResolveRepo(name string) (*RepoCache, error) {
 	r, ok := c.repos[name]
 	if !ok {
@@ -69,12 +69,44 @@ func (c *MultiRepoCache) ResolveRepo(name string) (*RepoCache, error) {
 	return r, nil
 }
 
-func (c *MultiRepoCache) RegisterObserver(observer Observer) {
+// RegisterObserver registers an Observer on repo and entity, according to nameFilter and typename.
+// - if nameFilter is empty, the observer is registered on all available repo
+// - if nameFilter is not empty, the observer is registered on the repo with the matching name
+// - if typename is empty, the observer is registered on all available entities
+// - if typename is not empty, the observer is registered on the matching entity type only
+func (c *MultiRepoCache) RegisterObserver(observer Observer, nameFilter string, typename string) error {
+	if nameFilter == "" {
+		for repoName, repo := range c.repos {
+			if typename == "" {
+				repo.registerAllObservers(repoName, observer)
+			} else {
+				if err := repo.registerObserver(repoName, typename, observer); err != nil {
+					return err
+				}
+			}
+		}
+		return nil
+	}
 
+	r, err := c.ResolveRepo(nameFilter)
+	if err != nil {
+		return err
+	}
+	if typename == "" {
+		r.registerAllObservers(r.Name(), observer)
+	} else {
+		if err := r.registerObserver(r.Name(), typename, observer); err != nil {
+			return err
+		}
+	}
+	return nil
 }
 
+// UnregisterObserver deregisters the observer from all repos and all entity types.
 func (c *MultiRepoCache) UnregisterObserver(observer Observer) {
-
+	for _, repo := range c.repos {
+		repo.unregisterAllObservers(observer)
+	}
 }
 
 // Close will do anything that is needed to close the cache properly
diff --git a/cache/repo_cache.go b/cache/repo_cache.go
index 4a514e54d1ac916b67fec239c7fc4d10cb9363ed..c43cc0a1c7cd15066f699e0d0c3776f447954bab 100644
--- a/cache/repo_cache.go
+++ b/cache/repo_cache.go
@@ -5,6 +5,7 @@ import (
 	"io"
 	"os"
 	"strconv"
+	"strings"
 	"sync"
 
 	"github.com/git-bug/git-bug/entities/bug"
@@ -37,6 +38,8 @@ type cacheMgmt interface {
 	RemoveAll() error
 	MergeAll(remote string) <-chan entity.MergeResult
 	GetNamespace() string
+	RegisterObserver(repoName string, observer Observer)
+	UnregisterObserver(observer Observer)
 	Close() error
 }
 
@@ -139,28 +142,6 @@ func NewRepoCacheNoEvents(r repository.ClockedRepo) (*RepoCache, error) {
 	return cache, nil
 }
 
-func (c *RepoCache) registerObserver(repoRef string, typename string, observer Observer) {
-	switch typename {
-	case bug.Typename:
-		c.bugs.RegisterObserver(repoRef, observer)
-	case identity.Typename:
-		c.identities.RegisterObserver(repoRef, observer)
-	default:
-		panic(fmt.Sprintf("unknown typename %q", typename))
-	}
-}
-
-func (c *RepoCache) unregisterObserver(typename string, observer Observer) {
-	switch typename {
-	case bug.Typename:
-		c.bugs.UnregisterObserver(observer)
-	case identity.Typename:
-		c.identities.UnregisterObserver(observer)
-	default:
-		panic(fmt.Sprintf("unknown typename %q", typename))
-	}
-}
-
 // Bugs gives access to the Bug entities
 func (c *RepoCache) Bugs() *RepoCacheBug {
 	return c.bugs
@@ -251,6 +232,34 @@ func (c *RepoCache) buildCache(events chan BuildEvent) {
 	wg.Wait()
 }
 
+func (c *RepoCache) registerObserver(repoName string, typename string, observer Observer) error {
+	switch typename {
+	case bug.Typename:
+		c.bugs.RegisterObserver(repoName, observer)
+	case identity.Typename:
+		c.identities.RegisterObserver(repoName, observer)
+	default:
+		var allTypenames []string
+		for _, subcache := range c.subcaches {
+			allTypenames = append(allTypenames, subcache.Typename())
+		}
+		return fmt.Errorf("unknown typename `%s`, available types are [%s]", typename, strings.Join(allTypenames, ", "))
+	}
+	return nil
+}
+
+func (c *RepoCache) registerAllObservers(repoName string, observer Observer) {
+	for _, subcache := range c.subcaches {
+		subcache.RegisterObserver(repoName, observer)
+	}
+}
+
+func (c *RepoCache) unregisterAllObservers(observer Observer) {
+	for _, subcache := range c.subcaches {
+		subcache.UnregisterObserver(observer)
+	}
+}
+
 // repoIsAvailable check is the given repository is locked by a Cache.
 // Note: this is a smart function that will clean the lock file if the
 // corresponding process is not there anymore.
diff --git a/cache/repo_cache_test.go b/cache/repo_cache_test.go
index a2a4e510f34b492cdd3705b7e5728a479ddb96c4..19445b409b513d97663c1df28fc81a2f4f2d53db 100644
--- a/cache/repo_cache_test.go
+++ b/cache/repo_cache_test.go
@@ -1,7 +1,6 @@
 package cache
 
 import (
-	"fmt"
 	"strings"
 	"testing"
 	"time"
@@ -22,22 +21,21 @@ type observerEvent struct {
 	id       entity.Id
 }
 
+var _ Observer = &observer{}
+
 type observer struct {
 	created []observerEvent
 	updated []observerEvent
 	removed []observerEvent
 }
 
-func (o *observer) EntityEvent(event EntityEventType, typename string, id entity.Id) {
+func (o *observer) EntityEvent(event EntityEventType, _ string, typename string, id entity.Id) {
 	switch event {
 	case EntityEventCreated:
-		fmt.Printf("Created %s: %s\n", typename, id.Human())
 		o.created = append(o.created, observerEvent{typename, id})
 	case EntityEventUpdated:
-		fmt.Printf("Updated %s: %s\n", typename, id.Human())
 		o.updated = append(o.updated, observerEvent{typename, id})
 	case EntityEventRemoved:
-		fmt.Printf("Removed %s: %s\n", typename, id.Human())
 		o.removed = append(o.removed, observerEvent{typename, id})
 	}
 }
@@ -69,8 +67,8 @@ func TestCache(t *testing.T) {
 		require.NoError(t, err)
 
 		var obsIdentity, obsBug observer
-		cache.RegisterObserver(identity.Typename, &obsIdentity)
-		cache.RegisterObserver(bug.Typename, &obsBug)
+		require.NoError(t, cache.registerObserver("repotest", identity.Typename, &obsIdentity))
+		require.NoError(t, cache.registerObserver("repotest", bug.Typename, &obsBug))
 
 		// Create, set and get user identity
 		iden1, err := cache.Identities().New("René Descartes", "rene@descartes.fr")
@@ -149,6 +147,12 @@ func TestCache(t *testing.T) {
 		require.NoError(t, err)
 		require.Len(t, res, 1)
 
+		// Updating
+		_, _, err = bug1.AddComment("new comment")
+		require.NoError(t, err)
+		assertOberserverEvent(obsIdentity, 2, 0, 0)
+		assertOberserverEvent(obsBug, 2, 1, 0)
+
 		// Close
 		require.NoError(t, cache.Close())
 		require.Empty(t, cache.bugs.cached)
@@ -160,8 +164,8 @@ func TestCache(t *testing.T) {
 		// to check the signatures, we also load the identity used above
 		cache, err = NewRepoCacheNoEvents(repo)
 		require.NoError(t, err)
-		cache.RegisterObserver(identity.Typename, &obsIdentity)
-		cache.RegisterObserver(bug.Typename, &obsBug)
+		require.NoError(t, cache.registerObserver("repotest", identity.Typename, &obsIdentity))
+		require.NoError(t, cache.registerObserver("repotest", bug.Typename, &obsBug))
 
 		require.Len(t, cache.bugs.cached, 0)
 		require.Len(t, cache.bugs.excerpts, 2)
@@ -196,11 +200,11 @@ func TestCache(t *testing.T) {
 		err = cache.Identities().Remove(iden1.Id().String()[:10])
 		require.NoError(t, err)
 		assertOberserverEvent(obsIdentity, 2, 0, 1)
-		assertOberserverEvent(obsBug, 2, 0, 0)
+		assertOberserverEvent(obsBug, 2, 1, 0)
 		err = cache.Bugs().Remove(bug1.Id().String()[:10])
 		require.NoError(t, err)
 		assertOberserverEvent(obsIdentity, 2, 0, 1)
-		assertOberserverEvent(obsBug, 2, 0, 1)
+		assertOberserverEvent(obsBug, 2, 1, 1)
 		require.Len(t, cache.bugs.cached, 0)
 		require.Len(t, cache.bugs.excerpts, 1)
 		require.Len(t, cache.identities.cached, 0)
@@ -211,16 +215,16 @@ func TestCache(t *testing.T) {
 		_, err = cache.Identities().New("René Descartes", "rene@descartes.fr")
 		require.NoError(t, err)
 		assertOberserverEvent(obsIdentity, 3, 0, 1)
-		assertOberserverEvent(obsBug, 2, 0, 1)
+		assertOberserverEvent(obsBug, 2, 1, 1)
 		_, _, err = cache.Bugs().NewRaw(iden2, time.Now().Unix(), "title", "message", nil, nil)
 		require.NoError(t, err)
 		assertOberserverEvent(obsIdentity, 3, 0, 1)
-		assertOberserverEvent(obsBug, 3, 0, 1)
+		assertOberserverEvent(obsBug, 3, 1, 1)
 
 		err = cache.RemoveAll()
 		require.NoError(t, err)
 		assertOberserverEvent(obsIdentity, 3, 0, 3)
-		assertOberserverEvent(obsBug, 3, 0, 3)
+		assertOberserverEvent(obsBug, 3, 1, 3)
 		require.Len(t, cache.bugs.cached, 0)
 		require.Len(t, cache.bugs.excerpts, 0)
 		require.Len(t, cache.identities.cached, 0)
diff --git a/cache/subcache.go b/cache/subcache.go
index 5fccfc39a970f7f2e854105c64d32b1ec8961d38..c9aa5f68444d632dd2d951119d90e0760eb54626 100644
--- a/cache/subcache.go
+++ b/cache/subcache.go
@@ -61,7 +61,7 @@ type SubCache[EntityT entity.Interface, ExcerptT Excerpt, CacheT CacheEntity] st
 	lru      lruIdCache
 
 	muObservers sync.RWMutex
-	observers   map[Observer]struct{}
+	observers   map[Observer]string // observer --> repo name
 }
 
 func NewSubCache[EntityT entity.Interface, ExcerptT Excerpt, CacheT CacheEntity](
@@ -335,13 +335,13 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) Close() error {
 	return nil
 }
 
-func (sc *SubCache[EntityT, ExcerptT, CacheT]) RegisterObserver(observer Observer) {
+func (sc *SubCache[EntityT, ExcerptT, CacheT]) RegisterObserver(repoName string, observer Observer) {
 	sc.muObservers.Lock()
 	defer sc.muObservers.Unlock()
 	if sc.observers == nil {
-		sc.observers = make(map[Observer]struct{})
+		sc.observers = make(map[Observer]string)
 	}
-	sc.observers[observer] = struct{}{}
+	sc.observers[observer] = repoName
 }
 
 func (sc *SubCache[EntityT, ExcerptT, CacheT]) UnregisterObserver(observer Observer) {
@@ -641,8 +641,8 @@ func (sc *SubCache[EntityT, ExcerptT, CacheT]) entityUpdated(id entity.Id) error
 // notifyObservers notifies all the observers when something happening for an entity
 func (sc *SubCache[EntityT, ExcerptT, CacheT]) notifyObservers(event EntityEventType, id entity.Id) {
 	sc.muObservers.RLock()
-	for observer := range sc.observers {
-		observer.EntityEvent(event, sc.repo.sc.typename, id)
+	for observer, repoName := range sc.observers {
+		observer.EntityEvent(event, repoName, sc.typename, id)
 	}
 	sc.muObservers.RUnlock()
 }