diff --git a/src/main/java/dev/escalated/services/WorkflowExecutorService.java b/src/main/java/dev/escalated/services/WorkflowExecutorService.java
new file mode 100644
index 0000000..d0af6e7
--- /dev/null
+++ b/src/main/java/dev/escalated/services/WorkflowExecutorService.java
@@ -0,0 +1,262 @@
+package dev.escalated.services;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import dev.escalated.models.AgentProfile;
+import dev.escalated.models.Department;
+import dev.escalated.models.Reply;
+import dev.escalated.models.Tag;
+import dev.escalated.models.Ticket;
+import dev.escalated.models.TicketPriority;
+import dev.escalated.models.TicketStatus;
+import dev.escalated.repositories.AgentProfileRepository;
+import dev.escalated.repositories.DepartmentRepository;
+import dev.escalated.repositories.ReplyRepository;
+import dev.escalated.repositories.TagRepository;
+import dev.escalated.repositories.TicketRepository;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+/**
+ * Performs the side-effects dictated by a matched {@code Workflow}.
+ *
+ *
Distinct from {@link WorkflowEngine}, which only evaluates
+ * conditions. This service parses the JSON action array stored on
+ * {@code Workflow.actions} and dispatches each entry against the
+ * relevant repository.
+ *
+ *
Action catalog: {@code change_priority}, {@code change_status},
+ * {@code assign_agent}, {@code set_department}, {@code add_tag},
+ * {@code remove_tag}, {@code add_note}, {@code insert_canned_reply}.
+ * Mirrors the NestJS reference impl in
+ * {@code escalated-nestjs/src/services/workflow-executor.service.ts}.
+ *
+ *
Unknown or malformed actions are logged at {@code warn} and
+ * skipped — one bad action never halts execution of the other
+ * actions on the same workflow.
+ */
+@Service
+public class WorkflowExecutorService {
+
+ private static final Logger log = LoggerFactory.getLogger(WorkflowExecutorService.class);
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ private final TicketRepository ticketRepository;
+ private final TagRepository tagRepository;
+ private final AgentProfileRepository agentRepository;
+ private final DepartmentRepository departmentRepository;
+ private final ReplyRepository replyRepository;
+
+ public WorkflowExecutorService(
+ TicketRepository ticketRepository,
+ TagRepository tagRepository,
+ AgentProfileRepository agentRepository,
+ DepartmentRepository departmentRepository,
+ ReplyRepository replyRepository) {
+ this.ticketRepository = ticketRepository;
+ this.tagRepository = tagRepository;
+ this.agentRepository = agentRepository;
+ this.departmentRepository = departmentRepository;
+ this.replyRepository = replyRepository;
+ }
+
+ /**
+ * Execute every action in {@code actionsJson} against {@code ticket}.
+ * Returns the list of parsed action maps so callers (e.g. the
+ * runner) can serialize them into a {@code WorkflowLog} audit row.
+ *
+ * @param actionsJson the JSON string stored on {@code Workflow.actions}
+ * @return parsed actions (never null; empty on malformed input)
+ */
+ public List