<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Anatoly (Vensus)</title>
    <description>The latest articles on DEV Community by Anatoly (Vensus) (@vensus).</description>
    <link>https://dev.to/vensus</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3719534%2F687f303a-28b1-4bc0-86ee-83ecb810daa9.jpg</url>
      <title>DEV Community: Anatoly (Vensus)</title>
      <link>https://dev.to/vensus</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vensus"/>
    <language>en</language>
    <item>
      <title>Coreness Flow: Event-Driven AI Agent — No Cloud, No Code</title>
      <dc:creator>Anatoly (Vensus)</dc:creator>
      <pubDate>Wed, 11 Mar 2026 11:37:00 +0000</pubDate>
      <link>https://dev.to/vensus/coreness-flow-event-driven-ai-agent-no-cloud-no-code-f9</link>
      <guid>https://dev.to/vensus/coreness-flow-event-driven-ai-agent-no-cloud-no-code-f9</guid>
      <description>&lt;p&gt;Most AI assistants are just a chat: you type a question, you get an answer. &lt;strong&gt;Coreness Flow&lt;/strong&gt; was built differently from the start. It's a local Windows application where an agent &lt;strong&gt;reacts to events&lt;/strong&gt; — a message, a webhook, a cron schedule — and executes chains of actions defined in YAML. Want to change the agent's behavior? Edit the config, not the code. No cloud component: everything runs on your own machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/Vensus137/Coreness-Flow/blob/main/README_EN.md" rel="noopener noreferrer"&gt;github.com/Vensus137/Coreness-Flow&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The project and most of its documentation are currently in Russian.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Idea: Not "Question–Answer", But "Event → Chain"
&lt;/h2&gt;

&lt;p&gt;A standard AI chat is a synchronous loop: the user writes something, the model responds. Coreness Flow breaks that loop.&lt;/p&gt;

&lt;p&gt;The central unit here is an &lt;strong&gt;event&lt;/strong&gt;. A chat message arrives — that's an event. A cron fires at 9 AM — event. A webhook comes in from an external service — also an event. The scenario engine finds the matching scenario by trigger and &lt;strong&gt;executes a chain of steps&lt;/strong&gt;: reads data, calls an LLM, searches the knowledge base, sends a reply. The same mechanism handles all sources.&lt;/p&gt;

&lt;p&gt;This enables workflows that in a regular chatbot you'd have to implement in code — &lt;strong&gt;here everything is described in YAML&lt;/strong&gt;: a daily digest on a schedule, an automatic webhook response, a reaction to a specific command with branching logic.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Agent behavior is defined by config and scenarios, not code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhabrastorage.org%2Fwebt%2Fpz%2F-t%2F-w%2Fpz-t-wizouipvs3_1qqxixulvg4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhabrastorage.org%2Fwebt%2Fpz%2F-t%2F-w%2Fpz-t-wizouipvs3_1qqxixulvg4.png" alt="Event Flow" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture: Three Layers, One Bus
&lt;/h2&gt;

&lt;p&gt;The application is split into three layers that have no direct knowledge of each other — they communicate only through the &lt;strong&gt;API Bus&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;UI Layer&lt;/strong&gt; — Electron + React. The frontend is purely a view layer. It connects to the Python backend via WebSocket, sends actions, and receives events. No business logic in React — only rendering and bus calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend Layer&lt;/strong&gt; — Python plugins. All logic lives here: message processing, LLM calls, database access, RAG, scheduler. Each plugin is isolated and communicates with others only through the API Bus.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Bus&lt;/strong&gt; — a unified contract between all participants. Two modes:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;call&lt;/code&gt; — blocking call with a result (use when the next step depends on the outcome)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;call_nowait&lt;/code&gt; — fire-and-forget background execution (for long chains, external requests)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Every call returns a unified response format: &lt;code&gt;{ result, error, response_data }&lt;/code&gt;. The scenario engine treats them all the same — it calls actions by name and reads the result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhabrastorage.org%2Fwebt%2Ff7%2F8x%2Fjw%2Ff78xjwiqpzriv-h4vroa96zrjum.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhabrastorage.org%2Fwebt%2Ff7%2F8x%2Fjw%2Ff78xjwiqpzriv-h4vroa96zrjum.png" alt="Api Bus" width="800" height="1083"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Separation
&lt;/h3&gt;

