diff --git a/ActionSequencer/ActionSequencer.csproj b/ActionSequencer/ActionSequencer.csproj
new file mode 100644
index 0000000..0f4379c
--- /dev/null
+++ b/ActionSequencer/ActionSequencer.csproj
@@ -0,0 +1,28 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>netframework48</TargetFramework>
+    <RootNamespace>IngameScript</RootNamespace>
+    <LangVersion>6</LangVersion>
+    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
+    <Configurations>Release;Debug</Configurations>
+    <Platforms>x64</Platforms>
+  </PropertyGroup>
+  <ItemGroup>
+    <PackageReference Include="Mal.Mdk2.PbAnalyzers" Version="2.1.11">
+      <PrivateAssets>all</PrivateAssets>
+      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+    </PackageReference>
+    <PackageReference Include="Mal.Mdk2.PbPackager" Version="2.1.1">
+      <PrivateAssets>all</PrivateAssets>
+      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+    </PackageReference>
+    <PackageReference Include="Mal.Mdk2.References" Version="2.2.4" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Remove="Instructions.readme" />
+    <AdditionalFiles Include="Instructions.readme" />
+    <AdditionalFiles Include="thumb.png" />
+  </ItemGroup>
+  <Import Project="..\Mixins\Console\Console.projitems" Label="shared" />
+  <Import Project="..\Mixins\Sequencer\Sequencer.projitems" Label="shared" />
+</Project>
\ No newline at end of file
diff --git a/ActionSequencer/ActionSequencer.mdk.ini b/ActionSequencer/ActionSequencer.mdk.ini
new file mode 100644
index 0000000..5add8f4
--- /dev/null
+++ b/ActionSequencer/ActionSequencer.mdk.ini
@@ -0,0 +1,22 @@
+; This file is project specific and should be checked in to source control.
+
+[mdk]
+; This is a programmable block script project.
+; You should not change this.
+type=programmableblock
+
+; Toggle trace (on|off) (verbose output)
+trace=off
+
+; What type of minification to use (none|trim|stripcomments|lite|full)
+; none: No minification
+; trim: Removes unused types (NOT members).
+; stripcomments: trim + removes comments.
+; lite: stripcomments + removes leading/trailing whitespace.
+; full: lite + renames identifiers to shorter names.
+minify=none
+
+; A list of files and folder to ignore when creating the script.
+; This is a comma separated list of glob patterns. 
+; See https://code.visualstudio.com/docs/editor/glob-patterns
+ignores=obj/**/*,MDK/**/*,**/*.debug.cs
diff --git a/ActionSequencer/ActionSequencer.mdk.local.ini b/ActionSequencer/ActionSequencer.mdk.local.ini
new file mode 100644
index 0000000..4b66820
--- /dev/null
+++ b/ActionSequencer/ActionSequencer.mdk.local.ini
@@ -0,0 +1,7 @@
+; This file is _local_ to your machine and should not be checked in to source control.
+
+[mdk]
+; Where to output the script to (auto|specific path)
+output=auto
+; Override the default binary path (auto|specific path)
+binarypath=auto
\ No newline at end of file
diff --git a/ActionSequencer/ActionSequencer.sln b/ActionSequencer/ActionSequencer.sln
new file mode 100644
index 0000000..2deb726
--- /dev/null
+++ b/ActionSequencer/ActionSequencer.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActionSequencer", "ActionSequencer.csproj", "{3E6FA78A-C1AD-444F-A36A-6A5CFD35A785}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{3E6FA78A-C1AD-444F-A36A-6A5CFD35A785}.Debug|Any CPU.ActiveCfg = Debug|x64
+		{3E6FA78A-C1AD-444F-A36A-6A5CFD35A785}.Debug|Any CPU.Build.0 = Debug|x64
+		{3E6FA78A-C1AD-444F-A36A-6A5CFD35A785}.Release|Any CPU.ActiveCfg = Release|x64
+		{3E6FA78A-C1AD-444F-A36A-6A5CFD35A785}.Release|Any CPU.Build.0 = Release|x64
+	EndGlobalSection
+EndGlobal
diff --git a/ActionSequencer/Instructions.readme b/ActionSequencer/Instructions.readme
new file mode 100644
index 0000000..ed30ab7
--- /dev/null
+++ b/ActionSequencer/Instructions.readme
@@ -0,0 +1,5 @@
+R e a d m e
+-----------
+
+In this file you can include any instructions or other comments you want to have injected onto the 
+top of your final script. You can safely delete this file if you do not want any such comments.
diff --git a/ActionSequencer/Program.cs b/ActionSequencer/Program.cs
new file mode 100644
index 0000000..aad3513
--- /dev/null
+++ b/ActionSequencer/Program.cs
@@ -0,0 +1,121 @@
+using Sandbox.Game.EntityComponents;
+using Sandbox.ModAPI.Ingame;
+using Sandbox.ModAPI.Interfaces;
+using SpaceEngineers.Game.ModAPI.Ingame;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+using VRage;
+using VRage.Collections;
+using VRage.Game;
+using VRage.Game.Components;
+using VRage.Game.GUI.TextPanel;
+using VRage.Game.ModAPI.Ingame;
+using VRage.Game.ModAPI.Ingame.Utilities;
+using VRage.Game.ObjectBuilders.Definitions;
+using VRageMath;
+
+namespace IngameScript
+{
+    public partial class Program : MyGridProgram, IConsoleProgram
+    {
+        public MyIni Ini { get; } = new MyIni();
+        public IConsole Console { get; private set; }
+
+        private MyCommandLine _cli = new MyCommandLine();
+        private List<IEnumerator<bool>> _jobs = new List<IEnumerator<bool>>();
+        private Dictionary<string, Sequencer> _sequences = new Dictionary<string, Sequencer>();
+
+        public Program()
+        {
+            Console = new MainConsole(this, "Action Sequencer");
+
+            // initialize all the sequencers
+            List<IMyTerminalBlock> blocks = new List<IMyTerminalBlock>();
+            GridTerminalSystem.GetBlocksOfType(blocks, block => MyIni.HasSection(block.CustomData, "sequence"));
+            foreach (IMyTerminalBlock block in blocks)
+            {
+                Ini.TryParse(block.CustomData);
+                string id = Ini.Get("sequence", "id").ToString();
+
+                if (id == "")
+                {
+                    Console.Print($"No id found for '{block.CustomName}'. Skipping.");
+                    continue;
+                }
+                if (id == "all")
+                {
+                    Console.Print($"'All' is a reserved keyword. Skipping '{block.CustomName}'.");
+                    continue;
+                }
+
+                if (!_sequences.ContainsKey(id)) _sequences[id] = new Sequencer(this, id);
+
+                ISequenceable wrapped = SequenceableFactory.MakeSequenceable(block, Ini, id);
+                if (wrapped == null)
+                {
+                    Console.Print($"Skipping incompatible block '{block.CustomName}'.");
+                    continue;
+                }
+
+                _sequences[id].AddBlock(wrapped);
+            }
+
+            Console.Print($"Found {_sequences.Count} sequences.");
+        }
+
+        public void Main(string argument, UpdateType updateSource)
+        {
+            Console.UpdateTickCount();
+
+            if (argument != "")
+            {
+                _cli.TryParse(argument);
+
+                List<Sequencer> sequencesToRun = new List<Sequencer>();
+                bool deploy = !_cli.Switch("stow");
+
+                if (_cli.ArgumentCount > 0 && _cli.Argument(0) == "all")
+                {
+                    sequencesToRun = _sequences.Values.ToList();
+                }
+                for (int i = 0; i < _cli.ArgumentCount; i++)
+                {
+                    string id = _cli.Argument(i);
+                    if (!_sequences.ContainsKey(id))
+                    {
+                        Console.Print($"Ignoring non-existent sequence '{id}'");
+                        continue;
+                    }
+                    sequencesToRun.Add(_sequences[id]);
+                }
+
+                foreach (Sequencer sequence in sequencesToRun)
+                {
+                    Console.Print($"Activating sequence '{sequence.Name}'");
+                    _jobs.Add(sequence.RunSequence(deploy));
+                }
+                if (_jobs.Count > 0) Runtime.UpdateFrequency = UpdateFrequency.Update10;
+            }
+
+            // Process running jobs
+            for (int i = 0; i < _jobs.Count; i++)
+            {
+                if (_jobs[i].MoveNext()) continue;
+
+                _jobs[i].Dispose();
+                _jobs.Remove(_jobs[i]);
+                i--;
+                Console.Print("Operation Complete.");
+            }
+
+            if (_jobs.Count == 0)
+            {
+                Runtime.UpdateFrequency = UpdateFrequency.None;
+            }
+        }
+    }
+}
diff --git a/ActionSequencer/thumb.png b/ActionSequencer/thumb.png
new file mode 100644
index 0000000..a8dc2ef
Binary files /dev/null and b/ActionSequencer/thumb.png differ
diff --git a/SpaceEngineers.sln b/SpaceEngineers.sln
index 47c3ade..2c4992d 100644
--- a/SpaceEngineers.sln
+++ b/SpaceEngineers.sln
@@ -7,6 +7,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Airlock", "Airlock\Airlock.
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MechanicalDoor", "MechanicalDoor\MechanicalDoor.csproj", "{67FE627E-176A-4973-9FB7-FA5133CC8288}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ActionSequencer", "ActionSequencer\ActionSequencer.csproj", "{1F5CCD1E-028F-4C08-B2FE-43C0670028DB}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AirMonitor", "AirMonitor\AirMonitor.csproj", "{40B352CD-100E-4F87-A7A0-FF00F4B8846C}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -24,5 +28,13 @@ Global
 		{67FE627E-176A-4973-9FB7-FA5133CC8288}.Debug|Any CPU.Build.0 = Debug|x64
 		{67FE627E-176A-4973-9FB7-FA5133CC8288}.Release|Any CPU.ActiveCfg = Release|x64
 		{67FE627E-176A-4973-9FB7-FA5133CC8288}.Release|Any CPU.Build.0 = Release|x64
+		{1F5CCD1E-028F-4C08-B2FE-43C0670028DB}.Debug|Any CPU.ActiveCfg = Debug|x64
+		{1F5CCD1E-028F-4C08-B2FE-43C0670028DB}.Debug|Any CPU.Build.0 = Debug|x64
+		{1F5CCD1E-028F-4C08-B2FE-43C0670028DB}.Release|Any CPU.ActiveCfg = Release|x64
+		{1F5CCD1E-028F-4C08-B2FE-43C0670028DB}.Release|Any CPU.Build.0 = Release|x64
+		{40B352CD-100E-4F87-A7A0-FF00F4B8846C}.Debug|Any CPU.ActiveCfg = Debug|x64
+		{40B352CD-100E-4F87-A7A0-FF00F4B8846C}.Debug|Any CPU.Build.0 = Debug|x64
+		{40B352CD-100E-4F87-A7A0-FF00F4B8846C}.Release|Any CPU.ActiveCfg = Release|x64
+		{40B352CD-100E-4F87-A7A0-FF00F4B8846C}.Release|Any CPU.Build.0 = Release|x64
 	EndGlobalSection
 EndGlobal