diff --git a/modules/bots/bots.go b/modules/bots/bots.go
index 6013ec6ae8..4c11a60e75 100644
--- a/modules/bots/bots.go
+++ b/modules/bots/bots.go
@@ -11,18 +11,11 @@ import (
 
 	"code.gitea.io/gitea/models/webhook"
 	"code.gitea.io/gitea/modules/git"
-	"code.gitea.io/gitea/modules/graceful"
 	"code.gitea.io/gitea/modules/log"
-	"code.gitea.io/gitea/modules/queue"
 
 	"github.com/nektos/act/pkg/model"
 )
 
-func Init() {
-	jobEmitterQueue = queue.CreateUniqueQueue("bots_ready_job", jobEmitterQueueHandle, new(jobUpdate))
-	go graceful.GetManager().RunWithShutdownFns(jobEmitterQueue.Run)
-}
-
 func ListWorkflows(commit *git.Commit) (git.Entries, error) {
 	tree, err := commit.SubTree(".gitea/workflows")
 	if _, ok := err.(git.ErrNotExist); ok {
diff --git a/routers/api/bots/runner/runner.go b/routers/api/bots/runner/runner.go
index 873b502a7f..850d54f161 100644
--- a/routers/api/bots/runner/runner.go
+++ b/routers/api/bots/runner/runner.go
@@ -19,6 +19,7 @@ import (
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
+	bot_service "code.gitea.io/gitea/services/bots"
 	secret_service "code.gitea.io/gitea/services/secrets"
 
 	runnerv1 "gitea.com/gitea/proto-go/runner/v1"
@@ -214,7 +215,7 @@ func (s *Service) UpdateTask(
 	}
 
 	if req.Msg.State.Result != runnerv1.Result_RESULT_UNSPECIFIED {
-		if err := bots.EmitJobsIfReady(task.Job.RunID); err != nil {
+		if err := bot_service.EmitJobsIfReady(task.Job.RunID); err != nil {
 			log.Error("Emit ready jobs of run %d: %v", task.Job.RunID, err)
 		}
 	}
diff --git a/routers/init.go b/routers/init.go
index cb543fa719..329be7d58a 100644
--- a/routers/init.go
+++ b/routers/init.go
@@ -11,7 +11,6 @@ import (
 
 	"code.gitea.io/gitea/models"
 	asymkey_model "code.gitea.io/gitea/models/asymkey"
-	"code.gitea.io/gitea/modules/bots"
 	"code.gitea.io/gitea/modules/cache"
 	"code.gitea.io/gitea/modules/eventsource"
 	"code.gitea.io/gitea/modules/git"
@@ -41,6 +40,7 @@ import (
 	"code.gitea.io/gitea/services/auth"
 	"code.gitea.io/gitea/services/auth/source/oauth2"
 	"code.gitea.io/gitea/services/automerge"
+	bot_service "code.gitea.io/gitea/services/bots"
 	"code.gitea.io/gitea/services/cron"
 	"code.gitea.io/gitea/services/mailer"
 	markup_service "code.gitea.io/gitea/services/markup"
@@ -175,7 +175,7 @@ func GlobalInitInstalled(ctx context.Context) {
 	auth.Init()
 	svg.Init()
 
-	bots.Init()
+	bot_service.Init()
 
 	// Finally start up the cron
 	cron.NewContext(ctx)
diff --git a/services/bots/bots.go b/services/bots/bots.go
new file mode 100644
index 0000000000..db7a2d1522
--- /dev/null
+++ b/services/bots/bots.go
@@ -0,0 +1,15 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package bots
+
+import (
+	"code.gitea.io/gitea/modules/graceful"
+	"code.gitea.io/gitea/modules/queue"
+)
+
+func Init() {
+	jobEmitterQueue = queue.CreateUniqueQueue("bots_ready_job", jobEmitterQueueHandle, new(jobUpdate))
+	go graceful.GetManager().RunWithShutdownFns(jobEmitterQueue.Run)
+}
diff --git a/modules/bots/job_emitter.go b/services/bots/job_emitter.go
similarity index 100%
rename from modules/bots/job_emitter.go
rename to services/bots/job_emitter.go
diff --git a/modules/bots/job_emitter_test.go b/services/bots/job_emitter_test.go
similarity index 100%
rename from modules/bots/job_emitter_test.go
rename to services/bots/job_emitter_test.go