&lt;p&gt;The most common architectural mistake in apps like this — UI knows about the database, business logic calls components directly. Add a new feature and you're doing surgery in three places at once.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Coreness Flow&lt;/strong&gt;, the UI has no idea which plugins are loaded. On startup, the frontend asks the backend for a description of what plugins contribute to the interface, then builds the screen from that data. A new plugin means a new tab — with zero frontend changes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Add a plugin folder — the interface adapts automatically.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Lifecycle: How the App Starts
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Electron main process
↓ spawn
Python backend
↓
API Bus initializes
↓
Container scans plugins/, merges configs, creates instances
↓
Heavy plugin initialization (splash shows progress)
↓
Plugin background tasks
↓
WebSocket server starts
↓
Main window opens, frontend connects
↓
Collect plugin descriptions → UI builds sidebar, tabs, settings

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Each stage is isolated.&lt;/strong&gt; If a plugin crashes during initialization, the rest continue loading. &lt;strong&gt;Graceful shutdown:&lt;/strong&gt; plugin and worker timeouts are set in &lt;code&gt;app.json&lt;/code&gt;; the app closes cleanly on exit or update.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plugins: VS Code-style, But for a Desktop App
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;plugin system&lt;/strong&gt; is one of the most interesting parts of the architecture. The idea is borrowed from VS Code: a plugin doesn't just add backend logic — it &lt;strong&gt;declares&lt;/strong&gt; its contribution to the application via &lt;code&gt;config.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Each plugin is a folder with two key files: &lt;code&gt;config.json&lt;/code&gt; (what the plugin does and what it adds to the UI) and a Python module (how it's implemented). On startup, the container recursively scans &lt;code&gt;plugins/&lt;/code&gt; and loads every folder that contains a &lt;code&gt;config.json&lt;/code&gt;. The folder name becomes the plugin identifier. No registry, no explicit module list in the core code.&lt;/p&gt;

&lt;h3&gt;
  
  
  config.json — The Heart of a Plugin
&lt;/h3&gt;

&lt;p&gt;The config describes four things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;metadata&lt;/strong&gt; — plugin name and description.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;settings&lt;/strong&gt; — settings schema with defaults. On startup, the container merges defaults from the config with user overrides from &lt;code&gt;user_settings.json&lt;/code&gt;. The plugin always receives the final merged config.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;actions&lt;/strong&gt; — the list of actions the plugin registers in the API Bus. Each action has a defined input (payload schema) and output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;contributes&lt;/strong&gt; — what the plugin adds to the UI. This is the most interesting part.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Python class methods whose names match keys in &lt;code&gt;actions&lt;/code&gt; automatically become call handlers — &lt;strong&gt;no explicit registration needed&lt;/strong&gt;. Declare an action in the config and implement a method with the same name — the engine wires them together on plugin load.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Config + method with the same name — a binding with no explicit registration.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Contributes: UI From Config
&lt;/h3&gt;

