diff --git a/LICENSE b/LICENSE
index 6c37a958ffd68e166da0a58e6d55aaac03f704cb..43c35547190ea7ef95e8b33f47bcfa59de4f0639 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,16 +1,18 @@
-Copyright 2023 Distant Magic Mateusz Charytoniuk
+The MIT License (MIT)
 
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the “Software”), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-of the Software, and to permit persons to whom the Software is furnished to do
-so, subject to the following conditions:
+Copyright (c) 2023-2024 Distantmagic Mateusz Charytoniuk
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
 
 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.
 
-THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
diff --git a/Makefile b/Makefile
index 645d4b4f03a6185493916ce64bcb2f4014148eff..ea4baa64fe66340174f5326e9513da86dd4e3b0a 100644
--- a/Makefile
+++ b/Makefile
@@ -54,8 +54,7 @@ docs/artifact.tar: docs/build
 
 docs/build: config.ini esbuild vendor $(MD_SOURCES) $(PHP_SOURCES)
 	${PHP_BIN} ./bin/resonance.php static-pages:build;
-	cp resources/images/ogimage.webp docs/build/ogimage.webp;
-	cp resources/images/favicon.ico docs/build/favicon.ico;
+	cp resources/images/* docs/build;
 
 node_modules: yarn.lock
 	yarnpkg install --check-files --frozen-lockfile --non-interactive;
diff --git a/app/Template/StaticPageLayout/Turbo/Tutorial.php b/app/Template/StaticPageLayout/Turbo/Tutorial.php
index 16c87f348c21553e8f982ede0a1513a58aa5ad8f..206b29c2f82ec69d5316dc552a70dbb0938cada7 100644
--- a/app/Template/StaticPageLayout/Turbo/Tutorial.php
+++ b/app/Template/StaticPageLayout/Turbo/Tutorial.php
@@ -132,6 +132,18 @@ final readonly class Tutorial extends Turbo
                 <div class="formatted-content tutorial__readme">
                     {$renderedOutput}
                 </div>
+                <div class="formatted-content tutorial__comments">
+                    <h3>Comments</h3>
+                    <p>
+                        If you want to leave a comment
+                        <a
+                            href="https://github.com/distantmagic/resonance/discussions"
+                            target="_blank"
+                        >Start a discussion on GitHub</a>
+                        or join our
+                        <a href="/community/">Community</a>
+                    </p>
+                </div>
             </div>
         </div>
         HTML;
diff --git a/docs/pages/index.md b/docs/pages/index.md
index baac3f89d28adbfea7df0aab65f0030fa3d16403..6445c24771019c6d64203f772d8850184b715f8b 100644
--- a/docs/pages/index.md
+++ b/docs/pages/index.md
@@ -42,6 +42,23 @@ description: >
 <div class="homepage-gallery">
     <h3>New Releases</h3>
     <ul class="homepage-gallery__items">
+        <li class="homepage-gallery__item">
+            <h4>
+                <a href="/tutorials/semi-scripted-conversational-applications/">
+                    Semi-Scripted Conversational Applications
+                    <span class="homepage-gallery__version">tutorial</span>
+                </a>
+            </h4>
+            <p>
+                Large Language Models, when used as a user interface, enable 
+                users to create advanced applications that can replace most 
+                traditional interfaces.
+            </p>
+            <a 
+                class="homepage-gallery__item__learnmore"
+                href="/tutorials/semi-scripted-conversational-applications/"
+            >Learn More</a>
+        </li>
         <li class="homepage-gallery__item">
             <h4>
                 <a href="/docs/features/conversational-applications/dialogue-nodes/">
diff --git a/docs/pages/tutorials/semi-scripted-conversational-applications/index.md b/docs/pages/tutorials/semi-scripted-conversational-applications/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..ae0e77bccd581e72a8bffce06902181fca1576a0
--- /dev/null
+++ b/docs/pages/tutorials/semi-scripted-conversational-applications/index.md
@@ -0,0 +1,275 @@
+---
+layout: dm:tutorial
+parent: index
+title: Semi-Scripted Conversational Applications
+description: >
+    Large Language Models, when used as a user interface, enable users to 
+    create advanced applications that can replace most traditional interfaces.
+---
+
+## What Are Conversational Applications?
+
+Conversational applications are more than just chatbots. Large Language Models, 
+when used as a user interface, enables users to create advanced applications that 
+can replace most of the traditional interfaces.
+
+They can use LLM (Large Language Models) or other means of processing
+human input as a front-end. The simplest example is a custom GPT that can use
+your API to interact with your application.
+
+Is it a silver bullet to solve all the problems? Of course not. Currently most 
+of AI integrations act as copilots or assistants. What if we try to 
+inverse that dynamic where CUI (Conversational User Interface) is an add-on to 
+the Graphical User Interface and make GUI components a complimentary feature 
+instead?
+
+### How It Started
+
+Ok, maybe [Zork](https://en.wikipedia.org/wiki/Zork) is not really a 
+conversational application, but it is really symbolic for me. 
+
+All the interactions with the game are done through a series of specific 
+commands. That kind of interface is rigorous in its usage because 
+although it theoretically allows us to input any text, we still have to
+guess our options.
+
+It is strict both on input and output data with no room for interpretation -
+like using some scripting programming language.
+
+![alt text](/zork.png "Zork")
+*image source: [lemon64](https://www.lemon64.com/game/zork-1-golden-edition)*
+
+### How Is It Going
+
+Several observations:
+
+1. We have an AI that can recreate the Zork experience in seconds without 
+    requiring almost any human labor.
+2. AI understands any input that we can throw at it.
+3. AI responds in the style and tone we asked, making the experience 
+    uniquely personalized.
+
+Now, we can create a conversational application that can
+understand any input and provide a unique experience for 
+each user. It is, however, very general in its use cases - which is both a 
+blessing and a curse in disguise (see: 
+[infamous Chevrolet chatbot incident](https://www.reddit.com/r/ChatGPT/comments/18kvlzc/i_gaslit_the_chevrolet_support_bot_into_thinking/)).
+
+![alt text](/chatgpt.webp "ChatGPT")
+*image source: [ChatGPT](https://chat.openai.com/)*
+
+### Where Is It Going?
+
+What if we could mold a general-use AI to fit our specific applications and use
+cases while benefiting from its reasoning and language capabilities? To
+be flexible when accepting inputs and simultaneously avoiding hallucinations.
+How about building the applications themselves without writing code?
+
+## Semi-Scripted Dialogues
+
+Let us meet the AI halfway. We will create a semi-scripted dialogue to list 
+predefined dialogue nodes and potential responses. The system will accept any 
+input and try to match it with one of the predefined responses. This allows us 
+flexibility when accepting inputs and be strict and predictable about our o
+utputs.
+
+### Ingredients
+
+1. [Swoole](https://swoole.com/)
+1. {{docs/features/ai/server/llama-cpp/extractors/index}} 
+    (to interpret the user's input and allow the AI to make further decisions based
+    on it)
+1. {{docs/features/conversational-applications/dialogue-nodes/index}}
+    (to create a conversational flow)
+1. {{docs/features/websockets/index}} (to actually serve the application)
+
+### What Are We Going to Build?
+
+We will create a simple CRUD task management system. User will be able to name 
+a new task, add a description, and list available tasks through a conversational
+interface.
+
+We are going to use an LLM (through {{docs/features/ai/server/llama-cpp/index}})
+to match user inputs with specific conversational nodes.
+
+The application itself will alternate between handling potential responses from
+the user and executing side effects.
+
+```graphviz render
+digraph { 
+    dialogue_node [label="Dialogue Node"];
+    response [label="Response"];
+    side_effects [label="Side Effects"];
+
+    dialogue_node -> response;
+    response -> side_effects;
+    response -> dialogue_node;
+}
+```
+
+We will model the following conversation in a semi-scripted way so it will be
+capable of handling pretty much any user input:
+
+```
+System: 
+    Message: Hello! How can I help you with your tasks?
+    Potential Responses:
+        - I want to create a task
+        - I want to list all my tasks.
+```
+
+You can serve it in any way, either as a standalone application, a 
+[Telegram](https://telegram.org/) bot or a web application. We will focus on 
+the core of the conversational application and skip the delivery method for 
+now.
+
+### Dialogue Controllers
+
+We are all familiar with HTTP {{docs/features/http/controllers}} who mediate
+between the user and the application. Why don't we reuse a similar concept and
+create a dialogue controller?
+
+It should keep the general state of the conversation and be able to react to
+user requests.
+
+First, let us define the initial dialogue node and all the potential responses:
+
+```php file:app/DialogueCOntroller/TaskConversationController.php
+#[Singleton]
+readonly class TaskConversationController extends DialogueController
+{
+    private DialogueNodeInterface $rootNode;
+
+    public function __construct(
+        private DoctrineEntityManagerRepository $doctrineEntityManagerRepository,
+        private HelpfulAssistant $helpfulAssistantPersona,
+        private LlamaCppClientInterface $llamaCppClient,
+        LlamaCppExtractWhenInterface $llamaCppExtractWhen,
+        private LlamaCppExtractSubject $llamaCppExtractSubject,
+    ) {
+        $this->rootNode = DialogueNode::withMessage('Hello! How can I help you with your tasks?');
+        $this->rootNode->addPotentialResponse(new CatchAllResponse(
+            followUp: $this->welcomeNode,
+        ));
+
+        $this->welcomeNode->addPotentialResponse(new LlamaCppExtractWhenResponse(
+            llamaCppExtractWhen: $llamaCppExtractWhen,
+            condition: 'User wants to create a new task',
+            persona: $helpfulAssistantPersona,
+            whenProvided: $this->whenUserWantsToCreateTask(...)
+        ));
+
+        $this->welcomeNode->addPotentialResponse(new LlamaCppExtractWhenResponse(
+            llamaCppExtractWhen: $llamaCppExtractWhen,
+            condition: 'User wants to list all their tasks',
+            persona: $helpfulAssistantPersona,
+            whenProvided: $this->whenUserWantsToListAllTasks(...)
+        ));
+    }
+
+    public function getRootDialogueNode(): DialogueNodeInterface
+    {
+        return $this->rootNode;
+    }
+
+// (...)
+```
+
+Now, we need to handle all the predefined responses. Let's start with the 
+one to create a new task. Let us try to extract the task name. If that is
+successful, let us move forward and add the task; in any other case, ask the 
+user to specify further:
+
+```php file:app/DialogueCOntroller/TaskConversationController.php
+// (...)
+
+    private function whenUserWantsToCreateTask(LlamaCppExtractWhenResult $response): Generator
+    {
+        if ($response->result->isNo()) {
+            return DialogueResponseResolution::cannotRespond();
+        }
+
+        $result = $this->llamaCppExtractSubject->extract(
+            input: $response->input,
+            topic: 'task name',
+        );
+
+        //user provided a name of the task
+        if ($result->content) {
+            $followUp->addSideEffect(new CreateTask(
+                taskName: $result->content,
+            ));
+
+            $followUp = new DialogueNode(new LlamaCppPromptMessageProducer(
+                llamaCppClient: $this->llamaCppClient,
+                request: new LlamaCppCompletionRequest(
+                    llmChatHistory: new LlmChatHistory([
+                        new LlmChatMessage(
+                            actor: 'system',
+                            message: 'Let user know that their task is added',
+                        ),
+                    ]),
+                ),
+            ));
+        } else {
+            $followUp = new DialogueNode(new LlamaCppPromptMessageProducer(
+                llamaCppClient: $this->llamaCppClient,
+                request: new LlamaCppCompletionRequest(
+                    llmChatHistory: new LlmChatHistory([
+                        new LlmChatMessage(
+                            actor: 'system',
+                            message: 'Ask user to specify the name of the task',
+                        ),
+                    ]),
+                ),
+            ));
+        }
+
+        // Go back to the primary dialogue node in any case
+        $followUp->copyResponsesFrom($this->rootNode);
+
+        return DialogueResponseResolution::canRespond($followUp);
+    }
+
+// (...)
+```
+
+When a user wants to list all tasks, just produce a response in a similar way
+you would produce an HTML view:
+
+```php file:app/DialogueCOntroller/TaskConversationController.php
+// (...)
+
+    private function whenUserWantsToCreateTask(LlamaCppExtractWhenResult $response): Generator
+    {
+        if ($response->result->isNo()) {
+            return DialogueResponseResolution::cannotRespond();
+        }
+
+        $responseMessage = "Your tasks:\n%s";
+
+        $tasks = $this
+            ->doctrineEntityManagerRepository
+            ->withEntityManager(function (EntityManagerInterface $entityManager) {
+                // ...obtain user tasks somehow
+            })
+        ;
+
+        foreach ($tasks as $task) {
+            $responseMessage .= "- {$task->name}\n";
+        }
+
+        $followUp = DialogueNode::withMessage($responseMessage);
+
+        // Go back to the primary dialogue node in any case
+        $followUp->copyResponsesFrom($this->rootNode);
+
+        return DialogueResponseResolution::canRespond($followUp);
+    }
+}
+```
+
+## Summary
+
+It was a simple example of a conversational application; it's like writing a 
+basic "Hello, world!". I hope it inspires you to create something incredible!
diff --git a/resources/css/docs-formatted-content.css b/resources/css/docs-formatted-content.css
index fd40881bbbaad69a36648dfdc66bfb91e1e990ff..44f8ef223d402ff43d83d48ff5a9e3f3327e5f52 100644
--- a/resources/css/docs-formatted-content.css
+++ b/resources/css/docs-formatted-content.css
@@ -156,6 +156,17 @@
     width: 100%;
   }
 
+  img + em {
+    align-items: center;
+    column-gap: 1ch;
+    display: flex;
+    flex-direction: row;
+    font-size: var(--font-size-smaller);
+    justify-content: center;
+    margin-top: -24px;
+    text-align: center;
+  }
+
   ol:not([class]),
   ul:not([class]) {
     display: flex;
diff --git a/resources/css/docs-page-homepage.css b/resources/css/docs-page-homepage.css
index 483f41802348a0d6e8a6e9018a408146f560a964..f6579c8f27cc2793f1a7492d35fb446f81921234 100644
--- a/resources/css/docs-page-homepage.css
+++ b/resources/css/docs-page-homepage.css
@@ -58,6 +58,7 @@
   h4 a {
     align-items: center;
     border-bottom: 1px solid var(--color-border);
+    column-gap: 1ch;
     display: flex;
     font-weight: bold;
     justify-content: space-between;
diff --git a/resources/css/docs-page-tutorial.css b/resources/css/docs-page-tutorial.css
index e122bbc158b5e8f1b8dc6ea99509040a12f3cbc4..7a34ccad1d99c277310f695eab90d1ad1971af39 100644
--- a/resources/css/docs-page-tutorial.css
+++ b/resources/css/docs-page-tutorial.css
@@ -29,6 +29,15 @@
   max-width: 100vw;
 }
 
+.tutorial__comments {
+  border-top: 1px solid var(--color-border);
+  display: flex;
+  flex-direction: column;
+  margin: 80px 0 1em 0;
+  padding: 20px 0;
+  row-gap: 1em;
+}
+
 .tutorial__content {
   display: grid;
   padding-bottom: 60px;
diff --git a/resources/images/chatgpt.webp b/resources/images/chatgpt.webp
new file mode 100644
index 0000000000000000000000000000000000000000..f96a7b302a8d575a80fb6fe21c4ec6263c804c54
Binary files /dev/null and b/resources/images/chatgpt.webp differ
diff --git a/resources/images/intentt_background_small.webp b/resources/images/intentt_background_small.webp
deleted file mode 100644
index 55dc7d43faec34d025828469996b4146045a9841..0000000000000000000000000000000000000000
Binary files a/resources/images/intentt_background_small.webp and /dev/null differ
diff --git a/resources/images/zork.png b/resources/images/zork.png
new file mode 100644
index 0000000000000000000000000000000000000000..35dc3bf2e6c9fda7f7dc224ff4213a371cfc142b
Binary files /dev/null and b/resources/images/zork.png differ