diff --git a/code/__DEFINES/dcs/signals/signals_global_object.dm b/code/__DEFINES/dcs/signals/signals_global_object.dm
index 51b7a38a94a..bed06ff176c 100644
--- a/code/__DEFINES/dcs/signals/signals_global_object.dm
+++ b/code/__DEFINES/dcs/signals/signals_global_object.dm
@@ -1,8 +1,11 @@
/// signals from globally accessible objects
-///Whenever SetupOccupations() is called, called all occupations are set
+///from SSJob whenever SetupOccupations() is called, all occupations are set
#define COMSIG_OCCUPATIONS_SETUP "occupations_setup"
+///from SSJob when DivideOccupations is called
+#define COMSIG_OCCUPATIONS_DIVIDED "occupations_divided"
+
///from SSsun when the sun changes position : (azimuth)
#define COMSIG_SUN_MOVED "sun_moved"
diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm
index 96c25333b49..697beaee19b 100644
--- a/code/controllers/subsystem/job.dm
+++ b/code/controllers/subsystem/job.dm
@@ -389,6 +389,7 @@ SUBSYSTEM_DEF(job)
//Setup new player list and get the jobs list
JobDebug("Running DO, allow_all = [allow_all], pure = [pure]")
run_divide_occupation_pure = pure
+ SEND_SIGNAL(src, COMSIG_OCCUPATIONS_DIVIDED, pure, allow_all)
//Get the players who are ready
for(var/i in GLOB.new_player_list)
diff --git a/code/datums/station_traits/job_traits.dm b/code/datums/station_traits/job_traits.dm
index 42e5f680786..f2bd456aaee 100644
--- a/code/datums/station_traits/job_traits.dm
+++ b/code/datums/station_traits/job_traits.dm
@@ -205,8 +205,7 @@
SIGNAL_HANDLER
var/datum/job_department/department = SSjob.joinable_departments_by_type[/datum/job_department/silicon]
department.remove_job(/datum/job/ai)
- var/datum/station_trait/triple_ai/triple_ais = locate() in SSstation.station_traits
- if(triple_ais)
+ if(GLOB.triple_ai_controller)
position_amount = 3
/// Gives the AI SAT a fax machine if it doesn't have one. This is copy pasted from Bridge Assistant's coffee maker.
diff --git a/code/datums/station_traits/neutral_traits.dm b/code/datums/station_traits/neutral_traits.dm
index 0ecb49f96a0..9ed43f43696 100644
--- a/code/datums/station_traits/neutral_traits.dm
+++ b/code/datums/station_traits/neutral_traits.dm
@@ -409,37 +409,6 @@
targets += roundstart_non_secure_closets[1]
GLOB.eigenstate_manager.create_new_link(targets)
-/datum/station_trait/triple_ai
- name = "AI Triumvirate"
- trait_type = STATION_TRAIT_NEUTRAL
- trait_flags = parent_type::trait_flags | STATION_TRAIT_REQUIRES_AI
- show_in_report = TRUE
- weight = 1
- report_message = "Your station has been instated with three Nanotrasen Artificial Intelligence models."
-
-/datum/station_trait/triple_ai/New()
- . = ..()
- RegisterSignal(SSjob, COMSIG_OCCUPATIONS_SETUP, PROC_REF(on_occupations_setup))
-
-/datum/station_trait/triple_ai/revert()
- UnregisterSignal(SSjob, COMSIG_OCCUPATIONS_SETUP)
- return ..()
-
-/datum/station_trait/triple_ai/proc/on_occupations_setup(datum/controller/subsystem/job/source)
- SIGNAL_HANDLER
-
- //allows for latejoining AIs
- for(var/obj/effect/landmark/start/ai/secondary/secondary_ai_spawn in GLOB.start_landmarks_list)
- secondary_ai_spawn.latejoin_active = TRUE
-
- var/datum/station_trait/job/human_ai/ai_trait = locate() in SSstation.station_traits
- //human AI quirk will handle adding its own job positions, but for now don't allow more AI slots.
- if(ai_trait)
- return
- for(var/datum/job/ai/ai_datum in SSjob.joinable_occupations)
- ai_datum.spawn_positions = 3
- ai_datum.total_positions = 3
-
#define PRO_SKUB "pro-skub"
#define ANTI_SKUB "anti-skub"
diff --git a/code/modules/admin/verbs/ai_triumvirate.dm b/code/modules/admin/verbs/ai_triumvirate.dm
new file mode 100644
index 00000000000..d63994a25c3
--- /dev/null
+++ b/code/modules/admin/verbs/ai_triumvirate.dm
@@ -0,0 +1,49 @@
+
+///global reference to the current theme, if there is one.
+GLOBAL_DATUM(triple_ai_controller, /datum/triple_ai_controller)
+
+/**
+ * The triple ai controller handles the admin triple AI mode, if enabled.
+ * It is first created when "Toggle AI Triumvirate" triggers it, and it can be referenced from GLOB.triple_ai_controller
+ * After it handles roundstart business, it cleans itself up.
+ */
+/datum/triple_ai_controller
+
+/datum/triple_ai_controller/New()
+ . = ..()
+ RegisterSignal(SSjob, COMSIG_OCCUPATIONS_DIVIDED, PROC_REF(on_occupations_divided))
+
+/datum/triple_ai_controller/proc/on_occupations_divided(datum/source, pure, allow_all)
+ SIGNAL_HANDLER
+
+ for(var/datum/job/ai/ai_datum in SSjob.joinable_occupations)
+ ai_datum.spawn_positions = 3
+ if(!pure)
+ for(var/obj/effect/landmark/start/ai/secondary/secondary_ai_spawn in GLOB.start_landmarks_list)
+ secondary_ai_spawn.latejoin_active = TRUE
+ qdel(src)
+
+/datum/triple_ai_controller/Destroy(force)
+ UnregisterSignal(SSjob, COMSIG_OCCUPATIONS_DIVIDED)
+ GLOB.triple_ai_controller = null
+ . = ..()
+
+/client/proc/triple_ai()
+ set category = "Admin.Events"
+ set name = "Toggle AI Triumvirate"
+
+ if(SSticker.current_state > GAME_STATE_PREGAME)
+ to_chat(usr, "This option is currently only usable during pregame. This may change at a later date.", confidential = TRUE)
+ return
+
+ var/datum/job/job = SSjob.GetJobType(/datum/job/ai)
+ if(!job)
+ to_chat(usr, "Unable to locate the AI job", confidential = TRUE)
+ CRASH("triple_ai() called, no /datum/job/ai to be found.")
+
+ if(!GLOB.triple_ai_controller)
+ GLOB.triple_ai_controller = new()
+ else
+ QDEL_NULL(GLOB.triple_ai_controller)
+ to_chat(usr, "There will[GLOB.triple_ai_controller ? "" : "not"] be an AI Triumvirate at round start.")
+ message_admins(span_adminnotice("[key_name_admin(usr)] has toggled [GLOB.triple_ai_controller ? "on" : "off"] triple AIs at round start."))
diff --git a/code/modules/admin/verbs/secrets.dm b/code/modules/admin/verbs/secrets.dm
index c124a5fba22..107d74d9c39 100644
--- a/code/modules/admin/verbs/secrets.dm
+++ b/code/modules/admin/verbs/secrets.dm
@@ -257,6 +257,11 @@ ADMIN_VERB(secrets, R_NONE, "Secrets", "Abuse harder than you ever have before w
return
holder.anon_names()
SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Anonymous Names"))
+ if("tripleAI")
+ if(!is_funmin)
+ return
+ holder.triple_ai()
+ SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Triple AI"))
if("onlyone")
if(!is_funmin)
return
diff --git a/tgstation.dme b/tgstation.dme
index 550c0429abb..22b700b3eb9 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -2888,6 +2888,7 @@
#include "code\modules\admin\verbs\adminjump.dm"
#include "code\modules\admin\verbs\adminpm.dm"
#include "code\modules\admin\verbs\adminsay.dm"
+#include "code\modules\admin\verbs\ai_triumvirate.dm"
#include "code\modules\admin\verbs\anonymousnames.dm"
#include "code\modules\admin\verbs\atmosdebug.dm"
#include "code\modules\admin\verbs\beakerpanel.dm"
diff --git a/tgui/packages/tgui/interfaces/Secrets.jsx b/tgui/packages/tgui/interfaces/Secrets.jsx
index a0896ec7507..3e3ccd70cc0 100644
--- a/tgui/packages/tgui/interfaces/Secrets.jsx
+++ b/tgui/packages/tgui/interfaces/Secrets.jsx
@@ -354,13 +354,13 @@ const FunTab = (props) => {
/>
-
- Your admin button here, coder!
-
+ content="Triple AI mode"
+ onClick={() => act('tripleAI')}
+ />