&lt;p&gt;Four contribution points:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Point&lt;/th&gt;
&lt;th&gt;What it adds&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;workspace&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A tab in the main content area with a widget (list, settings form, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;settings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A section on the shared "Settings" tab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sidebar&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;An item in the sidebar (click triggers an action)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;menus&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Items in dropdown menus&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The widget type for a tab is described with a descriptor: &lt;code&gt;settingsForm&lt;/code&gt;, &lt;code&gt;list&lt;/code&gt; — the frontend renders content without any custom code. List columns, data source via action, form fields — all in the plugin's JSON config. The "Vector Store" tab with a chunk list and delete buttons is defined in the plugin like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"contributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"workspace"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vector_store_admin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Vector Store"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Vector Store"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"widget"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vectorStoreAdmin"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;vectorStoreAdmin&lt;/code&gt; is a built-in widget type registered in the frontend; the plugin only references it by name. &lt;strong&gt;Not a single line of React in the plugin — just config.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This creates a clear separation of concerns: a backend developer writes the plugin and its config, the UI layer adapts itself. Want to remove a tab? Delete the plugin folder. Want to rename it? Change &lt;code&gt;label&lt;/code&gt; in the config.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hot Reload for Settings
&lt;/h3&gt;

&lt;p&gt;A nice touch: when a user changes settings in the UI, the backend notifies the affected plugin — it recreates clients and clears caches without restarting the app. Change an API key or model — &lt;strong&gt;the plugin picks it up immediately&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scenarios: Orchestration Without Code
&lt;/h2&gt;

&lt;p&gt;If plugins are services with actions, scenarios are the way to orchestrate those actions without writing code. All scenarios live in YAML files in &lt;code&gt;config/scenarios/&lt;/code&gt;. The engine picks them up recursively from all subdirectories. You can organize them however you like: &lt;code&gt;commands/&lt;/code&gt;, &lt;code&gt;system/&lt;/code&gt;, &lt;code&gt;scheduled/&lt;/code&gt; — scenario names are global: any scenario can call another by name.&lt;/p&gt;

&lt;p&gt;Basic structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;daily_report&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;9&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;   &lt;span class="c1"&gt;# Every day at 9:00&lt;/span&gt;
  &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get_storage"&lt;/span&gt;
      &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;group_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;report_config"&lt;/span&gt;
        &lt;span class="na"&gt;_response_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;config"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completion"&lt;/span&gt;
      &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Generate&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;morning&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;digest.&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Context:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{_cache.config}"&lt;/span&gt;
        &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{_cache.config.model}"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;send_chat_message"&lt;/span&gt;
      &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{_cache.response}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each step is a plugin action call. The result is placed in &lt;code&gt;_cache&lt;/code&gt; under the key &lt;code&gt;_response_key&lt;/code&gt;. The next step reads data via the &lt;code&gt;{_cache.key}&lt;/code&gt; placeholder.&lt;/p&gt;

&lt;h3&gt;
  
  
  Placeholders — Lightweight Templating
&lt;/h3&gt;

&lt;p&gt;Placeholders work in all step parameters and support a &lt;strong&gt;modifier chain&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;{event_text}&lt;/code&gt; — the event's text content&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{_cache.system.routing_model}&lt;/code&gt; — a nested field from the cache&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{now|format:datetime}&lt;/code&gt; — current time with formatting&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{_cache.field|fallback:default}&lt;/code&gt; — value with a default if the field is empty&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{_cache.result|exists}&lt;/code&gt; — boolean: whether the field exists&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This enables flexible chains without any Python code — just value substitution through templates.&lt;/p&gt;

&lt;h3&gt;
  
  
  Triggers: From Simple to Complex
&lt;/h3&gt;

&lt;p&gt;The simplest trigger form — event type plus text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;event_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message"&lt;/span&gt;
    &lt;span class="na"&gt;event_text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/help"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complex form — a &lt;code&gt;condition&lt;/code&gt; field with an expression in a mini-language (operators: &lt;code&gt;==&lt;/code&gt;, &lt;code&gt;~&lt;/code&gt; for "contains", &lt;code&gt;regex&lt;/code&gt;, &lt;code&gt;is_null&lt;/code&gt;, etc.; fields via &lt;code&gt;$event_text&lt;/code&gt;, &lt;code&gt;$event_type&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;event_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message"&lt;/span&gt;
    &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;$event_text&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;~&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'/'"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multiple triggers in the list work as &lt;code&gt;OR&lt;/code&gt;. Fields within a single trigger work as &lt;code&gt;AND&lt;/code&gt;. No need to duplicate a scenario for every variant — just add a trigger to the list.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transitions and Branching
&lt;/h3&gt;

&lt;p&gt;After a step, you can define a &lt;code&gt;transition&lt;/code&gt; — a list of rules: based on the action result (&lt;code&gt;action_result&lt;/code&gt;: success, error, etc.), a transition is executed (&lt;code&gt;transition_action&lt;/code&gt; and optionally &lt;code&gt;transition_value&lt;/code&gt;). For example, jumping to another scenario:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search_chunks"&lt;/span&gt;
  &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{event_text}"&lt;/span&gt;
    &lt;span class="na"&gt;_response_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rag_result"&lt;/span&gt;
  &lt;span class="na"&gt;transition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action_result&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success"&lt;/span&gt;
      &lt;span class="na"&gt;transition_action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jump_to_scenario"&lt;/span&gt;
      &lt;span class="na"&gt;transition_value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;step_with_rag"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action_result&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error"&lt;/span&gt;
      &lt;span class="na"&gt;transition_action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jump_to_scenario"&lt;/span&gt;
      &lt;span class="na"&gt;transition_value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;step_without_rag"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Found something in RAG — go to one scenario; not found — go to another. All in config.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Step chains and branching — in YAML, no code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A typical utility scenario: fetch a document by URL, split it into chunks, store in the vector database; on error — transition to an error-handling scenario.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration: Merging Without Magic
&lt;/h2&gt;

&lt;p&gt;The configuration schema is intentionally simple and uniform throughout:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;App defaults&lt;/strong&gt; — &lt;code&gt;config/app.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plugin defaults&lt;/strong&gt; — the &lt;code&gt;settings&lt;/code&gt; section in the plugin's &lt;code&gt;config.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User overrides&lt;/strong&gt; — &lt;code&gt;%APPDATA%\CorenessFlow\user_settings.json&lt;/code&gt; (changed keys only)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On startup, the container &lt;strong&gt;merges&lt;/strong&gt; defaults with user values and passes the final config to each plugin. Secrets and API tokens are stored in SQLite in &lt;code&gt;%APPDATA%&lt;/code&gt; — they never end up in the repository.&lt;/p&gt;

&lt;p&gt;The same &lt;code&gt;config.json&lt;/code&gt; format is used for both the application and every plugin — the container code is unified, and documentation stays consistent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storage: Key–Value for Agent Configuration
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;database&lt;/code&gt; plugin gives scenarios a simple &lt;strong&gt;key–value&lt;/strong&gt; store with grouping: &lt;code&gt;group_key&lt;/code&gt; + &lt;code&gt;key&lt;/code&gt; → value. But the more interesting part: initial data is defined directly in YAML files in &lt;code&gt;config/storage/&lt;/code&gt; and synchronized into SQLite on startup.&lt;/p&gt;

&lt;p&gt;This makes agent behavior &lt;strong&gt;configurable without editing scenarios&lt;/strong&gt;. The router's system prompt, available tools list, model parameters, limits — all stored in storage, scenarios read this data at runtime. Changing the model for simple requests means editing storage, not YAML scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent Routing: How One Request Becomes a Chain of Decisions
&lt;/h2&gt;

&lt;p&gt;Agent routing in &lt;strong&gt;Coreness Flow&lt;/strong&gt; is not a built-in core function — it's a set of system scenarios layered on top of the general mechanism. The user sees: sent a message → "Processing..." → response. Behind the scenes — a chain of several scenarios and multiple LLM calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Message Processing Pipeline
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhabrastorage.org%2Fwebt%2Fi7%2Fxd%2Fj4%2Fi7xdj4h_kn4bevdideau_dpwf-s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhabrastorage.org%2Fwebt%2Fi7%2Fxd%2Fj4%2Fi7xdj4h_kn4bevdideau_dpwf-s.png" alt="AI-routing" width="800" height="1195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When a chat message arrives, the following sequence runs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Load context&lt;/strong&gt; — settings are read from storage: system prompt, list of tools and response scenarios, step limit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assemble history&lt;/strong&gt; — chat history is fetched with a size limit; relevant RAG fragments are optionally injected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Routing&lt;/strong&gt; — an LLM request with the prompt and tool descriptions. The model decides: invoke one of the tools (e.g., knowledge base search) or generate a direct response.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execution&lt;/strong&gt; — if a tool is selected, the corresponding scenario runs, then routing again (loop until step limit).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Finalization&lt;/strong&gt; — a final model request, and the response is sent to the chat.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;New tool = a description in the config + a scenario file; no core changes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Tools are just YAML scenarios: adding a new one means describing it in the storage config and adding a scenario file. The application core is untouched.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhabrastorage.org%2Fwebt%2Fj5%2Fd_%2Fdz%2Fj5d_dzcamjcqndvciw8k5devyy4.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhabrastorage.org%2Fwebt%2Fj5%2Fd_%2Fdz%2Fj5d_dzcamjcqndvciw8k5devyy4.gif" alt="Chat" width="634" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Local RAG: No Servers, No Cloud
&lt;/h2&gt;

&lt;p&gt;The vector store in &lt;strong&gt;Coreness Flow&lt;/strong&gt; is a separate plugin with several key design decisions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhabrastorage.org%2Fwebt%2Flp%2Fsy%2Fys%2Flpsyysbcclv07aa5dzp84rhwuxk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhabrastorage.org%2Fwebt%2Flp%2Fsy%2Fys%2Flpsyysbcclv07aa5dzp84rhwuxk.png" alt="RAG" width="800" height="1460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  BGE-M3 in ONNX Format, INT8 Quantization
&lt;/h3&gt;

&lt;p&gt;A multilingual model that generates both dense and sparse vectors simultaneously. The model runs via &lt;strong&gt;ONNX Runtime&lt;/strong&gt; — no PyTorch, no CUDA. INT8 quantization reduces memory consumption by roughly 4× compared to float32 with minimal quality loss. &lt;strong&gt;Runs on a regular CPU.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Qdrant in Embedded Mode
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Qdrant&lt;/strong&gt; runs inside the application process and persists data to disk. No separate service, no ports, no Docker needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hybrid Search with RRF
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;BGE-M3&lt;/strong&gt; produces both dense vectors (semantics) and sparse vectors (keywords). Searching across both types and merging results via Reciprocal Rank Fusion delivers better quality than semantic-only search — especially for queries with specific terms and proper nouns.&lt;/p&gt;

&lt;p&gt;In scenarios this is two actions: &lt;code&gt;add_chunks&lt;/code&gt; for indexing and &lt;code&gt;search_chunks&lt;/code&gt; for retrieval. The search result is placed in &lt;code&gt;_cache&lt;/code&gt; and substituted into the next step's prompt. Documents never leave the machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Model Loading and the Splash Screen
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;BGE-M3&lt;/strong&gt; is a heavy model, and loading it on startup requires a dedicated solution. It loads &lt;strong&gt;before the main window is shown&lt;/strong&gt;: the user sees a splash screen with initialization progress; the main window opens only when everything is ready. No frozen UI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhabrastorage.org%2Fwebt%2Fv9%2Fud%2F0t%2Fv9ud0tfxjq0m7bmcea0dh1iplu8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhabrastorage.org%2Fwebt%2Fv9%2Fud%2F0t%2Fv9ud0tfxjq0m7bmcea0dh1iplu8.gif" alt="Splash screen on startup" width="634" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Async Without the Pain
&lt;/h2&gt;

&lt;p&gt;Actions in the &lt;strong&gt;API Bus&lt;/strong&gt; execute in a worker pool — separate threads with their own event loop. The number of workers is configurable. This means a long LLM call doesn't block processing of another incoming event.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;call_nowait&lt;/code&gt; is particularly useful. You kick off a long scenario chain, don't wait for it to finish, and continue. The result arrives as an event in the UI. This is how the entire chat works: the user sends a message, the backend launches the chain via &lt;code&gt;call_nowait&lt;/code&gt;, the UI shows a loading indicator and never blocks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plugins subscribe to bus events.&lt;/strong&gt; This enables reaction to any system events without direct dependencies between plugins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Coreness-Flow/
├── run_backend.py          # Backend entry point
├── app/
│   ├── runtime/
│   │   ├── container.py    # Plugin scanning, config merging, instantiation
│   │   ├── api_bus.py      # Bus: actions, events, workers
│   │   └── ...
│   ├── settings.py         # Config loading and merging
│   └── ws_server.py        # WebSocket for the frontend
├── frontend/               # Electron + React
├── plugins/
│   ├── core/               # Core modules: chats, database, ai_service, vector_store, ...
│   ├── base/               # Non-critical plugins
│   └── extensions/         # Custom extensions
└── config/
    ├── app.json            # App defaults
    ├── scenarios/          # YAML scenarios
    └── storage/            # Initial storage data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;core/&lt;/code&gt; contains the foundation: chats, database, LLM calls, vector store, scenario engine. &lt;code&gt;base/&lt;/code&gt; holds additional plugins — the app runs without them. &lt;code&gt;extensions/&lt;/code&gt; is where you put your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Start
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;From source&lt;/strong&gt; (Python 3.11, Node.js, Windows):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;requirements.txt&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;frontend&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;\scripts\run-dev.ps1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Backend and window start with a single command. In dev mode: hot reload on config and plugin code changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From releases:&lt;/strong&gt; a Windows installer is available under Releases — no Python or Node needed on the target machine.&lt;/p&gt;

&lt;p&gt;After the first launch: set your AI provider in settings (any OpenAI-compatible API), optionally load documents into the vector store, and configure storage to fit your needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frontend&lt;/td&gt;
&lt;td&gt;Electron + React&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Python 3.11, async/await&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI ↔ Backend&lt;/td&gt;
&lt;td&gt;WebSocket + API Bus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM&lt;/td&gt;
&lt;td&gt;Any OpenAI-compatible API (OpenRouter, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAG&lt;/td&gt;
&lt;td&gt;BGE-M3 ONNX INT8 + Qdrant embedded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;SQLite + JSON (config) + YAML (scenarios, storage)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build&lt;/td&gt;
&lt;td&gt;Electron Builder + Python backend&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Key Design Decisions
&lt;/h2&gt;

&lt;p&gt;What sets &lt;strong&gt;Coreness Flow&lt;/strong&gt; apart:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Plugins without a registry&lt;/strong&gt; — a folder with a config and module; the container picks it up on startup; unified contract for everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI from config&lt;/strong&gt; — plugins declare tabs, settings, menu items; the frontend assembles the UI from this data, no plugin list in code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scenarios instead of code&lt;/strong&gt; — action orchestration in YAML: triggers, steps, result-based transitions; the engine calls actions by name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local RAG&lt;/strong&gt; — embeddings and vector store on your own machine, ONNX and Qdrant in-process, offline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Events and bus&lt;/strong&gt; — plugins communicate only through the API Bus and subscribe to events; no direct calls between modules.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/Vensus137/Coreness-Flow/blob/main/README_EN.md" rel="noopener noreferrer"&gt;github.com/Vensus137/Coreness-Flow&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation:&lt;/strong&gt; &lt;a href="https://github.com/Vensus137/Coreness-Flow-Dev/tree/main/docs" rel="noopener noreferrer"&gt;docs/&lt;/a&gt; in the repository (currently in Russian)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Author:&lt;/strong&gt; &lt;a href="https://t.me/vensus137" rel="noopener noreferrer"&gt;@vensus137&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Coreness&lt;/strong&gt; — Create. Automate. Scale.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>python</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Build AI-Powered Telegram bots without code: Introducing Coreness</title>
      <dc:creator>Anatoly (Vensus)</dc:creator>
      <pubDate>Tue, 10 Feb 2026 10:00:00 +0000</pubDate>
      <link>https://dev.to/vensus/build-ai-powered-telegram-bots-without-code-introducing-coreness-1kh2</link>
      <guid>https://dev.to/vensus/build-ai-powered-telegram-bots-without-code-introducing-coreness-1kh2</guid>
      <description>&lt;p&gt;Hey DEV community! 👋&lt;/p&gt;

&lt;p&gt;This is my first post here, so a quick intro: I'm a developer who got tired of rebuilding the same Telegram bot infrastructure again and again. Each new bot meant redoing webhooks, storage, admin tooling, and deployment—or paying for SaaS products with limited flexibility.&lt;/p&gt;

&lt;p&gt;I wanted a &lt;strong&gt;self-hosted&lt;/strong&gt; solution where bot behavior is easy to change without rewriting code every time. So I built &lt;strong&gt;Coreness&lt;/strong&gt; — an event-driven platform for deploying AI-powered Telegram bots using declarative YAML scenarios. And now I'm making it &lt;strong&gt;open source&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Vensus137/Coreness/blob/main/README_EN.md" rel="noopener noreferrer"&gt;https://github.com/Vensus137/Coreness&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Documentation&lt;/strong&gt;: &lt;a href="https://docs.coreness.tech/en/" rel="noopener noreferrer"&gt;docs.coreness.tech&lt;/a&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note on language:&lt;/strong&gt; The project supports English (docs, code, tooling). You may still run into occasional inaccuracies or mixed-language bits.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;If you've ever built Telegram bots (especially more than one), you probably recognize these pain points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rebuilding the same basics every time:&lt;/strong&gt; webhook handling, storage, user state, admin utilities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SaaS limitations:&lt;/strong&gt; you get convenience, but you're locked into someone else’s feature set and pricing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaling overhead:&lt;/strong&gt; multiple bots often become multiple deployments, multiple databases, multiple headaches&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI integration friction:&lt;/strong&gt; wiring LLMs, context management, and RAG tends to become a project of its own&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Coreness is the infrastructure layer I wanted: one platform instance that can run multiple isolated bots, while still being configurable enough for real-world scenarios.&lt;/p&gt;
&lt;h2&gt;
  
  
  Introducing Coreness
&lt;/h2&gt;

&lt;p&gt;Coreness is a &lt;strong&gt;multi-tenant&lt;/strong&gt; platform where you describe bot behavior in YAML, and the platform handles execution, storage, and integrations. A single server instance can run multiple isolated tenants (bots), each with its own configuration and data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you get:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🎯 &lt;strong&gt;YAML-based scenarios&lt;/strong&gt; — no code, just configuration&lt;/li&gt;
&lt;li&gt;🏢 &lt;strong&gt;Built-in multi-tenancy&lt;/strong&gt; — complete data isolation via PostgreSQL Row-Level Security&lt;/li&gt;
&lt;li&gt;🤖 &lt;strong&gt;AI integration&lt;/strong&gt; — OpenAI, Anthropic, Google, DeepSeek support via aggregators&lt;/li&gt;
&lt;li&gt;📚 &lt;strong&gt;RAG out of the box&lt;/strong&gt; — vector search with pgvector&lt;/li&gt;
&lt;li&gt;⏰ &lt;strong&gt;Scheduled scenarios&lt;/strong&gt; — cron-style automation&lt;/li&gt;
&lt;li&gt;🔌 &lt;strong&gt;Plugin architecture&lt;/strong&gt; — extend features cleanly&lt;/li&gt;
&lt;li&gt;💳 &lt;strong&gt;Payment handling&lt;/strong&gt; — Telegram Stars and other providers&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;Instead of writing code, you describe bot behavior declaratively. Here's a simple bot that responds to &lt;code&gt;/start&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;event_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message"&lt;/span&gt;
      &lt;span class="na"&gt;event_text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/start"&lt;/span&gt;

  &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;send_message"&lt;/span&gt;
      &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;👋 Hello, {first_name}!&lt;/span&gt;

          &lt;span class="s"&gt;Welcome to my bot!&lt;/span&gt;
        &lt;span class="na"&gt;inline&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[{&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;📋&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Menu"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;menu"&lt;/span&gt;&lt;span class="pi"&gt;},&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ℹ️&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Help"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help"&lt;/span&gt;&lt;span class="pi"&gt;}]&lt;/span&gt;

&lt;span class="na"&gt;menu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;event_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;callback"&lt;/span&gt;
      &lt;span class="na"&gt;callback_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;menu"&lt;/span&gt;

  &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;send_message"&lt;/span&gt;
      &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Choose&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;an&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;action:"&lt;/span&gt;
        &lt;span class="na"&gt;inline&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[{&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🤖&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;About"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;about"&lt;/span&gt;&lt;span class="pi"&gt;}]&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[{&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🔙&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Back"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start"&lt;/span&gt;&lt;span class="pi"&gt;}]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What's happening here:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;trigger&lt;/code&gt; defines when the scenario runs (command or button press)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;step&lt;/code&gt; is a sequence of actions executed in order&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;{first_name}&lt;/code&gt; is a placeholder resolved from user/context data&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;inline&lt;/code&gt; defines Telegram inline buttons&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The platform automatically handles webhook processing, database storage, and user context. You just describe what should happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  RAG in Action
&lt;/h2&gt;

&lt;p&gt;Want your bot to answer questions using a knowledge base? Here’s what a basic RAG flow can look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ask_question&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;event_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message"&lt;/span&gt;

  &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Search for relevant context&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;search_embedding"&lt;/span&gt;
      &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;query_text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{event_text}"&lt;/span&gt;
        &lt;span class="na"&gt;document_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;knowledge"&lt;/span&gt;
        &lt;span class="na"&gt;limit_chunks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
        &lt;span class="na"&gt;min_similarity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.7&lt;/span&gt;

    &lt;span class="c1"&gt;# Generate AI response with context&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completion"&lt;/span&gt;
      &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;prompt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{event_text}"&lt;/span&gt;
        &lt;span class="na"&gt;system_prompt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;are&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;helpful&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;assistant.&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Answer&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;based&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;on&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;provided&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;context."&lt;/span&gt;
        &lt;span class="na"&gt;rag_chunks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{_cache.chunks}"&lt;/span&gt;
        &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4o-mini"&lt;/span&gt;

    &lt;span class="c1"&gt;# Send response&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;send_message"&lt;/span&gt;
      &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{_cache.response_completion}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this flow, the system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retrieves relevant chunks from the vector store&lt;/li&gt;
&lt;li&gt;Builds a context payload for the LLM&lt;/li&gt;
&lt;li&gt;Sends a completion request to the selected model&lt;/li&gt;
&lt;li&gt;Returns a contextual response to the user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key idea: you compose RAG behavior by chaining actions, not by rewriting RAG plumbing for each project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-tenancy Magic
&lt;/h2&gt;

&lt;p&gt;The platform provides automatic data isolation using &lt;strong&gt;PostgreSQL Row-Level Security&lt;/strong&gt;. Each tenant gets their own sandbox — settings, knowledge bases, prompts — everything is isolated at the database level.&lt;/p&gt;

&lt;p&gt;RLS automatically filters queries by &lt;code&gt;tenant_id&lt;/code&gt;, so you never accidentally access another tenant's data. No need to add &lt;code&gt;WHERE tenant_id = ...&lt;/code&gt; to every query.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adding a new bot is simple:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a folder like &lt;code&gt;config/tenant/tenant_101/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;bots/telegram.yaml&lt;/code&gt; with bot token (the &lt;code&gt;bots/&lt;/code&gt; folder can hold configs for different bot types)&lt;/li&gt;
&lt;li&gt;Add your YAML scenarios&lt;/li&gt;
&lt;li&gt;Sync via GitHub or using the &lt;strong&gt;Master Bot&lt;/strong&gt; (a built-in management interface, similar to &lt;a class="mentioned-user" href="https://dev.to/botfather"&gt;@botfather&lt;/a&gt;, that lets you control tenants, sync configs, and manage the platform from Telegram)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Done. The platform picks it up automatically and starts processing events for that bot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started (5-Minute Setup)
&lt;/h2&gt;

&lt;p&gt;Here's how to get your first bot running:&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Deploy the Platform
&lt;/h3&gt;

&lt;p&gt;Coreness includes the &lt;strong&gt;Core Manager&lt;/strong&gt; utility for deployment and updates. It configures the environment, database, and containers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone repository&lt;/span&gt;
git clone https://github.com/Vensus137/Coreness.git
&lt;span class="nb"&gt;cd &lt;/span&gt;Coreness

&lt;span class="c"&gt;# Run Core Manager&lt;/span&gt;
python tools/core_manager/core_manager.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On first run the utility will ask for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Environment&lt;/strong&gt; (test / prod)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment mode&lt;/strong&gt; (docker / native) — native is often easier on Windows; docker is typical on Linux and servers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interface language&lt;/strong&gt; (English / Русский)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Settings are saved in &lt;code&gt;config/.version&lt;/code&gt;. The menu then offers: system update from GitHub (with migrations and backup), database operations (migrations, backup, restore), utility self-update, and language switch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Create a Tenant
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;config/tenant/tenant_101/bots/telegram.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;bot_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_BOT_TOKEN_FROM_BOTFATHER"&lt;/span&gt;
&lt;span class="na"&gt;is_active&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Configure Scenario
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;config/tenant/tenant_101/scenarios/start.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;event_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message"&lt;/span&gt;
      &lt;span class="na"&gt;event_text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/start"&lt;/span&gt;

  &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;send_message"&lt;/span&gt;
      &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;👋 Hello, {first_name}!&lt;/span&gt;

          &lt;span class="s"&gt;This is a bot powered by Coreness.&lt;/span&gt;
        &lt;span class="na"&gt;inline&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;[{&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;📋&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Menu"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;menu"&lt;/span&gt;&lt;span class="pi"&gt;},&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ℹ️&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Help"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help"&lt;/span&gt;&lt;span class="pi"&gt;}]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Sync
