diff --git a/app/MindWork AI Studio.sln.DotSettings b/app/MindWork AI Studio.sln.DotSettings
index 550cf7cb..ac41b88e 100644
--- a/app/MindWork AI Studio.sln.DotSettings	
+++ b/app/MindWork AI Studio.sln.DotSettings	
@@ -1,2 +1,4 @@
 
-	AI
\ No newline at end of file
+	AI
+	MSG
+	True
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Components/Blocks/Changelog.Logs.cs b/app/MindWork AI Studio/Components/Blocks/Changelog.Logs.cs
index f6430f2d..664b8367 100644
--- a/app/MindWork AI Studio/Components/Blocks/Changelog.Logs.cs	
+++ b/app/MindWork AI Studio/Components/Blocks/Changelog.Logs.cs	
@@ -13,8 +13,9 @@ public partial class Changelog
     
     public static readonly Log[] LOGS = 
     [
+        new (156, "v0.6.0, build 156 (2024-06-30 12:49 UTC)", "v0.6.0.md"),
+        new (155, "v0.5.2, build 155 (2024-06-25 18:07 UTC)", "v0.5.2.md"),
         new (154, "v0.5.1, build 154 (2024-06-25 15:35 UTC)", "v0.5.1.md"),
-        new (154, "v0.5.2, build 154 (2024-06-25 15:35 UTC)", "v0.5.2.md"),
         new (149, "v0.5.0, build 149 (2024-06-02 18:51 UTC)", "v0.5.0.md"),
         new (138, "v0.4.0, build 138 (2024-05-26 13:26 UTC)", "v0.4.0.md"),
         new (120, "v0.3.0, build 120 (2024-05-18 21:57 UTC)", "v0.3.0.md"),
diff --git a/app/MindWork AI Studio/Components/Blocks/InnerScrolling.razor b/app/MindWork AI Studio/Components/Blocks/InnerScrolling.razor
index fbd9f58f..24c2ab78 100644
--- a/app/MindWork AI Studio/Components/Blocks/InnerScrolling.razor	
+++ b/app/MindWork AI Studio/Components/Blocks/InnerScrolling.razor	
@@ -1,3 +1,5 @@
+@inherits AIStudio.Tools.MSGComponentBase
+
 
     
         @this.ChildContent
diff --git a/app/MindWork AI Studio/Components/Blocks/InnerScrolling.razor.cs b/app/MindWork AI Studio/Components/Blocks/InnerScrolling.razor.cs
index f9d7ed7c..4dee88c7 100644
--- a/app/MindWork AI Studio/Components/Blocks/InnerScrolling.razor.cs	
+++ b/app/MindWork AI Studio/Components/Blocks/InnerScrolling.razor.cs	
@@ -1,12 +1,15 @@
+using AIStudio.Components.Layout;
+using AIStudio.Tools;
+
 using Microsoft.AspNetCore.Components;
 
 namespace AIStudio.Components.Blocks;
 
-public partial class InnerScrolling : ComponentBase
+public partial class InnerScrolling : MSGComponentBase
 {
     /// 
     /// Set the height of anything above the scrolling content; usually a header.
-    /// What we do is calc(100vh - THIS). Means, you can use multiple measures like
+    /// What we do is calc(100vh - HeaderHeight). Means, you can use multiple measures like
     /// 230px - 3em. Default is 3em.
     /// 
     [Parameter]
@@ -21,5 +24,34 @@ public partial class InnerScrolling : ComponentBase
     [Parameter]
     public RenderFragment? FooterContent { get; set; }
     
-    private string Height => $"height: calc(100vh - {this.HeaderHeight});";
+    [CascadingParameter]
+    private MainLayout MainLayout { get; set; } = null!;
+
+    #region Overrides of ComponentBase
+
+    protected override async Task OnInitializedAsync()
+    {
+        this.ApplyFilters([], [ Event.STATE_HAS_CHANGED ]);
+        await base.OnInitializedAsync();
+    }
+
+    #endregion
+
+    #region Overrides of MSGComponentBase
+
+    public override Task ProcessMessage
(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
+    {
+        switch (triggeredEvent)
+        {
+            case Event.STATE_HAS_CHANGED:
+                this.StateHasChanged();
+                break;
+        }
+        
+        return Task.CompletedTask;
+    }
+
+    #endregion
+
+    private string Height => $"height: calc(100vh - {this.HeaderHeight} - {this.MainLayout.AdditionalHeight});";
 }
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Components/CommonDialogs/DialogOptions.cs b/app/MindWork AI Studio/Components/CommonDialogs/DialogOptions.cs
new file mode 100644
index 00000000..02a0e8c5
--- /dev/null
+++ b/app/MindWork AI Studio/Components/CommonDialogs/DialogOptions.cs	
@@ -0,0 +1,17 @@
+namespace AIStudio.Components.CommonDialogs;
+
+public static class DialogOptions
+{
+    public static readonly MudBlazor.DialogOptions FULLSCREEN = new()
+    {
+        CloseOnEscapeKey = true,
+        FullWidth = true, MaxWidth = MaxWidth.Medium,
+    };
+    
+    public static readonly MudBlazor.DialogOptions FULLSCREEN_NO_HEADER = new()
+    {
+        NoHeader = true,
+        CloseOnEscapeKey = true,
+        FullWidth = true, MaxWidth = MaxWidth.Medium,
+    };
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Components/CommonDialogs/UpdateDialog.razor b/app/MindWork AI Studio/Components/CommonDialogs/UpdateDialog.razor
new file mode 100644
index 00000000..80544496
--- /dev/null
+++ b/app/MindWork AI Studio/Components/CommonDialogs/UpdateDialog.razor	
@@ -0,0 +1,14 @@
+@using AIStudio.Tools
+
+    
+        
+            
+            Update from v@(META_DATA.Version) to v@(this.UpdateResponse.NewVersion)
+        
+        
+    
+    
+        Install later
+        Install now
+    
+
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Components/CommonDialogs/UpdateDialog.razor.cs b/app/MindWork AI Studio/Components/CommonDialogs/UpdateDialog.razor.cs
new file mode 100644
index 00000000..340788d4
--- /dev/null
+++ b/app/MindWork AI Studio/Components/CommonDialogs/UpdateDialog.razor.cs	
@@ -0,0 +1,26 @@
+using System.Reflection;
+
+using AIStudio.Tools;
+
+using Microsoft.AspNetCore.Components;
+
+namespace AIStudio.Components.CommonDialogs;
+
+/// 
+/// The update dialog that is used to inform the user about an available update.
+/// 
+public partial class UpdateDialog : ComponentBase
+{
+    private static readonly Assembly ASSEMBLY = Assembly.GetExecutingAssembly();
+    private static readonly MetaDataAttribute META_DATA = ASSEMBLY.GetCustomAttribute()!;
+    
+    [CascadingParameter]
+    private MudDialogInstance MudDialog { get; set; } = null!;
+    
+    [Parameter]
+    public UpdateResponse UpdateResponse { get; set; }
+    
+    private void Cancel() => this.MudDialog.Cancel();
+    
+    private void Confirm() => this.MudDialog.Close(DialogResult.Ok(true));
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Components/ConfigurationSelectData.cs b/app/MindWork AI Studio/Components/ConfigurationSelectData.cs
index 1fb9a573..1e77a1d4 100644
--- a/app/MindWork AI Studio/Components/ConfigurationSelectData.cs	
+++ b/app/MindWork AI Studio/Components/ConfigurationSelectData.cs	
@@ -21,4 +21,13 @@ public static class ConfigurationSelectDataFactory
         yield return new("Modifier key + enter is sending the input", SendBehavior.MODIFER_ENTER_IS_SENDING);
         yield return new("Enter is sending the input", SendBehavior.ENTER_IS_SENDING);
     }
+    
+    public static IEnumerable> GetUpdateBehaviorData()
+    {
+        yield return new("No automatic update checks", UpdateBehavior.NO_CHECK);
+        yield return new("Once at startup", UpdateBehavior.ONCE_STARTUP);
+        yield return new("Check every hour", UpdateBehavior.HOURLY);
+        yield return new("Check every day", UpdateBehavior.DAILY);
+        yield return new ("Check every week", UpdateBehavior.WEEKLY);
+    }
 }
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Components/Layout/MainLayout.razor b/app/MindWork AI Studio/Components/Layout/MainLayout.razor
index 8a0b6a42..8843c671 100644
--- a/app/MindWork AI Studio/Components/Layout/MainLayout.razor	
+++ b/app/MindWork AI Studio/Components/Layout/MainLayout.razor	
@@ -2,31 +2,57 @@
 
 
     
-        
-            
-                
-                    
-                        Home
-                    
-                    
-                        Chats
-                    
-                    
-                        Supporters
-                    
-                    
-                        About
-                    
-                    
-                        Settings
-                    
-                
-            
-        
+        @if (!this.performingUpdate)
+        {
+            
+                
+                    
+                        
+                            Home
+                        
+                        
+                            Chats
+                        
+                        
+                            Supporters
+                        
+                        
+                            About
+                        
+                        
+                            Settings
+                        
+                    
+                
+            
+        }
 
         
             
-                @this.Body
+                @if (!this.performingUpdate && this.IsUpdateAlertVisible)
+                {
+                    
+                        
+                            
+                            An update to version @this.updateToVersion is available.
+                            
+                                Show details
+                            
+                        
+                    
+                }
+
+                @if (!this.performingUpdate)
+                {
+                    
+                        @this.Body
+                    
+                }
+
+                
+                    Please wait for the update to complete...
+                    
+                
             
         
     
diff --git a/app/MindWork AI Studio/Components/Layout/MainLayout.razor.cs b/app/MindWork AI Studio/Components/Layout/MainLayout.razor.cs
index de25ecd7..609f8b95 100644
--- a/app/MindWork AI Studio/Components/Layout/MainLayout.razor.cs	
+++ b/app/MindWork AI Studio/Components/Layout/MainLayout.razor.cs	
@@ -1,12 +1,37 @@
+using AIStudio.Components.CommonDialogs;
 using AIStudio.Settings;
+using AIStudio.Tools;
+
 using Microsoft.AspNetCore.Components;
 
+using DialogOptions = AIStudio.Components.CommonDialogs.DialogOptions;
+
 namespace AIStudio.Components.Layout;
 
-public partial class MainLayout : LayoutComponentBase
+public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver
 {
     [Inject]
-    private IJSRuntime JsRuntime { get; set; } = null!;
+    private IJSRuntime JsRuntime { get; init; } = null!;
+    
+    [Inject]
+    private SettingsManager SettingsManager { get; init; } = null!;
+    
+    [Inject]
+    private MessageBus MessageBus { get; init; } = null!;
+    
+    [Inject]
+    public IDialogService DialogService { get; init; } = null!;
+    
+    [Inject]
+    public Rust Rust { get; init; } = null!;
+
+    public string AdditionalHeight { get; private set; } = "0em";
+    
+    private bool isUpdateAvailable;
+    private bool performingUpdate;
+    private bool userDismissedUpdate;
+    private string updateToVersion = string.Empty;
+    private UpdateResponse? currentUpdateResponse;
     
     #region Overrides of ComponentBase
 
@@ -23,8 +48,98 @@ public partial class MainLayout : LayoutComponentBase
         SettingsManager.ConfigDirectory = configDir;
         SettingsManager.DataDirectory = dataDir;
         
+        // Ensure that all settings are loaded:
+        await this.SettingsManager.LoadSettings();
+        
+        // Register this component with the message bus:
+        this.MessageBus.RegisterComponent(this);
+        this.MessageBus.ApplyFilters(this, [], [ Event.UPDATE_AVAILABLE, Event.USER_SEARCH_FOR_UPDATE ]);
+        
+        // Set the js runtime for the update service:
+        UpdateService.SetJsRuntime(this.JsRuntime);
+        
         await base.OnInitializedAsync();
     }
 
     #endregion
+
+    #region Implementation of IMessageBusReceiver
+
+    public async Task ProcessMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
+    {
+        switch (triggeredEvent)
+        {
+            case Event.USER_SEARCH_FOR_UPDATE:
+                this.userDismissedUpdate = false;
+                break;
+            
+            case Event.UPDATE_AVAILABLE:
+                if (data is UpdateResponse updateResponse)
+                {
+                    this.currentUpdateResponse = updateResponse;
+                    this.isUpdateAvailable = updateResponse.UpdateIsAvailable;
+                    this.updateToVersion = updateResponse.NewVersion;
+                    
+                    await this.InvokeAsync(this.StateHasChanged);
+                    await this.SendMessage(Event.STATE_HAS_CHANGED);
+                }
+                
+                break;
+        }
+    }
+
+    #endregion
+
+    private async Task DismissUpdate()
+    {
+        this.userDismissedUpdate = true;
+        this.AdditionalHeight = "0em";
+        
+        await this.SendMessage(Event.STATE_HAS_CHANGED);
+    }
+    
+    private bool IsUpdateAlertVisible
+    {
+        get
+        {
+            var state = this.isUpdateAvailable && !this.userDismissedUpdate;
+            this.AdditionalHeight = state ? "3em" : "0em";
+            
+            return state;
+        }
+    }
+
+    private async Task ShowUpdateDialog()
+    {
+        if(this.currentUpdateResponse is null)
+            return;
+        
+        //
+        // Replace the fir line with `# Changelog`:
+        //
+        var changelog = this.currentUpdateResponse.Value.Changelog;
+        if (!string.IsNullOrWhiteSpace(changelog))
+        {
+            var lines = changelog.Split('\n');
+            if (lines.Length > 0)
+                lines[0] = "# Changelog";
+            
+            changelog = string.Join('\n', lines);
+        }
+        
+        var updatedResponse = this.currentUpdateResponse.Value with { Changelog = changelog };
+        var dialogParameters = new DialogParameters
+        {
+            { x => x.UpdateResponse, updatedResponse }
+        };
+
+        var dialogReference = await this.DialogService.ShowAsync("Update", dialogParameters, DialogOptions.FULLSCREEN_NO_HEADER);
+        var dialogResult = await dialogReference.Result;
+        if (dialogResult.Canceled)
+            return;
+        
+        this.performingUpdate = true;
+        this.StateHasChanged();
+        await this.Rust.InstallUpdate(this.JsRuntime);
+    }
 }
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Components/Pages/About.razor b/app/MindWork AI Studio/Components/Pages/About.razor
index fbd31df5..a5cef1ae 100644
--- a/app/MindWork AI Studio/Components/Pages/About.razor	
+++ b/app/MindWork AI Studio/Components/Pages/About.razor	
@@ -18,6 +18,9 @@
                 
                 
             
+            
+                Check for updates
+            
         
         
         
diff --git a/app/MindWork AI Studio/Components/Pages/About.razor.cs b/app/MindWork AI Studio/Components/Pages/About.razor.cs
index d1bfc3c5..e9f74fea 100644
--- a/app/MindWork AI Studio/Components/Pages/About.razor.cs	
+++ b/app/MindWork AI Studio/Components/Pages/About.razor.cs	
@@ -1,11 +1,16 @@
 using System.Reflection;
 
+using AIStudio.Tools;
+
 using Microsoft.AspNetCore.Components;
 
 namespace AIStudio.Components.Pages;
 
 public partial class About : ComponentBase
 {
+    [Inject]
+    private MessageBus MessageBus { get; init; } = null!;
+    
     private static readonly Assembly ASSEMBLY = Assembly.GetExecutingAssembly();
     private static readonly MetaDataAttribute META_DATA = ASSEMBLY.GetCustomAttribute()!;
 
@@ -135,4 +140,9 @@ public partial class About : ComponentBase
         OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         SOFTWARE.
         """;
+    
+    private async Task CheckForUpdate()
+    {
+        await this.MessageBus.SendMessage(this, Event.USER_SEARCH_FOR_UPDATE);
+    }
 }
diff --git a/app/MindWork AI Studio/Components/Pages/Chat.razor.cs b/app/MindWork AI Studio/Components/Pages/Chat.razor.cs
index 738b4353..53c22aa0 100644
--- a/app/MindWork AI Studio/Components/Pages/Chat.razor.cs	
+++ b/app/MindWork AI Studio/Components/Pages/Chat.razor.cs	
@@ -37,9 +37,6 @@ public partial class Chat : ComponentBase
 
     protected override async Task OnInitializedAsync()
     {
-        // Ensure that the settings are loaded:
-        await this.SettingsManager.LoadSettings();
-        
         // Configure the spellchecking for the user input:
         this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
         
diff --git a/app/MindWork AI Studio/Components/Pages/Settings.razor b/app/MindWork AI Studio/Components/Pages/Settings.razor
index 667f4d49..fdc51e4b 100644
--- a/app/MindWork AI Studio/Components/Pages/Settings.razor	
+++ b/app/MindWork AI Studio/Components/Pages/Settings.razor	
@@ -52,5 +52,6 @@
         
         
         
+        
     
 
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Components/Pages/Settings.razor.cs b/app/MindWork AI Studio/Components/Pages/Settings.razor.cs
index ee23e07f..13b06844 100644
--- a/app/MindWork AI Studio/Components/Pages/Settings.razor.cs	
+++ b/app/MindWork AI Studio/Components/Pages/Settings.razor.cs	
@@ -3,6 +3,8 @@ using AIStudio.Provider;
 using AIStudio.Settings;
 using Microsoft.AspNetCore.Components;
 
+using DialogOptions = AIStudio.Components.CommonDialogs.DialogOptions;
+
 // ReSharper disable ClassNeverInstantiated.Global
 
 namespace AIStudio.Components.Pages;
@@ -18,22 +20,6 @@ public partial class Settings : ComponentBase
     [Inject]
     public IJSRuntime JsRuntime { get; init; } = null!;
 
-    private static readonly DialogOptions DIALOG_OPTIONS = new()
-    {
-        CloseOnEscapeKey = true,
-        FullWidth = true, MaxWidth = MaxWidth.Medium,
-    };
-
-    #region Overrides of ComponentBase
-
-    protected override async Task OnInitializedAsync()
-    {
-        await this.SettingsManager.LoadSettings();
-        await base.OnInitializedAsync();
-    }
-
-    #endregion
-
     #region Provider related
 
     private async Task AddProvider()
@@ -43,7 +29,7 @@ public partial class Settings : ComponentBase
             { x => x.IsEditing, false },
         };
         
-        var dialogReference = await this.DialogService.ShowAsync("Add Provider", dialogParameters, DIALOG_OPTIONS);
+        var dialogReference = await this.DialogService.ShowAsync("Add Provider", dialogParameters, DialogOptions.FULLSCREEN);
         var dialogResult = await dialogReference.Result;
         if (dialogResult.Canceled)
             return;
@@ -67,7 +53,7 @@ public partial class Settings : ComponentBase
             { x => x.IsEditing, true },
         };
 
-        var dialogReference = await this.DialogService.ShowAsync("Edit Provider", dialogParameters, DIALOG_OPTIONS);
+        var dialogReference = await this.DialogService.ShowAsync("Edit Provider", dialogParameters, DialogOptions.FULLSCREEN);
         var dialogResult = await dialogReference.Result;
         if (dialogResult.Canceled)
             return;
@@ -90,7 +76,7 @@ public partial class Settings : ComponentBase
             { "Message", $"Are you sure you want to delete the provider '{provider.InstanceName}'?" },
         };
         
-        var dialogReference = await this.DialogService.ShowAsync("Delete Provider", dialogParameters, DIALOG_OPTIONS);
+        var dialogReference = await this.DialogService.ShowAsync("Delete Provider", dialogParameters, DialogOptions.FULLSCREEN);
         var dialogResult = await dialogReference.Result;
         if (dialogResult.Canceled)
             return;
diff --git a/app/MindWork AI Studio/Program.cs b/app/MindWork AI Studio/Program.cs
index 5d54b793..4c1ad4a2 100644
--- a/app/MindWork AI Studio/Program.cs	
+++ b/app/MindWork AI Studio/Program.cs	
@@ -25,10 +25,12 @@ builder.Services.AddMudServices(config =>
 });
 
 builder.Services.AddMudMarkdownServices();
+builder.Services.AddSingleton(MessageBus.INSTANCE);
 builder.Services.AddSingleton();
 builder.Services.AddMudMarkdownClipboardService();
 builder.Services.AddSingleton();
 builder.Services.AddSingleton();
+builder.Services.AddHostedService();
 builder.Services.AddRazorComponents()
     .AddInteractiveServerComponents()
     .AddHubOptions(options =>
diff --git a/app/MindWork AI Studio/Settings/Data.cs b/app/MindWork AI Studio/Settings/Data.cs
index 1c629b80..8052c271 100644
--- a/app/MindWork AI Studio/Settings/Data.cs	
+++ b/app/MindWork AI Studio/Settings/Data.cs	
@@ -6,7 +6,7 @@ namespace AIStudio.Settings;
 public sealed class Data
 {
     /// 
-    /// The version of the settings file. Allows us to upgrade the settings,
+    /// The version of the settings file. Allows us to upgrade the settings
     /// when a new version is available.
     /// 
     public Version Version { get; init; } = Version.V1;
@@ -36,4 +36,9 @@ public sealed class Data
     /// Should we enable spellchecking for all input fields?
     /// 
     public bool EnableSpellchecking { get; set; }
+
+    /// 
+    /// If and when we should look for updates.
+    /// 
+    public UpdateBehavior UpdateBehavior { get; set; } = UpdateBehavior.ONCE_STARTUP;
 }
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Settings/UpdateBehavior.cs b/app/MindWork AI Studio/Settings/UpdateBehavior.cs
new file mode 100644
index 00000000..30579f76
--- /dev/null
+++ b/app/MindWork AI Studio/Settings/UpdateBehavior.cs	
@@ -0,0 +1,10 @@
+namespace AIStudio.Settings;
+
+public enum UpdateBehavior
+{
+    NO_CHECK,
+    ONCE_STARTUP,
+    HOURLY,
+    DAILY,
+    WEEKLY,
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/Event.cs b/app/MindWork AI Studio/Tools/Event.cs
new file mode 100644
index 00000000..2b259f4a
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/Event.cs	
@@ -0,0 +1,13 @@
+namespace AIStudio.Tools;
+
+public enum Event
+{
+    NONE,
+    
+    // Common events:
+    STATE_HAS_CHANGED,
+    
+    // Update events:
+    USER_SEARCH_FOR_UPDATE,
+    UPDATE_AVAILABLE,
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/IMessageBusReceiver.cs b/app/MindWork AI Studio/Tools/IMessageBusReceiver.cs
new file mode 100644
index 00000000..401a2118
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/IMessageBusReceiver.cs	
@@ -0,0 +1,8 @@
+using Microsoft.AspNetCore.Components;
+
+namespace AIStudio.Tools;
+
+public interface IMessageBusReceiver
+{
+    public Task ProcessMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data);
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/MSGComponentBase.cs b/app/MindWork AI Studio/Tools/MSGComponentBase.cs
new file mode 100644
index 00000000..b8ccaf82
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/MSGComponentBase.cs	
@@ -0,0 +1,44 @@
+using Microsoft.AspNetCore.Components;
+
+namespace AIStudio.Tools;
+
+public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBusReceiver
+{
+    [Inject]
+    protected MessageBus MessageBus { get; init; } = null!;
+
+    #region Overrides of ComponentBase
+
+    protected override void OnInitialized()
+    {
+        this.MessageBus.RegisterComponent(this);
+        base.OnInitialized();
+    }
+
+    #endregion
+
+    #region Implementation of IMessageBusReceiver
+
+    public abstract Task ProcessMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data);
+
+    #endregion
+
+    #region Implementation of IDisposable
+
+    public void Dispose()
+    {
+        this.MessageBus.Unregister(this);
+    }
+
+    #endregion
+    
+    protected async Task SendMessage(Event triggeredEvent, T? data = default)
+    {
+        await this.MessageBus.SendMessage(this, triggeredEvent, data);
+    }
+    
+    protected void ApplyFilters(ComponentBase[] components, Event[] events)
+    {
+        this.MessageBus.ApplyFilters(this, components, events);
+    }
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/MessageBus.cs b/app/MindWork AI Studio/Tools/MessageBus.cs
new file mode 100644
index 00000000..53b215c7
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/MessageBus.cs	
@@ -0,0 +1,67 @@
+using System.Collections.Concurrent;
+
+using Microsoft.AspNetCore.Components;
+// ReSharper disable RedundantRecordClassKeyword
+
+namespace AIStudio.Tools;
+
+public sealed class MessageBus
+{
+    public static readonly MessageBus INSTANCE = new();
+    
+    private readonly ConcurrentDictionary componentFilters = new();
+    private readonly ConcurrentDictionary componentEvents = new();
+    private readonly ConcurrentQueue messageQueue = new();
+    private readonly SemaphoreSlim sendingSemaphore = new(1, 1);
+
+    private MessageBus()
+    {
+    }
+
+    public void ApplyFilters(IMessageBusReceiver receiver, ComponentBase[] components, Event[] events)
+    {
+        this.componentFilters[receiver] = components;
+        this.componentEvents[receiver] = events;
+    }
+    
+    public void RegisterComponent(IMessageBusReceiver receiver)
+    {
+        this.componentFilters.TryAdd(receiver, []);
+        this.componentEvents.TryAdd(receiver, []);
+    }
+    
+    public void Unregister(IMessageBusReceiver receiver)
+    {
+        this.componentFilters.TryRemove(receiver, out _);
+        this.componentEvents.TryRemove(receiver, out _);
+    }
+    
+    private record class Message(ComponentBase? SendingComponent, Event TriggeredEvent, object? Data);
+    
+    public async Task SendMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data = default)
+    {
+        this.messageQueue.Enqueue(new Message(sendingComponent, triggeredEvent, data));
+        
+        try
+        {
+            await this.sendingSemaphore.WaitAsync();
+            while (this.messageQueue.TryDequeue(out var message))
+            {
+                foreach (var (receiver, componentFilter) in this.componentFilters)
+                {
+                    if (componentFilter.Length > 0 && sendingComponent is not null && !componentFilter.Contains(sendingComponent))
+                        continue;
+
+                    var eventFilter = this.componentEvents[receiver];
+                    if (eventFilter.Length == 0 || eventFilter.Contains(triggeredEvent))
+                        // We don't await the task here because we don't want to block the message bus:
+                        _ = receiver.ProcessMessage(message.SendingComponent, message.TriggeredEvent, message.Data);
+                }
+            }
+        }
+        finally
+        {
+            this.sendingSemaphore.Release();
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/MessageBusExtensions.cs b/app/MindWork AI Studio/Tools/MessageBusExtensions.cs
new file mode 100644
index 00000000..7956c27e
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/MessageBusExtensions.cs	
@@ -0,0 +1,16 @@
+using Microsoft.AspNetCore.Components;
+
+namespace AIStudio.Tools;
+
+public static class MessageBusExtensions
+{
+    public static async Task SendMessage(this ComponentBase component, Event triggeredEvent, T? data = default)
+    {
+        await MessageBus.INSTANCE.SendMessage(component, triggeredEvent, data);
+    }
+    
+    public static void ApplyFilters(this IMessageBusReceiver component, ComponentBase[] components, Event[] events)
+    {
+        MessageBus.INSTANCE.ApplyFilters(component, components, events);
+    }
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/Rust.cs b/app/MindWork AI Studio/Tools/Rust.cs
index 9763b39d..af85765d 100644
--- a/app/MindWork AI Studio/Tools/Rust.cs	
+++ b/app/MindWork AI Studio/Tools/Rust.cs	
@@ -39,4 +39,16 @@ public sealed class Rust
             };
         });
     }
+    
+    public async Task CheckForUpdate(IJSRuntime jsRuntime)
+    {
+        var cts = new CancellationTokenSource(TimeSpan.FromSeconds(16));
+        return await jsRuntime.InvokeAsync("window.__TAURI__.invoke", cts.Token, "check_for_update");
+    }
+    
+    public async Task InstallUpdate(IJSRuntime jsRuntime)
+    {
+        var cts = new CancellationTokenSource();
+        await jsRuntime.InvokeVoidAsync("window.__TAURI__.invoke", cts.Token, "install_update");
+    }
 }
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/SetClipboardResponse.cs b/app/MindWork AI Studio/Tools/SetClipboardResponse.cs
index 7a65caa9..e7a8fcc3 100644
--- a/app/MindWork AI Studio/Tools/SetClipboardResponse.cs	
+++ b/app/MindWork AI Studio/Tools/SetClipboardResponse.cs	
@@ -3,6 +3,6 @@ namespace AIStudio.Tools;
 /// 
 /// The response from the set clipboard operation.
 /// 
-/// True when the operation was successful.
-/// The issue that occurred during the operation, empty when successful.
+/// True, when the operation was successful.
+/// The issues, which occurred during the operation, empty when successful.
 public readonly record struct SetClipboardResponse(bool Success, string Issue);
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/UpdateResponse.cs b/app/MindWork AI Studio/Tools/UpdateResponse.cs
new file mode 100644
index 00000000..5a5e3e2b
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/UpdateResponse.cs	
@@ -0,0 +1,16 @@
+using System.Text.Json.Serialization;
+
+namespace AIStudio.Tools;
+
+/// 
+/// The response of the update check.
+/// 
+/// True if an update is available.
+/// The new version, when available.
+/// The changelog of the new version, when available.
+public readonly record struct UpdateResponse(
+    [property:JsonPropertyName("update_is_available")] bool UpdateIsAvailable, 
+    [property:JsonPropertyName("error")] bool Error, 
+    [property:JsonPropertyName("new_version")] string NewVersion, 
+    string Changelog
+);
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/UpdateService.cs b/app/MindWork AI Studio/Tools/UpdateService.cs
new file mode 100644
index 00000000..4b7143df
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/UpdateService.cs	
@@ -0,0 +1,105 @@
+using AIStudio.Settings;
+
+using Microsoft.AspNetCore.Components;
+
+namespace AIStudio.Tools;
+
+public sealed class UpdateService : BackgroundService, IMessageBusReceiver
+{
+    // We cannot inject IJSRuntime into our service. This is due to the fact that
+    // the service is not a Blaozor component. We need to pass the IJSRuntime from
+    // the MainLayout component to the service.
+    private static IJSRuntime? JS_RUNTIME;
+    private static bool IS_INITALIZED;
+    
+    private readonly SettingsManager settingsManager;
+    private readonly TimeSpan updateInterval;
+    private readonly MessageBus messageBus;
+    private readonly Rust rust;
+    
+    public UpdateService(MessageBus messageBus, SettingsManager settingsManager, Rust rust)
+    {
+        this.settingsManager = settingsManager;
+        this.messageBus = messageBus;
+        this.rust = rust;
+        
+        this.messageBus.RegisterComponent(this);
+        this.ApplyFilters([], [ Event.USER_SEARCH_FOR_UPDATE ]);
+        
+        this.updateInterval = settingsManager.ConfigurationData.UpdateBehavior switch
+        {
+            UpdateBehavior.NO_CHECK => Timeout.InfiniteTimeSpan,
+            UpdateBehavior.ONCE_STARTUP => Timeout.InfiniteTimeSpan,
+            
+            UpdateBehavior.HOURLY => TimeSpan.FromHours(1),
+            UpdateBehavior.DAILY => TimeSpan.FromDays(1),
+            UpdateBehavior.WEEKLY => TimeSpan.FromDays(7),
+            
+            _ => TimeSpan.FromHours(1)
+        };
+    }
+    
+    #region Overrides of BackgroundService
+
+    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+    {
+        while (!stoppingToken.IsCancellationRequested && !IS_INITALIZED)
+        {
+            await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken);
+        }
+
+        await this.settingsManager.LoadSettings();
+        if(this.settingsManager.ConfigurationData.UpdateBehavior != UpdateBehavior.NO_CHECK)
+            await this.CheckForUpdate();
+        
+        while (!stoppingToken.IsCancellationRequested)
+        {
+            await Task.Delay(this.updateInterval, stoppingToken);
+            await this.CheckForUpdate();
+        }
+    }
+
+    #endregion
+
+    #region Implementation of IMessageBusReceiver
+
+    public async Task ProcessMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data)
+    {
+        switch (triggeredEvent)
+        {
+            case Event.USER_SEARCH_FOR_UPDATE:
+                await this.CheckForUpdate();
+                break;
+        }
+    }
+
+    #endregion
+
+    #region Overrides of BackgroundService
+
+    public override async Task StopAsync(CancellationToken cancellationToken)
+    {
+        this.messageBus.Unregister(this);
+        await base.StopAsync(cancellationToken);
+    }
+
+    #endregion
+
+    private async Task CheckForUpdate()
+    {
+        if(!IS_INITALIZED)
+            return;
+        
+        var response = await this.rust.CheckForUpdate(JS_RUNTIME!);
+        if (response.UpdateIsAvailable)
+        {
+            await this.messageBus.SendMessage(null, Event.UPDATE_AVAILABLE, response);
+        }
+    }
+    
+    public static void SetJsRuntime(IJSRuntime jsRuntime)
+    {
+        JS_RUNTIME = jsRuntime;
+        IS_INITALIZED = true;
+    }
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.6.0.md b/app/MindWork AI Studio/wwwroot/changelog/v0.6.0.md
new file mode 100644
index 00000000..ba2a6e1b
--- /dev/null
+++ b/app/MindWork AI Studio/wwwroot/changelog/v0.6.0.md	
@@ -0,0 +1,8 @@
+# v0.6.0, build 156 (2024-06-30 12:49 UTC)
+- Added a setting to determine whether and how often to check for updates
+- Added a bidirectional message bus for inter-component communication
+- Added an update dialog for the app
+- Added an update banner to show when a new version is available
+- Added an option to manually check for updates to the about page
+- Fixed an issue with previous automatic updates on Windows, where background processes were not terminated
+- Disabled Tauri's built-in update dialog
\ No newline at end of file
diff --git a/metadata.txt b/metadata.txt
index 3f294abf..3a6cb4b2 100644
--- a/metadata.txt
+++ b/metadata.txt
@@ -1,9 +1,9 @@
-0.5.2
-2024-06-25 18:07:06 UTC
-155
+0.6.0
+2024-06-30 12:49:38 UTC
+156
 8.0.206 (commit bb12410699)
 8.0.6 (commit 3b8b000a0e)
 1.79.0 (commit 129f3b996)
 6.20.0
 1.6.1
-2818aa93411, release
+dab121a7217, release
diff --git a/runtime/Cargo.lock b/runtime/Cargo.lock
index 3d46acbe..afe5286c 100644
--- a/runtime/Cargo.lock
+++ b/runtime/Cargo.lock
@@ -2313,12 +2313,13 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
 
 [[package]]
 name = "mindwork-ai-studio"
-version = "0.5.2"
+version = "0.6.0"
 dependencies = [
  "arboard",
  "flexi_logger",
  "keyring",
  "log",
+ "once_cell",
  "reqwest 0.12.4",
  "serde",
  "serde_json",
diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml
index e2bb8483..44b47690 100644
--- a/runtime/Cargo.toml
+++ b/runtime/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "mindwork-ai-studio"
-version = "0.5.2"
+version = "0.6.0"
 edition = "2021"
 description = "MindWork AI Studio"
 authors = ["Thorsten Sommer"]
@@ -18,6 +18,7 @@ arboard = "3.4.0"
 tokio = "1.37.0"
 flexi_logger = "0.28"
 log = "0.4"
+once_cell = "1.19.0"
 
 [target.'cfg(target_os = "linux")'.dependencies]
 # See issue https://github.com/tauri-apps/tauri/issues/4470
diff --git a/runtime/src/main.rs b/runtime/src/main.rs
index 0c63bc7a..57d9a4e7 100644
--- a/runtime/src/main.rs
+++ b/runtime/src/main.rs
@@ -5,6 +5,7 @@ extern crate core;
 
 use std::net::TcpListener;
 use std::sync::{Arc, Mutex};
+use once_cell::sync::Lazy;
 
 use arboard::Clipboard;
 use keyring::Entry;
@@ -15,6 +16,11 @@ use tauri::utils::config::AppUrl;
 use tokio::time;
 use flexi_logger::{AdaptiveFormat, Logger};
 use log::{debug, error, info, warn};
+use tauri::updater::UpdateResponse;
+
+static SERVER: Lazy>>> = Lazy::new(|| Arc::new(Mutex::new(None)));
+static MAIN_WINDOW: Lazy>> = Lazy::new(|| Mutex::new(None));
+static CHECK_UPDATE_RESPONSE: Lazy>>> = Lazy::new(|| Mutex::new(None));
 
 fn main() {
 
@@ -30,7 +36,14 @@ fn main() {
     let tauri_version = metadata_lines.next().unwrap();
     let app_commit_hash = metadata_lines.next().unwrap();
 
-    Logger::try_with_str("debug").expect("Cannot create logging")
+    // Set the log level according to the environment:
+    // In debug mode, the log level is set to debug, in release mode to info.
+    let log_level = match is_dev() {
+        true => "debug",
+        false => "info",
+    };
+
+    Logger::try_with_str(log_level).expect("Cannot create logging")
         .log_to_stdout()
         .adaptive_format_for_stdout(AdaptiveFormat::Detailed)
         .start().expect("Cannot start logging");
@@ -69,8 +82,7 @@ fn main() {
     info!("Try to start the .NET server on {app_url_log}...");
 
     // Arc for the server process to stop it later:
-    let server: Arc>> = Arc::new(Mutex::new(None));
-    let server_spawn_clone = server.clone();
+    let server_spawn_clone = SERVER.clone();
 
     // Channel to communicate with the server process:
     let (sender, mut receiver) = tauri::async_runtime::channel(100);
@@ -114,9 +126,8 @@ fn main() {
         warn!("Running in development mode, no .NET server will be started.");
     }
 
-    let main_window: Arc>> = Arc::new(Mutex::new(None));
-    let main_window_spawn_clone = main_window.clone();
-    let server_receive_clone = server.clone();
+    let main_window_spawn_clone = &MAIN_WINDOW;
+    let server_receive_clone = SERVER.clone();
 
     // Create a thread to handle server events:
     tauri::async_runtime::spawn(async move {
@@ -181,29 +192,93 @@ fn main() {
     });
 
     info!("Starting Tauri app...");
-    tauri::Builder::default()
+    let app = tauri::Builder::default()
         .setup(move |app| {
             let window = app.get_window("main").expect("Failed to get main window.");
-            *main_window.lock().unwrap() = Some(window);
+            *MAIN_WINDOW.lock().unwrap() = Some(window);
             Ok(())
         })
         .plugin(tauri_plugin_window_state::Builder::default().build())
-        .invoke_handler(tauri::generate_handler![store_secret, get_secret, delete_secret, set_clipboard])
-        .run(tauri::generate_context!())
+        .invoke_handler(tauri::generate_handler![
+            store_secret, get_secret, delete_secret, set_clipboard,
+            check_for_update, install_update
+        ])
+        .build(tauri::generate_context!())
         .expect("Error while running Tauri application");
+    
+    app.run(|app_handle, event| match event {
+
+        tauri::RunEvent::WindowEvent { event, label, .. } => {
+            match event {
+                tauri::WindowEvent::CloseRequested { .. } => {
+                    warn!("Window '{label}': close was requested.");
+                }
+
+                tauri::WindowEvent::Destroyed => {
+                    warn!("Window '{label}': was destroyed.");
+                }
+
+                tauri::WindowEvent::FileDrop(files) => {
+                    info!("Window '{label}': files were dropped: {files:?}");
+                }
+
+                _ => (),
+            }
+        }
+
+        tauri::RunEvent::Updater(updater_event) => {
+            match updater_event {
+
+                tauri::UpdaterEvent::UpdateAvailable { body, date, version } => {
+                    let body_len = body.len();
+                    info!("Updater: update available: body size={body_len} time={date:?} version={version}");
+                }
+
+                tauri::UpdaterEvent::Pending => {
+                    info!("Updater: update is pending!");
+                }
+
+                tauri::UpdaterEvent::DownloadProgress { chunk_length, content_length } => {
+                    info!("Updater: downloaded {} of {:?}", chunk_length, content_length);
+                }
+
+                tauri::UpdaterEvent::Downloaded => {
+                    info!("Updater: update has been downloaded!");
+                    warn!("Try to stop the .NET server now...");
+                    stop_server();
+                }
+
+                tauri::UpdaterEvent::Updated => {
+                    info!("Updater: app has been updated");
+                    warn!("Try to restart the app now...");
+                    app_handle.restart();
+                }
+
+                tauri::UpdaterEvent::AlreadyUpToDate => {
+                    info!("Updater: app is already up to date");
+                }
+
+                tauri::UpdaterEvent::Error(error) => {
+                    warn!("Updater: failed to update: {error}");
+                }
+            }
+        }
+
+        tauri::RunEvent::ExitRequested { .. } => {
+            warn!("Run event: exit was requested.");
+        }
+        
+        tauri::RunEvent::Ready => {
+            info!("Run event: Tauri app is ready.");
+        }
+
+        _ => {}
+    });
 
     info!("Tauri app was stopped.");
     if is_prod() {
         info!("Try to stop the .NET server as well...");
-        if let Some(server_process) = server.lock().unwrap().take() {
-            let server_kill_result = server_process.kill();
-            match server_kill_result {
-                Ok(_) => info!("The .NET server process was stopped."),
-                Err(e) => error!("Failed to stop the .NET server process: {e}."),
-            }
-        } else {
-            warn!("The .NET server process was not started or already stopped.");
-        }
+        stop_server();
     }
 }
 
@@ -230,6 +305,87 @@ fn get_available_port() -> Option {
         .ok()
 }
 
+fn stop_server() {
+    if let Some(server_process) = SERVER.lock().unwrap().take() {
+        let server_kill_result = server_process.kill();
+        match server_kill_result {
+            Ok(_) => info!("The .NET server process was stopped."),
+            Err(e) => error!("Failed to stop the .NET server process: {e}."),
+        }
+    } else {
+        warn!("The .NET server process was not started or already stopped.");
+    }
+}
+
+#[tauri::command]
+async fn check_for_update() -> CheckUpdateResponse {
+    let app_handle = MAIN_WINDOW.lock().unwrap().as_ref().unwrap().app_handle();
+    tauri::async_runtime::spawn(async move {
+        let response = app_handle.updater().check().await;
+        match response {
+            Ok(update_response) => match update_response.is_update_available() {
+                true => {
+                    *CHECK_UPDATE_RESPONSE.lock().unwrap() = Some(update_response.clone());
+                    let new_version = update_response.latest_version();
+                    info!("Updater: update to version '{new_version}' is available.");
+                    let changelog = update_response.body();
+                    CheckUpdateResponse {
+                        update_is_available: true,
+                        error: false,
+                        new_version: new_version.to_string(),
+                        changelog: match changelog {
+                            Some(c) => c.to_string(),
+                            None => String::from(""),
+                        },
+                    }
+                },
+
+                false => {
+                    info!("Updater: no updates available.");
+                    CheckUpdateResponse {
+                        update_is_available: false,
+                        error: false,
+                        new_version: String::from(""),
+                        changelog: String::from(""),
+                    }
+                },
+            },
+
+            Err(e) => {
+                warn!("Failed to check updater: {e}.");
+                CheckUpdateResponse {
+                    update_is_available: false,
+                    error: true,
+                    new_version: String::from(""),
+                    changelog: String::from(""),
+                }
+            },
+        }
+    }).await.unwrap()
+}
+
+#[derive(Serialize)]
+struct CheckUpdateResponse {
+    update_is_available: bool,
+    error: bool,
+    new_version: String,
+    changelog: String,
+}
+
+#[tauri::command]
+async fn install_update() {
+    let cloned_response_option = CHECK_UPDATE_RESPONSE.lock().unwrap().clone();
+    match cloned_response_option {
+        Some(update_response) => {
+            update_response.download_and_install().await.unwrap();
+        },
+
+        None => {
+            error!("Update installer: no update available to install. Did you check for updates first?");
+        },
+    }
+}
+
 #[tauri::command]
 fn store_secret(destination: String, user_name: String, secret: String) -> StoreSecretResponse {
     let service = format!("mindwork-ai-studio::{}", destination);
diff --git a/runtime/tauri.conf.json b/runtime/tauri.conf.json
index 1c7b2212..9a841f09 100644
--- a/runtime/tauri.conf.json
+++ b/runtime/tauri.conf.json
@@ -6,7 +6,7 @@
   },
   "package": {
     "productName": "MindWork AI Studio",
-	"version": "0.5.2"
+	"version": "0.6.0"
   },
   "tauri": {
     "allowlist": {
@@ -76,7 +76,7 @@
       "endpoints": [
         "https://github.com/MindWorkAI/AI-Studio/releases/latest/download/latest.json"
       ],
-      "dialog": true,
+      "dialog": false,
       "windows": {
         "installMode": "passive"
       },