<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Architecture on Jahvon Dockery</title>
    <link>https://jahvon.dev/architecture/</link>
    <description>Recent content in Architecture on Jahvon Dockery</description>
    <generator>Hugo -- 0.148.1</generator>
    <language>en</language>
    <atom:link href="https://jahvon.dev/architecture/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Flow</title>
      <link>https://jahvon.dev/architecture/flow/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://jahvon.dev/architecture/flow/</guid>
      <description>Flow is a local-first developer platform designed around composable YAML &amp;ldquo;executables&amp;rdquo;, built-in secret management, and cross-project automation.</description>
      <content:encoded><![CDATA[<div class="notice note" >
    <p class="notice-title">
        <span class="icon-notice baseline">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 128 300 300">
  <path d="M150 128c82.813 0 150 67.188 150 150 0 82.813-67.188 150-150 150C67.187 428 0 360.812 0 278c0-82.813 67.188-150 150-150Zm25 243.555v-37.11c0-3.515-2.734-6.445-6.055-6.445h-37.5c-3.515 0-6.445 2.93-6.445 6.445v37.11c0 3.515 2.93 6.445 6.445 6.445h37.5c3.32 0 6.055-2.93 6.055-6.445Zm-.39-67.188 3.515-121.289c0-1.367-.586-2.734-1.953-3.516-1.172-.976-2.93-1.562-4.688-1.562h-42.968c-1.758 0-3.516.586-4.688 1.563-1.367.78-1.953 2.148-1.953 3.515l3.32 121.29c0 2.734 2.93 4.882 6.64 4.882h36.134c3.515 0 6.445-2.148 6.64-4.883Z"/>
</svg>

        </span>Note</p><p>This page provides a public overview of the architecture of the <a href="https://flowexec.io">flow</a> ecosystem.
I&rsquo;ll try to keep it up to date to reflects flow&rsquo;s current design and evolving vision.</p>
<p><strong>Last updated</strong>: September 9, 2025 | <em>flow v1.1.0</em></p></div>

<p>Flow is a YAML-driven task runner and workflow automation tool designed around composable executables, built-in secret management, and cross-project automation. This document covers the technical architecture and implementation details.</p>
<h2 id="design-philosophy">Design Philosophy</h2>
<ul>
<li><strong>Developer-Centric</strong>: Flow should be designed with developers in mind, providing a powerful yet intuitive interface for managing automation tasks.</li>
<li><strong>Composable</strong>: Flow should allow users to compose complex workflows from simple, reusable executables, enabling cross-project automation and collaboration.</li>
<li><strong>Discoverable</strong>: Flow should make it easy to find and run executables across multiple projects and workspaces, with a focus on discoverability and usability.</li>
<li><strong><a href="https://www.inkandswitch.com/essay/local-first/">Local-first</a></strong>: Flow should be able to be run entirely on a local machine, with no external dependencies or cloud services required.</li>
<li><strong><a href="https://www.inkandswitch.com/malleable-software/">Malleable</a></strong>: Flow should be easily extensible and customizable, allowing users to adapt it to their specific needs and use it in a variety of contexts.</li>
</ul>
<h2 id="system-overview">System Overview</h2>
<p>Flow&rsquo;s architecture centers on a CLI-first design where the Go-based CLI engine handles all business logic, with other interfaces acting as presentation or integration layers.</p>
<p><img alt="Flow System Overview" loading="lazy" src="/images/flow-system-overview.png"></p>
<p>The desktop app and other interfaces communicate with the CLI through process execution, with each user action spawning a discrete CLI command. Because of this, it&rsquo;s important that CLI operations are fast and efficient, which is achieved through caching and optimized data integrations.</p>
<h3 id="technical-stack">Technical Stack</h3>
<h4 id="core-languages">Core Languages</h4>
<ul>
<li><strong>Go</strong> - CLI engine, business logic, and execution runtime</li>
<li><strong>TypeScript/React</strong> - Desktop frontend with type-safe CLI communication</li>
<li><strong>Rust</strong> - Desktop backend via Tauri framework</li>
</ul>
<h4 id="user-interface">User Interface</h4>
<ul>
<li><strong>Terminal</strong>: <a href="https://github.com/charmbracelet/bubbletea">Bubble Tea</a> with custom <a href="https://github.com/flowexec/tuikit">tuikit</a> component library</li>
<li><strong>Desktop</strong>: <a href="https://tauri.app/">Tauri</a> + <a href="https://mantine.dev/">Mantine UI</a> for VSCode-like interface</li>
<li><strong>CLI</strong>: <a href="https://github.com/spf13/cobra">Cobra</a> for command structure and auto-completion</li>
</ul>
<h4 id="core-libraries">Core Libraries</h4>
<ul>
<li><strong>YAML Processing</strong>: <a href="https://gopkg.in/yaml.v3">yaml.v3</a> for YAML parsing and serialization</li>
<li><strong>Process Management</strong>: <a href="https://github.com/mvdan/sh">mvdan/sh</a> for shell execution</li>
<li><strong>Expression Engine</strong>: Go&rsquo;s <code>text/template</code> + <a href="https://expr-lang.org/">Expr</a> for conditional logic and templating</li>
<li><strong>Markdown Rendering</strong>: <a href="https://github.com/charmbracelet/glamour">Glamour</a> and <a href="https://github.com/remarkjs/react-markdown">react-markdown</a> for auto-generated documentation UI viewers</li>
</ul>
<h4 id="development-tools">Development Tools</h4>
<ul>
<li><strong>Type Generation</strong>: <a href="https://github.com/atombender/go-jsonschema">go-jsonschema</a>, <a href="https://github.com/oxidecomputer/typify">typify</a>, and <a href="https://github.com/bcherny/json-schema-to-typescript">json-schema-to-typescript</a> for cross-language code generation</li>
<li><strong>Go Testing</strong>: <a href="https://onsi.github.io/ginkgo/">Ginkgo</a> + <a href="https://onsi.github.io/gomega/">Gomega</a> for BDD-style tests and <a href="https://github.com/uber-go/mock">GoMock</a> for interface testing</li>
<li><strong>Build &amp; Release</strong>: <a href="https://github.com/marketplace/actions/flow-execute">Flow Execute GitHub Action</a> and <a href="https://goreleaser.com/">GoReleaser</a> for cross-platform CLI distribution</li>
</ul>
<h2 id="organizational-model">Organizational Model</h2>
<p>Flow&rsquo;s organizational system creates a hierarchical structure that scales from individual projects to complex multi-project ecosystems. The system balances discoverability with isolation, enabling both focused work within projects and cross-project composition.</p>


<p><details >
  <summary markdown="span">Requirements</summary>
  <ul>
<li>Enable cross-project composition while maintaining workspace isolation</li>
<li>Provide intuitive reference resolution with minimal typing overhead</li>
<li>Target of sub-100ms executable discovery across all registered workspaces (<em>Note to self: Some performance testing needed</em>)</li>
<li>Zero configuration required for single-project usage</li>
<li>Must work entirely from filesystem</li>
<li>CLI can also be run in isolated, containerized environments (like CI pipelines)</li>
</ul>

</details></p>

<h3 id="hierarchy-structure">Hierarchy Structure</h3>
<p><strong>Workspaces</strong> serve as the top-level organizational unit, typically mapping to Git repositories or major project boundaries. Each workspace contains its own configuration, executable discovery rules, and isolated namespace hierarchy.</p>
<p><strong>Namespaces</strong> provide logical grouping within workspaces, similar to packages in programming languages. They enable organizational flexibility - a single workspace might have namespaces for <code>frontend</code>, <code>backend</code>, <code>deploy</code>, or <code>tools</code>. Namespaces are optional but recommended for workspaces with many executables.</p>
<p><strong>Executables</strong> are the atomic units of automation, uniquely identified within their namespace by their name and verb combination. This allows multiple executables with the same name but different purposes (<code>build api</code> vs <code>deploy api</code>).</p>
<h3 id="reference-system">Reference System</h3>
<p>Flow uses a URI-like reference system for executable identification:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>workspace/namespace:name
</span></span><span style="display:flex;"><span>    │         │       │
</span></span><span style="display:flex;"><span>    │         │       └─ Executable name (Optional but unique within verb group + namespace)
</span></span><span style="display:flex;"><span>    │         └───────── Optional namespace grouping
</span></span><span style="display:flex;"><span>    └─────────────────── Workspace boundary
</span></span></code></pre></div><p><strong>Reference Resolution Rules:</strong></p>
<ul>
<li><code>my-task</code> → Current workspace, current namespace, name=&ldquo;my-task&rdquo;</li>
<li><code>backend:api</code> → Current workspace, namespace=&ldquo;backend&rdquo;, name=&ldquo;api&rdquo;</li>
<li><code>project/deploy:prod</code> → workspace=&ldquo;project&rdquo;, namespace=&ldquo;deploy&rdquo;, name=&ldquo;prod&rdquo;</li>
<li><code>project/</code> → workspace=&ldquo;project&rdquo;, no namespace, nameless executable</li>
</ul>
<p><strong>Reference Format Trade-offs:</strong></p>
<ul>
<li><strong>Chosen:</strong> Slightly more verbose for simple cases</li>
<li><strong>Avoided:</strong> Naming collisions, poor tooling support, brittle file/directory coupling</li>
</ul>
<h3 id="verb-system">Verb System</h3>
<p>Verbs describe the action an executable performs while enabling natural language interaction.
Verbs can be organized into semantic groups with aliases:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#859289;font-style:italic"># Executable definition</span>
</span></span><span style="display:flex;"><span><span style="color:#7a8478">verb</span>: build
</span></span><span style="display:flex;"><span><span style="color:#7a8478">verbAliases</span>: [compile, package, bundle]
</span></span><span style="display:flex;"><span><span style="color:#7a8478">name</span>: my-app
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#859289;font-style:italic"># With the above, all of these commands are equivalent:</span>
</span></span><span style="display:flex;"><span>flow build my-app
</span></span><span style="display:flex;"><span>flow compile my-app
</span></span><span style="display:flex;"><span>flow package my-app
</span></span></code></pre></div><p>This system allows developers to use whichever verb feels most natural while maintaining executable uniqueness through the <code>[verb group + name]</code> constraint.</p>
<p><strong>Default Verb Groups:</strong></p>
<p>I&rsquo;ve decided to significantly reduce the number of default verb groups to focus on the most common actions with the most semantic clarity. See the <a href="https://flowexec.io/types/flowfile?id=default-verb-aliases">flow documentation</a> for the latest default list.</p>
<p><img alt="Flow Workspace Tree Example" loading="lazy" src="/images/flow-ws-tree.png"></p>
<h3 id="context-awareness">Context Awareness</h3>
<p>Flow maintains context awareness to reduce typing and improve ergonomics:</p>
<p><strong>Current Workspace Resolution:</strong></p>
<ul>
<li><strong>Dynamic Mode</strong>: Automatically detects workspace based on current directory</li>
<li><strong>Fixed Mode</strong>: Uses explicitly set workspace regardless of location</li>
</ul>
<p><strong>Namespace Scoping:</strong></p>
<ul>
<li>Commands inherit current namespace setting</li>
<li>Explicit namespace references override current context</li>
</ul>
<p><em>Note to self: Explicit command overrides of workspace / namespace may become an emerging need with the Desktop UI and MCP server usage.</em></p>
<p><strong>Discovery Rules:</strong>
Each workspace can configure which directories to include or exclude from executable discovery, enabling fine-grained control over what Flow considers part of the automation ecosystem.</p>
<h3 id="cross-project-composition">Cross-Project Composition</h3>
<p>The reference system enables powerful cross-project workflows:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#7a8478">executables</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#7a8478">verb</span>: deploy
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">name</span>: full-stack
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">serial</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#7a8478">execs</span>:
</span></span><span style="display:flex;"><span>        - <span style="color:#7a8478">ref</span>: <span style="color:#b2c98f">&#34;build frontend/&#34;</span>     <span style="color:#859289;font-style:italic"># Different workspace</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#7a8478">ref</span>: <span style="color:#b2c98f">&#34;build backend:api&#34;</span>   <span style="color:#859289;font-style:italic"># Different namespace</span>
</span></span><span style="display:flex;"><span>        - <span style="color:#7a8478">ref</span>: <span style="color:#b2c98f">&#34;deploy&#34;</span>              <span style="color:#859289;font-style:italic"># Current context</span>
</span></span></code></pre></div><p>This allows building complex automation that spans multiple repositories while maintaining clear boundaries and dependencies.</p>
<h2 id="execution-engine">Execution Engine</h2>
<p>The execution engine is the core of Flow, responsible for running executables defined in YAML files.</p>
<h3 id="runner-interface">Runner Interface</h3>
<p>The execution system uses a runner interface pattern where each executable type implements:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e67e80">type</span> Runner <span style="color:#e67e80">interface</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#b2c98f">Name</span>() <span style="color:#dbbc7f">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#b2c98f">Exec</span>(
</span></span><span style="display:flex;"><span>		ctx context.Context, <span style="color:#859289;font-style:italic">// execution context</span>
</span></span><span style="display:flex;"><span>		exec <span style="color:#7a8478">*</span>executable.Executable, <span style="color:#859289;font-style:italic">// executable to run</span>
</span></span><span style="display:flex;"><span>		eng engine.Engine, <span style="color:#859289;font-style:italic">// optional engine for execution</span>
</span></span><span style="display:flex;"><span>		inputEnv <span style="color:#e67e80">map</span>[<span style="color:#dbbc7f">string</span>]<span style="color:#dbbc7f">string</span>, <span style="color:#859289;font-style:italic">// environment variables for execution</span>
</span></span><span style="display:flex;"><span>		inputArgs []<span style="color:#dbbc7f">string</span>, <span style="color:#859289;font-style:italic">// command line arguments</span>
</span></span><span style="display:flex;"><span>	) <span style="color:#dbbc7f">error</span>
</span></span><span style="display:flex;"><span>	<span style="color:#b2c98f">IsCompatible</span>(executable <span style="color:#7a8478">*</span>executable.Executable) <span style="color:#dbbc7f">bool</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Current runner implementations include:</p>
<ul>
<li><strong>Exec Runner</strong>: Shell command execution</li>
<li><strong>Request Runner</strong>: HTTP request handling</li>
<li><strong>Launch Runner</strong>: Application/URI launching</li>
<li><strong>Render Runner</strong>: Markdown rendering</li>
<li><strong>Serial Runner</strong>: Sequential execution of multiple executables</li>
<li><strong>Parallel Runner</strong>: Concurrent execution with resource limits</li>
</ul>


<p><details >
  <summary markdown="span">Executable Runner Requirements</summary>
  <p><strong>Exec Type Requirements:</strong></p>
<ul>
<li>Must support running shell commands</li>
<li>Must support running shell files</li>
<li>Must support defining environment variables</li>
<li>Must support command line arguments</li>
<li>Must support defining a working directory</li>
</ul>
<p><strong>Request Type Requirements:</strong></p>
<ul>
<li>Must support making HTTP requests</li>
<li>Must support defining request method (GET, POST, etc.)</li>
<li>Must support defining request headers with resolved environment variables</li>
<li>Must support defining request body with templating</li>
<li>Must support transforming response data with templating</li>
<li>Must support outputting response data to a file</li>
<li>Must support validating response status codes</li>
</ul>
<p><strong>Launch Type Requirements:</strong></p>
<ul>
<li>Must support launching applications or URIs</li>
<li>Must support defining application arguments with resolved environment variables</li>
</ul>
<p><strong>Render Type Requirements:</strong></p>
<ul>
<li>Must support rendering Markdown files in the TUI and Desktop UI</li>
<li>Must support rendering Markdown files with templating (data provided as YAML or JSON)</li>
<li>Must support printing rendered Markdown when run in non-interactive mode</li>
</ul>
<p><strong>Serial and Parallel Requirements:</strong></p>
<ul>
<li>Must support executing multiple executables in sequence or parallel</li>
<li>Must support referencing other executables by reference</li>
<li>Must support defining a command to execute in-line</li>
<li>Must support defining environment variables for each step</li>
<li>Must support defining command line arguments for each step</li>
<li>Must support conditional execution based on previous step results</li>
<li>Must support pausing for user input before continuing (serial only)</li>
<li>Must support limiting parallel execution to a maximum number of concurrent steps (parallel only)</li>
<li>Must support sharing environment variables and working directory of the parent executable</li>
</ul>

</details></p>

<h3 id="workflows-serial-and-parallel">Workflows (Serial and Parallel)</h3>
<p>The serial and parallel runners allow for composing complex workflows from simpler executables. You do this
by defining <code>exec</code> steps with a <code>RefConfig</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e67e80">type</span> SerialRefConfig <span style="color:#e67e80">struct</span> {
</span></span><span style="display:flex;"><span>  Cmd <span style="color:#dbbc7f">string</span> <span style="color:#859289;font-style:italic">// The command to execute. One of `cmd` or `ref` must be set.</span>
</span></span><span style="display:flex;"><span>  Ref Ref <span style="color:#859289;font-style:italic">// A reference to another executable. One of `cmd` or `ref` must be set.</span>
</span></span><span style="display:flex;"><span>  Args []<span style="color:#dbbc7f">string</span> <span style="color:#859289;font-style:italic">// Arguments to pass to the executable.</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  If <span style="color:#dbbc7f">string</span> <span style="color:#859289;font-style:italic">// An expression that determines whether the executable should run</span>
</span></span><span style="display:flex;"><span>  Retries <span style="color:#dbbc7f">int</span> <span style="color:#859289;font-style:italic">// The number of times to retry the executable if it fails.</span>
</span></span><span style="display:flex;"><span>  ReviewRequired <span style="color:#dbbc7f">bool</span> <span style="color:#859289;font-style:italic">// Prompt the user to review the output of the executable before continuing.</span>
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#859289;font-style:italic">// Parallel is very similar to Serial</span>
</span></span><span style="display:flex;"><span><span style="color:#e67e80">type</span> ParallelRefConfig <span style="color:#e67e80">struct</span> {
</span></span><span style="display:flex;"><span>  Cmd <span style="color:#dbbc7f">string</span>
</span></span><span style="display:flex;"><span>  Ref Ref
</span></span><span style="display:flex;"><span>  Args []<span style="color:#dbbc7f">string</span>
</span></span><span style="display:flex;"><span>  If <span style="color:#dbbc7f">string</span>
</span></span><span style="display:flex;"><span>  Retries <span style="color:#dbbc7f">int</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Additionally, limiting the number of concurrent executions in parallel workflows is supported.</p>
<p>Execution and result handling is managed by the internal <code>engine.Engine</code> interface. The <a href="https://github.com/flowexec/flow/tree/main/internal/runner/engine">current implementation</a> includes retry logic, error handling, and result aggregation.</p>
<h3 id="execution-environment-and-state">Execution Environment and State</h3>
<p><strong>Environment Inheritance Hierarchy:</strong></p>
<p>Environment variables are provided to the running executable in the following order:</p>
<ol>
<li>System environment variables (lowest priority)</li>
<li>Dotenv files (<code>.env</code>, workspace-specific)</li>
<li>Flow context variables (<code>FLOW_WORKSPACE_PATH</code>, <code>FLOW_NAMESPACE</code>, etc.)</li>
<li>Executable <code>params</code> (secrets, prompts, static values)</li>
<li>Executable <code>args</code> (command-line arguments)</li>
<li>CLI <code>--param</code> overrides (highest priority)</li>
</ol>
<p><strong>State Management</strong></p>
<p>Currently there are two ways that state can be managed when composing workflows within a serial or parallel executable:</p>
<ul>
<li>Cache Store: Key-value persistence across executions with scoped lifetime—values set outside executables persist globally, while values set within executables are automatically cleaned up on completion. This store used <a href="https://go.etcd.io/bbolt">go.etcd.io/bbolt</a> for data storage across processes.</li>
<li>Temporary Directories: Isolated temporary directories (<code>f:tmp</code>) provide scratch space for executables with automatic cleanup, while supporting shared access across serial/parallel workflow steps.</li>
</ul>
<p>This, of course, is in addition to the general usage of shared state from the secrets vault, environment variables, etc.</p>
<p><strong>File System Access</strong></p>
<p>By default, execution&rsquo;s working directory is determined is the directory containing the flow file that defines the executable.
This can be configured per-executable using special prefixes like <code>//</code> (workspace root), <code>~/</code> (user home), <code>f:tmp</code> (temporary).</p>
<p>At this time, there is no automatic sandboxing—executables, they inherit full user permissions. <em>Flow assumes users understand their workflows&rsquo; scope and potential for system modification, prioritizing automation flexibility over execution isolation.</em> This limitation could be improved with containerized executions, a feature that hasn&rsquo;t shipped yet.</p>
<p>See <a href="https://flowexec.io/guide/executables">executable guide</a> and <a href="https://flowexec.io/guide/advanced?id=managing-state">state management</a> for usage details.</p>
<h2 id="performance-and-caching">Performance and Caching</h2>
<h3 id="cache-hierarchy">Cache Hierarchy</h3>
<p>Flow implements multi-level caching:</p>
<ol>
<li><strong>Memory Cache</strong>: Recently accessed executables and workspace data</li>
<li><strong>Disk Cache</strong>: Workspace and executable file/directory mapping and template registry</li>
<li><strong>File System</strong>: Source of truth for all configuration</li>
</ol>
<p>The cache is updated on demand with the <code>flow sync</code> command or <code>--sync</code> global argument.
It should only need to be updated if executable locations or identifiers change, such as when adding new executables or changing workspace directories.</p>
<p><em>There is some logic to resync if there is a cache miss, but this only happens for the <code>exec</code> commands.</em></p>
<h3 id="executable-discovery">Executable Discovery</h3>
<p>Workspace scanning uses <code>filepath.WalkDir</code> with early termination for excluded directories. Exclusions and inclusions are defined in the workspace configuration file (flow.yaml).
The discovery process searches for all YAML files with the <code>.flow</code>, <code>.flow.yaml</code>, or <code>.flow.yml</code> extensions, and
parses them to build the executable registry.</p>
<p>During this process, expanding references for verb and name aliases is handled, allowing for quick lookups by verb and name combination in future operations.</p>
<p><strong>Eager Discovery vs. Lazy Loading:</strong></p>
<ul>
<li><strong>Chosen:</strong> Eager discovery with caching</li>
<li><strong>Rationale:</strong> Quicker response time is better than lower memory usage</li>
<li><strong>Cost:</strong> Memory usage that scales with # of workspaces and executables <em>Note to self: Some performance testing needed</em></li>
<li><strong>Benefit:</strong> Instant autocomplete, consistent performance</li>
</ul>
<p><strong>Imported / Generated Executables</strong></p>
<p>To support the ease of onboarding workflow into the flow ecosystem, executables can be generated from a couple of common
developer workflow files. The <a href="https://github.com/flowexec/flow/tree/main/internal/fileparser">current implementation</a> includes
importing shell scripts, Makefile targets, package.json scripts, and common Docker Compose commands. Imported executables are treated identically to YAML-defined ones in the discovery system, appearing in the browse interface and supporting the same reference patterns.</p>
<p>In the current implementation, the &ldquo;verb&rdquo; and &ldquo;name&rdquo; of the executable is derived from the context of the imported workflow. For instance, shell script file names become the executable name with the default <code>exec</code> verb, a package.json script with a name like <code>test</code> would create a nameless executable with the <code>test</code> verb, etc. This does create more opportunities for executable duplicates if not done with care. The sync command must maintain clear warnings when this is the case.</p>
<p>For shell files and make targets, most executable metadata can be overwritten. For instance, including the following at the top of the
shell file or directly above a make target would create the <code>update my-exec</code> executable with a <code>SECRET_VAR</code> provided from the <code>password</code> secret on sync:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#859289;font-style:italic"># f:name=my-exec f:verb=update</span>
</span></span><span style="display:flex;"><span><span style="color:#859289;font-style:italic"># f:params=secretRef:password:SECRET_VAR</span>
</span></span><span style="display:flex;"><span><span style="color:#859289;font-style:italic"># f:description=&#34;a description&#34;</span>
</span></span></code></pre></div><p><em>Also see the <a href="https://github.com/flowexec/flow/blob/main/internal/fileparser/config.go">source</a> for this parsing logic</em></p>
<h2 id="vault-system">Vault System</h2>
<p>The vault system is a core component of Flow, providing secure storage, management, and retrieval of secrets across workspaces and executables. It extends the executable environment with multiple encryption backends.</p>
<p><strong>Implementation</strong>: <a href="https://github.com/flowexec/vault">github.com/flowexec/vault</a></p>
<h3 id="provider-architecture">Provider Architecture</h3>
<p>The vault system supports multiple storage backends, implemented through a common <code>Provider</code> interface:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e67e80">type</span> Provider <span style="color:#e67e80">interface</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#b2c98f">ID</span>() <span style="color:#dbbc7f">string</span>
</span></span><span style="display:flex;"><span>  <span style="color:#b2c98f">GetSecret</span>(key <span style="color:#dbbc7f">string</span>) (Secret, <span style="color:#dbbc7f">error</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#b2c98f">SetSecret</span>(key <span style="color:#dbbc7f">string</span>, value Secret) <span style="color:#dbbc7f">error</span>
</span></span><span style="display:flex;"><span>  <span style="color:#b2c98f">DeleteSecret</span>(key <span style="color:#dbbc7f">string</span>) <span style="color:#dbbc7f">error</span>
</span></span><span style="display:flex;"><span>  <span style="color:#b2c98f">ListSecrets</span>() ([]<span style="color:#dbbc7f">string</span>, <span style="color:#dbbc7f">error</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#b2c98f">HasSecret</span>(key <span style="color:#dbbc7f">string</span>) (<span style="color:#dbbc7f">bool</span>, <span style="color:#dbbc7f">error</span>)
</span></span><span style="display:flex;"><span>  <span style="color:#b2c98f">Metadata</span>() Metadata
</span></span><span style="display:flex;"><span>  <span style="color:#b2c98f">Close</span>() <span style="color:#dbbc7f">error</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h4 id="current-providers">Current Providers</h4>
<ul>
<li><strong>Unencrypted Provider</strong>: Simple key-value store for development and testing</li>
<li><strong>AES Provider</strong>: Symmetric file encryption using AES-256-GCM sSingle key management)</li>
<li><strong>Age Provider</strong>: Asymmetric file encryption using the <a href="https://github.com/FiloSottile/age">Age</a> specification (supports multiple recipients)</li>
<li><strong>Keyring Provider</strong>: Uses system keyring (macOS Keychain, Linux Secret Service)</li>
<li><strong>External Provider</strong>: Integration with other external CLI tools (1Password, Bitwarden) via command execution</li>
</ul>
<h3 id="vault-switching">Vault Switching</h3>
<p>Vaults can be switched using a context-based system:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>flow vault switch development
</span></span><span style="display:flex;"><span>flow secret <span style="color:#d699b6">set</span> api-key <span style="color:#b2c98f">&#34;dev-value&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>flow vault switch production
</span></span><span style="display:flex;"><span>flow secret <span style="color:#d699b6">set</span> api-key <span style="color:#b2c98f">&#34;prod-value&#34;</span>
</span></span></code></pre></div><p>Secret references support both current vault context (<code>secretRef: &quot;api-key&quot;</code>) and explicit vault specification (<code>secretRef: &quot;production/api-key&quot;</code>).</p>
<h2 id="template-system">Template System</h2>
<p>The templating system is supplemental to Flow&rsquo;s core functionality, allowing users to define reusable templates for executables and workspaces. It uses Go&rsquo;s <code>text/template</code> engine combined with the Expr expression language for dynamic content generation. The core implementation is in the <a href="https://github.com/jahvon/expression">github.com/jahvon/expression</a> package.</p>
<p>The Expression library is used in all places where Flow allows templating or needs to evaluate expressions, such as in the <code>if</code> conditions of executables. The backing Expr language definition provides a powerful way evaluate and manipulate data for this use.</p>
<h3 id="template-processing">Template Processing</h3>
<p>Define templates in YAML files with placeholders for dynamic content</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#7a8478">form</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#7a8478">key</span>: <span style="color:#b2c98f">&#34;namespace&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">prompt</span>: <span style="color:#b2c98f">&#34;Target namespace?&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">default</span>: <span style="color:#b2c98f">&#34;default&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">validate</span>: <span style="color:#b2c98f">&#34;^[a-z][a-z0-9-]*$&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#7a8478">template</span>: |<span style="color:#b2c98f">
</span></span></span><span style="display:flex;"><span><span style="color:#b2c98f">  executables:
</span></span></span><span style="display:flex;"><span><span style="color:#b2c98f">    - verb: deploy
</span></span></span><span style="display:flex;"><span><span style="color:#b2c98f">      name: &#34;{{ name }}&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#b2c98f">      exec:
</span></span></span><span style="display:flex;"><span><span style="color:#b2c98f">        cmd: kubectl apply -f deployment.yaml -n {{ form[&#34;namespace&#34;] }}</span>
</span></span></code></pre></div><p>Creation of executable definitions (flow files) is optional. The template system is flexible enough
to allow for use for Flow contexts or setups completely unrelated to Flow, such as generating repos and files for new projects:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#7a8478">form</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#7a8478">key</span>: <span style="color:#b2c98f">&#34;namespace&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">prompt</span>: <span style="color:#b2c98f">&#34;Deployment namespace?&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">default</span>: <span style="color:#b2c98f">&#34;default&#34;</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#7a8478">key</span>: <span style="color:#b2c98f">&#34;image&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">prompt</span>: <span style="color:#b2c98f">&#34;Container image?&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">required</span>: <span style="color:#e67e80">true</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#7a8478">key</span>: <span style="color:#b2c98f">&#34;replicas&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">prompt</span>: <span style="color:#b2c98f">&#34;Number of replicas?&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">default</span>: <span style="color:#b2c98f">&#34;3&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">validate</span>: <span style="color:#b2c98f">&#34;^[1-9][0-9]*$&#34;</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#7a8478">key</span>: <span style="color:#b2c98f">&#34;expose&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">prompt</span>: <span style="color:#b2c98f">&#34;Expose via LoadBalancer?&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">type</span>: <span style="color:#b2c98f">&#34;confirm&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#7a8478">artifacts</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#7a8478">srcName</span>: <span style="color:#b2c98f">&#34;k8s-deployment.yaml.tmpl&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">dstName</span>: <span style="color:#b2c98f">&#34;deployment.yaml&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">asTemplate</span>: <span style="color:#e67e80">true</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#7a8478">srcName</span>: <span style="color:#b2c98f">&#34;k8s-service.yaml.tmpl&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">dstName</span>: <span style="color:#b2c98f">&#34;service.yaml&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">asTemplate</span>: <span style="color:#e67e80">true</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">if</span>: form[&#34;expose&#34;]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#7a8478">postRun</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#7a8478">cmd</span>: echo &#34;Generated Kubernetes manifests&#34;
</span></span><span style="display:flex;"><span>  - <span style="color:#7a8478">cmd</span>: kubectl apply -f .
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">if</span>: form[&#34;deploy&#34;]
</span></span></code></pre></div><h2 id="terminal-ui">Terminal UI</h2>
<p>The terminal UI is built using a couple of <a href="https://charm.sh">charm.sh</a> libraries, including Bubble Tea and Glamour, to provide a rich, interactive experience for managing workspaces, executables, and vaults directly from the terminal.</p>
<p>Most views are defined in the <a href="https://github.com/flowexec/tuikit">tuikit</a> package, with the exception of the browse/library view. These views are pending some refactoring to improve the user experience and make them more consistent with the rest of the UI and Desktop UI vision.</p>
<p><img alt="Flow Terminal UI" loading="lazy" src="/images/flow-tui-demo.gif"></p>
<h2 id="desktop-application">Desktop Application</h2>
<p><em>The Desktop is not released yet, I&rsquo;ll come back to update this section once I have a more complete implementation.</em></p>
<video autoplay loop muted playsinline width="100%">
  <source src="/images/flow-desktop-demo.mp4" type="video/mp4">
</video>
<h3 id="process-communication">Process Communication</h3>
<p>The desktop application uses Tauri (Rust + TypeScript) and communicates with the CLI through process execution. Each desktop operation corresponds to a CLI command, with JSON used for data exchange.</p>
<p><strong>Why Tauri?</strong></p>
<p>Tauri provides a lightweight, secure, and performant framework for building cross-platform applications with a web-based UI.
Realistically, I decided to use it because it seemed like the most interesting choice at the time given my desire to learn Rust and build a desktop app without the bloat of Electron.</p>
<h3 id="type-generation">Type Generation</h3>
<p><img alt="Docs and Code Generation" loading="lazy" src="/images/flow-gen.png"></p>
<p>Type safety across Go, TypeScript, and Rust is maintained through code generation from a shared JSON schema. Schema changes automatically propagate to all three languages during the build process. The same schema is used to generate markdown pages for the documentation site.</p>
<p>This has been a huge quality-of-life improvement for development, as I can make changes in one place and have confidence that the types are consistent across the entire codebase. The schema also provides additional validation and documentation in IDEs with minimal effort.</p>
<h2 id="extensions--integrations">Extensions / Integrations</h2>
<h3 id="github-actions--cicd">GitHub Actions &amp; CI/CD</h3>
<p>Being able to run the same flow executables that you use locally in your CI/CD pipelines has always been a goal of Flow. To that end, a <a href="https://github.com/marketplace/actions/flow-execute">GitHub Action</a> has been created and a Docker image is available for use in other CI/CD systems. All flowexec organization repositories should use this action!</p>
<h3 id="model-context-protocol">Model Context Protocol</h3>
<p>MCP server integration for AI assistant compatibility:</p>
<ul>
<li>Workspace browsing and executable discovery</li>
<li>Natural language workflow generation</li>
<li>Debug assistance with full context</li>
</ul>
<p>Framework chosen: <a href="https://mcp-go.dev/">mcp-go</a></p>
<p><strong>Limitations</strong></p>
<p>While implementing, I wanted to implement <a href="https://modelcontextprotocol.io/specification/2025-06-18/server/resources">MCP resources</a> but support for them across clients is pretty low. In order to provide the best experience for the most clients, I decided to implement the MCP server with just <a href="https://modelcontextprotocol.io/specification/2025-06-18/server/tools">Tools</a> and <a href="https://modelcontextprotocol.io/specification/2025-06-18/server/prompts">Prompts</a>.</p>
<p>There is a <code>get_info</code> tool that provides information to the AI about Flow, the current contexts, and expected schemas. This along with server instructions provided the best experience with AI assistants during my testing.</p>
<h2 id="known-limitations--future-work">Known Limitations &amp; Future Work</h2>
<ul>
<li><strong><a href="https://github.com/flowexec/flow/issues/185">WASM Plugin System</a></strong>: Allowing Flow to be extended with WebAssembly plugins for custom executable types and template generators.
<ul>
<li><em>Note to self: I did a small POC of this but wasn&rsquo;t very happy with the development experience on the plugin side. WASM may not be the right fit for Flow&rsquo;s extensibility model, but I want to keep it open as a possibility.</em></li>
</ul>
</li>
<li><strong><a href="https://github.com/flowexec/flow/issues/290">AI Runner</a></strong>: Integrating AI models to assist with workflow generation, debugging, and context-aware suggestions.</li>
<li><strong><a href="https://github.com/flowexec/flow/issues/245">Containerized Execution</a></strong>: Running executables in isolated containers for security and resource management.</li>
<li><strong>Current Technical Debt:</strong>
<ul>
<li>No dependency graph analysis (circular reference detection)</li>
</ul>
</li>
</ul>
<h2 id="build-system">Build System</h2>
<p><strong>Target platforms</strong>: macOS (Intel/Apple Silicon), Linux (x64/ARM)</p>
<p>The build pipeline needs to coordinate compilation across three languages:</p>
<ol>
<li><strong>Type Generation</strong>: JSON schema → Go/TypeScript/Rust types</li>
<li><strong>Documentation Generation</strong>: Markdown documentation from JSON schema</li>
<li><strong>CLI Build</strong>: Go compilation with generated types</li>
<li><strong>Desktop Build</strong>: Tauri compilation with TypeScript types and CLI binary</li>
<li><strong>Distribution</strong>: Platform-specific packaging and code signing</li>
</ol>
<hr>
<p><em>For implementation details, see the <a href="https://github.com/flowexec/flow">repository</a> and <a href="https://flowexec.io">documentation</a>.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>HubExt</title>
      <link>https://jahvon.dev/architecture/hubext/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://jahvon.dev/architecture/hubext/</guid>
      <description>Smart Home API Gateway that provides a unified control plane for my smart home ecosystems. Providing device management, automation rules, and scene control across multiple platforms through a gateway.</description>
      <content:encoded><![CDATA[<style type="text/css">
     
    .notice {
        --title-color: #fff;
        --title-background-color: #6be;
        --content-color: #444;
        --content-background-color: #e7f2fa;
    }

    .notice.info {
        --title-background-color: #fb7;
        --content-background-color: #fec;
    }

    .notice.tip {
        --title-background-color: #5a5;
        --content-background-color: #efe;
    }

    .notice.warning {
        --title-background-color: #c33;
        --content-background-color: #fee;
    }

     
    @media (prefers-color-scheme:dark) {
        .notice {
            --title-color: #fff;
            --title-background-color: #069;
            --content-color: #ddd;
            --content-background-color: #023;
        }

        .notice.info {
            --title-background-color: #a50;
            --content-background-color: #420;
        }

        .notice.tip {
            --title-background-color: #363;
            --content-background-color: #121;
        }

        .notice.warning {
            --title-background-color: #800;
            --content-background-color: #400;
        }
    }

    body.dark .notice {
        --title-color: #fff;
        --title-background-color: #069;
        --content-color: #ddd;
        --content-background-color: #023;
    }

    body.dark .notice.info {
        --title-background-color: #a50;
        --content-background-color: #420;
    }

    body.dark .notice.tip {
        --title-background-color: #363;
        --content-background-color: #121;
    }

    body.dark .notice.warning {
        --title-background-color: #800;
        --content-background-color: #400;
    }

     
    .notice {
        padding: 18px;
        line-height: 24px;
        margin-bottom: 24px;
        border-radius: 4px;
        color: var(--content-color);
        background: var(--content-background-color);
    }

    .notice p:last-child {
        margin-bottom: 0
    }

     
    .notice-title {
        margin: -18px -18px 12px;
        padding: 4px 18px;
        border-radius: 4px 4px 0 0;
        font-weight: 700;
        color: var(--title-color);
        background: var(--title-background-color);
    }

     
    .icon-notice {
        display: inline-flex;
        align-self: center;
        margin-right: 8px;
    }

    .icon-notice img,
    .icon-notice svg {
        height: 1em;
        width: 1em;
        fill: currentColor;
    }

    .icon-notice img,
    .icon-notice.baseline svg {
        top: .125em;
        position: relative;
    }
</style><div class="notice info" >
    <p class="notice-title">
        <span class="icon-notice baseline">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="92 59.5 300 300">
  <path d="M292 303.25V272c0-3.516-2.734-6.25-6.25-6.25H267v-100c0-3.516-2.734-6.25-6.25-6.25h-62.5c-3.516 0-6.25 2.734-6.25 6.25V197c0 3.516 2.734 6.25 6.25 6.25H217v62.5h-18.75c-3.516 0-6.25 2.734-6.25 6.25v31.25c0 3.516 2.734 6.25 6.25 6.25h87.5c3.516 0 6.25-2.734 6.25-6.25Zm-25-175V97c0-3.516-2.734-6.25-6.25-6.25h-37.5c-3.516 0-6.25 2.734-6.25 6.25v31.25c0 3.516 2.734 6.25 6.25 6.25h37.5c3.516 0 6.25-2.734 6.25-6.25Zm125 81.25c0 82.813-67.188 150-150 150-82.813 0-150-67.188-150-150 0-82.813 67.188-150 150-150 82.813 0 150 67.188 150 150Z"/>
</svg>

        </span>Info</p><p>This page is a public overview of a closed source project. I hope to one day
open source it, but for now this is a way to share the architecture and design.</p>
<p><strong>Last updated</strong>: August 11, 2025</p></div>

<p>HubExt is a system that aggregates multiple smart home platforms and protocols into a single API gateway, providing unified device management, automation rules, and scene control across different ecosystems. The architecture handles real-time device synchronization, event processing, and cross-platform automation orchestration powered by <a href="/tags/flow/">flow</a> and an eventual HubExt Dashboard.</p>
<h2 id="design-philosophy">Design Philosophy</h2>
<ul>
<li><strong>Unified Control Plane</strong>: Provide a single API for managing devices across multiple smart home platforms.</li>
<li><strong>Extensible Architecture</strong>: Support new platforms and devices through modular integration layers.</li>
<li><strong>Declarative Automation</strong>: Use a rules engine to define automation logic in a platform-agnostic way.</li>
</ul>
<h2 id="system-architecture">System Architecture</h2>
<p>The gateway operates as a centralized control plane that bridges local smart home protocols with cloud services while maintaining responsive local control capabilities.</p>
<p><img alt="HubExt System Overview" loading="lazy" src="/images/hubext-system-overview.png"></p>
<h2 id="technical-stack">Technical Stack</h2>
<ul>
<li><strong>Go</strong> - Backend <a href="https://github.com/gin-gonic/gin">Gin</a> API server and core logic</li>
<li><strong>TypeScript/React</strong> - WIP Dashboard for device management and automation configuration</li>
<li><strong>Flow Powered Workflows</strong> - Declarative workflows build on top of the <a href="https://flowexec.io/">flow</a> platform for observability and management via the CLI and UI</li>
</ul>
<h2 id="current-platform-integrations">Current Platform Integrations</h2>
<p><strong>Hubitat Hub Integration</strong></p>
<ul>
<li>Protocol: HTTP REST API using Makers API</li>
<li>Authentication: Access token with App ID</li>
<li>Communication: Local network for minimal latency</li>
<li>Event Handling: Webhook-based real-time device updates</li>
</ul>
<p><strong>Flair HVAC Integration</strong></p>
<ul>
<li>Protocol: OAuth 2.0 REST API with automatic token refresh</li>
<li>Components: Structures, rooms, HVAC units, sensor bridges</li>
<li>Capabilities: Mini-split control, room temperature monitoring</li>
</ul>
<p><strong>Weather Service Integration</strong></p>
<ul>
<li>Provider: WeatherAPI.com with API key authentication</li>
<li>Data Points: Temperature, humidity, conditions, feels-like temperature</li>
<li>Caching: Local cache with 30-minute refresh intervals</li>
</ul>
<p><strong>Google Calendar Integration</strong></p>
<ul>
<li>Protocol: OAuth 2.0 REST API with automatic token refresh</li>
<li>Use Case: Event-based automation triggers and conditions</li>
</ul>
<p><strong>Pushover Notifications</strong></p>
<ul>
<li>Protocol: HTTP POST requests to Pushover API</li>
<li>Use Case: Real-time alerts for device events and automation triggers</li>
</ul>
<p><strong>AI Integration</strong> (WIP)</p>
<ul>
<li>Provider: Anthropic API for natural language processing</li>
<li>Use Case: Natural language automation rule creation and device control</li>
</ul>
<h3 id="device-management-system">Device Management System</h3>
<p>Most of my devices are registered in the Hubitat platform, which provides a local API for device management.
I have also been evaluating Home Assistant as a potential alternative for future integrations but have not yet migrated.
The core requirements for the device management system include:</p>
<ul>
<li><strong>Unified Device Model</strong>: Abstract representation of devices across platforms</li>
<li><strong>Capability-Based Architecture</strong>: Devices expose capabilities like switches, sensors, and thermostats</li>
<li><strong>Room Organization</strong>: Devices are grouped by rooms with hierarchical structure</li>
</ul>
<p>Device states are synchronized into local cache storage, providing fast API responses while maintaining eventual consistency with upstream platforms.</p>
<p><strong>Abstract Device Interface</strong></p>
<p>All devices implement a common interface to ensure consistent interaction across platforms:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e67e80">type</span> Device <span style="color:#e67e80">interface</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#b2c98f">ID</span>() <span style="color:#dbbc7f">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#b2c98f">Name</span>() <span style="color:#dbbc7f">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#b2c98f">Room</span>() room.Room
</span></span><span style="display:flex;"><span>	<span style="color:#b2c98f">MatchesName</span>(<span style="color:#dbbc7f">string</span>) <span style="color:#dbbc7f">bool</span>
</span></span><span style="display:flex;"><span>	<span style="color:#b2c98f">Capabilities</span>() []CapabilityName
</span></span><span style="display:flex;"><span>	<span style="color:#b2c98f">As</span>(CapabilityName) Capability
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This interface is implemented for each platform / device type once and reused across the system.
It allows for flexible device management and interaction without needing to know the underlying platform details.</p>
<h4 id="capability-based-controls">Capability-Based Controls</h4>
<p>Devices expose capabilities that define their functionality, allowing for flexible control and automation. For instance:</p>
<ul>
<li>Switch capabilities for on/off control</li>
<li>Sensor capabilities for environmental data</li>
<li>Thermostat capabilities for HVAC control</li>
<li>Button capabilities for trigger events</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e67e80">type</span> Capability <span style="color:#e67e80">interface</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#b2c98f">Name</span>() CapabilityName
</span></span><span style="display:flex;"><span>	<span style="color:#b2c98f">IsStateful</span>() <span style="color:#dbbc7f">bool</span>
</span></span><span style="display:flex;"><span>	<span style="color:#b2c98f">CurrentState</span>() CapabilityState
</span></span><span style="display:flex;"><span>	<span style="color:#b2c98f">Merge</span>(Capability)
</span></span><span style="display:flex;"><span>	<span style="color:#b2c98f">SendCommand</span>(<span style="color:#dbbc7f">string</span>) <span style="color:#dbbc7f">error</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h4 id="room-organization">Room Organization</h4>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e67e80">type</span> Room <span style="color:#e67e80">struct</span> {
</span></span><span style="display:flex;"><span>	Name    <span style="color:#dbbc7f">string</span>   <span style="color:#b2c98f">`json:&#34;name,omitempty&#34;`</span>
</span></span><span style="display:flex;"><span>	Aliases []<span style="color:#dbbc7f">string</span> <span style="color:#b2c98f">`json:&#34;aliases,omitempty&#34;`</span>
</span></span><span style="display:flex;"><span>	Groups  []Group  <span style="color:#b2c98f">`json:&#34;groups,omitempty&#34;`</span>
</span></span><span style="display:flex;"><span>	Rank    <span style="color:#dbbc7f">uint</span>     <span style="color:#b2c98f">`json:&#34;rank,omitempty&#34;`</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><ul>
<li>Hierarchical room structure with groups and aliases</li>
<li>Device-to-room mapping for contextual automation</li>
<li>Room-based filtering and bulk operations functionality</li>
</ul>
<h3 id="automation-engine">Automation Engine</h3>
<h4 id="event-processing">Event Processing</h4>
<ol>
<li>Device state change triggers webhook OR data sync causes event generation</li>
<li>Event parsed and validated</li>
<li>Matching rules evaluated</li>
<li>Actions executed across relevant platforms</li>
<li>Results logged and metrics updated</li>
</ol>
<h4 id="rules-engine">Rules Engine</h4>
<p>Event-driven automation with logic defined in Go handlers.</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e67e80">type</span> Rule <span style="color:#e67e80">struct</span> {
</span></span><span style="display:flex;"><span>	Name       <span style="color:#dbbc7f">string</span>
</span></span><span style="display:flex;"><span>	Aliases    []<span style="color:#dbbc7f">string</span>
</span></span><span style="display:flex;"><span>	HandleFunc Handler
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#e67e80">type</span> Handler <span style="color:#e67e80">func</span>(Event, room.List, device.List) (handled <span style="color:#dbbc7f">bool</span>, err <span style="color:#dbbc7f">error</span>)
</span></span></code></pre></div><p>The handle function is responsible for processing events and executing actions based on the rule&rsquo;s logic. At the moment, all events
flow through all rule handlers so they must handle filtering. Some smarter filter logic would be nice to have here but is unecessary at the current scale.</p>
<div class="notice note" >
    <p class="notice-title">
        <span class="icon-notice baseline">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 128 300 300">
  <path d="M150 128c82.813 0 150 67.188 150 150 0 82.813-67.188 150-150 150C67.187 428 0 360.812 0 278c0-82.813 67.188-150 150-150Zm25 243.555v-37.11c0-3.515-2.734-6.445-6.055-6.445h-37.5c-3.515 0-6.445 2.93-6.445 6.445v37.11c0 3.515 2.93 6.445 6.445 6.445h37.5c3.32 0 6.055-2.93 6.055-6.445Zm-.39-67.188 3.515-121.289c0-1.367-.586-2.734-1.953-3.516-1.172-.976-2.93-1.562-4.688-1.562h-42.968c-1.758 0-3.516.586-4.688 1.563-1.367.78-1.953 2.148-1.953 3.515l3.32 121.29c0 2.734 2.93 4.882 6.64 4.882h36.134c3.515 0 6.445-2.148 6.64-4.883Z"/>
</svg>

        </span>Note</p><p>The rules engine design is still a work in progress. The goal is to provide a seamless integration with
external rules engines via the platform integrations but those aren&rsquo;t consistently exposed. The cufrrent implementation
is a basic event-driven system that evaluates rules based on device state changes and executes actions across platforms but
the definition can be streamlined further.</p></div>

<h4 id="scene-management">Scene Management</h4>
<p>Scenes follow a similar structure to rules but are predefined automation scenarios that coordinate multiple devices across platforms.</p>
<p>When defined in Go, the handler function is responsible for executing the scene by sending commands to the relevant devices.</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e67e80">func</span> <span style="color:#b2c98f">pbChillHandler</span>(_ room.List, curDevices device.List) (<span style="color:#dbbc7f">bool</span>, <span style="color:#dbbc7f">error</span>) {
</span></span><span style="display:flex;"><span>	tableLamp <span style="color:#7a8478">:=</span> curDevices.<span style="color:#b2c98f">GetDevice</span>(registry.PrimaryBedroomTableLamp)
</span></span><span style="display:flex;"><span>	ceilingLight <span style="color:#7a8478">:=</span> curDevices.<span style="color:#b2c98f">GetDevice</span>(registry.PrimaryBedroomCeilingLight)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#e67e80">switch</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#e67e80">case</span> timeframe.<span style="color:#b2c98f">CurrentDay</span>().<span style="color:#b2c98f">IsWeekday</span>() <span style="color:#7a8478">&amp;&amp;</span> timeframe.<span style="color:#b2c98f">Night</span>().<span style="color:#b2c98f">CurTimeInFrame</span>():
</span></span><span style="display:flex;"><span>		<span style="color:#e67e80">if</span> err <span style="color:#7a8478">:=</span> tableLamp.<span style="color:#b2c98f">As</span>(device.SwitchName).<span style="color:#b2c98f">SendCommand</span>(device.OnCommand); err <span style="color:#7a8478">!=</span> <span style="color:#e67e80">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#e67e80">return</span> <span style="color:#e67e80">false</span>, err
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#e67e80">if</span> err <span style="color:#7a8478">:=</span> ceilingLight.<span style="color:#b2c98f">As</span>(device.SwitchName).<span style="color:#b2c98f">SendCommand</span>(device.OffCommand); err <span style="color:#7a8478">!=</span> <span style="color:#e67e80">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#e67e80">return</span> <span style="color:#e67e80">false</span>, err
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#e67e80">return</span> <span style="color:#e67e80">true</span>, <span style="color:#e67e80">nil</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e67e80">default</span>:
</span></span><span style="display:flex;"><span>		<span style="color:#e67e80">return</span> <span style="color:#e67e80">false</span>, <span style="color:#e67e80">nil</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This currently feels a bit clunky, the end goal is to get to a point where these can be defined in simple YAML like:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#7a8478">scenes</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#7a8478">PBRChill</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">devices</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#7a8478">room</span>: <span style="color:#b2c98f">&#34;primary_bedroom&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#7a8478">type</span>: <span style="color:#b2c98f">&#34;switch&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#7a8478">action</span>: <span style="color:#b2c98f">&#34;dim_to_30&#34;</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#7a8478">room</span>: <span style="color:#b2c98f">&#34;primary_bedroom&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#7a8478">type</span>: <span style="color:#b2c98f">&#34;hvac&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#7a8478">action</span>: <span style="color:#b2c98f">&#34;cool_to_68&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">window</span>: <span style="color:#b2c98f">&#34;weekday night&#34;</span>
</span></span></code></pre></div>]]></content:encoded>
    </item>
  </channel>
</rss>