&lt;/h3&gt;

&lt;p&gt;If using GitHub sync:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add config/tenant/tenant_101/
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add tenant 101"&lt;/span&gt;
git push
&lt;span class="c"&gt;# Webhook automatically syncs changes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or manually via Master Bot:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;code&gt;master_bot&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Send &lt;code&gt;/tenant&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Enter tenant ID (101)&lt;/li&gt;
&lt;li&gt;Click "Sync"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it! Your bot is live and responding to commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: Adding Payments
&lt;/h2&gt;

&lt;p&gt;Want to monetize your bot? Here's how to add Telegram Stars payments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;buy_premium&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;event_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message"&lt;/span&gt;
      &lt;span class="na"&gt;event_text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/buy"&lt;/span&gt;

  &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;create_invoice"&lt;/span&gt;
      &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Premium&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Subscription"&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Access&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;premium&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;features&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;month"&lt;/span&gt;
        &lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;  &lt;span class="c1"&gt;# 100 stars&lt;/span&gt;
        &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;XTR"&lt;/span&gt;

&lt;span class="na"&gt;handle_pre_checkout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;event_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pre_checkout_query"&lt;/span&gt;

  &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;confirm_payment"&lt;/span&gt;
      &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;pre_checkout_query_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{pre_checkout_query_id}"&lt;/span&gt;
        &lt;span class="na"&gt;invoice_payload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{invoice_payload}"&lt;/span&gt;

