<?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>Mochi on Jahvon Dockery</title>
    <link>https://jahvon.dev/architecture/mochi/</link>
    <description>Recent content in Mochi on Jahvon Dockery</description>
    <image>
      <title>Jahvon Dockery</title>
      <url>https://jahvon.dev/images/og-default.png</url>
      <link>https://jahvon.dev/images/og-default.png</link>
    </image>
    <generator>Hugo -- 0.153.4</generator>
    <language>en-us</language>
    <atom:link href="https://jahvon.dev/architecture/mochi/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Discovery</title>
      <link>https://jahvon.dev/architecture/mochi/discovery/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://jahvon.dev/architecture/mochi/discovery/</guid>
      <description>How Mochi finds what you can run, and why discovered executables are generated at read time instead of written into your repo.</description>
      <content:encoded><![CDATA[<h2 id="discovery">Discovery</h2>
<p>Discovery is the &ldquo;find&rdquo; half, and it&rsquo;s the piece I&rsquo;m happiest with architecturally.</p>
<p>A provider is about as small as an interface gets. It answers one question, given a directory:
which files here are importable? It also has to be a cheap check that cannot fail.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">DiscoveryProvider</span><span class="w"> </span><span class="kd">interface</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nf">Name</span><span class="p">()</span><span class="w"> </span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nf">Imports</span><span class="p">(</span><span class="nx">dir</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="p">[]</span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>It returns file paths, not executables. Turning a file into executables is Flow&rsquo;s job (or, for
the formats Flow doesn&rsquo;t know about, a parser on Mochi&rsquo;s side). Nine providers ship today:
Makefile, <code>package.json</code>, docker-compose, loose shell scripts, Justfile, Taskfile, GitHub
Actions, Dockerfile, and Cargo. The first four are parsed by Flow itself; the rest are Mochi&rsquo;s.
Registration order matters. The first provider to claim a file wins, which is how conflicts get
resolved without a merge policy.</p>
<h3 id="nothing-is-written-to-disk">Nothing is written to disk</h3>
<p>The important design decision: discovered executables are generated <strong>at read time</strong>, not
imported into your repo.</p>
<p>Mochi decorates Flow&rsquo;s executable cache. Discovery persists only a selection of importable files
per workspace, and the decorator regenerates their executables on every read, so they appear
identically in the desktop, in <code>mochi run</code>, and in <code>flow browse</code>, while your repo stays exactly
as it was. Generation happens against a virtual flow file that only ever exists in memory; its
directory is the only thing that matters, because that&rsquo;s what imports resolve relative to.</p>
<p>Failures degrade to nothing rather than propagating, so a bad provider can never break the cache
it&rsquo;s wrapping. Repeat scans are cheap: state stores file modification times as a fingerprint and
short-circuits when nothing has changed.</p>
<p>Attribution has a constraint worth mentioning, because it shaped the design. Flow doesn&rsquo;t inherit
flow-file annotations into generated executables, and a slashed namespace would break its
reference parser. Discovered executables get a single namespace plus a per-executable
annotation naming the provider that found them.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Desktop and CLI</title>
      <link>https://jahvon.dev/architecture/mochi/desktop/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://jahvon.dev/architecture/mochi/desktop/</guid>
      <description>One binary, two surfaces. The Tauri sidecar boundary, the JSON contract between Rust and Go, and how types stay honest across three languages.</description>
      <content:encoded><![CDATA[<h2 id="desktop-and-cli">Desktop and CLI</h2>
<p>The desktop app holds no business logic. Every operation is a Tauri command that shells out to
the bundled <code>mochi</code> binary and parses its JSON output. Same binary, two surfaces.</p>
<p>A few details that turned out to matter more than expected:</p>
<p><strong>It goes through a shell.</strong> A GUI app on macOS doesn&rsquo;t inherit a login shell&rsquo;s environment, so
invocations source the user&rsquo;s profile first. Arguments are POSIX-quoted, which matters because
executable references contain a space. <code>validate flow/ns:name</code> has to survive the round trip as
one token instead of being split into two.</p>
<p><strong>Runs are tagged with their origin.</strong> Every spawned process gets <code>FLOW_RUN_SOURCE=desktop</code>.
Without it, a run started by clicking Execute is indistinguishable from one typed into a
terminal. Both would record <code>cli</code>, because that&rsquo;s what Flow assumes when nothing says otherwise.
The desktop is its own origin, and history should say so.</p>
<p><strong>Secrets reach providers through the environment, never argv.</strong> Arguments are visible to every
process on the machine; a child process&rsquo;s environment is not.</p>
<h2 id="type-safety-across-three-languages">Type Safety Across Three Languages</h2>
<p>Mochi is Go, TypeScript, and Rust in one repo. JSON Schemas are the contract, vendored from Flow,
which owns them. TypeScript types and the raw schema modules are generated for the
frontend; Rust types are generated for the Tauri backend; Go gets the same types by importing
Flow as a library rather than by generating them.</p>
<p>That&rsquo;s a better arrangement than it sounds like: the schema is what keeps the Rust and
TypeScript mirrors honest against the Go types they&rsquo;re shadowing. Generation is orchestrated by
Flow executables (of course), and CI fails if generated code is out of date.</p>
<p><img src="https://jahvon.dev/images/flow-gen.png" srcset="https://jahvon.dev/images/flow-gen_hu_c5bd86eebb9a6853.png 380w, https://jahvon.dev/images/flow-gen.png 761w" sizes="(min-width: 768px) 720px, 100vw" width="761" height="328"
     alt="Docs and code generation"
     loading="lazy" decoding="async">
</p>
]]></content:encoded>
    </item>
    <item>
      <title>Run History</title>
      <link>https://jahvon.dev/architecture/mochi/history/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://jahvon.dev/architecture/mochi/history/</guid>
      <description>What Mochi remembers about every run, where it is stored, and how the dashboard turns it into something readable.</description>
      <content:encoded><![CDATA[<h2 id="run-history">Run History</h2>
<p>This is the &ldquo;remember&rdquo; pillar, and it&rsquo;s the reason Mochi exists at all. Agents run a lot of
commands on your behalf. The transcript is ephemeral and unstructured, there one moment and gone
the next. Mochi keeps the record: what ran, how long it took, whether it failed, and why.</p>
<p>Almost none of that storage is Mochi&rsquo;s. Flow already records every execution to a shared
embedded datastore, with a record that carries the reference, timing, status, exit code, process
ID, log archive, and the useful part, <a href="https://flowexec.io/guides/run-provenance">provenance</a>:
<code>source</code> (cli, desktop, or mcp), <code>clientName</code> (claude-code, cursor), <code>sessionId</code>, and
<code>workingDir</code>. Records are lifecycle-aware: they appear as running the moment a run starts and
update in place when it finishes.</p>
<p>Two of those fields have justifications I like. <code>workingDir</code> exists because the workspace is
already recoverable from the reference but the path is not, and it&rsquo;s the only thing separating
two checkouts of the same repo. <code>sessionId</code> exists so one assistant&rsquo;s related runs stay grouped.</p>
<p>Mochi&rsquo;s contribution is aggregation. A single pass over history rolls executions up per
executable and per workspace, keyed by executable ID rather than by reference so that verb
aliases like <code>run</code>, <code>exec</code>, and <code>start</code> of the same thing all collapse into one bucket. That same
rollup feeds both search ranking and the dashboard, so history gets swept once per invocation
rather than once per feature.</p>
<p><img src="https://jahvon.dev/images/mochi-dashboard_hu_e3b29148d92f8bd4.png" srcset="https://jahvon.dev/images/mochi-dashboard_hu_3e56f64abc0a2da6.png 700w, https://jahvon.dev/images/mochi-dashboard_hu_e3b29148d92f8bd4.png 1400w" sizes="(min-width: 768px) 720px, 100vw" data-zoom-src="https://jahvon.dev/images/mochi-dashboard.945fc006d28c9346c4f337b5bfca0a8d040d3f504c20472a5ac2b9b8230d6469.png" width="1400" height="900"
     alt="The Mochi dashboard"
     loading="lazy" decoding="async">
</p>
<p>The dashboard turns it into four purpose-built views rather than one generic screen: Welcome for
first-run setup, Pulse for health and what&rsquo;s happening now, Launch for getting back to work, and
Insights for activity over time. Insights surfaces the busiest, least reliable, and slowest
workflows, plus recommendations. Those are plain heuristics today, a reliability rule and a
duration rule, with stable IDs so that dismissing one survives regeneration. They are
deliberately built as the seam that model-generated insights will later plug into.</p>
]]></content:encoded>
    </item>
    <item>
      <title>AI Enrichment</title>
      <link>https://jahvon.dev/architecture/mochi/ai/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://jahvon.dev/architecture/mochi/ai/</guid>
      <description>A provider-agnostic AI layer, bring your own key, and why the secret never lands in Mochi configuration.</description>
      <content:encoded><![CDATA[<h2 id="ai-enrichment">AI Enrichment</h2>
<p>The AI layer is provider-agnostic behind a two-method interface. Features depend on that
interface rather than on a concrete client, which keeps <em>where the tokens come from</em> a
resolution-time decision.</p>
<p>You bring your own key: OpenAI, Anthropic, Gemini, Ollama, or any OpenAI-compatible endpoint.
Keys are never stored in Mochi&rsquo;s config. The config file holds only a pointer to a vault entry,
and the secret itself lives in the Flow vault. Each provider gets its own slot, so you can hold
an OpenAI key and an Anthropic key at once and flip between them without re-entering anything.</p>
<p>Reaching the vault involved a small workaround. Mochi&rsquo;s AI package can&rsquo;t
import Flow&rsquo;s vault resolution, since it&rsquo;s internal to Flow and off-limits the same way it is to
the Rust layer. Instead it shells out to <em>its own binary&rsquo;s</em> inherited <code>secret</code> command, exactly
as the desktop does. Vault access always goes through Flow&rsquo;s real implementation rather than a
reimplementation of it.</p>
<p>The agent loop is bounded rather than trusted to stop on its own: a round budget and a wall-clock
timeout, sized for analyzing a whole workspace while still stopping well short of a runaway.
Tools come from Mochi&rsquo;s own MCP server behind a three-tier permission policy, and every call is
recorded as an audit entry. Confirmation is designed to cross a process boundary. If no
confirmation handler is set, the loop returns a pending request rather than blocking, so the
desktop can ask the user and resume.</p>
<p>Usage is logged locally as one JSON line per generation, with age and size retention, so you can
see what your own key is being spent on.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