&lt;span class="na"&gt;handle_payment_successful&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;event_type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;payment_successful"&lt;/span&gt;

  &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mark_invoice_as_paid"&lt;/span&gt;
      &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;invoice_payload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{invoice_payload}"&lt;/span&gt;
        &lt;span class="na"&gt;telegram_payment_charge_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{telegram_payment_charge_id}"&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;set_user_storage"&lt;/span&gt;
      &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;premium_active"&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;send_message"&lt;/span&gt;
      &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;✅&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Payment&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;successful!&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Premium&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;activated."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The entire payment flow is declarative — no manual payment handling code needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Under the Hood
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python 3.11+&lt;/strong&gt; with direct Telegram Bot API integration (no aiogram — fewer dependencies, better performance)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL 16+&lt;/strong&gt; with pgvector extension for RAG (or SQLite for simplified version)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker + docker-compose&lt;/strong&gt; for deployment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM aggregators&lt;/strong&gt; for model access (OpenAI, Anthropic, Google, DeepSeek via OpenRouter, Azure OpenAI)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why no aiogram?&lt;/strong&gt; Direct work with Telegram Bot API via &lt;code&gt;aiohttp&lt;/code&gt; saves resources, runs faster, and has fewer dependencies. All you need is JSON parsing and HTTP requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;p&gt;Coreness is built on &lt;strong&gt;event-driven architecture&lt;/strong&gt; with clear layer separation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Telegram → Event Processor → Scenario Engine → Step Executor → Services → Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each service is self-contained and communicates through events. No tangled dependency web, just clean vertical slices of functionality.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhabrastorage.org%2Fwebt%2Fkf%2Fzt%2F7h%2Fkfzt7hja_dnvev9adkb7ktijve0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fhabrastorage.org%2Fwebt%2Fkf%2Fzt%2F7h%2Fkfzt7hja_dnvev9adkb7ktijve0.png" alt="Architecture" width="800" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Plugin System:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every feature is a separate plugin in the &lt;code&gt;plugins/&lt;/code&gt; folder. Need integration with an external API? Just write a new plugin and drop it in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plugins/
├── utilities/          # Helper utilities
│   ├── foundation/     # Core (logger, plugins_manager)
│   ├── telegram/       # Telegram utilities
│   └── core/           # Infrastructure (event_processor, database)
└── services/           # Business services
    ├── hub/
    │   ├── telegram/   # Bot management (Telegram)
    │   └── tenant_hub/ # Tenant management
    └── ai_service/     # AI and RAG
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plugins are isolated and communicate via events. Add a new plugin — the DI container automatically discovers and wires it, and the service registers its actions through &lt;code&gt;action_hub&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Performance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Async processing&lt;/strong&gt; via asyncio — all operations are non-blocking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching&lt;/strong&gt; of data and settings — reduces DB load&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vector search optimization&lt;/strong&gt; with HNSW indexes (pgvector) — fast search even on large datasets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parallel processing&lt;/strong&gt; — bots can handle multiple events simultaneously&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Direct Telegram API&lt;/strong&gt; — no middleware overhead&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters at scale. One server can handle dozens of bots simultaneously without performance degradation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Row-Level Security&lt;/strong&gt; for data isolation — impossible to accidentally access another tenant's data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation&lt;/strong&gt; via Pydantic — all input parameters checked against schemas&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secrets&lt;/strong&gt; in environment variables — tokens and keys not stored in code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated DB backups&lt;/strong&gt; with configurable interval&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexible access control&lt;/strong&gt; — configure read-only users with access to specific tenants&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;I'm making Coreness open source to grow it with the community. Here's what's on the roadmap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Extended RAG capabilities&lt;/strong&gt; — support for files (PDF, DOCX), improved document processing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More ready-to-use plugins&lt;/strong&gt; — integrations with popular APIs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplified Master Bot&lt;/strong&gt; — better tenant management interface&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telegram Mini App&lt;/strong&gt; — additional management features through Telegram Mini App&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It Out
&lt;/h2&gt;

&lt;p&gt;I built &lt;strong&gt;Coreness&lt;/strong&gt; because I needed it for my own projects, and I'm betting others face the same problems. If you're tired of rebuilding bot infrastructure or paying for limited SaaS solutions, give it a try.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Vensus137/Coreness/blob/main/README_EN.md" rel="noopener noreferrer"&gt;https://github.com/Vensus137/Coreness&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Documentation&lt;/strong&gt;: &lt;a href="https://docs.coreness.tech/en/" rel="noopener noreferrer"&gt;docs.coreness.tech&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Contact me:&lt;/strong&gt; &lt;a href="https://t.me/vensus137" rel="noopener noreferrer"&gt;@vensus137&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;⭐ &lt;strong&gt;Star the repo&lt;/strong&gt; if you find it useful&lt;br&gt;&lt;br&gt;
💬 &lt;strong&gt;Open issues or PRs&lt;/strong&gt; — all feedback helps improve the project&lt;br&gt;&lt;br&gt;
📢 &lt;strong&gt;Share with others&lt;/strong&gt; who might benefit&lt;/p&gt;

&lt;p&gt;I'd love to hear your thoughts and contributions! 🚀&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Coreness&lt;/strong&gt; — Create. Automate. Scale.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>showdev</category>
      <category>python</category>
    </item>
  </channel>
</rss>
