<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>udctl Blog</title>
        <link>https://udctl.com/blog/</link>
        <description>udctl Blog</description>
        <lastBuildDate>Thu, 30 Jul 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[Why a Ledger Gets Harder to Continue After One Missing Entry]]></title>
            <link>https://udctl.com/blog/2026/07/30/reconciliation-you-can-audit/</link>
            <guid>https://udctl.com/blog/2026/07/30/reconciliation-you-can-audit/</guid>
            <pubDate>Thu, 30 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A missing entry breaks trust in the ledger. An auditable reconciliation finds omissions, duplicates, refunds, and old-data problems while leaving judgment to the human.]]></description>
            <content:encoded><![CDATA[<p>When one entry is missing, the first thing that breaks is often not the monthly report. It is your trust in the ledger.</p>
<p>You know it is already inaccurate. The next time you open it, adding that one entry is not enough. Did you miss others before it? Did the same purchase appear once in each of two channels? Was the original expense reversed after a refund arrived? If any of those questions remains unanswered, continuing feels like adding numbers to a crooked table.</p>
<p>That is where many people stop. They still want a record of their spending, but they do not want to spend more time on a result they no longer trust.</p>
<p>A recent real reconciliation covered four months, several payment and banking channels, and hundreds of existing records. There was no assumption about where the problems would be. The comparison surfaced omissions, duplicates, refunds, and old-data issues:</p>
<ul>
<li>Some expenses appeared on statements but not in the ledger.</li>
<li>Some purchases left a record in more than one channel.</li>
<li>Some recorded expenses were later refunded in full but remained in the totals.</li>
<li>Some old records had no date, so monthly reports could not see them.</li>
</ul>
<p>Memory is a poor way to reconstruct any of these. Refunds and undated old records are especially difficult because they do not look like errors. They quietly change the result.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/reconciliation-you-can-audit/duplicate-channels.png" alt="Cross-channel duplicate: one purchase leaves similar records in two channels and the existing ledger, so it is flagged as a possible duplicate for human confirmation" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="reconcile-first-then-change-the-ledger">Reconcile First, Then Change the Ledger<a href="https://udctl.com/blog/2026/07/30/reconciliation-you-can-audit/#reconcile-first-then-change-the-ledger" class="hash-link" aria-label="Direct link to Reconcile First, Then Change the Ledger" title="Direct link to Reconcile First, Then Change the Ledger">​</a></h3>
<p>The agent did not read the statements and immediately change the data.</p>
<p>It first compared the statements with the existing records and produced an analysis: confirmed omissions, possible cross-channel duplicates, possible refunds, and cases that required a decision. At this stage, it presented evidence without writing or deleting anything.</p>
<p>The human supplied the judgment. Whether a transfer counts as spending, how a refund should be classified, and which of two duplicate records should remain all depend on the owner's accounting rules. The agent wrote and corrected records only after the proposed treatment was approved.</p>
<p>Writing was not the end. It read the ledger back and checked the resulting count and total against the approved list. For every deduplication or deletion, it retained the record identifier, the reason, and the outcome. A conclusion could be traced back to the original record, and the write itself could be checked for mistakes.</p>
<p>Auditability matters more than a claim that the work was automatic. If automation says only, “I recorded it for you,” the person still does not know what it missed or merged. Analysis first, approval before writing, and a read-back afterward make the work safe to delegate.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/reconciliation-you-can-audit/auditable-flow.png" alt="Auditable reconciliation flow: analyze and show evidence, wait for human confirmation, write approved changes, read back and check, then retain the record and reason" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="a-missing-entry-no-longer-means-starting-over">A Missing Entry No Longer Means Starting Over<a href="https://udctl.com/blog/2026/07/30/reconciliation-you-can-audit/#a-missing-entry-no-longer-means-starting-over" class="hash-link" aria-label="Direct link to A Missing Entry No Longer Means Starting Over" title="Direct link to A Missing Entry No Longer Means Starting Over">​</a></h3>
<p>The ledger does not require a perfect daily habit. At the end of the month, the agent can compare statements from different sources with the existing records and surface omissions. Forgetting one entry does not invalidate everything recorded before it.</p>
<p>Duplicates do not have to be guessed by eye either. Similar times, amounts, and descriptions are only clues. Uncertain matches are listed for a human decision instead of being merged silently.</p>
<p>The person's work is reduced to the few places that require judgment: defining the rules, reviewing evidence, and confirming the treatment. The agent handles the statement-by-statement comparison, cross-checking, data entry, and read-back.</p>
<p>When a reconciliation can show what it inspected, what it proposed changing, and what it changed, a missing entry becomes one issue to resolve instead of a reason to abandon the entire ledger.</p>]]></content:encoded>
            <category>Story</category>
            <category>agents</category>
        </item>
        <item>
            <title><![CDATA[No Server? Your Desktop Is the Server]]></title>
            <link>https://udctl.com/blog/2026/07/26/your-desktop-is-the-server/</link>
            <guid>https://udctl.com/blog/2026/07/26/your-desktop-is-the-server/</guid>
            <pubDate>Sun, 26 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A zero-cost, full data self-custody setup: the UnDercontrol desktop app's embedded backend + a free Cloudflare Tunnel, with the iOS app connected to your own endpoint from anywhere.]]></description>
            <content:encoded><![CDATA[<p>Where your data lives is a fork in the road when you pick a tool. SaaS is the convenient path: sign up and go, nothing to maintain, at the cost of your data sitting on the provider's servers. UnDercontrol will offer that hosted option too. But if what you want is full data self-custody — tasks, notes, and ledgers stored only on your own devices, where backup means copying a file and migration means moving to another machine — this tutorial is for you.</p>
<p>Self-custody usually means running your own server, and that is exactly where most people give up. Here is a zero-cost path that skips the server entirely: the UnDercontrol desktop app ships with a full backend and keeps your data in a SQLite file on your own disk; Cloudflare Tunnel exposes that machine to the internet for free; the iOS app connects straight to your own endpoint. Check tasks, jot notes, and log expenses from the subway, while the data itself never lives anywhere but your computer. No public IP, no router port forwarding, and no domain to buy (optional, only if you want a fixed address).</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/your-desktop-is-the-server/concept-tunnel.png" alt="Architecture: the desktop app embeds a server, cloudflared dials out to Cloudflare's edge, and iOS reaches it over HTTPS from any network" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="step-1-install-the-desktop-app-meet-the-embedded-server">Step 1: Install the desktop app, meet the embedded server<a href="https://udctl.com/blog/2026/07/26/your-desktop-is-the-server/#step-1-install-the-desktop-app-meet-the-embedded-server" class="hash-link" aria-label="Direct link to Step 1: Install the desktop app, meet the embedded server" title="Direct link to Step 1: Install the desktop app, meet the embedded server">​</a></h2>
<p>Install the UnDercontrol desktop app (macOS / Windows / Linux) from the <a href="https://oatnil.com/download" target="_blank" rel="noopener noreferrer">download page</a>. It is not a web-page shell: a complete Go backend ships inside, and the moment the app starts, a local server is listening on port 8888 (it moves to the next free port if 8888 is taken), storing data as a SQLite file on your own disk.</p>
<p>On first launch you will see "Welcome to UnDercontrol" and a Start button. One click and you are in: the app auto-creates a local account <code>personal@undercontrol.local</code> with the default password <code>personal123</code>, no registration, no invite code. Remember these credentials, you will need them on the phone in step 4. And change the default password before exposing anything to the internet; the Security section below shows how.</p>
<p>Confirm the server is alive:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">curl http://localhost:8888/health</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># {"service":"ud-go-production","status":"healthy"}</span><br></span></code></pre></div></div>
<p>If you see <code>healthy</code>, your "server" is ready. It is the computer you are using right now.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="step-2-one-command-to-reach-the-internet">Step 2: One command to reach the internet<a href="https://udctl.com/blog/2026/07/26/your-desktop-is-the-server/#step-2-one-command-to-reach-the-internet" class="hash-link" aria-label="Direct link to Step 2: One command to reach the internet" title="Direct link to Step 2: One command to reach the internet">​</a></h2>
<p>Cloudflare Tunnel works by reverse connection: <code>cloudflared</code> on your computer dials out to Cloudflare's edge, and outside traffic is relayed back through that connection. Because the connection is outbound, you need no public IP and never touch your router settings.</p>
<p>Install cloudflared:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># macOS</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">brew install cloudflared</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Windows</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">winget install Cloudflare.cloudflared</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Linux (Debian/Ubuntu): add the repo from https://pkg.cloudflare.com first</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">sudo apt install cloudflared</span><br></span></code></pre></div></div>
<p>Then one command:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">cloudflared tunnel --url http://localhost:8888</span><br></span></code></pre></div></div>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/your-desktop-is-the-server/shot-terminal-quick-tunnel.png" alt="Real terminal output: quick tunnel created, and curl against the public address returns healthy" class="img_ev3q"></p>
<p>A few seconds later the terminal prints a random <code>https://xxx.trycloudflare.com</code> address: your public endpoint, HTTPS included, no Cloudflare account required. Verify from your phone (turn off Wi-Fi so you are on cellular, which proves the path is really public): open <code>https://xxx.trycloudflare.com/health</code> in the browser. <code>healthy</code> means the whole chain works.</p>
<p>Quick tunnels are for trying the flow out: the address changes every time you restart <code>cloudflared</code>, and Cloudflare makes no uptime promise for them. For daily use, spend five minutes upgrading to a named tunnel.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="step-3-recommended-a-named-tunnel-with-a-fixed-address">Step 3 (recommended): A named tunnel with a fixed address<a href="https://udctl.com/blog/2026/07/26/your-desktop-is-the-server/#step-3-recommended-a-named-tunnel-with-a-fixed-address" class="hash-link" aria-label="Direct link to Step 3 (recommended): A named tunnel with a fixed address" title="Direct link to Step 3 (recommended): A named tunnel with a fixed address">​</a></h2>
<p>A named tunnel needs a free Cloudflare account and a domain hosted on Cloudflare. The domain costs a few dollars a year and is the only place money can enter this setup.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># 1. Log in; authorize your domain in the browser</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cloudflared tunnel login</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># 2. Create the tunnel</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cloudflared tunnel create ud</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># 3. Point a subdomain at it</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cloudflared tunnel route dns ud ud.example.com</span><br></span></code></pre></div></div>
<p>Write a config file at <code>~/.cloudflared/config.yml</code>:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">tunnel</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> ud</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">credentials-file</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> /Users/you/.cloudflared/&lt;tunnel</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">&gt;</span><span class="token plain">.json</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">ingress</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">hostname</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> ud.example.com</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">service</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> http</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">//localhost</span><span class="token punctuation" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">8888</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">service</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> http_status</span><span class="token punctuation" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">404</span><br></span></code></pre></div></div>
<p>Run it, then register it as a system service so it survives reboots:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">cloudflared tunnel run ud        # run manually first</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">sudo cloudflared service install # then install as a service</span><br></span></code></pre></div></div>
<p>From now on <code>https://ud.example.com</code> is your fixed endpoint. Remember to stop the computer from sleeping in the system power settings — it is your server now.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="step-4-point-the-ios-app-at-your-endpoint">Step 4: Point the iOS app at your endpoint<a href="https://udctl.com/blog/2026/07/26/your-desktop-is-the-server/#step-4-point-the-ios-app-at-your-endpoint" class="hash-link" aria-label="Direct link to Step 4: Point the iOS app at your endpoint" title="Direct link to Step 4: Point the iOS app at your endpoint">​</a></h2>
<p>The iOS app is in <a href="https://testflight.apple.com/join/st2TnaBF" target="_blank" rel="noopener noreferrer">public beta on TestFlight</a> (install TestFlight from the App Store first).</p>
<p>Open the app. At the bottom of the sign-in screen there is an <strong>API server</strong> section showing the current server. Tap <strong>Change</strong> and enter your public address, <strong>including the <code>/api/v1</code> suffix</strong>:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">https://xxx.trycloudflare.com/api/v1</span><br></span></code></pre></div></div>
<p>The status dot probes as you type: when it turns green with <code>connected · Personal</code>, your phone has reached the server on your computer. Tap <strong>Save</strong>, then sign in with the account from step 1: <code>personal@undercontrol.local</code> and your password.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/your-desktop-is-the-server/shot-ios-connected.png" alt="iOS sign-in screen: API server pointing at the trycloudflare address, status connected · Personal" class="img_ev3q"></p>
<p>What you see on the phone is the data on your computer: tasks, notes, ledgers. Edit on the phone and the desktop has it.</p>
<p>One detail: iOS requires HTTPS for public domains (Cloudflare Tunnel provides it, nothing to do). If you enter a LAN or private-mesh IP instead, write <code>http://</code> explicitly; without a scheme the app assumes <code>https://</code>.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="security">Security<a href="https://udctl.com/blog/2026/07/26/your-desktop-is-the-server/#security" class="hash-link" aria-label="Direct link to Security" title="Direct link to Security">​</a></h2>
<p>Public exposure means anyone with the URL can reach your endpoint. Three things, in order of importance:</p>
<p><strong>1. Change the default password first.</strong> Every data endpoint requires login, but the desktop's default password <code>personal123</code> is public knowledge. Exposing it unchanged is taping the key to the door. The password comes from the <code>PERSONAL_TIER_PASSWORD</code> environment variable; on macOS, launch from a terminal with it set:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">PERSONAL_TIER_PASSWORD='your-strong-password' /Applications/UnderControl.app/Contents/MacOS/UnderControl</span><br></span></code></pre></div></div>
<p>On Windows, add <code>PERSONAL_TIER_PASSWORD</code> in System Environment Variables and restart the app. Phone and CLI then log in with the new password.</p>
<p><strong>2. A random URL is not a security mechanism.</strong> The trycloudflare address is hard to guess, but do not treat that as protection; think before sharing the link.</p>
<p><strong>3. For an extra gate</strong>, <a href="https://developers.cloudflare.com/cloudflare-one/policies/access/" target="_blank" rel="noopener noreferrer">Cloudflare Access</a> on the free plan can put an email-code check in front of a named tunnel. Configure it once and it applies to every device.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="dont-want-public-exposure-at-all">Don't want public exposure at all?<a href="https://udctl.com/blog/2026/07/26/your-desktop-is-the-server/#dont-want-public-exposure-at-all" class="hash-link" aria-label="Direct link to Don't want public exposure at all?" title="Direct link to Don't want public exposure at all?">​</a></h2>
<p>If all you need is "my devices can reach each other", skip the public internet entirely: use a private mesh network tool to put your phone and computer on the same virtual LAN. Install the client on each device and they see each other; your computer gets a virtual-LAN IP, and the iOS app endpoint becomes <code>http://&lt;virtual-lan-ip&gt;:8888/api/v1</code> (write <code>http://</code> explicitly). Zero public exposure, and the free tiers of these tools are usually plenty for personal use. The mesh setup itself is out of scope here; every vendor's docs cover the phone-plus-computer case.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="after-it-works">After it works<a href="https://udctl.com/blog/2026/07/26/your-desktop-is-the-server/#after-it-works" class="hash-link" aria-label="Direct link to After it works" title="Direct link to After it works">​</a></h2>
<p>The bill: VPS $0, tunnel $0, HTTPS certificate $0. The only optional cost is a domain at a few dollars a year.</p>
<ul>
<li>Desktop download: <a href="https://oatnil.com/download" target="_blank" rel="noopener noreferrer">https://oatnil.com/download</a></li>
<li>iOS TestFlight: <a href="https://testflight.apple.com/join/st2TnaBF" target="_blank" rel="noopener noreferrer">https://testflight.apple.com/join/st2TnaBF</a></li>
</ul>]]></content:encoded>
            <category>Tutorial</category>
            <category>Self-Hosting</category>
        </item>
        <item>
            <title><![CDATA[Pin the Discussion to the Line It's About]]></title>
            <link>https://udctl.com/blog/2026/07/25/anchored-comments/</link>
            <guid>https://udctl.com/blog/2026/07/25/anchored-comments/</guid>
            <pubDate>Sat, 25 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Anchored comments in UnDercontrol: select a sentence in a task, comment on it, and the quote stays next to the text. Threads resolve like code review, and an @mention pulls an agent into the same thread.]]></description>
            <content:encoded><![CDATA[<p>You send out a spec and the discussion immediately comes apart from it. Someone replies in Slack: "that field in the third section, is it nullable?" Now you scroll back to the doc, count down to the third section, and guess which field they meant. Read the same message a day later and the context is gone.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/anchored-comments/concept-anchored.png" alt="Anchored comments: discussion pinned to the line it is about" class="img_ev3q"></p>
<p>Anchored comments in UnDercontrol keep the discussion next to the original text. Select a sentence in the task body, click Comment in the bubble menu, and a comment carrying that quote appears in the right sidebar while the sentence itself turns into a highlight. Click the highlight later and the sidebar jumps to the matching thread. The quote lives on the comment, so the markdown in the task body is never touched. Copy it out or pull it down from the command line and you get exactly what you wrote.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/anchored-comments/shot-1-anchored-thread.png" alt="A highlighted sentence in the task body with its anchored comment thread open in the sidebar" class="img_ev3q"></p>
<p>When a discussion is settled, hit Resolve. The highlight disappears from the body, the thread collapses into a single grey line, and you can reopen it whenever you want. Threads update on their own when someone replies, with no page refresh. On a phone there are no highlights and the comments sit below the body.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="reviewing-a-spec-like-you-review-code">Reviewing a spec like you review code<a href="https://udctl.com/blog/2026/07/25/anchored-comments/#reviewing-a-spec-like-you-review-code" class="hash-link" aria-label="Direct link to Reviewing a spec like you review code" title="Direct link to Reviewing a spec like you review code">​</a></h3>
<p>A reviewer no longer has to write "third section, second sentence". They select the line that says "supports bulk import", ask "what's the upper limit?", and the author answers in the same thread. Agree, resolve, move on.</p>
<p>Once you have been through a spec, the highlights still lit on the page are the parts nobody settled. There is no separate write-up to do afterwards, because the unresolved threads are the to-do list.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/anchored-comments/shot-2-review-pass.png" alt="The comments sidebar after a review pass: resolved threads collapsed, unsettled ones still open" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="point-at-a-line-and-let-an-agent-change-it">Point at a line and let an agent change it<a href="https://udctl.com/blog/2026/07/25/anchored-comments/#point-at-a-line-and-let-an-agent-change-it" class="hash-link" aria-label="Direct link to Point at a line and let an agent change it" title="Direct link to Point at a line and let an agent change it">​</a></h3>
<p>This is the part that is not like a normal comment section. Select the requirement you want changed, leave a comment that @mentions your agent, and say what it should become. The mention starts an agent session, reusing one that is already running, and when the agent is done its reply is the next comment in that thread, sitting alongside your colleagues' replies with the commit hash in it. Click the highlight and you are back at the text that changed, side by side.</p>
<p>People and agents talk in one thread, so you are not relaying messages between two tools.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/anchored-comments/shot-3-agent-thread.png" alt="A human comment mentions an agent and the agent replies in the same thread with a commit hash" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="section-by-section-discussion-on-long-documents">Section-by-section discussion on long documents<a href="https://udctl.com/blog/2026/07/25/anchored-comments/#section-by-section-discussion-on-long-documents" class="hash-link" aria-label="Direct link to Section-by-section discussion on long documents" title="Direct link to Section-by-section discussion on long documents">​</a></h3>
<p>Write the task body as a design doc and record each iteration as a note. Discussions anchor to their own section: comments on the body in one group, comments on each note in a group of their own, and unanchored chatter in a third. Three months later, why a decision went the way it did is written next to the sentence it changed.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="the-same-thread-from-your-terminal">The same thread from your terminal<a href="https://udctl.com/blog/2026/07/25/anchored-comments/#the-same-thread-from-your-terminal" class="hash-link" aria-label="Direct link to The same thread from your terminal" title="Direct link to The same thread from your terminal">​</a></h3>
<p>Comments are just as readable and writable from a terminal. The ud CLI treats them as a resource like any other, so an agent can read the discussion a human left in the browser and reply to it:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># read every discussion on a task</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud get comments --task 02c137b3</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># reply to a thread</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cat &lt;&lt;'EOF' | ud apply -f -</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">---</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">task_id: 02c137b3</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">parent_id: 7a1c9f2e</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">---</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Per-user rate limiting is in, commit a1b2c3d4.</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">EOF</span><br></span></code></pre></div></div>
<p>Claude Code, Codex, OpenCode, or any terminal-based agent works here without an adapter layer. Close a thread out with <code>ud patch comment &lt;id&gt; --status resolved</code>.</p>
<p>Anchored comments have been available since v0.88.0, in the browser, in the desktop app, and from the CLI.</p>]]></content:encoded>
            <category>Feature</category>
            <category>collaboration</category>
        </item>
        <item>
            <title><![CDATA[Turn a Task into a Parcel Locker]]></title>
            <link>https://udctl.com/blog/2026/07/24/file-drop-share/</link>
            <guid>https://udctl.com/blog/2026/07/24/file-drop-share/</guid>
            <pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Attach files to a task and share one public link — a 6-letter pickup code and QR included. Open it on any device with no login; downloads run through 15-minute presigned URLs.]]></description>
            <content:encoded><![CDATA[<p>The computer at a print shop isn't yours, and you're not going to log into anything on it. The usual ways to move a file there are all awkward: messaging apps want a QR scan and a login session, cloud-drive share pages nag you to install their app, and the USB stick is at home.</p>
<p>A task in UnDercontrol is already a container: body text, notes, and file attachments. Generate a public share link for it, and anyone who has the link can read the content and download the files. No account, nothing to install. It works like a parcel locker: you drop things in, get a pickup code, and whoever has the code takes them out.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/file-drop-share/concept-locker.png" alt="Concept: a task working as a parcel locker" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="step-1-put-the-files-on-a-task">Step 1: put the files on a task<a href="https://udctl.com/blog/2026/07/24/file-drop-share/#step-1-put-the-files-on-a-task" class="hash-link" aria-label="Direct link to Step 1: put the files on a task" title="Direct link to Step 1: put the files on a task">​</a></h3>
<p>Create a task and upload the files in its attachments section. The task body is Markdown, so write the instructions right next to the files. Whoever opens the link sees your print requirements, not a bare file list.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/file-drop-share/task-detail.png" alt="Task detail: attachments and print notes" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="step-2-generate-the-share-link">Step 2: generate the share link<a href="https://udctl.com/blog/2026/07/24/file-drop-share/#step-2-generate-the-share-link" class="hash-link" aria-label="Direct link to Step 2: generate the share link" title="Direct link to Step 2: generate the share link">​</a></h3>
<p>Open <strong>Share to Public</strong> in the task menu. Two settings:</p>
<ul>
<li><strong>Expiry</strong>: 1 hour / 1 day / 7 days / never</li>
<li><strong>Allow attachment downloads</strong>: off by default; check it when the point is the files</li>
</ul>
<p>Click Generate Link and you get three things at once: the full link, a 6-letter pickup code, and a pre-composed message (Copy Share Message) that carries both, ready to paste into any chat.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/file-drop-share/share-modal.png" alt="Share dialog: link, pickup code, expiry" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="step-3-pick-up-on-any-device">Step 3: pick up on any device<a href="https://udctl.com/blog/2026/07/24/file-drop-share/#step-3-pick-up-on-any-device" class="hash-link" aria-label="Direct link to Step 3: pick up on any device" title="Direct link to Step 3: pick up on any device">​</a></h3>
<p>Three ways in, for three situations:</p>
<ul>
<li><strong>You have the link</strong>: open it. It's a read-only page with the title, body, notes, and attachments, each with a download button.</li>
<li><strong>You have only the code</strong>: open the share entry page and type the 6 letters. Handy when the code was read out loud, or on a public computer where you'd rather not type a long URL.</li>
<li><strong>You're standing next to the person</strong>: the share page shows a QR code; scanning it lands on the same page.</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/file-drop-share/share-viewer.png" alt="Public share page: QR code, expiry countdown, downloads" class="img_ev3q"></p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/file-drop-share/code-entry.png" alt="Pickup code entry page" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="the-security-details">The security details<a href="https://udctl.com/blog/2026/07/24/file-drop-share/#the-security-details" class="hash-link" aria-label="Direct link to The security details" title="Direct link to The security details">​</a></h3>
<ul>
<li>The token in the link is 32 random bytes (64 hex characters), so it can't be enumerated</li>
<li>Links expire on schedule. The dialog lists each link's remaining time, and any link can be revoked at any moment</li>
<li>Attachment downloads go through presigned URLs generated per click and valid for 15 minutes; the file in storage is never public</li>
<li>The shared page is read-only and carries only the title, body, notes, and attachments. No account info, and comments stay private</li>
<li>Self-hosted instances can switch public sharing off entirely in system config</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="a-parcel-locker-for-documents-too">A parcel locker for documents too<a href="https://udctl.com/blog/2026/07/24/file-drop-share/#a-parcel-locker-for-documents-too" class="hash-link" aria-label="Direct link to A parcel locker for documents too" title="Direct link to A parcel locker for documents too">​</a></h3>
<p>The task body and notes are Markdown, written in the same editor UnDercontrol uses everywhere else (notes, finance records). So the thing you share doesn't have to be a file. Meeting minutes, a PC-build checklist, a travel plan — the other side opens a formatted read-only page. Files and documents travel through the same link.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="typical-scenarios">Typical scenarios<a href="https://udctl.com/blog/2026/07/24/file-drop-share/#typical-scenarios" class="hash-link" aria-label="Direct link to Typical scenarios" title="Direct link to Typical scenarios">​</a></h3>
<ul>
<li><strong>Print shop</strong>: attach the PDFs, type the pickup code on the shop computer, download and print, revoke the link when you get home</li>
<li><strong>Sending files to someone without an account</strong>: paste the Copy Share Message; one message carries the link and the code</li>
<li><strong>A shared meeting-room computer</strong>: type the 6-letter code to open the document read-only; whoever wants a copy afterward scans the QR on the page</li>
<li><strong>Deliverables for an outside collaborator</strong>: pick the 7-day expiry and the link shuts off by itself</li>
<li><strong>Output from terminal AI agents</strong> (Claude Code, Codex, OpenCode, or any terminal-based agent): the agent attaches its report and artifacts to a task; you review, then forward one link</li>
</ul>
<p>The entry point is Share to Public in the ⋮ menu on the task detail page. Self-hosting? Check that the public share switch in system config is on — it is by default.</p>]]></content:encoded>
            <category>Feature</category>
            <category>sharing</category>
        </item>
        <item>
            <title><![CDATA[Agents of Agents: When AI Learns to Lead a Team]]></title>
            <link>https://udctl.com/blog/2026/07/20/agent-of-agents-team-lead/</link>
            <guid>https://udctl.com/blog/2026/07/20/agent-of-agents-team-lead/</guid>
            <pubDate>Mon, 20 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Three agents didn't triple your output — you became the router. A lead agent gets its roster injected at session start, delegates through two channels, and the org tree grows recursively. Your job shifts from dispatching to writing job descriptions.]]></description>
            <content:encoded><![CDATA[<p>You set up three agents expecting three times the output. Instead your day looks like this: copy A's conclusion over to B, remember to chase C, read three sets of output at once, and hold "who is doing what" in your head.</p>
<p><strong>More agents didn't fix the bottleneck — it moved. You no longer lack workers. You lack managers, because there's exactly one: you.</strong></p>
<p>That's not a configuration problem, it's an org-structure problem. One person directly managing ten reports doesn't work in human companies either, and the fix was never "hire stronger individuals." The fix is <strong>a layer of management</strong>.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/agent-of-agents-team-lead/concept-hero.png" alt="One human routing N agents versus a lead that routes for you" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="a-lead-isnt-a-smarter-agent--its-an-agent-that-knows-the-roster">A lead isn't a smarter agent — it's an agent that knows the roster<a href="https://udctl.com/blog/2026/07/20/agent-of-agents-team-lead/#a-lead-isnt-a-smarter-agent--its-an-agent-that-knows-the-roster" class="hash-link" aria-label="Direct link to A lead isn't a smarter agent — it's an agent that knows the roster" title="Direct link to A lead isn't a smarter agent — it's an agent that knows the roster">​</a></h3>
<p>In UnDercontrol you can organize agents into a <strong>Team</strong>: one <strong>lead</strong>, any number of <strong>members</strong>, and each member carries a <strong>delegation hint</strong> — "what kind of work routes here."</p>
<p>That hint is a job description. It's short, but it decides whether work reaches the right specialist:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">- @db-expert [reviewer] — schema changes, slow queries, migration scripts</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">- @ud-tester — requirement acceptance, black-box regression; don't let it touch code</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">- @infra-ops [remote — mention only] — servers, deploys, DNS</span><br></span></code></pre></div></div>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/agent-of-agents-team-lead/app-teams.png" alt="A Team — one lead, four members, each with a delegation hint" class="img_ev3q"></p>
<p>Here's the part that matters: <strong>the moment a lead's session starts, that roster is injected into its initial prompt.</strong> You don't introduce the team in conversation, and it doesn't rediscover them each time. It is born knowing who reports to it, what each one is good at, and how to hand work off.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/agent-of-agents-team-lead/app-injected-prompt.png" alt="The roster as it lands in the lead's prompt at session init" class="img_ev3q"></p>
<p>Injected alongside it is a plain but consequential instruction: <strong>"As team lead, prefer delegating over implementing yourself. Your job is to coordinate, keep talking to the user, and review the specialist's output. Only implement directly when no member covers the work."</strong></p>
<p>If you've ever managed people, you'll recognize that sentence as the hardest lesson a new lead has to learn.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="two-delegation-channels-one-fast-one-on-the-record">Two delegation channels: one fast, one on the record<a href="https://udctl.com/blog/2026/07/20/agent-of-agents-team-lead/#two-delegation-channels-one-fast-one-on-the-record" class="hash-link" aria-label="Direct link to Two delegation channels: one fast, one on the record" title="Direct link to Two delegation channels: one fast, one on the record">​</a></h3>
<p>This is the design most people don't anticipate. Delegation isn't one move. A lead has two channels, and it defaults to the fast one.</p>
<p><strong>Channel 1 — native subagent (default)</strong></p>
<p>The lead runs <code>ud describe agent &lt;name&gt; -o prompt</code> to fetch the member's identity prompt, then spins it up using <strong>its own host tool's native subagent capability</strong>, appending the task context. The subagent runs inside the lead's session and permissions; the lead consolidates the result and reports it, noting "via @member."</p>
<p>The split is clean: <strong>ud supplies identity, skills, and context; the host tool executes.</strong> Fast, synchronous, no overhead — right for "take a look at this SQL" work.</p>
<p><strong>Channel 2 — subtask + @mention (escalation)</strong></p>
<p>The lead escalates to the second channel when any of these hold:</p>
<ul>
<li>The member is pinned to <strong>another machine</strong> (an ops agent that only runs on a production jump box, marked <code>[remote — mention only]</code> in the roster)</li>
<li>The work is <strong>long-running</strong> and must outlive the lead's session</li>
<li>You want the work to have its <strong>own accountability thread</strong> you can audit separately</li>
</ul>
<p>The move is: create a subtask → <code>ud link task &lt;parent&gt; &lt;child&gt; --subtask</code> → @mention the member in a comment on that subtask. The mention automatically spins up a workspace session for them, and <strong>the delegation chain preserves your access scope</strong> — a member never receives broader permissions than you have. It reports its conclusion back into the thread.</p>
<p>In one line: <strong>fast work goes to a subagent; anything that needs a record, another machine, or a long runway goes to a subtask plus an @mention.</strong></p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/agent-of-agents-team-lead/concept-channels.png" alt="Two delegation channels compared" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="recursion-agents-of-agents-of-agents">Recursion: agents of agents of agents<a href="https://udctl.com/blog/2026/07/20/agent-of-agents-team-lead/#recursion-agents-of-agents-of-agents" class="hash-link" aria-label="Direct link to Recursion: agents of agents of agents" title="Direct link to Recursion: agents of agents of agents">​</a></h3>
<p>Teams nest — a member can itself be the lead of another Team. Hierarchy grows naturally, and <strong>each agent only ever needs to know its direct reports</strong>, never the whole tree. That's how real organizations work.</p>
<p>It goes one step further: the default <code>ud</code> agent is the <strong>root manager</strong> of the whole org. On top of any teams it explicitly leads, <strong>every top-level team lead is automatically injected as one of its direct reports</strong> — so no matter how many layers your org grows, the entire tree stays reachable from <code>ud</code>.</p>
<p>You don't have to remember who to @. Just @ <code>ud</code> and let it walk down.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/agent-of-agents-team-lead/concept-tree.png" alt="The org tree — each node only knows its direct reports" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="so-your-job-changes">So your job changes<a href="https://udctl.com/blog/2026/07/20/agent-of-agents-team-lead/#so-your-job-changes" class="hash-link" aria-label="Direct link to So your job changes" title="Direct link to So your job changes">​</a></h3>
<p>From "dispatching N agents" to three things that look a lot more like management:</p>
<ol>
<li><strong>Write the job descriptions.</strong> A vague delegation hint sends work to the wrong specialist. Writing a crisp "this belongs here, that doesn't" beats upgrading an agent to a pricier model.</li>
<li><strong>Define acceptance criteria.</strong> You review output, not process. Put the criteria in the task description: the lead uses them to check its members, you use them to check the lead.</li>
<li><strong>Hold the review gate.</strong> However deep the delegation chain runs, you're still the one signing off.</li>
</ol>
<p>Worth noting: everything agents write back and forth — task descriptions, comments, notes — lives in the same Markdown system as anything you type by hand. And not just tasks: notes, expenses, accounts — every surface in UnDercontrol where you can write text shares one editing experience. Humans and agents read and write the same artifact. There's no "format for the AI" and separate "format for people."</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="traps-worth-knowing-about">Traps worth knowing about<a href="https://udctl.com/blog/2026/07/20/agent-of-agents-team-lead/#traps-worth-knowing-about" class="hash-link" aria-label="Direct link to Traps worth knowing about" title="Direct link to Traps worth knowing about">​</a></h3>
<ul>
<li><strong>The lead does the work itself.</strong> The most common failure mode, and usually it means the delegation hints were too vague — the lead couldn't tell who to route to, so it just did it.</li>
<li><strong>An offline machine makes @mentions silently vanish.</strong> Mentions <strong>don't queue</strong> waiting for a machine to come online; the delivery is dropped as daemon-offline. Confirm the machine is connected before delegating remote work.</li>
<li><strong>One active session per member, per task.</strong> To have an agent push two things in parallel, split them into two tasks — @mentioning it twice on one task won't do it.</li>
<li><strong>Nested teams have no cycle detection yet.</strong> Nothing stops you configuring A leads B leads A. Keep the depth in your head — three levels is about the ceiling; past that you can't follow what happened in the middle of the chain at review time.</li>
<li><strong>Review debt.</strong> The smoother delegation gets, the faster unreviewed output piles up. The bottleneck migrates from dispatching to accepting — and that part can't be outsourced yet.</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="in-the-end">In the end<a href="https://udctl.com/blog/2026/07/20/agent-of-agents-team-lead/#in-the-end" class="hash-link" aria-label="Direct link to In the end" title="Direct link to In the end">​</a></h3>
<p>Any agent can be the lead. Claude Code, Codex, OpenCode, any terminal-based agent can be a lead or a member — roles are defined by prompts and skills, tools by Agent CLI configuration, and every one of them talks to the org through the same <a href="https://udctl.com/docs/cli-ai-integration/">AI agent CLI</a>. UnDercontrol isn't tied to any AI vendor.</p>
<p>It does one thing: <strong>when you have enough agents that they need managing, it gives you a management layer — instead of making you be that layer.</strong></p>]]></content:encoded>
            <category>Feature</category>
            <category>agents</category>
        </item>
        <item>
            <title><![CDATA[Claude Plans, Codex Executes]]></title>
            <link>https://udctl.com/blog/2026/07/19/claude-plans-codex-executes/</link>
            <guid>https://udctl.com/blog/2026/07/19/claude-plans-codex-executes/</guid>
            <pubDate>Sun, 19 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Turn two AI subscriptions into one team: a planner agent splits work into subtasks with acceptance criteria, delegates via @mention, an executor agent implements — you only review. Agent Teams give the lead its roster automatically.]]></description>
            <content:encoded><![CDATA[<p>Plenty of developers already work this way: hash out the approach in Claude Code, then copy the conclusion into Codex to implement. The reason is practical, too — you're paying for both subscriptions anyway. Claude is great at planning; letting Codex do the heavy lifting doesn't burn your Claude quota. Both subscriptions earn their keep. The only problem: the "copy-paste" role in the middle is <em>you</em>.</p>
<p><strong>In UnDercontrol, this pipeline works out of the box</strong>: Claude plans, Codex executes, and the task system is the whiteboard they share — you only step in to review at the key points.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/claude-plans-codex-executes/concept-hero.png" alt="Claude plans, Codex executes — tasks are the shared whiteboard between your AI agents" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="five-steps-to-a-working-pipeline">Five steps to a working pipeline<a href="https://udctl.com/blog/2026/07/19/claude-plans-codex-executes/#five-steps-to-a-working-pipeline" class="hash-link" aria-label="Direct link to Five steps to a working pipeline" title="Direct link to Five steps to a working pipeline">​</a></h3>
<ol>
<li><strong>Configure two agents.</strong> Claude Code, Codex, and OpenCode are built-in Agent CLI templates — just pick one (any custom command works too). Give the planner Claude Code and the executor Codex. Don't feel like configuring? Even this step can be outsourced — there's a built-in <strong>Agent Creator</strong>: @mention it, describe the combo you want, and it sets the agents up for you.</li>
<li><strong>Toss the requirement to the planner.</strong> A rough task is fine — just @planner it. The daemon spins up a Claude Code session on your dev machine: it reads the description, clarifies the requirement, and splits it into subtasks with acceptance criteria.</li>
<li><strong>The planner delegates to the executor.</strong> It @executor-mentions each subtask — an agent-to-agent @mention automatically launches a new workspace session for the other agent, and the delegation chain preserves your access scope.</li>
<li><strong>Codex executes.</strong> Each session picks up one subtask: writes code, runs tests, commits, notes progress back on the task, and marks it <code>pending</code> when done.</li>
<li><strong>You step in at exactly two points.</strong> Approve the plan; review the output.</li>
</ol>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/claude-plans-codex-executes/concept-flow.png" alt="From a rough idea to reviewed commits — one pipeline" class="img_ev3q"></p>
<p>The whole collaboration happens on the task itself — the description is the spec, comments are the conversation, notes are the progress log. Here's a real task detail: the planner's acceptance criteria on the left, the three-way comment thread on the right:</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/claude-plans-codex-executes/app-task.png" alt="Task detail — the planner writes the spec, the executor reports commits, everything on the record" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="form-a-squad-agent-teams">Form a squad: Agent Teams<a href="https://udctl.com/blog/2026/07/19/claude-plans-codex-executes/#form-a-squad-agent-teams" class="hash-link" aria-label="Direct link to Form a squad: Agent Teams" title="Direct link to Form a squad: Agent Teams">​</a></h3>
<p>Don't want to conduct two agents by hand every time? Organize them into a <strong>Team</strong>.</p>
<p>A Team has one <strong>lead</strong> and any number of <strong>members</strong>, and each member carries a delegation hint — "what kind of work routes here." When the lead's session starts, its roster is injected into the prompt automatically — <strong>the lead is born knowing who reports to it, what each specialist is good at, and how to delegate</strong>.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/claude-plans-codex-executes/app-teams.png" alt="Agent Teams — a lead plus members with delegation hints, roster auto-injected into the lead's session" class="img_ev3q"></p>
<p>"Claude plans, Codex executes" becomes a one-liner: @mention the dev-team's lead and it arranges the rest. Teams also nest — a member can itself lead another Team, so hierarchy grows naturally while each agent only ever needs to know its direct reports.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="watch-everything-live">Watch everything live<a href="https://udctl.com/blog/2026/07/19/claude-plans-codex-executes/#watch-everything-live" class="hash-link" aria-label="Direct link to Watch everything live" title="Direct link to Watch everything live">​</a></h3>
<p>Open a board for the big picture: how much got planned, what's executing, what's waiting for review — one glance. Every agent session's terminal output also streams back to the web in real time — on your laptop, or your phone.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/claude-plans-codex-executes/app-board.png" alt="The AI team delivery board — Planning / Executing / Pending Review / Done" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="typical-scenarios">Typical scenarios<a href="https://udctl.com/blog/2026/07/19/claude-plans-codex-executes/#typical-scenarios" class="hash-link" aria-label="Direct link to Typical scenarios" title="Direct link to Typical scenarios">​</a></h3>
<ul>
<li><strong>Queue work before bed, review after breakfast.</strong> Drop three feature requests on the planner at night; wake up to split subtasks and several <code>pending</code> implementations, each with its own commits.</li>
<li><strong>Slice a big refactor.</strong> Claude writes the migration plan and cuts it into 20 small tasks; Codex executes them one by one — roll back exactly the step that goes wrong.</li>
<li><strong>A bug-triage pipeline.</strong> The planner reproduces, localizes, and writes up the root cause before delegating the fix — the executor gets "what to change, why, and how to verify," not "there's a bug, fix it."</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="the-combo-is-yours-to-choose">The combo is yours to choose<a href="https://udctl.com/blog/2026/07/19/claude-plans-codex-executes/#the-combo-is-yours-to-choose" class="hash-link" aria-label="Direct link to The combo is yours to choose" title="Direct link to The combo is yours to choose">​</a></h3>
<p>Claude + Codex is just one pairing. UnDercontrol is not tied to any AI tool — Claude Code, Codex, OpenCode, or any terminal-based agent can be a lead or a member, because each of them reaches the shared task board through the same <a href="https://udctl.com/docs/cli-ai-integration/">AI agent CLI</a>. Roles are defined by prompts and skills; tools by Agent CLI configuration. Whatever subscriptions you hold and whatever each model is best at, the orchestration is in your hands.</p>
<p>One more thing: the progress agents write back lives in the same Markdown system as everything you write by hand — tasks, notes, comments, even finance records share one editing experience.</p>
<hr>
<p>Want to try it? <a href="https://oatnil.com/docs/download" target="_blank" rel="noopener noreferrer">Download UnDercontrol</a>, start the daemon, create a Team, and @mention the lead on your first task.</p>]]></content:encoded>
            <category>Feature</category>
            <category>agents</category>
        </item>
        <item>
            <title><![CDATA[Virtual Path: Where Tasks Settle into Knowledge and Docs]]></title>
            <link>https://udctl.com/blog/2026/07/18/virtual-path-tasks-become-knowledge/</link>
            <guid>https://udctl.com/blog/2026/07/18/virtual-path-tasks-become-knowledge/</guid>
            <pubDate>Sat, 18 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Tasks shouldn't die at Done. Virtual Path files finished tasks into a curated knowledge tree, agents distill many tasks into one doc, and git-style vault sync checks the whole tree out as offline, Obsidian-native Markdown.]]></description>
            <content:encoded><![CDATA[<p>I've watched too many tasks die the moment they hit "Done." The card gets archived and nobody ever opens it again — why we designed it that way, what pitfalls we hit, how it was verified, all buried inside. Two months later someone asks, and you retell it from memory.</p>
<p>Building UnDercontrol, I've held onto one conviction: a task shouldn't be disposable. Its body is already a Markdown document, its Notes record how it evolved step by step, and the comments keep the arguments from back then. Throwing all of that away when the work is done is just wasteful.</p>
<p>That's why Virtual Path exists.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/virtual-path-tasks-become-knowledge/concept-1.png" alt="A task's endpoint isn't Done — it's a place in your knowledge tree" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="give-a-task-a-place">Give a task a place<a href="https://udctl.com/blog/2026/07/18/virtual-path-tasks-become-knowledge/#give-a-task-a-place" class="hash-link" aria-label="Direct link to Give a task a place" title="Direct link to Give a task a place">​</a></h3>
<p>The idea is simple: every task, every uploaded file, even every Skill can carry a virtual path like <code>/engineering/backend/</code>. Everything with a path forms one tree — the Explorer. It looks like a file manager, but there are no real directories underneath. Wherever a path exists, a "folder" exists; delete the contents and the folder disappears on its own. You never have to maintain a pile of empty directories.</p>
<p>When a task is done and worth keeping, drag it into <code>/decisions/</code> or <code>/engineering/</code> — it turns from a finished card into a document in your knowledge base. No copy-pasting into another note app; the Notes and comments stay attached.</p>
<p>And the ones not worth keeping? Leave them. Unfiled tasks sit at the tree root and in the "Unfiled" area, never mixing into the folders you've organized. The tree is curated, not accumulated — you decide what earns a place.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/virtual-path-tasks-become-knowledge/explorer.jpg" alt="Explorer tree + task as document: the knowledge tree on the left, a settled architecture doc on the right" class="img_ev3q"></p>
<p>Diagrams, screenshots, whiteboard photos can live at the same path too, so a topic's text and assets sit side by side — no more digging through a cloud drive for "that one image."</p>
<p>By the way, this isn't just about tasks. Every writing surface in UnDercontrol — tasks, notes, expense records, account memos — shares the same Markdown editor, so any content can settle down the same way.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="many-tasks-one-document">Many tasks, one document<a href="https://udctl.com/blog/2026/07/18/virtual-path-tasks-become-knowledge/#many-tasks-one-document" class="hash-link" aria-label="Direct link to Many tasks, one document" title="Direct link to Many tasks, one document">​</a></h3>
<p>The more common case is this: a topic wraps up after seven or eight tasks, each holding a few scattered findings, none of them usable as a document on its own.</p>
<p>These days I hand that job to an agent. Claude Code, Codex, OpenCode — any terminal agent can read through the batch via the ud CLI, distill it into one new document, and file it into the right folder of the tree. The original tasks stay untouched, kept as raw material you can always trace back to. What the knowledge base keeps is the distilled conclusion, not a pile of process.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="sync-it-local--offline-and-yours">Sync it local — offline, and yours<a href="https://udctl.com/blog/2026/07/18/virtual-path-tasks-become-knowledge/#sync-it-local--offline-and-yours" class="hash-link" aria-label="Direct link to Sync it local — offline, and yours" title="Direct link to Sync it local — offline, and yours">​</a></h3>
<p>Knowledge living on a server is only step one. My own habit: documents must be able to land on my own disk, editable with whatever editor I like.</p>
<p>The ud CLI syncs git-style (I wrote about the basics before: <a href="https://oatnil.com/blog/2026/07/07/ud-pull-push/" target="_blank" rel="noopener noreferrer">Manage Your Tasks Like git: ud pull / push</a>):</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ud init /Knowledge/   # bind the current folder to a virtual path</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud pull               # tasks become local .md files</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud push               # local edits go back to the server</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud status             # see what differs</span><br></span></code></pre></div></div>
<p>The default vault layout is Obsidian-native:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">MyVault/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├─ engineering/backend/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  └─ SSE reconnect backoff.md     ← one .md = one task</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├─ notes/SSE reconnect backoff/    ← the task's Notes, grouped per task</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│  └─ soak test results.md</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├─ attachments/diagram.png         ← resources (--fetch-attachments)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">└─ .udignore</span><br></span></code></pre></div></div>
<p>After a pull, this is a fully offline local knowledge base. Plain Markdown on your disk — readable and editable without a connection, works fine on a plane, one <code>ud push</code> after landing. The data is yours. Both copies.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/virtual-path-tasks-become-knowledge/concept-2.png" alt="vault sync: the virtual path tree on the server ⇄ a local Obsidian vault" class="img_ev3q"></p>
<p>A lot of care went into the details; a few worth mentioning: ud writes exactly one <code>ud-id</code> line into the frontmatter and leaves the rest untouched; rename a file locally and the task title follows after a push; <code>[[wikilinks]]</code> written in Obsidian get translated into ud's <code>task://</code> links on push and back again on pull, so clicking works on both sides; files you don't want synced go into <code>.udignore</code>, gitignore syntax; conflicts are handled git-style — items changed on both sides get flagged and skipped by default, never silently overwritten. And moving over from the older folder structure is a single <code>ud migrate-layout</code> away.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="let-an-agent-tidy-your-tree">Let an agent tidy your tree<a href="https://udctl.com/blog/2026/07/18/virtual-path-tasks-become-knowledge/#let-an-agent-tidy-your-tree" class="hash-link" aria-label="Direct link to Let an agent tidy your tree" title="Direct link to Let an agent tidy your tree">​</a></h3>
<p>Every knowledge base gets messy over time. Folders grow deeper, names drift, stale docs mix with fresh ones — mine too.</p>
<p>But tidying is exactly the kind of bulk work agents are best at. The vault is just a local folder: after <code>ud pull</code>, let an agent read the whole tree — restructure folders, merge duplicates, unify naming, move the stale stuff into an archive. All it does is ordinary file operations; you review the diff with git or <code>ud status</code>, then <code>ud push</code> when satisfied. The Explorer on the web immediately shows the tidied shape.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/virtual-path-tasks-become-knowledge/concept-3.png" alt="Layered model: the content layer is your vault, the collaboration layer is ud's overlay" class="img_ev3q"></p>
<p>What makes it safe to let an agent loose here is the clean layering: the vault layer is content — plain text, git-able, anyone can edit it; status, boards, comments, and the Notes timeline are ud's collaboration metadata sitting on top, the way PR comments never get written into the git tree. The agent touches the content layer; the collaboration history loses nothing.</p>
<p>And a tidied tree isn't locked to one machine. Web, desktop, and mobile all see the same tree: skim an ADR on your phone during the commute, edit properly on the web at your desk, hand batch work to the CLI and agents in the terminal. Organize once, use it anywhere.</p>
<hr>
<p>A task's endpoint shouldn't be "Done." Give the things worth keeping a place in your knowledge tree — one drag, one command, or hand it to your agent.</p>]]></content:encoded>
            <category>Feature</category>
            <category>Guide</category>
        </item>
        <item>
            <title><![CDATA[Growing with UnDercontrol: Time Compounds, and So Does Knowledge]]></title>
            <link>https://udctl.com/blog/2026/07/17/knowledge-compounding/</link>
            <guid>https://udctl.com/blog/2026/07/17/knowledge-compounding/</guid>
            <pubDate>Fri, 17 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[I studied architecture, switched to software, and moved to Singapore. The one constant was writing things down — through Joplin, Obsidian, and finally a tool I built myself. On the compound interest of knowledge.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/knowledge-compounding/concept-1.png" alt="The compounding curve of knowledge" class="img_ev3q"></p>
<p>I studied architecture. Then I switched careers and became a programmer. Later I left everything familiar behind and moved to Singapore, where I now work as a fullstack / DevOps engineer.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="career-switchers-know-what-accumulation-really-means">Career switchers know what "accumulation" really means<a href="https://udctl.com/blog/2026/07/17/knowledge-compounding/#career-switchers-know-what-accumulation-really-means" class="hash-link" aria-label="Direct link to Career switchers know what &quot;accumulation&quot; really means" title="Direct link to Career switchers know what &quot;accumulation&quot; really means">​</a></h3>
<p>Switching careers means starting from zero: no four years of CS coursework to lean on — every concept had to be chewed through on my own. That's when I picked up a habit: write down every pitfall I hit, every concept that finally clicked, every config I got working. Not out of diligence — out of fear. If I didn't write it down today, I'd pay for it again tomorrow.</p>
<p>Looking back, that habit is the highest-return investment I've ever made. It carried me through the career switch, through job changes, through moving countries, and eventually through building my own product. Knowledge, like money, compounds — but only if you have an account for it to grow in.</p>
<p>Over the years, my "account" changed three times: Joplin → Obsidian → UnDercontrol. Each migration wasn't chasing something shiny — it was my understanding of <em>how knowledge should work</em> leveling up.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/knowledge-compounding/concept-2.png" alt="Two journeys, one habit" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="the-joplin-era-get-things-written-down-collect">The Joplin era: get things written down (Collect)<a href="https://udctl.com/blog/2026/07/17/knowledge-compounding/#the-joplin-era-get-things-written-down-collect" class="hash-link" aria-label="Direct link to The Joplin era: get things written down (Collect)" title="Direct link to The Joplin era: get things written down (Collect)">​</a></h3>
<p>My first stop was Joplin. Open source, free, Markdown, encrypted sync — for someone just getting serious about notes, it solved the most important problem: <strong>making "write it down" actually happen</strong>.</p>
<p>My early study notes, interview prep, and debugging logs from the career-switch years all piled up there.</p>
<p>But over time, the notes felt like drawers: easy to put things in, but every note lived in isolation. The notes themselves sat inside the app's database; finding things meant folders and keyword search; there were no real connections between notes. The knowledge was stored — but it just lay there. It didn't appreciate.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="the-obsidian-era-connect-the-knowledge-connect">The Obsidian era: connect the knowledge (Connect)<a href="https://udctl.com/blog/2026/07/17/knowledge-compounding/#the-obsidian-era-connect-the-knowledge-connect" class="hash-link" aria-label="Direct link to The Obsidian era: connect the knowledge (Connect)" title="Direct link to The Obsidian era: connect the knowledge (Connect)">​</a></h3>
<p>So I moved to Obsidian: plain local Markdown files, bidirectional links, a knowledge graph, a rich plugin ecosystem.</p>
<p>This was a genuine upgrade. For the first time, my notes went from "a pile of files" to "a network" — writing something new naturally pulled old notes in. My vault passed a thousand notes in this era, and I could feel the accumulated weight supporting me in retrospectives, in writing, in technical decisions.</p>
<p>The career switch and the move abroad — the two biggest jumps of my life — were both backed by the same body of notes. That was the first time "the compound interest of knowledge" stopped being a platitude for me.</p>
<p>But the deeper the accumulation, the more visible the cracks:</p>
<ul>
<li><strong>My knowledge was locked in a local folder.</strong> The classic scene: debugging a nasty issue at the office, I suddenly remember — I've hit this exact pit before, and the fix is written up in detail... in Obsidian, on my computer at home. End of story. I can't sync my entire vault onto every machine I touch.</li>
<li><strong>The graph is a beautiful lie.</strong> Mention <code>[[some concept]]</code> anywhere in a note and it enters the graph. A throwaway reference and a carefully built relationship get equal-weight edges. The more nodes, the prettier the graph — and the less it actually tells you.</li>
<li><strong>Attachments scattered everywhere.</strong> Architecture diagrams, flowcharts, PDFs spread across assets folders; move a folder and the links break.</li>
<li><strong>Every sync option hurts somewhere.</strong> Paid sync still needs a client installed; Syncthing can't go on a work machine; Git sync is technically possible and experientially miserable.</li>
</ul>
<p>What I needed was simple: <strong>a web page. Log in, full editing, knowledge available anywhere.</strong></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="undercontrol-make-knowledge-callable-compound">UnDercontrol: make knowledge callable (Compound)<a href="https://udctl.com/blog/2026/07/17/knowledge-compounding/#undercontrol-make-knowledge-callable-compound" class="hash-link" aria-label="Direct link to UnDercontrol: make knowledge callable (Compound)" title="Direct link to UnDercontrol: make knowledge callable (Compound)">​</a></h3>
<p>No tool on the market was shaped quite like what I wanted, so I decided to build one. It's also an experiment: how far can one person, plus AI, take a product?</p>
<p>UnDercontrol is my answer to every crack from the first two eras:</p>
<ul>
<li><strong>Available anywhere</strong>: log in from any browser; plus a CLI and a desktop app, with <code>ud pull / ud push</code> syncing tasks to local Markdown files bidirectionally — the convenience of the web <em>and</em> the reassurance of local files.</li>
<li><strong>Links I build explicitly</strong>: only deliberately created relationships (peer / parent-child) enter the knowledge graph. Every edge means something. High signal, low noise.</li>
<li><strong>Centralized resources</strong>: images, PDFs, and attachments stay attached to their context, but you can browse, search, and reuse them all in one unified library.</li>
<li><strong>Not just tasks</strong>: UnDercontrol's Markdown editor is shared across every text surface — tasks, notes, finance records, documents, all the same editing experience. A task can be a document, a decision record, a blog draft.</li>
<li><strong>AI-native</strong>: the knowledge base is open to AI. Claude Code, Codex, OpenCode, or any terminal-based agent can read and write the same knowledge through the CLI. In fact, the post you're reading right now was written by an AI agent inside an UnDercontrol task, drawing on my past notes.</li>
</ul>
<p>This is the third stage of compounding: knowledge is no longer just <em>stored</em> or <em>connected</em> — it can be <strong>called</strong>. By future me, and by the AI working on my behalf.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="time-compounds-into-strength">Time compounds into strength<a href="https://udctl.com/blog/2026/07/17/knowledge-compounding/#time-compounds-into-strength" class="hash-link" aria-label="Direct link to Time compounds into strength" title="Direct link to Time compounds into strength">​</a></h3>
<p>From architectural drawings to code, from China to Singapore, from Joplin to Obsidian to building my own tool — the career changed, the city changed, the software changed. The habit didn't: <strong>write down what you figured out today</strong>.</p>
<p>The essence of compounding isn't the rate of return. It's not stopping. The point of a tool is to make "not stopping" easy enough.</p>
<p>For me, UnDercontrol is both a product and the container for that habit. I use it every day to manage my tasks, notes, and finances — and to manage the building of UnDercontrol itself. I grow with it, and it remembers everything that time has laid down.</p>
<p>If you've migrated through Joplin, Obsidian, and a dozen other tools — if you also believe knowledge compounds — come try <a href="https://oatnil.com/" target="_blank" rel="noopener noreferrer">UnDercontrol</a>.</p>]]></content:encoded>
            <category>Story</category>
        </item>
        <item>
            <title><![CDATA[Why Your Board Won't Show What You Actually Care About]]></title>
            <link>https://udctl.com/blog/2026/07/16/kanban-boards/</link>
            <guid>https://udctl.com/blog/2026/07/16/kanban-boards/</guid>
            <pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[In most kanban tools a column is the status enum, hardwired — so every board looks the same. In UnDercontrol a column is a query, drag actions derive themselves, and you don't learn the syntax: you describe the board and an agent in your terminal builds it, dry-run first.]]></description>
            <content:encoded><![CDATA[<p>Monday morning, you open the board. What you want to know is what's about to blow up: what's already overdue, what's due this week. The board only tells you what's In Progress.</p>
<p>There's a lot it won't show you. You want to see work by client, by sprint, by document type — there's no option for that. You want to pull the unassigned tasks into their own column — also no. You want one column watching overdue items, one watching this week's urgent ones, one just holding whatever was touched most recently, all mixed together — can't do it. The only things you can really change are the column names and their order.</p>
<p>So the board shows you the tool's idea of your work, not yours. The dimensions you care about, it can't hold a single one.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/kanban-boards/concept-hero.png" alt="On the left, the three-column board every tool hands you, which can't hold the dimensions you care about; on the right, the board you described in one sentence and an agent built to fit" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="why-most-tools-work-this-way">Why most tools work this way<a href="https://udctl.com/blog/2026/07/16/kanban-boards/#why-most-tools-work-this-way" class="hash-link" aria-label="Direct link to Why most tools work this way" title="Direct link to Why most tools work this way">​</a></h3>
<p>This isn't one tool's problem. Switch to another and you'll probably still get Todo, In Progress, Done. Most tools look alike because they share an assumption underneath: <strong>a column isn't something you define — it is the status enum, hardwired.</strong> What you drag a card into is an enum value, not a viewpoint you decide.</p>
<p>Tools have a reason for this. Flexibility usually costs you tuition. A tool that lets a column be anything tends to hand you a query language, a docs site, and a weekend along with it. Most people don't want to pay that, which is fair, so the tool decides for you: three columns, don't overthink it.</p>
<p>UnDercontrol takes a different route. It's a task and knowledge management tool where the day-to-day operations can be handled by an AI agent in your terminal and a command line. On a board, that means a column is no longer equal to a status — it's a condition: what kind of task belongs here. Status is just one condition among many, with no special standing. You can split by due date, by person, by tag, by your own custom field, because to the system these are all the same thing.</p>
<p>As for that tuition, we'll get to how it's waived.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="even-the-out-of-the-box-board-isnt-only-statuses">Even the out-of-the-box board isn't only statuses<a href="https://udctl.com/blog/2026/07/16/kanban-boards/#even-the-out-of-the-box-board-isnt-only-statuses" class="hash-link" aria-label="Direct link to Even the out-of-the-box board isn't only statuses" title="Direct link to Even the out-of-the-box board isn't only statuses">​</a></h3>
<p>That sounds like a promise until you see it. Create a new board and two of the six default columns aren't statuses at all:</p>
<table><thead><tr><th>Column</th><th>What it holds</th></tr></thead><tbody><tr><td>To Do</td><td>Todo tasks</td></tr><tr><td>In Progress</td><td>In-progress tasks</td></tr><tr><td>Done</td><td>Done tasks</td></tr><tr><td>Archive</td><td>Archived tasks</td></tr><tr><td>Recent Created</td><td>The most recently created, whatever the status</td></tr><tr><td>Recent Updated</td><td>The most recently touched, whatever the status</td></tr></tbody></table>
<p>The last two have no filter at all — they just sort by time, newest first. They don't belong to any status. They're a viewpoint.</p>
<p>In a tool where a column equals a status, columns like those are hard to express. Here they're unremarkable, part of the default setup.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/kanban-boards/default-board.png" alt="The six default columns; the rightmost two, Recent Created and Recent Updated, sort only by time and belong to no status" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="so-what-about-the-tuition">So what about the tuition?<a href="https://udctl.com/blog/2026/07/16/kanban-boards/#so-what-about-the-tuition" class="hash-link" aria-label="Direct link to So what about the tuition?" title="Direct link to So what about the tuition?">​</a></h3>
<p>If it's this flexible, doesn't that mean I have to learn a syntax before I can assemble the board I want?</p>
<p><strong>No. You just say what you want.</strong></p>
<blockquote>
<p>You: build me a board that tracks deadlines — overdue, this week, next week, and no deadline set. Four columns.</p>
</blockquote>
<p>That's it. Claude Code, Codex, OpenCode, or any agent running in your terminal takes it from there. What the fields are called, how the conditions are written, what the YAML looks like, which commands to run — all of that is its job.</p>
<p>The query language does exist, and it's complete, but it isn't your homework. It's the agent's working language. You hire someone to renovate; you don't need to lay the bricks yourself.</p>
<p>If you're curious, here's what it wrote for you, without you typing a character:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Board</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Deadline Radar</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">columns</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Overdue</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">query</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"deadline &lt; now AND status != 'done'"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> This Week</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">query</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"deadline BETWEEN now AND +7d"</span><br></span></code></pre></div></div>
<p>Nice if you can read it, and it makes no difference to your use if you can't. That's the point.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="and-you-dont-have-to-teach-the-agent-either">And you don't have to teach the agent either<a href="https://udctl.com/blog/2026/07/16/kanban-boards/#and-you-dont-have-to-teach-the-agent-either" class="hash-link" aria-label="Direct link to And you don't have to teach the agent either" title="Direct link to And you don't have to teach the agent either">​</a></h4>
<p>Normally, to get an agent to use some tool, you first write it a manual: how to call it, what the fields are named, what the format is. You teach it once, the tool ships an update, the manual goes stale, and the agent starts making things up.</p>
<p>UnDercontrol's command line ships with a recipe catalog. The agent doesn't read source and you don't feed it docs — it asks <code>ud cook board</code> and the tool tells it how to build a board, complete with a runnable example. Its whole learning step is one command. The tool knows how to use itself, so you neither learn it nor teach it.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="drag-a-card-in-and-the-board-makes-it-true">Drag a card in, and the board makes it true<a href="https://udctl.com/blog/2026/07/16/kanban-boards/#drag-a-card-in-and-the-board-makes-it-true" class="hash-link" aria-label="Direct link to Drag a card in, and the board makes it true" title="Direct link to Drag a card in, and the board makes it true">​</a></h3>
<p>There's another half to the flexibility: how these columns respond to a drag is something no one has to configure.</p>
<p>The logic is plain. A column says it holds in-progress tasks. Drop a card into it, and the only reasonable meaning is to make that true, so the status flips to in-progress on its own. Another column says it holds cards tagged blog; drop one in and the tag is added, drag it out and the tag comes off.</p>
<p>You only said what each column holds; how to do it, the system works out itself. There's a quiet benefit to that: a column's description and its behavior can't drift apart, because the second is derived from the first — there's no second copy of the config to go wrong.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="it-knows-when-not-to-act">It knows when not to act<a href="https://udctl.com/blog/2026/07/16/kanban-boards/#it-knows-when-not-to-act" class="hash-link" aria-label="Direct link to It knows when not to act" title="Direct link to It knows when not to act">​</a></h3>
<p>Say a column is for what's already overdue. You drop a card into it — should the system change the deadline to yesterday?</p>
<p>That's a guess. It's probably not what you meant; you may have just dropped it in the wrong place. So it does nothing. Any condition that can't be satisfied by one honest write — time ranges, sorting, fuzzy matching — generates no action at all. Those columns become views you look at, and you can't break them by dragging.</p>
<p>Here's the real output, a four-column deadline radar:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">  Columns:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    1. Overdue</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">       query: deadline &lt; now AND status != 'done'</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    2. This Week</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">       query: deadline BETWEEN now AND +7d</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    3. Recently Touched</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">       query: ORDER BY updated_at DESC</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    4. Urgent</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">       query: tags CONTAINS 'urgent'</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">       on enter: ADD tags 'urgent'  (auto)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">       on exit: REMOVE tags 'urgent'  (auto)</span><br></span></code></pre></div></div>
<p>The first three — overdue, this week, recently touched — have no actions at all; they're read-only views. The fourth, Urgent, is defined by a tag, which one write can satisfy, so dropping a card in adds the tag and dragging it out removes it, all marked <code>(auto)</code>, none of it configured by hand.</p>
<p>The system won't invent a write it can't account for. That restraint isn't a shortfall in capability; it's exactly what lets "a column can be anything" be handed to you safely.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="you-see-the-real-thing-before-it-exists">You see the real thing before it exists<a href="https://udctl.com/blog/2026/07/16/kanban-boards/#you-see-the-real-thing-before-it-exists" class="hash-link" aria-label="Direct link to You see the real thing before it exists" title="Direct link to You see the real thing before it exists">​</a></h3>
<p>Handing board-building to an agent, the natural worry is what happens if it gets it wrong and makes a mess of your workspace.</p>
<p>It won't, because there's a gate in the middle. The agent runs a preview first: the system renders the board exactly as it would be created — what each column holds, what a drag would do, all laid out — and then stores nothing.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">Dry run — board would be created (nothing persisted):</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  Name:  Blog Pipeline</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  Type:  shared  (a group "Board: Blog Pipeline" would be created for sharing)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  Default tags: blog</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  Columns:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    1. Idea</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">       query: status = 'todo' AND tags CONTAINS 'blog'</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">       on enter: SET status = 'todo'  (auto)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">       on enter: ADD tags 'blog'  (auto)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">       on exit: REMOVE tags 'blog'  (auto)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    2. Drafting</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">       query: status = 'in-progress' AND tags CONTAINS 'blog'</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">       on enter: SET status = 'in-progress'  (auto)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">       ...</span><br></span></code></pre></div></div>
<p>Note the <code>nothing persisted</code>. At this point nothing has happened yet.</p>
<p>So the whole thing becomes: you describe it, the agent previews it, you look at the real result, and if it's off you say change the review column to pending, it previews again, you nod, it lands.</p>
<p>The key is that <strong>you're reviewing the system's output, not the agent's account of its plan</strong>. The agent can say the wrong thing, but it can't fake this step's output. So the trust here isn't built on the agent never being wrong — it's that being wrong is fine, because you see it first.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/kanban-boards/deadline-radar.png" alt="The deadline radar board an agent built — overdue, this week, and next week grouped by time window, cards showing due dates and assignees" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="where-this-shows-up">Where this shows up<a href="https://udctl.com/blog/2026/07/16/kanban-boards/#where-this-shows-up" class="hash-link" aria-label="Direct link to Where this shows up" title="Direct link to Where this shows up">​</a></h3>
<p>Enough mechanism. Here's where it actually lives.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="the-monday-morning-deadline-radar">The Monday-morning deadline radar<a href="https://udctl.com/blog/2026/07/16/kanban-boards/#the-monday-morning-deadline-radar" class="hash-link" aria-label="Direct link to The Monday-morning deadline radar" title="Direct link to The Monday-morning deadline radar">​</a></h4>
<p>You've got seven or eight things going at once, and on Monday morning there's only one thing you want to know: what's about to blow up.</p>
<p>Tell an agent to build a board that tracks deadlines — overdue, this week, next week, no deadline set — and the four columns are there. Every Monday after that, whether the overdue column has anything in it is a glance.</p>
<p>This board has a reassuring property too: those columns are computed from time, so you can't break them. If you fumble a card into Overdue, the system won't take it upon itself to change your deadline to yesterday — it does nothing. Columns like these are made to be looked at.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="a-team-workload-view">A team workload view<a href="https://udctl.com/blog/2026/07/16/kanban-boards/#a-team-workload-view" class="hash-link" aria-label="Direct link to A team workload view" title="Direct link to A team workload view">​</a></h4>
<p>Anyone running a team has had the question: who's buried, and who's actually free?</p>
<p>A board with one column per teammate answers it, with a last column for the unassigned. Open it at standup and whose column is stacked, which work is still hanging, you can see without asking.</p>
<p>A board like this is for looking. It flattens work scattered everywhere onto one screen. When you actually need to change who does what, you go back to the task itself.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="a-content-pipeline">A content pipeline<a href="https://udctl.com/blog/2026/07/16/kanban-boards/#a-content-pipeline" class="hash-link" aria-label="Direct link to A content pipeline" title="Direct link to A content pipeline">​</a></h4>
<p>Blogging, videos, announcements — underneath they're the same pipeline: idea, drafting, review, published.</p>
<p>This is a board you drag on. Move a card from Drafting to Review and the status follows, the tag is added, because each column has stated what it holds and the system derives the rest. You just drag; it makes what you said come true.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="the-plan-changed-so-say-one-more-sentence">The plan changed, so say one more sentence<a href="https://udctl.com/blog/2026/07/16/kanban-boards/#the-plan-changed-so-say-one-more-sentence" class="hash-link" aria-label="Direct link to The plan changed, so say one more sentence" title="Direct link to The plan changed, so say one more sentence">​</a></h4>
<p>Two weeks in, you notice there's no home for archived items. Tell an agent to add an archived column; it takes the board's full current state, previews the change for you, and only lands it once you nod.</p>
<p>In another tool, changing a board means clicking through it again. Here it's a sentence, and getting it wrong doesn't matter, because you saw it before it took effect.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="once-its-built-its-still-yours">Once it's built, it's still yours<a href="https://udctl.com/blog/2026/07/16/kanban-boards/#once-its-built-its-still-yours" class="hash-link" aria-label="Direct link to Once it's built, it's still yours" title="Direct link to Once it's built, it's still yours">​</a></h3>
<p>That "add an archived column" works because the board isn't a trail left by clicks — it's something you can lift out whole.</p>
<p>Export it, edit it, put it back — all fine. That's also why the agent never has to guess when it changes the board: it gets the full current state, not a reconstruction from the UI.</p>
<p>A side benefit: the board can go into git, be handed to a colleague, be copied to another project. It's an asset of yours, not config locked inside some screen.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="your-board-your-design-none-of-the-homework">Your board, your design, none of the homework<a href="https://udctl.com/blog/2026/07/16/kanban-boards/#your-board-your-design-none-of-the-homework" class="hash-link" aria-label="Direct link to Your board, your design, none of the homework" title="Direct link to Your board, your design, none of the homework">​</a></h3>
<p>Boards all look alike because the tool made the trade-off for you: simple but dumb, or powerful but you have to learn it.</p>
<p>UnDercontrol's answer is that you decide what you want, the agent handles how, and the system keeps it from doing the wrong thing.</p>
<p>So the four-column deadline radar, the by-person team view, the board sliced along your own business dimension — none of these are features someone designed ahead of time. They're just the result of a sentence you said.</p>
<p>If you want to try it, describe the board you want to your agent. It knows what to do from there.</p>]]></content:encoded>
            <category>Feature</category>
            <category>kanban</category>
        </item>
        <item>
            <title><![CDATA[AI Doesn't Just Work Inside UnDercontrol — It Runs UnDercontrol Itself]]></title>
            <link>https://udctl.com/blog/2026/07/14/agent-workbench-everything-as-code/</link>
            <guid>https://udctl.com/blog/2026/07/14/agent-workbench-everything-as-code/</guid>
            <pubDate>Tue, 14 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[The agent-workbench trend stops at the execution layer. UnDercontrol closes the gap with Everything as Code — tasks, boards, and skills are all Markdown + CLI, so one agent is both a worker and an admin that operates the platform itself.]]></description>
            <content:encoded><![CDATA[<p>The "agent workbench" idea is heating up. More and more people agree: an AI agent should act like a teammate — pick up tasks, report progress, leave durable output — not just answer questions in a chat box.</p>
<p>But there's a further question that rarely gets asked: <strong>can the AI operate the platform itself?</strong> Not just do work inside the system, but <em>run</em> it for you — tidy up boards, archive stale tasks, maintain tags, distill hard-won steps into a reusable capability.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/agent-workbench-everything-as-code/concept-hero.png" alt="Not just a worker — an operator of the platform itself" class="img_ev3q"></p>
<p>UnDercontrol's answer comes from its core idea: <strong>Everything as Code</strong>. Because of it, AI here is both a teammate that does the work and an admin that keeps the system in order.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="the-core-everything-as-code--the-platform-is-the-ais-control-surface">The core: Everything as Code — the platform is the AI's control surface<a href="https://udctl.com/blog/2026/07/14/agent-workbench-everything-as-code/#the-core-everything-as-code--the-platform-is-the-ais-control-surface" class="hash-link" aria-label="Direct link to The core: Everything as Code — the platform is the AI's control surface" title="Direct link to The core: Everything as Code — the platform is the AI's control surface">​</a></h3>
<p>In UnDercontrol, tasks, notes, boards, skills, custom fields — <strong>all of it is plain Markdown + YAML frontmatter</strong>, managed declaratively through one kubectl-style <code>ud</code> CLI:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># Creating or updating a task is just applying some text</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cat &lt;&lt;'EOF' | ud apply -f -</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">---</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">title: Ship v1</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">status: in-progress</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">tags: [release, urgent]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">metadata:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  priority: high</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">---</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Pre-release checklist…</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">EOF</span><br></span></code></pre></div></div>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/agent-workbench-everything-as-code/concept-eoc.png" alt="Everything as Code — tasks, boards, skills, metadata are all Markdown + one CLI" class="img_ev3q"></p>
<p>Here's the key: <strong>the same abilities an AI agent uses to read and write code — run commands, read and write text — work unchanged on the whole platform.</strong> It can create tasks, break out subtasks, change statuses, apply tags, build boards (a board is just a saved query), distill a repeated prompt into a new Skill, patch custom fields… Those management actions are themselves code.</p>
<p>In short: the AI isn't only a <strong>worker</strong> inside the platform — it can also be an <strong>admin</strong>, because managing the platform is itself programmable.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/agent-workbench-everything-as-code/app-board.png" alt="An Engineering board — columns are saved queries the agent can build and reshape" class="img_ev3q"></p>
<p><strong>Typical scenarios</strong></p>
<ul>
<li>"Archive everything marked done this week, then spin up a new board filtered by the <code>release</code> tag" — the agent just does it with <code>ud apply</code> / <code>ud patch</code>, no clicking around.</li>
<li>"Save the debugging steps you just walked through as a Skill" — the agent turns the method from the conversation into a reusable Skill with <code>ud apply</code>, and the whole team can use it next time.</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="breaking-it-down-the-undercontrol-agent-workbench">Breaking it down: the UnDercontrol agent workbench<a href="https://udctl.com/blog/2026/07/14/agent-workbench-everything-as-code/#breaking-it-down-the-undercontrol-agent-workbench" class="hash-link" aria-label="Direct link to Breaking it down: the UnDercontrol agent workbench" title="Direct link to Breaking it down: the UnDercontrol agent workbench">​</a></h3>
<p><strong>Agents as teammates.</strong> Write a requirement as a task, @mention an agent, and it claims the task, reads the description, notes, and links as context, writes code, runs tests, commits, and writes progress back into the notes. What it picks up isn't a black-box issue — it's <strong>structured Markdown it can also edit itself</strong>.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/agent-workbench-everything-as-code/app-task.png" alt="Task detail — the agent reads the description and writes progress + commits back into the notes timeline" class="img_ev3q"></p>
<p><strong>Daemon / runtime.</strong> The agent runs on your own dev machine — a background <strong>daemon</strong> stays connected over SSE and streams the terminal output back in real time. This remote workspace has been in the works since April 2026; today daemon registration, SSE, multi-surface triggering, and session viewing are all in place — you can start and monitor from the web, the desktop app, the CLI, even your phone.</p>
<p><strong>Reusable skills.</strong> A Skill is a reusable prompt template stored on the server and injected into any agent via <code>ud prompt</code>. And because everything is code, <strong>an agent can write and revise Skills itself with <code>ud apply</code></strong> — the skill library is a programmable asset that compounds over time.</p>
<p><strong>Not tied to any one agent.</strong> UnDercontrol is agent-agnostic: an Agent CLI command template plugs in Claude Code, Codex, Cursor, Hermes, or any terminal-based agent. Whichever you pick, it drives the same <a href="https://udctl.com/docs/cli-ai-integration/">AI agent CLI</a> — so you can swap models or tools without swapping workbenches.</p>
<p><strong>You own your data.</strong> Tasks and notes are plain Markdown files with YAML frontmatter, defined by an open <a href="https://github.com/oatnil-top/ud-schemas" target="_blank" rel="noopener noreferrer">JSON Schema</a> (MIT) — version them with Git, export anytime, and self-host (Docker Compose / K8s, SQLite / Postgres).</p>
<blockquote>
<p><strong>Not just tasks</strong>: this Markdown editor and the everything-as-code foundation run through every surface of UnDercontrol — tasks, notes, even expenses and resources. The agent workbench isn't a separate module; it's a capability that grows out of that foundation.</p>
</blockquote>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="why-ai-can-operate-the-platform-itself-matters">Why "AI can operate the platform itself" matters<a href="https://udctl.com/blog/2026/07/14/agent-workbench-everything-as-code/#why-ai-can-operate-the-platform-itself-matters" class="hash-link" aria-label="Direct link to Why &quot;AI can operate the platform itself&quot; matters" title="Direct link to Why &quot;AI can operate the platform itself&quot; matters">​</a></h3>
<p>In most tools, AI handles the <strong>execution layer</strong> (write code, run tests), while the <strong>management layer</strong> — tidying boards, archiving old tasks, maintaining a tag system, distilling experience into SOPs — still sits on humans.</p>
<p>When the platform itself is code, that line disappears: AI can share both execution and management. Your knowledge base is both <strong>content</strong> and a <strong>programmable control surface</strong>. You gain a teammate who does the work — and an admin who keeps the system in order.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/agent-workbench-everything-as-code/concept-layers.png" alt="Most tools stop at execution; here AI shares the management layer too" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="wrapping-up">Wrapping up<a href="https://udctl.com/blog/2026/07/14/agent-workbench-everything-as-code/#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping up" title="Direct link to Wrapping up">​</a></h3>
<p>The heart of an "agent workbench" is letting AI do the work reliably, leave durable output, and be directed. UnDercontrol goes one step further — via <strong>Everything as Code</strong>, it lets AI operate the platform itself:</p>
<ul>
<li>Tasks, boards, skills, and metadata are all plain Markdown + CLI-manageable code;</li>
<li>so the same agent can both <strong>work inside the platform</strong> (write code, report progress) and <strong>operate the platform itself</strong> (build boards, archive, write Skills, maintain structure);</li>
<li>all of it agent-agnostic, triggerable from any device, with your data your own.</li>
</ul>
<p>In UnDercontrol, AI is both a teammate and an admin.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/agent-workbench-everything-as-code/concept-cta.png" alt="Everything as Code — so AI can run the platform, not just work in it" class="img_ev3q"></p>]]></content:encoded>
            <category>Feature</category>
            <category>agents</category>
        </item>
        <item>
            <title><![CDATA[One Board, the Whole Project in View — A Week with Kanban]]></title>
            <link>https://udctl.com/blog/2026/07/13/one-board-whole-project/</link>
            <guid>https://udctl.com/blog/2026/07/13/one-board-whole-project/</guid>
            <pubDate>Mon, 13 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A story-driven walk through UnDercontrol's Kanban boards over one week — drag to change status, cards that hold a full Markdown doc plus notes and comments, custom columns that auto-tag and notify, saved-query columns beyond statuses, and shared boards for team alignment.]]></description>
            <content:encoded><![CDATA[<p>Monday, 9 a.m. You open UnDercontrol, glance at one board, and already know where this week starts.</p>
<p>This is your side project <strong>MorningRun</strong> — a running social app, currently in Sprint 12. Five columns line up across the board: Todo, In Progress, Review, Deployed, Done. What hasn't started, what's stuck, what's waiting on someone else — you can see it all without opening a single task. Let's walk through a whole week with this one board.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/kanban-story/hero.png" alt="One board, the whole project in view — drag a card, the status updates itself" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="monday-drag-from-todo-to-in-progress">Monday: drag from "Todo" to "In Progress"<a href="https://udctl.com/blog/2026/07/13/one-board-whole-project/#monday-drag-from-todo-to-in-progress" class="hash-link" aria-label="Direct link to Monday: drag from &quot;Todo&quot; to &quot;In Progress&quot;" title="Direct link to Monday: drag from &quot;Todo&quot; to &quot;In Progress&quot;">​</a></h3>
<p>You grab a card — "Implement Google OAuth login" — and drag it from <strong>Todo</strong> to <strong>In Progress</strong>. The moment you let go, the task's status flips to "In Progress" on its own. No dropdown, no form.</p>
<p>That's the heart of kanban in UnDercontrol: <strong>columns are the stages of your workflow, and dragging is the status change</strong>. Whichever column a card lands in, the task's status, tags, even custom fields update to match. And it's instant — the same task in your task list is already in sync.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/kanban-story/board-overview.png" alt="Sprint 12 board overview — Todo / In Progress / Review / Deployed / Done" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="tuesday-a-card-is-more-than-a-title">Tuesday: a card is more than a title<a href="https://udctl.com/blog/2026/07/13/one-board-whole-project/#tuesday-a-card-is-more-than-a-title" class="hash-link" aria-label="Direct link to Tuesday: a card is more than a title" title="Direct link to Tuesday: a card is more than a title">​</a></h3>
<p>You open that OAuth card. Inside isn't a single dry line — it's a full Markdown document: a requirements checklist, the technical approach, a couple of open questions. This is the task's living document, its single source of truth.</p>
<p>Below it you append a <strong>note</strong>: "Callback URL verified on the staging redirect — commit a1b2c3d." Notes are an append-only timeline that records how the work progressed, and each one keeps a full edit history you can roll back to anytime.</p>
<blockquote>
<p><strong>Not just tasks</strong>: UnDercontrol's editor is shared across every text surface. Task bodies, notes, expense memos, account notes — whatever you write, it's the same Markdown experience. Every card on the board is backed by a container that holds the complete picture.</p>
</blockquote>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/kanban-story/card-detail.png" alt="Card detail — Markdown body (requirements, open questions) and the notes timeline on the right" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="wednesday-customize-a-column-let-the-board-do-the-chores">Wednesday: customize a column, let the board do the chores<a href="https://udctl.com/blog/2026/07/13/one-board-whole-project/#wednesday-customize-a-column-let-the-board-do-the-chores" class="hash-link" aria-label="Direct link to Wednesday: customize a column, let the board do the chores" title="Direct link to Wednesday: customize a column, let the board do the chores">​</a></h3>
<p>The default columns are status-based. But your team added a code-review step, so you <strong>customize a column</strong>.</p>
<p>Open "Edit Column" and you only fill in three things:</p>
<ul>
<li><strong>Column name</strong>: Review</li>
<li><strong>Match condition</strong>: <code>status = 'pending' AND tags CONTAINS 'needs-review'</code> — which cards land here</li>
<li><strong>Automatic action</strong>: when a card enters this column, auto-add the <code>needs-review</code> tag and @-mention your teammate</li>
</ul>
<p>So when you drag the OAuth card into "Review," a few things happen on their own: the tag is added, your teammate gets pinged. <strong>A column's automatic actions are generated from its match condition</strong>, always in sync; for anything fancier you can add actions manually.</p>
<p>A few minutes later your teammate leaves a <strong>comment</strong> on the card: "The callback URL is missing the staging domain in the allow-list." You reply: "Good catch — added it 👍." Comments are a lightweight, threaded discussion — short and focused, back and forth, without switching to IM and without drowning in the body text. The body says "what it is," notes record "how it evolved," and comments are "let's quickly align on this one card."</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/kanban-story/comments.png" alt="The comment thread on the card — teammate flags the callback URL, you confirm the fix" class="img_ev3q"></p>
<p>One column isn't enough. You add a second custom column, <strong>"Deployed"</strong>: match condition <code>status = 'pending' AND tags CONTAINS 'deployed'</code>, and on enter it sets the status to <code>done</code>. Two custom columns later, the flow from review to shipped runs right on the board.</p>
<p>This is where kanban gets genuinely powerful — it doesn't just show, it works for you. Drag a card once, and all the status changes, tagging, and notifications happen in one move.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/kanban-story/edit-column.png" alt="Edit Column dialog — name, match conditions, and the auto-generated query and actions" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="thursday-columns-dont-have-to-be-statuses">Thursday: columns don't have to be statuses<a href="https://udctl.com/blog/2026/07/13/one-board-whole-project/#thursday-columns-dont-have-to-be-statuses" class="hash-link" aria-label="Direct link to Thursday: columns don't have to be statuses" title="Direct link to Thursday: columns don't have to be statuses">​</a></h3>
<p>Midweek, you realize status columns aren't enough. MorningRun ships next week, and what you really care about is "what must get done this week." So you spin up another board — "Release Sprint" — where the columns aren't Todo / Done but <strong>This Week / Next Week / Backlog</strong>.</p>
<p>Because behind every column is a query condition. "This Week" filters <code>deadline &lt;= this Sunday</code>; "Backlog" filters <code>status = 'todo' AND deadline IS NULL</code>. A board is essentially <strong>a saved query</strong>, not a folder that holds things — so the same task can appear on both "Sprint 12" and "Release Sprint" with zero duplication.</p>
<p><strong>Typical scenarios</strong>: one board to push daily development by status; another to plan priorities by time; a separate one per client project. Same set of tasks, several angles, each its own view.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/kanban-story/board-release.png" alt="Release Sprint board — columns become This Week / Next Week / Backlog, the same cards from another angle" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="friday-share-the-board-with-your-team">Friday: share the board with your team<a href="https://udctl.com/blog/2026/07/13/one-board-whole-project/#friday-share-the-board-with-your-team" class="hash-link" aria-label="Direct link to Friday: share the board with your team" title="Direct link to Friday: share the board with your team">​</a></h3>
<p>MorningRun's designer is taking over the UI cards this week. You open the board settings, click "Share," and pick your team group. She sees the board immediately — but <strong>only the tasks that belong to that group</strong>; your own private tasks stay hidden. Who's read-only and who can move cards is controlled by permissions.</p>
<p>No extra sync meeting, no "where are we at?" pings. A shared board is where the team stays aligned.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/kanban-story/share.png" alt="Share the board with your team group — members see only group tasks, with read/write permissions" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="the-week-in-summary">The week, in summary<a href="https://udctl.com/blog/2026/07/13/one-board-whole-project/#the-week-in-summary" class="hash-link" aria-label="Direct link to The week, in summary" title="Direct link to The week, in summary">​</a></h3>
<p>This week you didn't hold a single "sync meeting" and didn't bounce between apps. One board showed you the whole MorningRun project:</p>
<ul>
<li><strong>Drag = status change</strong> — columns are workflow stages, drop to update, synced instantly</li>
<li><strong>A card is a complete information container</strong> — Markdown body + notes timeline + comments behind every card</li>
<li><strong>Custom columns do the chores</strong> — auto-update status, tag, and notify from a match condition; two columns wired up the review-to-shipped flow</li>
<li><strong>Columns don't have to be statuses</strong> — This Week / Next Week / Backlog; a board is a saved query, not a folder</li>
<li><strong>A shared board = team alignment</strong> — only group tasks show, permissions controlled</li>
</ul>
<p>Want to try it? Open UnDercontrol and drag your first card — and watch the status change itself.</p>]]></content:encoded>
            <category>Feature</category>
            <category>kanban</category>
        </item>
        <item>
            <title><![CDATA[At 1 A.M., an AI Writes Blog Posts Inside My Task System]]></title>
            <link>https://udctl.com/blog/2026/07/12/ai-blog-pipeline/</link>
            <guid>https://udctl.com/blog/2026/07/12/ai-blog-pipeline/</guid>
            <pubDate>Sun, 12 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A daily scheduled job creates a task whose description doubles as an AI prompt. An agent drafts overnight, files the result as a note, and a human reviews with one line in the comment thread — no AI-specific infrastructure, just the task system.]]></description>
            <content:encoded><![CDATA[<p>At eight in the morning, coffee in hand, I open my kanban board. Sitting in the Pending column is a new task:</p>
<blockquote>
<p><strong>2026-07-12 Blog Draft (scheduled)</strong></p>
</blockquote>
<p>Inside it: a complete blog draft. And in the comment thread, two messages left overnight:</p>
<blockquote>
<p><strong>01:01</strong> — On it — topic scan done, picking task-management-overview. Drafting now.</p>
<p><strong>01:03</strong> — Done — draft is in the note. Task set to pending for review.</p>
</blockquote>
<p>The author is not a person. It's an AI agent. While I was asleep, it picked a topic, checked the product docs, wrote the draft, moved the task to "pending review", and said hi in the comment thread.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/ai-blog-pipeline/kanban-pending.png" alt="Kanban board with the blog draft task sitting in the Pending Review column" class="img_ev3q"></p>
<p>I read the draft and replied with one line: "Too broad. Find one concrete use case and tell it as a story." A few minutes later, the draft had been rewritten — the post you are reading now is the product of that rewrite.</p>
<p>This post is about how that pipeline is built, and why a task system is the most comfortable handoff point between humans and AI.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="a-task-is-not-just-a-to-do-item">A task is not just a to-do item<a href="https://udctl.com/blog/2026/07/12/ai-blog-pipeline/#a-task-is-not-just-a-to-do-item" class="hash-link" aria-label="Direct link to A task is not just a to-do item" title="Direct link to A task is not just a to-do item">​</a></h3>
<p>First, a core design idea: in UnDercontrol, <strong>a task is a universal information container</strong>, not just a to-do.</p>
<p>A task can be:</p>
<ul>
<li>An actual to-do ("fix the validation bug on the login page")</li>
<li>A document (a design proposal, meeting minutes, a wiki page)</li>
<li>A decision record (why we chose option B)</li>
<li>A blog draft (this post's draft lives as a note on a task)</li>
</ul>
<p>The task body is Markdown. Combined with tags and custom metadata, you decide what a task <em>is</em> — instead of being boxed in by the tool's data structure.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="the-problem-i-want-a-steady-blog-without-starting-from-zero-every-day">The problem: I want a steady blog, without starting from zero every day<a href="https://udctl.com/blog/2026/07/12/ai-blog-pipeline/#the-problem-i-want-a-steady-blog-without-starting-from-zero-every-day" class="hash-link" aria-label="Direct link to The problem: I want a steady blog, without starting from zero every day" title="Direct link to The problem: I want a steady blog, without starting from zero every day">​</a></h3>
<p>Anyone building a product knows this: content needs to ship consistently, but the activation energy of writing is brutal. Picking a topic, gathering material, structuring, drafting — every step burns energy that should have gone into code.</p>
<p>Let the AI write and publish fully automatically? No. Content that ships without a human gatekeeper burns your own reputation.</p>
<p>What I wanted was a <strong>human–AI pipeline</strong>: the AI does the grunt work (topic dedup, doc research, first draft), and I only do the one step that's actually worth my time — judgment and feedback.</p>
<p>So the question becomes: <strong>where does the AI's work go? Where do I review it? How does feedback flow back?</strong></p>
<p>The answer: all of it lives in the task system.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="the-pipeline-24-hours-in-the-life-of-one-task">The pipeline: 24 hours in the life of one task<a href="https://udctl.com/blog/2026/07/12/ai-blog-pipeline/#the-pipeline-24-hours-in-the-life-of-one-task" class="hash-link" aria-label="Direct link to The pipeline: 24 hours in the life of one task" title="Direct link to The pipeline: 24 hours in the life of one task">​</a></h3>
<p>There is no dedicated "AI platform" here. The pipeline is assembled from four native capabilities of the task system: scheduled jobs, @mentions, notes, and comments.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/ai-blog-pipeline/concept-hero.png" alt="The 24-hour handoff loop: scheduled job creates the task, mention wakes the agent, draft lands as a note, human reviews in the morning" class="img_ev3q"></p>
<p><strong>01:00 — The scheduled job fires</strong></p>
<p>A daily scheduled job (CRON) creates the day's draft task. The task description is the complete instruction set for the AI, and it opens by @mentioning my agent:</p>
<div class="language-markdown codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-markdown codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token url" style="color:#36acaa">[</span><span class="token url content" style="color:#36acaa">@ud-agent</span><span class="token url" style="color:#36acaa">](</span><span class="token url" style="color:#36acaa">mention://member/...</span><span class="token url" style="color:#36acaa">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Generate ONE blog DRAFT for review — do NOT run the full publish pipeline.</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">1.</span><span class="token plain"> Pick a topic from </span><span class="token code-snippet code keyword" style="color:#00009f">`auto/blog-topics.json`</span><span class="token plain">. Skip topics already covered —</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   check with </span><span class="token code-snippet code keyword" style="color:#00009f">`ud grep task "&lt;slug&gt;"`</span><span class="token plain">. Choose the first uncovered topic.</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">2.</span><span class="token plain"> Write ONLY the text draft as a NOTE on THIS task.</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">3.</span><span class="token plain"> Set THIS task status to </span><span class="token code-snippet code keyword" style="color:#00009f">`pending`</span><span class="token plain"> and reply in the comment thread.</span><br></span></code></pre></div></div>
<p>Notice the design here: <strong>the task description is the prompt</strong>. Instructions, boundaries ("do NOT publish"), acceptance criteria ("set to pending") — all written into the task. No separate configuration system needed. The task <em>is</em> the config.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/ai-blog-pipeline/scheduled-job.png" alt="Scheduled Jobs page showing the daily 01:00 blog-draft job" class="img_ev3q"></p>
<p><strong>01:01 — The @mention wakes the agent</strong></p>
<p>The mention triggers an agent work session. It wakes up on my Mac, and the first thing it does is not writing — it's detective work:</p>
<ul>
<li>Read the topic list (a JSON file in the repo, 18 candidate topics)</li>
<li>Search old tasks with <code>ud grep task</code> and <code>ud query "tags CONTAINS 'blog'"</code> to see which topics are already covered</li>
<li>Sweep 27 historical blog tasks, find only 2 topics left uncovered — pick the first one</li>
</ul>
<p>Then it leaves its first message in the comment thread ("On it — picking task-management-overview") before writing a word. It also reads the product docs to verify feature details first — an AI's worst habit is confidently making things up, and making it read the docs first cures most of that.</p>
<p><strong>01:03 — The draft lands, the task flips to pending</strong></p>
<p>The finished draft doesn't go to some "AI output panel" — it's attached to the task as a <strong>note</strong>. The task status is set to <code>pending</code>, which in our status model means "I'm done, waiting on someone else." The agent leaves a second message in the thread and clocks out.</p>
<p>The whole run took two minutes, and it happened after I fell asleep.</p>
<p><strong>08:00 — I come online, and only judge</strong></p>
<p>My review interface is just the task detail page: the draft in a note, the context in the description, the agent's work log in the comment thread. I don't need to open any other tool.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/ai-blog-pipeline/task-detail.png" alt="Task detail page: the scheduled prompt as description, the draft as a note below" class="img_ev3q"></p>
<p>I read the draft and replied with one line in the thread. That line triggered a new agent session — it read the feedback, <strong>updated the same note via <code>note_id</code></strong> (not a new note; notes keep full edit history, so a bad rewrite can always be rolled back), then replied in the thread describing what changed.</p>
<p>Back and forth, it feels like collaborating with a remote teammate through a ticket. Except this teammate starts work at 1 A.M. and never complains about rework.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/ai-blog-pipeline/comment-thread.png" alt="The comment thread: agent reports progress, human replies with one-line feedback, agent confirms the rewrite" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="why-the-handoff-point-is-a-task-not-a-chat-window">Why the handoff point is a task, not a chat window<a href="https://udctl.com/blog/2026/07/12/ai-blog-pipeline/#why-the-handoff-point-is-a-task-not-a-chat-window" class="hash-link" aria-label="Direct link to Why the handoff point is a task, not a chat window" title="Direct link to Why the handoff point is a task, not a chat window">​</a></h3>
<p>The biggest problem with collaborating with AI in a chat window is that <strong>output sinks</strong>. What it wrote yesterday is fifty screens up today; switch sessions and the context is gone.</p>
<p>Make the handoff point a task, and every part of the exchange gets a native container:</p>
<ul>
<li><strong>Task description</strong> = requirements and instructions (a living document, always current)</li>
<li><strong>Notes</strong> = deliverables and process records (an append-only timeline with edit history)</li>
<li><strong>Comments</strong> = lightweight conversation (short, threaded, with @ notifications)</li>
<li><strong>Status</strong> = position in the workflow (<code>pending</code> means "awaiting review" — one glance at the board shows everything waiting on you)</li>
</ul>
<p>And humans and AI access all of it through <strong>the same entrance</strong>. I read on the web and my phone; the agent reads and writes from the terminal via the CLI:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ud describe task 51c4981e      # read the task (notes, comments, attachments included)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cat draft.md | ud apply -f -   # write a note</span><br></span></code></pre></div></div>
<p>Because the entrance is CLI + Markdown, the pipeline is not tied to any specific AI tool — Claude Code, Codex, OpenCode, or any terminal-based agent can plug in. Swap the agent tomorrow; the pipeline doesn't change by a single line.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="where-else-this-pattern-applies">Where else this pattern applies<a href="https://udctl.com/blog/2026/07/12/ai-blog-pipeline/#where-else-this-pattern-applies" class="hash-link" aria-label="Direct link to Where else this pattern applies" title="Direct link to Where else this pattern applies">​</a></h3>
<p>The blog draft is just the most convenient example. The same loop — "scheduled job / @mention → agent works → notes for the record → pending for review → feedback in comments" — also runs our:</p>
<ul>
<li><strong>Release verification</strong>: after CI ships a version, a task is created with the release notes attached; the agent runs smoke tests and writes results into a note</li>
<li><strong>Daily standup</strong>: a scheduled job creates a standup task every day; the agent summarizes yesterday's changes across all tasks</li>
<li><strong>Code research</strong>: throw "evaluate option X" at the agent, wake up to a comparison in the notes, ask follow-ups in the comments</li>
</ul>
<p>There's only one common thread: <strong>every step of the AI's work stays in the task, and the human appears only at the checkpoints.</strong></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="takeaway">Takeaway<a href="https://udctl.com/blog/2026/07/12/ai-blog-pipeline/#takeaway" class="hash-link" aria-label="Direct link to Takeaway" title="Direct link to Takeaway">​</a></h3>
<p>This pipeline uses no concept invented specifically for AI. Scheduled jobs, @mentions, notes, comments, status — all of it is what a task system already has. It's just that when your task system meets three conditions, it naturally becomes a human–AI workbench:</p>
<ol>
<li><strong>Markdown-native</strong> — AI reads and writes without a translation layer</li>
<li><strong>A CLI entrance</strong> — any terminal agent can plug in; no vendor lock-in</li>
<li><strong>Status + comments + notes, all first-class</strong> — handoff, review, and records each have their place</li>
</ol>
<p>One more thing: this very post is what the pipeline produced in the small hours of July 12, 2026. The version you just read is the second draft — written after a human replied with a single line: "Too broad. Find one concrete use case and tell it as a story."</p>]]></content:encoded>
            <category>Feature</category>
            <category>workflow</category>
        </item>
        <item>
            <title><![CDATA[Turn Any Web Page into Clean Markdown for Your AI — UnDercontrol Web Clipper]]></title>
            <link>https://udctl.com/blog/2026/07/11/web-clipper/</link>
            <guid>https://udctl.com/blog/2026/07/11/web-clipper/</guid>
            <pubDate>Sat, 11 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[The Web Clipper Chrome extension copies any page as clean Markdown for AI chats with no login required, archives full HTML snapshots locally, and clips pages straight into UnDercontrol tasks that AI agents can read.]]></description>
            <content:encoded><![CDATA[<p>You want to hand a web article to an AI — to summarize it, translate it, or ask questions about it. How do you do that today?</p>
<p>Select-all and copy? You paste a mess of navigation bars, ads, recommendation widgets, and broken formatting. Send the link? Many AI tools can't fetch pages, and when they can, the result is often incomplete.</p>
<p>The UnDercontrol <strong>Web Clipper</strong> Chrome extension gives you a cleaner answer: click <strong>Copy Markdown</strong>, and the page's article content is extracted into tidy Markdown on your clipboard — <strong>no login, no account, no configuration</strong>. Install it and it works on any page. Paste it into Claude, ChatGPT, or any AI, and what it gets is pure content with zero noise.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/web-clipper/popup-setup.png" alt="Web Clipper popup — Copy Markdown and Save to Local, no login required" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="copy-markdown-copy-redesigned-for-the-ai-era">Copy Markdown: copy, redesigned for the AI era<a href="https://udctl.com/blog/2026/07/11/web-clipper/#copy-markdown-copy-redesigned-for-the-ai-era" class="hash-link" aria-label="Direct link to Copy Markdown: copy, redesigned for the AI era" title="Direct link to Copy Markdown: copy, redesigned for the AI era">​</a></h2>
<p>This is the extension's most-used feature, and it doesn't depend on an UnDercontrol server at all:</p>
<ul>
<li><strong>One-click copy</strong>: the page's article content lands on your clipboard as clean Markdown</li>
<li><strong>Smart extraction</strong>: powered by Readability + Turndown — ads, navigation, sidebars, and other non-content elements are stripped automatically</li>
<li><strong>Zero barrier</strong>: no sign-up, no login; works instantly on any page</li>
<li><strong>Robust</strong>: heavy elements like video and canvas are skipped automatically, so complex pages won't drag your browser down</li>
</ul>
<p>Markdown is an AI's native language — clean structure, no markup noise. For the same article, pasting Markdown instead of a raw select-all copy gets you noticeably better comprehension and answers, and saves tokens too.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/web-clipper/concept-copy-markdown.png" alt="Web page to clean Markdown to your AI — one click, no login" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="save-to-local-archive-without-logging-in">Save to Local: archive without logging in<a href="https://udctl.com/blog/2026/07/11/web-clipper/#save-to-local-archive-without-logging-in" class="hash-link" aria-label="Direct link to Save to Local: archive without logging in" title="Direct link to Save to Local: archive without logging in">​</a></h2>
<p>Local save also works without any login. One click downloads two files:</p>
<ol>
<li><strong>A complete HTML snapshot</strong>: built on the open-source <a href="https://github.com/gildas-lormeau/SingleFile" target="_blank" rel="noopener noreferrer">SingleFile</a>, it inlines HTML, CSS, images, and fonts into a single self-contained file that opens offline, exactly as the page looked</li>
<li><strong>The extracted Markdown</strong>: the same clean article content that Copy Markdown produces</li>
</ol>
<p>Links rot and pages get redesigned, but a snapshot is forever the page as it was the moment you saved it. Archive articles, research material, and time-sensitive pages — no server required.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="save-to-undercontrol-clipping-joins-your-workflow">Save to UnDercontrol: clipping joins your workflow<a href="https://udctl.com/blog/2026/07/11/web-clipper/#save-to-undercontrol-clipping-joins-your-workflow" class="hash-link" aria-label="Direct link to Save to UnDercontrol: clipping joins your workflow" title="Direct link to Save to UnDercontrol: clipping joins your workflow">​</a></h2>
<p>If you use UnDercontrol (the extension connects via login or an API key — self-hosted instances work out of the box), clipping goes one step further: the page becomes a <strong>task</strong>.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/web-clipper/popup-ready.png" alt="Web Clipper ready view — title prefilled, tags, Save Page" class="img_ev3q"></p>
<ul>
<li><strong>The Markdown content goes into the task description</strong> — readable and editable right inside the task</li>
<li><strong>The HTML snapshot is attached</strong> to the task, so you can always reconstruct the original page</li>
<li>From there you get the full task toolkit: tags, kanban boards, links to other tasks, notes for your conclusions</li>
<li><strong>AI agents can reach it</strong>: through the ud CLI, Claude Code, Codex, OpenCode, or any terminal-based AI agent can read the task — the description already contains ready-to-use Markdown, so the agent never needs to scrape the web page itself</li>
</ul>
<p>Here's a task produced by a real clip: the description starts with the source URL, the body is the extracted Markdown, and the full HTML snapshot hangs off the attachments panel:</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/web-clipper/task-detail.png" alt="A clipped task — source URL and Markdown in the description, HTML snapshot attached" class="img_ev3q"></p>
<p>In UnDercontrol, a task has never been just a to-do item — it's a universal information container. Web clipping is simply one more way for information to enter it.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="bonus-bilibili-video-subtitles-become-transcripts">Bonus: Bilibili video subtitles become transcripts<a href="https://udctl.com/blog/2026/07/11/web-clipper/#bonus-bilibili-video-subtitles-become-transcripts" class="hash-link" aria-label="Direct link to Bonus: Bilibili video subtitles become transcripts" title="Direct link to Bonus: Bilibili video subtitles become transcripts">​</a></h2>
<p>Web Clipper also ships with <strong>Bilibili transcript extraction</strong>: on a Bilibili video page, the extension detects the video and lights up a <strong>Save Transcript</strong> button that turns CC or AI-generated subtitles into a formatted Markdown transcript — download it locally or save it as an UnDercontrol task. Finish a tech talk video, and the transcript is archived in one click; handing it to an AI for a summary is one more.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/web-clipper/popup-bilibili.png" alt="Bilibili video detected — Save Transcript in one click" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="typical-scenarios">Typical scenarios<a href="https://udctl.com/blog/2026/07/11/web-clipper/#typical-scenarios" class="hash-link" aria-label="Direct link to Typical scenarios" title="Direct link to Typical scenarios">​</a></h2>
<ul>
<li><strong>Feed your AI</strong>: copy a long article as Markdown and paste it into any AI to summarize, translate, or interrogate — far cleaner than pasting a link or a raw copy</li>
<li><strong>Read-later that actually happens</strong>: clip a worthwhile article into a task with a deadline; write your action items into the task notes when you're done</li>
<li><strong>Competitive research</strong>: clip competitors' pricing and feature pages into a research board — when they redesign next week, you still hold today's snapshot</li>
<li><strong>Bug evidence</strong>: a third-party page misbehaves? Clip a full snapshot onto the bug task; even after the page recovers, the scene is preserved</li>
<li><strong>Video study notes</strong>: turn a Bilibili tech video's subtitles into a Markdown transcript — archive it or let an AI distill the key points</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="install-and-configure">Install and configure<a href="https://udctl.com/blog/2026/07/11/web-clipper/#install-and-configure" class="hash-link" aria-label="Direct link to Install and configure" title="Direct link to Install and configure">​</a></h2>
<p>Install from the <a href="https://chromewebstore.google.com/detail/undercontrol-web-clipper/mckkbigikfkoeddpcbhdmpncoljoagog" target="_blank" rel="noopener noreferrer">Chrome Web Store</a>. <strong>Copy Markdown and local save work immediately with zero configuration</strong>; only "save as task" needs a connection to your UnDercontrol server — log in directly, or create an API key (starts with <code>ak_</code>) under <strong>Settings → API Keys</strong> in the web app.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/web-clipper/popup-login.png" alt="Login to server — username/password or API key, only needed for saving as tasks" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="honest-limitations">Honest limitations<a href="https://udctl.com/blog/2026/07/11/web-clipper/#honest-limitations" class="hash-link" aria-label="Direct link to Honest limitations" title="Direct link to Honest limitations">​</a></h2>
<ul>
<li>Chrome internal pages (<code>chrome://</code>, <code>chrome-extension://</code>) cannot be captured</li>
<li>Full snapshots of pages heavy with dynamic content can take up to 2 minutes</li>
<li>A snapshot is a point-in-time copy — it won't change when the original page does (which is exactly the point)</li>
</ul>
<p>The Web Clipper browser extension itself is open source under AGPL-3.0: <a href="https://github.com/oatnil-top/ud-chrome-extension" target="_blank" rel="noopener noreferrer">github.com/oatnil-top/ud-chrome-extension</a>.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="wrapping-up">Wrapping up<a href="https://udctl.com/blog/2026/07/11/web-clipper/#wrapping-up" class="hash-link" aria-label="Direct link to Wrapping up" title="Direct link to Wrapping up">​</a></h2>
<p>Without logging in, it's a clean "web page to Markdown + local archive" tool, born for feeding content to AI. Logged in, every clip lands in a task system with tags, kanban boards, and AI-agent access. Next time you want to hand a web page to an AI, try Copy Markdown first.</p>]]></content:encoded>
            <category>Feature</category>
            <category>Guide</category>
        </item>
        <item>
            <title><![CDATA[Tags and Custom Fields: Organize Tasks by Your Own Dimensions]]></title>
            <link>https://udctl.com/blog/2026/07/09/tags-custom-fields/</link>
            <guid>https://udctl.com/blog/2026/07/09/tags-custom-fields/</guid>
            <pubDate>Thu, 09 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[UnDercontrol gives you three composable layers — free-form tags, typed custom fields, and arbitrary key-value metadata — all queryable with the cf.* prefix and freezable into kanban columns. Organize tasks your way, not the tool's.]]></description>
            <content:encoded><![CDATA[<p>Most tools decide for you what a task "looks like": a few fixed dropdowns, a hard-coded set of fields, someone else's taxonomy. But in reality every person, every team, every workflow wants to track different dimensions — one needs "priority", another needs "client", "estimate", "department", and someone else just wants to slap on a tag and filter later.</p>
<p>UnDercontrol doesn't impose structure. It gives you three composable layers: <strong>free-form tags</strong>, <strong>typed custom fields</strong>, and <strong>arbitrary key-value metadata</strong>. All three are directly queryable, so you can slice the <em>same</em> set of tasks along <em>your</em> dimensions instead of bending to the tool's taxonomy.</p>
<blockquote>
<p>This rests on a core UnDercontrol idea: <strong>a task is not just a to-do</strong> — it's a universal container for information. The same Markdown editor runs across tasks, notes, expenses, and accounts — every text surface. Tags and custom fields are how you layer <em>your own structure</em> on top of that container.</p>
</blockquote>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/tags-custom-fields/concept-1.png" alt="The same set of tasks, sliced freely by tags / custom fields / key-value metadata" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="1-free-form-tags-the-lightest-way-to-organize">1. Free-form tags: the lightest way to organize<a href="https://udctl.com/blog/2026/07/09/tags-custom-fields/#1-free-form-tags-the-lightest-way-to-organize" class="hash-link" aria-label="Direct link to 1. Free-form tags: the lightest way to organize" title="Direct link to 1. Free-form tags: the lightest way to organize">​</a></h2>
<p>A tag is just free text you jot down — no predefined vocabulary, write whatever you want (<code>work</code>, <code>urgent</code>, <code>client-acme</code>, <code>bug</code>, all fine).</p>
<ul>
<li><strong>Autocomplete built in</strong>: as you type, suggestions come from tags you've used before (ordered by recency) — free-form, yet you won't misspell or spawn ten spellings for one concept.</li>
<li><strong>Batch tagging</strong>: on the task search page you can add or remove tags across a batch of tasks at once, no need to open each one.</li>
<li><strong>Not only on tasks</strong>: tags also apply to resources (uploaded files), so you can thread files and tasks together with one shared vocabulary.</li>
</ul>
<p>Tags themselves are a queryable array field:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">tags CONTAINS 'work'                   -- has the work tag</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">tags IN ('work', 'personal')           -- has work or personal</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">tags CONTAINS_ALL ('work', 'urgent')   -- has both work and urgent</span><br></span></code></pre></div></div>
<p><strong>Typical scenario</strong>: a batch of related tasks lands on you — slap a shared <code>q3-launch</code> tag on all of them, then aggregate them from any board with <code>tags CONTAINS 'q3-launch'</code>. When the push is done, keep the tag as an archival breadcrumb, or batch-delete it.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/tags-custom-fields/screenshot-tags.png" alt="The task-detail tag input, with the historical-tag autocomplete dropdown expanded" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="2-custom-fields-typed-structured-dimensions">2. Custom fields: typed, structured dimensions<a href="https://udctl.com/blog/2026/07/09/tags-custom-fields/#2-custom-fields-typed-structured-dimensions" class="hash-link" aria-label="Direct link to 2. Custom fields: typed, structured dimensions" title="Direct link to 2. Custom fields: typed, structured dimensions">​</a></h2>
<p>Tags solve "attach a word". But when what you track has a <em>type</em> and a <em>value range</em> — priority is a number, department is a fixed set of options, owner is a specific member — reach for custom fields.</p>
<p>On the <strong>Custom Fields</strong> page (<code>/custom-fields</code>, also reachable from a task's properties, the kanban menu, or the command palette) you define a field and pick its type:</p>
<ul>
<li><strong>Text</strong> — free text value</li>
<li><strong>Number</strong> — numeric, supports comparisons</li>
<li><strong>Select</strong> — a fixed set of options you list up front</li>
<li><strong>Checkbox</strong> — a yes / no boolean</li>
<li><strong>User</strong> — pick a member from your workspace</li>
</ul>
<p>You can also mark a field required. Once defined, the field shows up in task detail for inline editing, and the value is stored on that task.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/tags-custom-fields/screenshot-cf-types.png" alt="The Custom Fields page and the &quot;New Field&quot; drawer with its 5 types: Text / Number / Select / Checkbox / User" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="scope-a-field-by-tag">Scope a field by tag<a href="https://udctl.com/blog/2026/07/09/tags-custom-fields/#scope-a-field-by-tag" class="hash-link" aria-label="Direct link to Scope a field by tag" title="Direct link to Scope a field by tag">​</a></h3>
<p>Here's the key design choice: a custom field doesn't have to apply to every task. When you create a field you pick its <strong>scope</strong>:</p>
<ul>
<li><strong>All tasks</strong> — every task shows this field</li>
<li><strong>Tasks with specific tags</strong> — only tasks carrying the chosen tags show the field</li>
</ul>
<p>So "tags" and "custom fields" mesh naturally: a task's detail page only surfaces the fields that <strong>match its tags</strong> — irrelevant fields stay out of your way. Tag a task <code>bug</code> and "repro steps" / "severity" appear; tag it <code>sales</code> and "client" / "amount" show up instead.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/tags-custom-fields/screenshot-cf-on-task.png" alt="A task tagged bug: the properties panel automatically shows bug-scoped fields like Severity and Reviewer" class="img_ev3q"></p>
<p><strong>Typical scenario</strong>: a team runs "engineering bugs" and "sales follow-ups" side by side. Scope the bug fields to the <code>bug</code> tag and the sales fields to the <code>sales</code> tag. In one board, each kind of task shows only its own field set, with no cross-talk — no need to split into two systems.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="3-arbitrary-key-value-for-the-ad-hoc-and-the-off-schema">3. Arbitrary key-value: for the ad-hoc and the off-schema<a href="https://udctl.com/blog/2026/07/09/tags-custom-fields/#3-arbitrary-key-value-for-the-ad-hoc-and-the-off-schema" class="hash-link" aria-label="Direct link to 3. Arbitrary key-value: for the ad-hoc and the off-schema" title="Direct link to 3. Arbitrary key-value: for the ad-hoc and the off-schema">​</a></h2>
<p>Not every piece of metadata is worth defining a field for. Task detail also has an <strong>extra metadata</strong> section where you drop arbitrary key-value pairs on a task (e.g. <code>jira: PROJ-123</code>, <code>source: customer email</code>) without declaring anything in settings first.</p>
<p>This layer is the buffer between "free tag" and "formal custom field": jot it down first, and once a key gets used often enough to become a stable dimension, promote it to a typed custom field.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/tags-custom-fields/screenshot-extra-metadata.png" alt="Free key-value pairs on a task: jira, external_id, source — no upfront definition needed" class="img_ev3q"></p>
<p><strong>Typical scenario</strong>: tasks synced in from an external system carry a pile of loose fields (ticket number, source link, external ID) — stash them as key-value pairs verbatim. Whichever key later turns out to need frequent filtering, formalize it into a custom field.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="4-querying-them-the-cf-prefix-and-kanban-columns">4. Querying them: the <code>cf.</code> prefix and kanban columns<a href="https://udctl.com/blog/2026/07/09/tags-custom-fields/#4-querying-them-the-cf-prefix-and-kanban-columns" class="hash-link" aria-label="Direct link to 4-querying-them-the-cf-prefix-and-kanban-columns" title="Direct link to 4-querying-them-the-cf-prefix-and-kanban-columns">​</a></h2>
<p>The real leverage of custom fields is that <strong>they're queryable just like built-in fields</strong>. Reference a custom field in a query with the <code>cf.</code> prefix:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">cf.priority &gt; 5                          -- priority above 5</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cf.priority IN (1, 2, 3)                 -- priority is 1, 2, or 3</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cf.department = 'engineering'            -- engineering department</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cf.priority IS NULL                      -- tasks with no priority set</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cf.priority &gt; 5 AND status = 'todo'      -- high-priority todos</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cf.priority &gt; 5 ORDER BY updated_at DESC -- high priority, newest first</span><br></span></code></pre></div></div>
<p>Custom fields combine freely with built-in fields (<code>status</code>, <code>tags</code>, <code>deadline</code>, …) in one query. And in UnDercontrol, <strong>every kanban column is essentially a saved query</strong> — so you can build a column called "High-priority todos" whose condition is <code>cf.priority &gt; 5 AND status = 'todo'</code>, and any task that matches drops into it automatically, no manual dragging. The task search page can surface custom fields as columns too.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/tags-custom-fields/screenshot-board-cf-query.png" alt="The kanban column condition builder: Tags has #bug AND Custom: Severity = critical, generating the query tags CONTAINS 'bug' AND cf.severity = 'critical'" class="img_ev3q"></p>
<p><strong>Typical scenario</strong>: build a "by client" board for the team, one column per client, each column's condition <code>cf.client = '...'</code>; a new task snaps into place the moment its client field is filled. The same set of tasks becomes "by priority" or "by department" with a different query — the view follows the dimension, the data stays single-source.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="wrap-up">Wrap-up<a href="https://udctl.com/blog/2026/07/09/tags-custom-fields/#wrap-up" class="hash-link" aria-label="Direct link to Wrap-up" title="Direct link to Wrap-up">​</a></h2>
<p>UnDercontrol doesn't decide which dimensions your tasks should have — it hands you three layers to stack from light to heavy as needed:</p>
<ul>
<li><strong>Free-form tags</strong> — zero-cost, jot-and-go, batchable, autocompleted</li>
<li><strong>Custom fields</strong> — typed (text / number / select / checkbox / user), optionally required, and <strong>scopable by tag</strong></li>
<li><strong>Arbitrary key-value</strong> — for ad-hoc and off-schema info, promotable to a real field anytime</li>
</ul>
<p>The common foundation under all three is one query language: <code>tags CONTAINS_ALL(...)</code>, <code>cf.priority &gt; 5</code>, freely combined with built-in fields, then frozen into kanban columns. The same set of tasks, sliced however you like — the structure is yours, not the tool's.</p>
<p>All of this rests on UnDercontrol's "a task is not just a to-do" idea: one unified information container, one editor across every text surface, plus tags and fields that let you define your own dimensions. Pair it with Claude Code, Codex, OpenCode, or any terminal-based agent to read and write this structured data — but you always keep the wheel on how it's organized.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/tags-custom-fields/concept-2.png" alt="One dataset, many cuts: the same tasks sliced into different kanban views by tag / cf.priority / cf.client" class="img_ev3q"></p>]]></content:encoded>
            <category>Feature</category>
            <category>Guide</category>
        </item>
        <item>
            <title><![CDATA[Everything is a Task: One Container for To-dos, Docs, Notes, and a Knowledge Graph]]></title>
            <link>https://udctl.com/blog/2026/07/08/task-management-overview/</link>
            <guid>https://udctl.com/blog/2026/07/08/task-management-overview/</guid>
            <pubDate>Wed, 08 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[In UnDercontrol a Task is not just a to-do — it's a universal container for docs, notes, reminders, and decisions. Many views, six statuses, virtual folders + AI, bi-directional links, one editor across the whole app.]]></description>
            <content:encoded><![CDATA[<p>Unix has a famous design philosophy: <strong>everything is a file</strong>. Directories, devices, pipes, sockets — all read and written through one uniform interface. Simple, unified, composable.</p>
<p>UnDercontrol borrows the same idea, just with a different primitive: <strong>everything is a Task</strong>.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/task-management-overview/concept-hero.png" alt="Everything is a Task" class="img_ev3q"></p>
<p>Most tools treat a "task" as a single line with a checkbox. UnDercontrol never did. Here, a <strong>Task is a universal information container</strong> — it can be a to-do, a document, a reminder, a blog draft, meeting minutes, a decision record, even a wiki page. One data source, organized your way, all in one place.</p>
<p>You don't hop between apps; an AI agent needs just one tool to reach everything you have.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="three-content-layers">Three content layers<a href="https://udctl.com/blog/2026/07/08/task-management-overview/#three-content-layers" class="hash-link" aria-label="Direct link to Three content layers" title="Direct link to Three content layers">​</a></h3>
<p>Every task is made of three layers, each with a design intent — but you're free to use them however you like:</p>
<table><thead><tr><th>Layer</th><th>Format</th><th>Design intent</th></tr></thead><tbody><tr><td><strong>Task body</strong></td><td>Markdown</td><td>The living, canonical document — the Single Source of Truth</td></tr><tr><td><strong>Notes</strong></td><td>Markdown</td><td>An append-only timeline — how this unit of information evolved</td></tr><tr><td><strong>Comments</strong></td><td>Plain text</td><td>Lightweight, threaded discussion — short and focused</td></tr></tbody></table>
<p>The body is a living document where you keep the complete, coherent narrative; notes record progress over time (session start, each commit, blockers hit); comments are the quick conversation around the content. Clean separation — and all plain text, so both humans and AI read it directly, with no conversion.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/task-management-overview/task-detail.png" alt="Task detail: Markdown body, tags, status, deadline, notes timeline, and a relation graph at a glance" class="img_ev3q"></p>
<p>A single task detail page makes the point: it's not one line of to-do, but a living document with a Markdown body, tags, status, deadline, a notes timeline, subtasks, and a relation graph.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="one-content-many-views">One content, many views<a href="https://udctl.com/blog/2026/07/08/task-management-overview/#one-content-many-views" class="hash-link" aria-label="Direct link to One content, many views" title="Direct link to One content, many views">​</a></h3>
<p>Because there's only one primitive underneath — the Task — UnDercontrol can offer many views over the same data, switchable anytime:</p>
<ul>
<li><strong>List view</strong>: linear and filterable, great for scanning and batch work</li>
<li><strong>Kanban view</strong>: drag cards to change status, manage in-flight workflow visually</li>
<li><strong>Calendar view</strong>: laid out by deadline, so timing is obvious at a glance</li>
<li><strong>Tree view</strong>: expand parent-child relationships to see the hierarchy</li>
<li><strong>Graph view</strong>: traverse links, parents, and subtasks as a graph to see how knowledge connects</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/task-management-overview/multi-view.png" alt="The same data, four perspectives: Kanban, List, Calendar, Graph" class="img_ev3q"></p>
<p>A board isn't "another container" — it's essentially a <strong>saved query</strong>. The same task can appear on many boards at once, with zero duplication. You change the perspective, not the data.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="six-statuses-that-match-real-workflow">Six statuses that match real workflow<a href="https://udctl.com/blog/2026/07/08/task-management-overview/#six-statuses-that-match-real-workflow" class="hash-link" aria-label="Direct link to Six statuses that match real workflow" title="Direct link to Six statuses that match real workflow">​</a></h3>
<p>Tasks support six statuses that fit how real work moves: <code>todo</code>, <code>in-progress</code>, <code>pending</code> (awaiting confirmation/review), <code>done</code>, <code>archived</code>, and <code>stale</code>. Dragging on the board updates status automatically — and status itself is information: one glance tells you where everything stands.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="tell-things-apart-with-tags-and-metadata-not-folders">Tell things apart with tags and metadata, not folders<a href="https://udctl.com/blog/2026/07/08/task-management-overview/#tell-things-apart-with-tags-and-metadata-not-folders" class="hash-link" aria-label="Direct link to Tell things apart with tags and metadata, not folders" title="Direct link to Tell things apart with tags and metadata, not folders">​</a></h3>
<p>If everything is a task, then a "document" and a "to-do" are structurally the same thing. What sets them apart is never the structure — it's <strong>tags, metadata, and views</strong>. UnDercontrol forces neither a tagging convention nor a folder hierarchy on you:</p>
<ul>
<li><strong>Tags</strong>: free-form labels you define (<code>doc</code>, <code>meeting</code>, <code>reminder</code> — anything)</li>
<li><strong>First-class metadata</strong>: title, status, tags, deadline, assignee, links — the 20% that covers 80%</li>
<li><strong>Custom metadata</strong>: unlimited key-value pairs (<code>doc_type: rfc</code>, <code>client: acme</code>), all queryable too</li>
</ul>
<p>When you need precise retrieval, UnDercontrol also gives you a SQL-like query language and natural-language queries — save common conditions as board columns and complex filtering is one step away.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="virtual-folders--ai-organize-information-like-files">Virtual folders + AI: organize information like files<a href="https://udctl.com/blog/2026/07/08/task-management-overview/#virtual-folders--ai-organize-information-like-files" class="hash-link" aria-label="Direct link to Virtual folders + AI: organize information like files" title="Direct link to Virtual folders + AI: organize information like files">​</a></h3>
<p>Prefer a "folder" style of organizing? UnDercontrol has that too — <strong>virtual folders</strong>. Every task carries a <code>path</code> field (a virtual path, a first-class part of the schema, e.g. <code>/Work/Alpha/</code>) that adds up to a familiar file tree in the app; drag to file things away. An empty path means "unfiled", <code>/</code> is the root, <code>/Work/Alpha/</code> is a specific folder — and it's not just tasks: resources (attachments), skills, and other entities share the same virtual paths, placed in one tree.</p>
<p>It even syncs between local and cloud like Git: <code>ud pull /Work/Alpha/</code> checks a virtual directory out into a local folder (creating a <code>.ud/</code> to hold sync state, just like <code>.git/</code>), edit it with whatever editor you like, and <code>ud push</code> sends it back. To move a folder, just move it locally and <code>ud push</code> updates the cloud path.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/task-management-overview/virtual-folders.png" alt="Virtual folders + AI: organize with a file tree, let AI re-file for you" class="img_ev3q"></p>
<p>What makes organizing truly effortless is <strong>adding AI</strong>. Because the whole structure is plain text plus paths, an AI agent can read your full picture and tidy it up for you:</p>
<ul>
<li>"Move all these scattered meeting notes under <code>/Meetings/2026-Q3/</code>" — the AI rewrites paths in bulk, one sentence</li>
<li>"Organize this project's tasks into a clean folder structure" — the AI reads the content, decides where things belong, and reorganizes</li>
<li>"Split this into subtasks and archive the old ones" — the AI handles the splitting, filing, and status changes together</li>
</ul>
<p>Claude Code, Codex, OpenCode, or any terminal-based agent can do this through one uniform CLI. You do the thinking; the AI puts information where it belongs.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="bi-directional-links-weave-tasks-into-a-knowledge-graph">Bi-directional links: weave tasks into a knowledge graph<a href="https://udctl.com/blog/2026/07/08/task-management-overview/#bi-directional-links-weave-tasks-into-a-knowledge-graph" class="hash-link" aria-label="Direct link to Bi-directional links: weave tasks into a knowledge graph" title="Direct link to Bi-directional links: weave tasks into a knowledge graph">​</a></h3>
<p>Tasks can connect in several ways: <strong>peer links</strong> relate two things; <strong>parent/subtask</strong> relationships break a big goal into executable steps. With the graph view, your task base becomes a traversable knowledge network rather than a pile of isolated entries.</p>
<p>Inside the body, notes, and comments you can also reference other entities with Markdown links — another task <code>[Title](task://...)</code>, a note <code>[## Heading](note://...)</code>, an @mention <code>[@name](mention://member/...)</code>, even an embedded uploaded image <code>![shot](resource://...)</code>. The whole base is interconnected.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="not-just-tasks-one-editor-across-every-text-surface">Not just tasks: one editor across every text surface<a href="https://udctl.com/blog/2026/07/08/task-management-overview/#not-just-tasks-one-editor-across-every-text-surface" class="hash-link" aria-label="Direct link to Not just tasks: one editor across every text surface" title="Direct link to Not just tasks: one editor across every text surface">​</a></h3>
<p>"Everything is a task" has one more layer: UnDercontrol's Markdown editor is <strong>not task-only</strong> — it's reused everywhere you write in the app: task bodies, notes, even the memos on finance records (expenses, accounts). The editing experience, Markdown syntax, and embedded links and images you know from tasks are identical elsewhere. Learn it once, use it everywhere.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="attachments-and-collaboration">Attachments and collaboration<a href="https://udctl.com/blog/2026/07/08/task-management-overview/#attachments-and-collaboration" class="hash-link" aria-label="Direct link to Attachments and collaboration" title="Direct link to Attachments and collaboration">​</a></h3>
<p>Tasks let you <strong>drag files in or paste from the clipboard</strong> — screenshots, design mockups, documents stay next to the task they belong to. Storage can be local or S3-compatible object storage; your data stays under your control.</p>
<p>For collaboration, tasks are shared through <strong>groups</strong> with role-based read/write permissions. The same task can appear on a shared board where teammates discuss and update progress — you share the information, not your whole account.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="typical-scenarios">Typical scenarios<a href="https://udctl.com/blog/2026/07/08/task-management-overview/#typical-scenarios" class="hash-link" aria-label="Direct link to Typical scenarios" title="Direct link to Typical scenarios">​</a></h3>
<ul>
<li><strong>Personal knowledge base</strong>: keep meeting notes, decision records, and reading notes all as tagged tasks; retrieve them anytime with queries and boards — one app holds it all.</li>
<li><strong>AI agent collaboration</strong>: Claude Code, Codex, OpenCode, or any terminal-based agent reads and writes tasks, appends notes, and records progress through one uniform CLI — human and AI share one source of truth.</li>
<li><strong>Product/engineering pipeline</strong>: requirements in the task body, progress in notes, review comments in comments; track status flow on a board and see dependencies in the graph.</li>
<li><strong>Content creation</strong>: a blog's Chinese draft, English draft, and social copy each live as a note; the task itself is the Single Source of Truth, and everything published outward is derived from it.</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/task-management-overview/graph.png" alt="Graph: tasks woven into a traversable knowledge network" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="in-summary">In summary<a href="https://udctl.com/blog/2026/07/08/task-management-overview/#in-summary" class="hash-link" aria-label="Direct link to In summary" title="Direct link to In summary">​</a></h3>
<p><strong>Everything is a task</strong> — not just a slogan, but UnDercontrol's underlying design: a Markdown-native, AI-ready universal container, with many views, six statuses, bi-directional links, flexible tags and metadata, and one editor across the whole app. No more shuttling things between a to-do tool, a notes tool, and a docs tool — it all belongs in one place.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="start-here">Start here<a href="https://udctl.com/blog/2026/07/08/task-management-overview/#start-here" class="hash-link" aria-label="Direct link to Start here" title="Direct link to Start here">​</a></h3>
<ul>
<li>🏠 <strong>Website &amp; docs</strong>: <a href="https://oatnil.com/" target="_blank" rel="noopener noreferrer">oatnil.com</a></li>
<li>📦 <strong>CLI installation guide</strong>: <a href="https://oatnil.com/docs/cli" target="_blank" rel="noopener noreferrer">oatnil.com/docs/cli</a></li>
<li>💻 <strong>Desktop App &amp; self-hosting</strong>: <a href="https://oatnil.com/subscribe" target="_blank" rel="noopener noreferrer">oatnil.com/subscribe</a></li>
</ul>
<p>One universal container, one home for all your work — try it now with <code>npm install -g @oatnil/ud</code>.</p>]]></content:encoded>
            <category>Feature</category>
            <category>Guide</category>
        </item>
        <item>
            <title><![CDATA[Manage Your Tasks Like git: ud pull / push Local Bidirectional Sync]]></title>
            <link>https://udctl.com/blog/2026/07/07/ud-pull-push/</link>
            <guid>https://udctl.com/blog/2026/07/07/ud-pull-push/</guid>
            <pubDate>Tue, 07 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[ud pull checks your cloud tasks, notes, and attachments out as local Markdown files; ud push sends your edits back. git-style sync between the UnDercontrol server and any folder — edit with Obsidian, VS Code, or any AI agent.]]></description>
            <content:encoded><![CDATA[<blockquote>
<p>In one sentence: <code>ud pull</code> checks your cloud tasks, notes, and attachments out as local Markdown files, and <code>ud push</code> sends your local edits back. As natural as <code>git clone</code> and <code>git push</code>.</p>
<p><strong>Many working copies, one server, edit from anywhere.</strong></p>
</blockquote>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/ud-pull-push/concept-1.png" alt="UnDercontrol server and a local folder syncing bidirectionally via ud pull / ud push — edit locally with Obsidian, VS Code, an AI agent, or any tool" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="why-we-built-this">Why we built this<a href="https://udctl.com/blog/2026/07/07/ud-pull-push/#why-we-built-this" class="hash-link" aria-label="Direct link to Why we built this" title="Direct link to Why we built this">​</a></h3>
<p>UnDercontrol has always been built on one core belief: <strong>your data is yours</strong>. Tasks, notes, ledgers, resources — every piece of text is plain Markdown, never locked into any single app.</p>
<p>But "exportable" isn't enough. What developers actually want is to <strong>treat that content as local files</strong> — edit it with their favorite editor, feed it to AI tools, and sync the changes back when done.</p>
<p>So we built a git-style command-line sync: <code>ud init</code> / <code>ud pull</code> / <code>ud push</code> / <code>ud status</code>. There are no new concepts to learn — if you know git, you already know how to use it.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="three-commands-the-exact-same-mental-model-as-git">Three commands, the exact same mental model as git<a href="https://udctl.com/blog/2026/07/07/ud-pull-push/#three-commands-the-exact-same-mental-model-as-git" class="hash-link" aria-label="Direct link to Three commands, the exact same mental model as git" title="Direct link to Three commands, the exact same mental model as git">​</a></h3>
<table><thead><tr><th>ud command</th><th>git equivalent</th><th>What it does</th></tr></thead><tbody><tr><td><code>ud pull /Work/Alpha/</code></td><td><code>git clone</code></td><td>Check a cloud virtual directory out into the current folder and bind them</td></tr><tr><td><code>ud status</code></td><td><code>git status</code></td><td>See what changed locally and remotely</td></tr><tr><td><code>ud push</code></td><td><code>git push</code></td><td>Push local changes back to the cloud</td></tr><tr><td><code>ud init /Work/Alpha/</code></td><td><code>git init</code></td><td>Publish an existing local folder as a cloud directory</td></tr></tbody></table>
<p>The first <code>ud pull /Work/Alpha/</code> <strong>binds</strong> the current directory to the virtual path <code>/Work/Alpha/</code>: a <code>.ud/</code> folder appears to hold sync state (just like <code>.git/</code>), and the contents of <code>/Work/Alpha/</code> land directly in the folder. Every <code>ud pull</code> after that is <strong>incremental</strong> — only changed items are downloaded.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># In an empty folder, init + checkout in one step</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud pull /Work/Alpha/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Later: incrementally pull all tracked paths</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud pull</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># See what changed on both sides</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud status</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Push local changes back (shows a change list for confirmation first)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud push</span><br></span></code></pre></div></div>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/ud-pull-push/concept-2.png" alt="Local folder layout: one task is one folder, index.md is the task body, every note is its own .md file, and .ud/ holds the sync state" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="what-it-looks-like-locally-a-task-is-a-folder-a-note-is-a-file">What it looks like locally: a task is a folder, a note is a file<a href="https://udctl.com/blog/2026/07/07/ud-pull-push/#what-it-looks-like-locally-a-task-is-a-folder-a-note-is-a-file" class="hash-link" aria-label="Direct link to What it looks like locally: a task is a folder, a note is a file" title="Direct link to What it looks like locally: a task is a folder, a note is a file">​</a></h3>
<p>The checked-out structure exactly mirrors the <strong>virtual folder</strong> hierarchy inside UnDercontrol:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">Alpha/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── .ud/                      # sync state (like .git/)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   └── tracking.json</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── Design-Doc/               # one task = one folder</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   ├── index.md              # task body (YAML frontmatter + description)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   ├── Kickoff-notes.md      # every note = one .md file</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   └── Review-notes.md</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── Sprint-Plan/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   └── index.md</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">└── architecture.png          # resources = plain files, placed by virtual path</span><br></span></code></pre></div></div>
<p>Open <code>index.md</code> and it's clean Markdown:</p>
<div class="language-markdown codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-markdown codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token front-matter-block punctuation" style="color:#393A34">---</span><span class="token front-matter-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token front-matter-block"></span><span class="token front-matter-block front-matter yaml language-yaml key atrule" style="color:#00a4db">id</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">:</span><span class="token front-matter-block front-matter yaml language-yaml"> 8f3c</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">...</span><span class="token front-matter-block front-matter yaml language-yaml"></span><br></span><span class="token-line" style="color:#393A34"><span class="token front-matter-block front-matter yaml language-yaml"></span><span class="token front-matter-block front-matter yaml language-yaml key atrule" style="color:#00a4db">title</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">:</span><span class="token front-matter-block front-matter yaml language-yaml"> Design Doc</span><br></span><span class="token-line" style="color:#393A34"><span class="token front-matter-block front-matter yaml language-yaml"></span><span class="token front-matter-block front-matter yaml language-yaml key atrule" style="color:#00a4db">status</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">:</span><span class="token front-matter-block front-matter yaml language-yaml"> in</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">-</span><span class="token front-matter-block front-matter yaml language-yaml">progress</span><br></span><span class="token-line" style="color:#393A34"><span class="token front-matter-block front-matter yaml language-yaml"></span><span class="token front-matter-block front-matter yaml language-yaml key atrule" style="color:#00a4db">tags</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">:</span><span class="token front-matter-block front-matter yaml language-yaml"> </span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">[</span><span class="token front-matter-block front-matter yaml language-yaml">design</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">,</span><span class="token front-matter-block front-matter yaml language-yaml"> alpha</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">]</span><span class="token front-matter-block front-matter yaml language-yaml"></span><br></span><span class="token-line" style="color:#393A34"><span class="token front-matter-block front-matter yaml language-yaml"></span><span class="token front-matter-block front-matter yaml language-yaml key atrule" style="color:#00a4db">deadline</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">:</span><span class="token front-matter-block front-matter yaml language-yaml"> </span><span class="token front-matter-block front-matter yaml language-yaml datetime number" style="color:#36acaa">2026-07-20</span><span class="token front-matter-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token front-matter-block"></span><span class="token front-matter-block punctuation" style="color:#393A34">---</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">##</span><span class="token title important"> Background</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">The task description body goes here…</span><br></span></code></pre></div></div>
<p>A few deliberate design choices:</p>
<ul>
<li><strong>Content is stored verbatim</strong> — in-app links like <code>resource://</code>, <code>task://</code>, and <code>note://</code> stay exactly as they are. Sync is a <strong>byte-level round-trip</strong>; your content is never rewritten.</li>
<li><strong>The virtual path is not written into frontmatter</strong> — the folder's location on disk is the single source of the path. Want to move a task to another directory? Move the folder locally, and <code>ud push</code> updates the cloud path.</li>
<li><strong>File names are sanitized</strong>, with the original names kept in <code>.ud/tracking.json</code> — special characters and non-ASCII names are handled for you.</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="on-push-ud-figures-out-what-you-did">On push, ud figures out what you did<a href="https://udctl.com/blog/2026/07/07/ud-pull-push/#on-push-ud-figures-out-what-you-did" class="hash-link" aria-label="Direct link to On push, ud figures out what you did" title="Direct link to On push, ud figures out what you did">​</a></h3>
<p><code>ud push</code> compares your working directory against the "last known state" in <code>.ud/tracking.json</code>, item by item, and infers the right action:</p>
<table><thead><tr><th>What you did locally</th><th>What ud push does</th></tr></thead><tbody><tr><td>Created a task folder (with index.md), or a loose <code>.md</code></td><td>Creates a task</td></tr><tr><td>Added a new <code>.md</code> inside a task folder</td><td>Creates a note on that task</td></tr><tr><td>Added a non-<code>.md</code> file</td><td>Uploads it as a resource</td></tr><tr><td>Edited index.md / a note / a resource file</td><td>Updates it in the cloud</td></tr><tr><td>Moved a file/folder to another directory</td><td>Updates the cloud virtual path</td></tr><tr><td>Deleted a file/folder</td><td>Soft-deletes in the cloud (recoverable)</td></tr></tbody></table>
<p>In other words: <strong>create a <code>My-Idea.md</code> file locally, run <code>ud push</code>, and it becomes a task in UnDercontrol</strong>. And the other way around: a task created in the cloud shows up locally after <code>ud pull</code>. Truly bidirectional.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="conflicts-like-git-nothing-gets-silently-overwritten">Conflicts: like git, nothing gets silently overwritten<a href="https://udctl.com/blog/2026/07/07/ud-pull-push/#conflicts-like-git-nothing-gets-silently-overwritten" class="hash-link" aria-label="Direct link to Conflicts: like git, nothing gets silently overwritten" title="Direct link to Conflicts: like git, nothing gets silently overwritten">​</a></h3>
<p>If the same task was modified both locally and in the cloud, <code>ud</code> marks it as a <strong>conflict</strong>:</p>
<ul>
<li><code>ud push</code> <strong>skips</strong> conflicting items by default (and tells you to pull first), or use <code>--force</code> to overwrite the cloud.</li>
<li><code>ud pull</code> <strong>keeps the local</strong> version by default, or use <code>--keep-remote</code> to let the cloud win.</li>
<li>In an interactive session, it asks you <strong>item by item</strong> which version to keep.</li>
</ul>
<p>Run <code>ud status</code> first, then decide — no data is ever silently lost.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="many-working-copies-one-server-anywhere">Many working copies, one server, anywhere<a href="https://udctl.com/blog/2026/07/07/ud-pull-push/#many-working-copies-one-server-anywhere" class="hash-link" aria-label="Direct link to Many working copies, one server, anywhere" title="Direct link to Many working copies, one server, anywhere">​</a></h3>
<p>In git's model, one origin can have any number of local clones. <code>ud</code> works the same way — <strong>on top of one server, you can have any number of local tracked folders</strong>, each bound to a different virtual path:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">Laptop              Desktop            Another Mac / server</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">~/work/alpha  →     D:\notes\alpha  →   ~/proj-x</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  binds /Work/Alpha/   binds /Work/Alpha/  binds /Projects/proj-x/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        \                 |                    /</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">         \                |                   /</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          ┌──────────────────────────────────┐</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          │      one UnDercontrol server      │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          │ (virtual folders = source of truth)│</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          └──────────────────────────────────┘</span><br></span></code></pre></div></div>
<p>This buys you a few very practical things:</p>
<ul>
<li><strong>One repo per place, fully independent</strong>: <code>/Work/Alpha/</code> and <code>/Projects/proj-x/</code> are separate tracking roots, each with its own <code>.ud/</code> state. Check out only the slice you care about right now instead of pulling your entire knowledge base.</li>
<li><strong>View and edit from anywhere</strong>: on any device, <code>ud pull</code> gets you the latest, and <code>ud push</code> syncs your edits to everyone and every client. On your phone? Just open the Web or desktop App — <strong>same server, same data</strong>.</li>
<li><strong>Humans and AI share one source of truth</strong>: you edit in Obsidian on your laptop, an AI agent edits via the CLI on a server, teammates edit in the App — everything converges on the same virtual path on the same server, and <code>ud status</code> always shows who changed what.</li>
</ul>
<p>In short: <strong>the server is the single source of truth, and local folders are just its working copies</strong>. It's the model git has validated for twenty years — applied to your tasks and knowledge base instead of code.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="not-just-tasks-your-whole-knowledge-base-as-files">Not just tasks: your whole knowledge base as files<a href="https://udctl.com/blog/2026/07/07/ud-pull-push/#not-just-tasks-your-whole-knowledge-base-as-files" class="hash-link" aria-label="Direct link to Not just tasks: your whole knowledge base as files" title="Direct link to Not just tasks: your whole knowledge base as files">​</a></h3>
<p>This part matters: UnDercontrol's Markdown editor is <strong>shared across every text surface</strong> — tasks, notes, even the memo fields on expenses and accounts are all the same Markdown. So what <code>ud pull</code> checks out isn't an isolated "todo-list export" — it's a <strong>local mirror of your entire knowledge base</strong>, ready to be opened, edited, and pushed back with any tool.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="typical-scenarios">Typical scenarios<a href="https://udctl.com/blog/2026/07/07/ud-pull-push/#typical-scenarios" class="hash-link" aria-label="Direct link to Typical scenarios" title="Direct link to Typical scenarios">​</a></h3>
<ul>
<li><strong>Manage tasks in Obsidian / VS Code</strong>: <code>ud pull</code> into your vault, enjoy your familiar editor, keybindings, and plugins, then <code>ud push</code> when done.</li>
<li><strong>Let AI agents read and write your tasks directly</strong>: hand the synced folder to Claude Code, Codex, OpenCode, or any terminal-based agent — it can read task context, append notes, even batch-create tasks, because to the agent they're just ordinary local <code>.md</code> files.</li>
<li><strong>Keep tasks next to your code</strong>: run <code>ud init /Projects/proj-x/</code> in the project root and version design docs and decision records right alongside the code.</li>
<li><strong>Write offline</strong>: edit local files on a plane or in the subway; one <code>ud push</code> syncs everything once you're back online.</li>
<li><strong>Local backup</strong>: a single <code>ud pull</code> is a plain-text, human-readable, never-expiring full backup.</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="get-started-in-three-steps">Get started in three steps<a href="https://udctl.com/blog/2026/07/07/ud-pull-push/#get-started-in-three-steps" class="hash-link" aria-label="Direct link to Get started in three steps" title="Direct link to Get started in three steps">​</a></h3>
<p>UnDercontrol is a workbench that puts tasks, knowledge base, and AI agents in one place, with a <strong>desktop App</strong> and <strong>self-hosting</strong> support — your data stays private, portable, and yours.</p>
<p><strong>1️⃣ Install the CLI (30 seconds)</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">npm install -g @oatnil/ud</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Don't want to install? Use npx:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">npx @oatnil/ud --help</span><br></span></code></pre></div></div>
<p><strong>2️⃣ Pull, then edit with whatever you like</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ud pull /Work/     # check a virtual directory out locally</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud status          # see what changed on both sides</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud push            # push it back</span><br></span></code></pre></div></div>
<p><strong>3️⃣ Grab the desktop App / self-host your own server</strong></p>
<p>Want a GUI, multi-device access, or the whole server running on your own machine? See the links below.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="start-here">Start here<a href="https://udctl.com/blog/2026/07/07/ud-pull-push/#start-here" class="hash-link" aria-label="Direct link to Start here" title="Direct link to Start here">​</a></h3>
<ul>
<li>🏠 <strong>Website &amp; docs</strong>: <a href="https://oatnil.com/" target="_blank" rel="noopener noreferrer">oatnil.com</a></li>
<li>📦 <strong>CLI installation guide</strong>: <a href="https://oatnil.com/docs/cli" target="_blank" rel="noopener noreferrer">oatnil.com/docs/cli</a></li>
<li>💻 <strong>Desktop App &amp; self-hosting</strong>: <a href="https://oatnil.com/subscribe" target="_blank" rel="noopener noreferrer">oatnil.com/subscribe</a></li>
</ul>
<p>Your data, your format, your tools. UnDercontrol just makes them flow — try it now with <code>npm install -g @oatnil/ud</code>.</p>]]></content:encoded>
            <category>Feature</category>
            <category>Guide</category>
        </item>
        <item>
            <title><![CDATA[Everything as Code: When Your Tasks Become Markdown You Can Commit to Git]]></title>
            <link>https://udctl.com/blog/2026/07/06/everything-as-code/</link>
            <guid>https://udctl.com/blog/2026/07/06/everything-as-code/</guid>
            <pubDate>Mon, 06 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[In UnDercontrol a task is Markdown with YAML frontmatter — version-controllable, applied via a kubectl-style CLI, and readable by any terminal AI agent. GitOps for your todos.]]></description>
            <content:encoded><![CDATA[<p>We've all gotten used to "X as Code" — infrastructure as code, configuration as code, docs as code. The reason is simple: plain text can be diffed, reviewed, version-controlled, and read or written directly by scripts and AI.</p>
<p>So why are your <strong>tasks, notes, and todos</strong> still locked inside some SaaS database, reachable only by clicking through its UI one item at a time?</p>
<p>UnDercontrol's answer is <strong>Everything as Code</strong>. Here, a task <em>is</em> a piece of Markdown with YAML frontmatter. That's it.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/everything-as-code/concept-1.png" alt="A task is Markdown: plain text with frontmatter renders into a task card after apply" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="what-a-task-looks-like">What a Task Looks Like<a href="https://udctl.com/blog/2026/07/06/everything-as-code/#what-a-task-looks-like" class="hash-link" aria-label="Direct link to What a Task Looks Like" title="Direct link to What a Task Looks Like">​</a></h3>
<div class="language-markdown codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-markdown codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token front-matter-block punctuation" style="color:#393A34">---</span><span class="token front-matter-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token front-matter-block"></span><span class="token front-matter-block front-matter yaml language-yaml key atrule" style="color:#00a4db">title</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">:</span><span class="token front-matter-block front-matter yaml language-yaml"> Add a smoke test to the release flow</span><br></span><span class="token-line" style="color:#393A34"><span class="token front-matter-block front-matter yaml language-yaml"></span><span class="token front-matter-block front-matter yaml language-yaml key atrule" style="color:#00a4db">status</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">:</span><span class="token front-matter-block front-matter yaml language-yaml"> in</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">-</span><span class="token front-matter-block front-matter yaml language-yaml">progress</span><br></span><span class="token-line" style="color:#393A34"><span class="token front-matter-block front-matter yaml language-yaml"></span><span class="token front-matter-block front-matter yaml language-yaml key atrule" style="color:#00a4db">tags</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">:</span><span class="token front-matter-block front-matter yaml language-yaml"> </span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">[</span><span class="token front-matter-block front-matter yaml language-yaml">release</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">,</span><span class="token front-matter-block front-matter yaml language-yaml"> testing</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">]</span><span class="token front-matter-block front-matter yaml language-yaml"></span><br></span><span class="token-line" style="color:#393A34"><span class="token front-matter-block front-matter yaml language-yaml"></span><span class="token front-matter-block front-matter yaml language-yaml key atrule" style="color:#00a4db">deadline</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:#393A34">:</span><span class="token front-matter-block front-matter yaml language-yaml"> </span><span class="token front-matter-block front-matter yaml language-yaml datetime number" style="color:#36acaa">2026-07-10</span><span class="token front-matter-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token front-matter-block"></span><span class="token front-matter-block punctuation" style="color:#393A34">---</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">##</span><span class="token title important"> Goal</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Run the critical path automatically before every release; block the tag on failure.</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token title important punctuation" style="color:#393A34">##</span><span class="token title important"> Steps</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> [x] Log in + create task</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> [ ] Browse the three core pages</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token list punctuation" style="color:#393A34">-</span><span class="token plain"> [ ] Assert no console errors</span><br></span></code></pre></div></div>
<p>The frontmatter is the task's <strong>structured metadata</strong>: title, status, tags, deadline, assignee, links — the 20% of fields that cover 80% of cases are all first-class, and all queryable. The body below the frontmatter is the task's <strong>canonical document (the Single Source of Truth)</strong>: a living piece of Markdown.</p>
<p>No proprietary format. No binary blob to export. No vendor lock-in. What you see is all there is.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="apply-just-like-kubectl">apply, Just Like kubectl<a href="https://udctl.com/blog/2026/07/06/everything-as-code/#apply-just-like-kubectl" class="hash-link" aria-label="Direct link to apply, Just Like kubectl" title="Direct link to apply, Just Like kubectl">​</a></h3>
<p>Save that Markdown to a file, then:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ud apply -f task.md</span><br></span></code></pre></div></div>
<p>The rules are exactly like <code>kubectl apply</code>: <strong>no id in the frontmatter means create, an id means update</strong>. apply is a <strong>full replace</strong> — the file is the source of truth.</p>
<p>Want to export in bulk, edit, and push it all back? <code>ud get task -o apply</code> prints your tasks in the same format apply accepts — a lossless round-trip. That means your task list can be processed like code with sed / jq / any script, then applied back in one shot.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/everything-as-code/concept-2.png" alt="Lossless round-trip: ud get -o apply exports to local md, scripts process it, ud apply pushes it back" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="why-this-is-a-superpower-for-developers-and-ai">Why This Is a Superpower for Developers (and AI)<a href="https://udctl.com/blog/2026/07/06/everything-as-code/#why-this-is-a-superpower-for-developers-and-ai" class="hash-link" aria-label="Direct link to Why This Is a Superpower for Developers (and AI)" title="Direct link to Why This Is a Superpower for Developers (and AI)">​</a></h3>
<p><strong>1. Version-controllable, GitOps-friendly.</strong> Tasks are plain text, so they naturally diff, live in Git, and go through PR review. Your project plan and your code can share the same commit history.</p>
<p><strong>2. Native I/O for AI agents.</strong> Markdown is human-readable and AI-readable. Any terminal-based coding agent — Claude Code, Codex, OpenCode, or any agent that lives in your shell — can read and write your tasks directly through the <code>ud</code> CLI, with no special plugin or SDK. The whole knowledge base is plain-text-native and AI-ready.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain"># things an agent can do right in your terminal</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud get task 'status = in-progress'</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud apply -f new-task.md</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud describe task 8b30140e</span><br></span></code></pre></div></div>
<p><strong>3. Scriptable and composable.</strong> A SQL-like query syntax plus kubectl-style verbs (get / describe / apply / delete / patch) turn task management into something you can drop into any automation. Create a task in CI, bulk-retag from a script, generate a weekly report from cron — each is just a few lines of shell.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/everything-as-code/concept-3.png" alt="Many entry points, one source of truth: CLI, AI agents, and CI scripts all write to the same Markdown data source through ud apply" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="not-just-tasks">Not Just Tasks<a href="https://udctl.com/blog/2026/07/06/everything-as-code/#not-just-tasks" class="hash-link" aria-label="Direct link to Not Just Tasks" title="Direct link to Not Just Tasks">​</a></h3>
<p>Here's a detail that's easy to miss: UnDercontrol's Markdown editor is <strong>not task-specific</strong>. It's a single editor shared across every text surface — tasks, notes, even expense and account memos all use the same editing experience and the same Markdown core.</p>
<p>So the "Everything" in "Everything as Code" is literal: your todos are Markdown, your notes are Markdown, the memo on an expense you logged is Markdown. One data source, organized the way you like, all in one place. No switching between apps, and an AI agent only has to learn a single tool to reach all of your information.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="three-content-layers">Three Content Layers<a href="https://udctl.com/blog/2026/07/06/everything-as-code/#three-content-layers" class="hash-link" aria-label="Direct link to Three Content Layers" title="Direct link to Three Content Layers">​</a></h3>
<p>Each information unit has three layers. Each has a design intent, but you're free to use them however suits you:</p>
<table><thead><tr><th>Layer</th><th>Format</th><th>Design Intent</th></tr></thead><tbody><tr><td><strong>Task body</strong></td><td>Markdown</td><td>The canonical, evolving document — Single Source of Truth</td></tr><tr><td><strong>Notes</strong></td><td>Markdown</td><td>An append-only timeline — how this unit evolved</td></tr><tr><td><strong>Comments</strong></td><td>Plain text</td><td>Lightweight, threaded conversation — short and focused</td></tr></tbody></table>
<p>What distinguishes a "doc" from a "task" isn't the data structure — it's <strong>tags, metadata, and the view</strong>. UnDercontrol doesn't force a folder hierarchy or a tagging convention on you: the same task can appear on multiple boards (a board is a <em>saved query</em>, not a container, so there's no duplication).</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="get-started">Get Started<a href="https://udctl.com/blog/2026/07/06/everything-as-code/#get-started" class="hash-link" aria-label="Direct link to Get Started" title="Direct link to Get Started">​</a></h3>
<p>If you're tired of trapping your ideas inside a UI, try turning them into Markdown you can apply:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ud apply -f my-first-task.md</span><br></span></code></pre></div></div>
<p>Everything as Code isn't just a slogan — it's how your tasks, your scripts, and your AI agents finally speak the same language.</p>]]></content:encoded>
            <category>Feature</category>
            <category>Guide</category>
        </item>
        <item>
            <title><![CDATA[Access Anywhere — Web, Desktop, CLI, and Beyond]]></title>
            <link>https://udctl.com/blog/2026/04/28/multi-platform/</link>
            <guid>https://udctl.com/blog/2026/04/28/multi-platform/</guid>
            <pubDate>Tue, 28 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[UnDercontrol provides four official clients — Web, Electron desktop, CLI, and Chrome extension — sharing a single self-hosted data source. Plus an open API for building your own.]]></description>
            <content:encoded><![CDATA[<p>When choosing a productivity tool, you often face a familiar dilemma: web apps are powerful but useless offline, desktop apps feel great but lock your data locally, and CLI tools are developer-friendly but lack a visual interface. The root cause is the absence of a Single Source of Truth — each platform is an island, and your data is scattered across them. UnDercontrol's answer: four form factors, one data source. And that data source is entirely under your control — self-hosted on your own server or stored on your local disk, never passing through any third party. Privacy and security are defined by you.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/multi-platform/slide-sst.png" alt="Single Source of Truth architecture" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="web-app--remote-access-zero-install">Web App — Remote Access, Zero Install<a href="https://udctl.com/blog/2026/04/28/multi-platform/#web-app--remote-access-zero-install" class="hash-link" aria-label="Direct link to Web App — Remote Access, Zero Install" title="Direct link to Web App — Remote Access, Zero Install">​</a></h3>
<p>UnDercontrol's web frontend is built with Vite + React + TypeScript and serves as the foundation of the entire platform. Open a browser and you're in — no installation required.</p>
<p>Core capabilities:</p>
<ul>
<li>Full task, budget, and expense management</li>
<li>Tiptap rich-text editor (code blocks, Mermaid diagrams, tables, checklists)</li>
<li>AI chat integration (supports Claude, OpenAI, and other providers)</li>
<li>SSE real-time notifications with multi-tab sync</li>
<li>Responsive design that works on mobile</li>
<li>Bilingual interface (Chinese / English)</li>
</ul>
<p>The web app also serves as the rendering layer for the Electron desktop app — same codebase, zero duplication.</p>
<p><strong>Typical scenarios</strong></p>
<ul>
<li>On the go, quickly check task progress or approve expenses from your phone's browser</li>
<li>Team members collaborate instantly — just open a link, no software to install</li>
<li>The convergence point for all clients — docs pushed from CLI, tasks created on desktop, pages clipped by the extension — all viewable and manageable in one place</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/multi-platform/web-app-dashboard.png" alt="UnDercontrol web app dashboard" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="electron-desktop-app--offline-first-local-data">Electron Desktop App — Offline First, Local Data<a href="https://udctl.com/blog/2026/04/28/multi-platform/#electron-desktop-app--offline-first-local-data" class="hash-link" aria-label="Direct link to Electron Desktop App — Offline First, Local Data" title="Direct link to Electron Desktop App — Offline First, Local Data">​</a></h3>
<p>The desktop app is far more than a web wrapper. It embeds a full Go backend and SQLite database, ready to use out of the box with no server configuration.</p>
<p><strong>Embedded backend architecture</strong></p>
<ul>
<li>Automatically launches the Go backend on startup</li>
<li>Dynamic port allocation to avoid conflicts</li>
<li>Data stored in <code>~/Library/Application Support/UnDercontrol/</code> (macOS)</li>
<li>Fully offline-capable — works without any network connection</li>
</ul>
<p><strong>Remote backend mode</strong></p>
<ul>
<li>The desktop app can also connect to a remote server, sharing the same data source as the web app</li>
<li>Switch the API address in settings — no reinstallation needed</li>
</ul>
<p><strong>Daemon mode</strong></p>
<ul>
<li>Background daemon process that listens to the task queue via SSE</li>
<li>Receives and automatically executes remotely dispatched tasks — ideal for AI Agent workflows</li>
</ul>
<p><strong>Desktop-exclusive features</strong></p>
<ul>
<li>System tray (Windows) — minimize to tray for quick access</li>
<li><code>.md</code> file association — double-click a Markdown file to open it in UnDercontrol</li>
<li>Multi-window editing — open multiple editor, task, and sticky-note windows simultaneously</li>
<li>Workspace management — local terminal windows for running commands</li>
</ul>
<p><strong>Build targets</strong></p>
<ul>
<li>macOS — DMG installer, Universal Binary (x64 + ARM64)</li>
<li>Windows — NSIS installer (x64)</li>
<li>Linux — AppImage (x64)</li>
</ul>
<p><strong>Typical scenarios</strong></p>
<ul>
<li>On a plane or train with no network — create tasks and edit documents as usual; everything syncs when you're back online</li>
<li>At home, connect the desktop app to your company server — see exactly the same data as the web app</li>
<li>Receive remotely dispatched AI tasks via Daemon and execute them automatically on your local machine</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/multi-platform/web-app-task-detail.png" alt="UnDercontrol task detail page" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="cli--kubectl-style-ai-agent-friendly">CLI — kubectl-Style, AI Agent Friendly<a href="https://udctl.com/blog/2026/04/28/multi-platform/#cli--kubectl-style-ai-agent-friendly" class="hash-link" aria-label="Direct link to CLI — kubectl-Style, AI Agent Friendly" title="Direct link to CLI — kubectl-Style, AI Agent Friendly">​</a></h3>
<p>The <code>ud</code> CLI is a terminal tool designed for developers and automation, using a kubectl-style verb-resource command system.</p>
<p><strong>Interactive TUI</strong></p>
<p>Run <code>ud</code> to enter a full-screen TUI for browsing and managing tasks with your keyboard.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/multi-platform/slide-sst.png" alt="Multi-platform architecture — Single Source of Truth" class="img_ev3q"></p>
<p><strong>One-liner operations</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">ud get task                          # list tasks</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud describe task abc123              # view details</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud apply -f task.md                  # create/update from file</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud task query "status:todo tag:api"  # query and filter</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">ud task nl "tasks completed last week" # natural language query</span><br></span></code></pre></div></div>
<p><strong>AI Agent integration</strong></p>
<p><code>ud prompt &lt;skill-name&gt;</code> outputs skill documents that teach AI Agents (Claude Code, Codex, OpenCode, and others) how to operate tasks via the ud CLI. Structured command output and Markdown-formatted input make it a natural fit for AI Agents to read and write.</p>
<p><strong>Typical scenarios</strong></p>
<ul>
<li>CI/CD pipelines automatically push release notes to ud — the team reads them on the web, no need to dig through Git logs</li>
<li>Technical docs in your code repo (architecture designs, API specs, runbooks) are synced to ud via scripts — non-developers can read them on the web without accessing the repository</li>
<li>AI Agents automatically record progress and decisions in task notes during coding — the team sees updates in real time</li>
<li>Ops scripts periodically collect inspection results and <code>ud apply</code> them as tasks, forming a traceable operations log</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="chrome-extension--one-click-clipping">Chrome Extension — One-Click Clipping<a href="https://udctl.com/blog/2026/04/28/multi-platform/#chrome-extension--one-click-clipping" class="hash-link" aria-label="Direct link to Chrome Extension — One-Click Clipping" title="Direct link to Chrome Extension — One-Click Clipping">​</a></h3>
<p>The Chrome extension turns any web page into an UnDercontrol task.</p>
<ul>
<li><strong>One-click save</strong> — captures a full page snapshot (HTML + all embedded resources)</li>
<li><strong>Markdown extraction</strong> — automatically extracts the page body as clean Markdown</li>
<li><strong>Offline mode</strong> — save directly to local disk without logging in</li>
<li><strong>Remote mode</strong> — log in to create an UnDercontrol task directly, with the snapshot uploaded as an attachment</li>
</ul>
<p><strong>Typical scenarios</strong></p>
<ul>
<li>Researching competitors — clip product pages as tasks, then compare and organize on the web later</li>
<li>Found a great technical article — clip it as Markdown into ud for the team to share</li>
<li>Received a customer bug report on a web page — clip it as a bug task with full context attached</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="sse-real-time-sync">SSE Real-Time Sync<a href="https://udctl.com/blog/2026/04/28/multi-platform/#sse-real-time-sync" class="hash-link" aria-label="Direct link to SSE Real-Time Sync" title="Direct link to SSE Real-Time Sync">​</a></h3>
<p>All clients connected to the same backend — whether browser tabs, the desktop app, or another device — stay in sync via SSE (Server-Sent Events).</p>
<ul>
<li>Task status changes, new comments, attachment uploads — all events pushed in real time</li>
<li>Daemon SSE Hub dispatches commands to desktop Daemon instances</li>
<li>Workspace session state is automatically coordinated between the desktop app and the backend</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/multi-platform/slide-clients.png" alt="Four official clients" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="one-codebase-consistent-experience">One Codebase, Consistent Experience<a href="https://udctl.com/blog/2026/04/28/multi-platform/#one-codebase-consistent-experience" class="hash-link" aria-label="Direct link to One Codebase, Consistent Experience" title="Direct link to One Codebase, Consistent Experience">​</a></h3>
<p>UnDercontrol's multi-platform strategy isn't "build once for each platform." Instead:</p>
<ul>
<li><strong>Web app</strong> is the single frontend codebase</li>
<li><strong>Electron</strong> loads the web build directly — zero code duplication</li>
<li><strong>CLI</strong> shares the same API and data model</li>
<li><strong>Chrome extension</strong> uses the same API endpoints</li>
</ul>
<table><thead><tr><th>Capability</th><th>Web</th><th>Desktop</th><th>CLI</th><th>Extension</th></tr></thead><tbody><tr><td>Remote API access</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td>Embedded backend</td><td>❌</td><td>✅ (switchable to remote)</td><td>❌</td><td>❌</td></tr><tr><td>Offline mode</td><td>❌</td><td>✅</td><td>❌</td><td>✅</td></tr><tr><td>System tray</td><td>❌</td><td>✅</td><td>❌</td><td>❌</td></tr><tr><td>TUI interface</td><td>❌</td><td>❌</td><td>✅</td><td>❌</td></tr><tr><td>Background Daemon</td><td>❌</td><td>✅</td><td>❌</td><td>❌</td></tr><tr><td>Web clipping</td><td>❌</td><td>❌</td><td>❌</td><td>✅</td></tr><tr><td>Real-time SSE</td><td>✅</td><td>✅</td><td>❌</td><td>❌</td></tr><tr><td>AI Agent friendly</td><td>✅</td><td>✅</td><td>✅</td><td>❌</td></tr></tbody></table>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/multi-platform/slide-capabilities.png" alt="Capability comparison table" class="img_ev3q"></p>
<p>And there's more — the open API lets you build any client you want.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="beyond-official-clients--build-your-own">Beyond Official Clients — Build Your Own<a href="https://udctl.com/blog/2026/04/28/multi-platform/#beyond-official-clients--build-your-own" class="hash-link" aria-label="Direct link to Beyond Official Clients — Build Your Own" title="Direct link to Beyond Official Clients — Build Your Own">​</a></h3>
<p>Beyond the four official clients, UnDercontrol exposes a full RESTful API. Generate an API Key in your profile settings, pair it with the Swagger docs, and build your own client in any language — Python scripts, automation bots, internal tool integrations, or even your own mobile app.</p>
<ul>
<li><strong>API Key authentication</strong> — generate in Profile → API Key, call with Bearer Token</li>
<li><strong>Scoped permissions</strong> — authorize independently by module (Tasks, Expenses, Budgets, Files, AI)</li>
<li><strong>Swagger docs</strong> — browse all endpoints at <code>https://your-server/swagger/index.html</code></li>
<li><strong>X-UD-Channel audit</strong> — custom channel identifier to trace every request's origin</li>
<li><strong>CI/CD friendly</strong> — environment variable configuration for seamless automation pipeline integration</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/multi-platform/slide-custom.png" alt="Build your own client" class="img_ev3q"></p>
<p><strong>Typical scenarios</strong></p>
<ul>
<li>Build a Slack Bot in Python — your team types <code>/task</code> in Slack to create and query ud tasks</li>
<li>Connect your internal admin panel's ticketing system to ud via API for bidirectional status sync</li>
<li>Build a native mobile app calling the same API for a personalized phone experience</li>
<li>Monitoring system alerts automatically create ud tasks with context attached — on-call engineers handle them on the web</li>
</ul>
<hr>
<p>Different scenarios, the right tool for the job — data always in sync, experience always consistent.</p>]]></content:encoded>
            <category>Feature</category>
            <category>platform</category>
            <category>self-hosted</category>
        </item>
        <item>
            <title><![CDATA[Rich Markdown in Your Tasks — Code, Diagrams, and More]]></title>
            <link>https://udctl.com/blog/2026/04/27/markdown-features/</link>
            <guid>https://udctl.com/blog/2026/04/27/markdown-features/</guid>
            <pubDate>Mon, 27 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[UnDercontrol tasks support full markdown with syntax-highlighted code blocks, Mermaid diagrams, tables, checklists, and a slash command menu for fast formatting.]]></description>
            <content:encoded><![CDATA[<h3 class="anchor anchorWithStickyNavbar_LWe7" id="what-is-a-task">What Is a Task?<a href="https://udctl.com/blog/2026/04/27/markdown-features/#what-is-a-task" class="hash-link" aria-label="Direct link to What Is a Task?" title="Direct link to What Is a Task?">​</a></h3>
<p>In UnDercontrol, a Task is the core unit of information. If you've used Jira, think of it as an Issue; if you've used Obsidian, think of it as a Note. A Task is essentially a piece of content bound to a status, with first-class attributes (title, tags, link relationships, etc.) and support for unlimited custom metadata fields (key-value pairs), so you can attach any information to it. Through the Notes mechanism, a Task can continuously evolve — recording progress, discussions, and decisions, accumulating into a complete knowledge context over time.</p>
<p>This post covers the rich-text capabilities shared across all text input surfaces in UnDercontrol. We say "tasks", but it's not just tasks — whether it's a task description, a note, an expense memo, or an account annotation, they all use the same Markdown editor. The code blocks, Mermaid diagrams, tables, and checklists you use in tasks work identically in notes and every other context.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="wysiwyg--a-markdown-editor-built-on-tiptap-3">WYSIWYG — A Markdown Editor Built on Tiptap 3<a href="https://udctl.com/blog/2026/04/27/markdown-features/#wysiwyg--a-markdown-editor-built-on-tiptap-3" class="hash-link" aria-label="Direct link to WYSIWYG — A Markdown Editor Built on Tiptap 3" title="Direct link to WYSIWYG — A Markdown Editor Built on Tiptap 3">​</a></h3>
<p>UnDercontrol's editor is built on <a href="https://tiptap.dev/" target="_blank" rel="noopener noreferrer">Tiptap 3</a>. You can write directly in Markdown syntax or use the visual toolbar — the editor renders in real time, what you write is what you see. No switching between "edit mode" and "preview mode" — the input is the final output.</p>
<p>The editor also supports switching to Source Mode to view and edit the raw Markdown directly — for users who prefer plain text editing or need precise format control, you can freely toggle between WYSIWYG and raw Markdown at any time.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="slash-commands--format-without-leaving-the-keyboard">Slash Commands — Format Without Leaving the Keyboard<a href="https://udctl.com/blog/2026/04/27/markdown-features/#slash-commands--format-without-leaving-the-keyboard" class="hash-link" aria-label="Direct link to Slash Commands — Format Without Leaving the Keyboard" title="Direct link to Slash Commands — Format Without Leaving the Keyboard">​</a></h3>
<p>Type <code>/</code> anywhere to open the command menu. Insert headings, code blocks, tables, Mermaid diagrams, checklists, and more — all without touching the mouse.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/markdown-features/slide-2.png" alt="Slash command menu in the editor" class="img_ev3q"></p>
<p>The slash menu supports:</p>
<ul>
<li><strong>Headings</strong> (H1–H5) for document structure</li>
<li><strong>Task lists</strong> with interactive checkboxes</li>
<li><strong>Code blocks</strong> with language selection and syntax highlighting</li>
<li><strong>Mermaid diagrams</strong> for flowcharts, sequence diagrams, and more</li>
<li><strong>Tables</strong> with full cell editing</li>
<li><strong>Blockquotes</strong>, dividers, and images</li>
</ul>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="syntax-highlighted-code-blocks">Syntax-Highlighted Code Blocks<a href="https://udctl.com/blog/2026/04/27/markdown-features/#syntax-highlighted-code-blocks" class="hash-link" aria-label="Direct link to Syntax-Highlighted Code Blocks" title="Direct link to Syntax-Highlighted Code Blocks">​</a></h3>
<p>Paste code snippets directly into your tasks. The editor supports 100+ programming languages with proper syntax highlighting — from TypeScript and Go to SQL and YAML.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/markdown-features/api-task-top.png" alt="Task with syntax-highlighted TypeScript code block" class="img_ev3q"></p>
<p>Code blocks include a language selector dropdown and a one-click copy button. Your code stays readable and properly formatted, whether you're documenting an API endpoint or saving a useful shell command.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="mermaid-diagrams--visualize-architecture-inline">Mermaid Diagrams — Visualize Architecture Inline<a href="https://udctl.com/blog/2026/04/27/markdown-features/#mermaid-diagrams--visualize-architecture-inline" class="hash-link" aria-label="Direct link to Mermaid Diagrams — Visualize Architecture Inline" title="Direct link to Mermaid Diagrams — Visualize Architecture Inline">​</a></h3>
<p>One of the most powerful features: embed <a href="https://mermaid.js.org/" target="_blank" rel="noopener noreferrer">Mermaid</a> diagrams directly in your task descriptions. Insert a Mermaid block via the slash menu, write your diagram syntax, and it renders live.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/markdown-features/arch-task-top.png" alt="Mermaid flowchart showing microservices architecture" class="img_ev3q"></p>
<p>Supported diagram types include:</p>
<ul>
<li><strong>Flowcharts</strong> — system architecture, decision trees</li>
<li><strong>Sequence diagrams</strong> — API flows, authentication handshakes</li>
<li><strong>Class diagrams</strong> — data models, entity relationships</li>
<li><strong>State diagrams</strong> — workflow states, lifecycle tracking</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/markdown-features/api-task-mermaid.png" alt="Mermaid sequence diagram showing authentication flow" class="img_ev3q"></p>
<p>The diagram viewer supports fullscreen preview, SVG download, and automatic dark/light theme switching.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="tables-for-structured-data">Tables for Structured Data<a href="https://udctl.com/blog/2026/04/27/markdown-features/#tables-for-structured-data" class="hash-link" aria-label="Direct link to Tables for Structured Data" title="Direct link to Tables for Structured Data">​</a></h3>
<p>Need to document status codes, compare metrics, or track a feature matrix? Insert a table and edit cells directly. Add or remove rows and columns, toggle header rows, and merge cells — all from a context menu.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/markdown-features/api-task-table-checklist.png" alt="Table with status codes and interactive checklist" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="checklists-that-actually-work">Checklists That Actually Work<a href="https://udctl.com/blog/2026/04/27/markdown-features/#checklists-that-actually-work" class="hash-link" aria-label="Direct link to Checklists That Actually Work" title="Direct link to Checklists That Actually Work">​</a></h3>
<p>Task lists render as interactive checkboxes. Check items off directly in the rendered view — no need to switch to edit mode. Great for tracking sub-steps, acceptance criteria, or deployment checklists.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/markdown-features/slide-5.png" alt="Interactive checklist in task description" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="images--attachments--no-more-obsidian-style-resource-management-headaches">Images &amp; Attachments — No More Obsidian-Style Resource Management Headaches<a href="https://udctl.com/blog/2026/04/27/markdown-features/#images--attachments--no-more-obsidian-style-resource-management-headaches" class="hash-link" aria-label="Direct link to Images &amp; Attachments — No More Obsidian-Style Resource Management Headaches" title="Direct link to Images &amp; Attachments — No More Obsidian-Style Resource Management Headaches">​</a></h3>
<p>If you've managed notes with images in Obsidian, you've probably experienced the pain: images scattered across local folders, paths break when you move things, attachments disappear on another device, multi-device sync creates endless conflicts. The root cause — Obsidian delegates resource management to the filesystem, and filesystems are inherently bad at cross-device sync.</p>
<p>UnDercontrol solves this at the foundation. All images and attachments are managed through the <code>resource://</code> protocol — upload once, stored in the database, no dependency on local paths. Whether you view content on the web, desktop, or via a shared link, images are always available — because resources follow the database, not the filesystem.</p>
<p>UnDercontrol also provides bidirectional local folder sync, keeping files in a local directory synchronized with the server resource library. We'll cover this feature in detail in a future post.</p>
<p><strong>Image Size Control</strong></p>
<p>Inserted images support three size presets — hover in edit mode to switch:</p>
<ul>
<li><strong>Small (25%)</strong> — thumbnail, suitable for inline display</li>
<li><strong>Medium (50%)</strong> — moderate size</li>
<li><strong>Large (100%)</strong> — full width</li>
</ul>
<p>Size information is stored in Obsidian-compatible format (<code>![description|s](resource://id)</code>). Click any image for fullscreen preview.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/markdown-features/image-size-controls.png" alt="Image size controls — S, M, L presets" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="drawio-diagrams--built-in-visual-drawing">Draw.io Diagrams — Built-in Visual Drawing<a href="https://udctl.com/blog/2026/04/27/markdown-features/#drawio-diagrams--built-in-visual-drawing" class="hash-link" aria-label="Direct link to Draw.io Diagrams — Built-in Visual Drawing" title="Direct link to Draw.io Diagrams — Built-in Visual Drawing">​</a></h3>
<p>UnDercontrol includes a built-in Draw.io editor, supporting <code>.drawio</code> and <code>.drawio.png</code> formats. Upload or create a Draw.io file, then edit it directly in the app — no desktop software needed. Saved as PNG with embedded XML source data, so it renders as a preview and can be re-edited at any time.</p>
<p>Thanks to the <code>resource://</code> protocol, Draw.io diagrams are managed as unified resources — no local path dependencies or sync issues.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="ai-powered-text-actions">AI-Powered Text Actions<a href="https://udctl.com/blog/2026/04/27/markdown-features/#ai-powered-text-actions" class="hash-link" aria-label="Direct link to AI-Powered Text Actions" title="Direct link to AI-Powered Text Actions">​</a></h3>
<p>Select any text to reveal the bubble menu. Beyond standard formatting (bold, italic, strikethrough), you get AI-powered actions:</p>
<ul>
<li><strong>Refine</strong> — rewrite selected text for clarity</li>
<li><strong>Translate</strong> — translate to another language instantly</li>
<li><strong>Chat</strong> — ask questions about the selected content</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/markdown-features/slide-7.png" alt="Feature overview — everything you need to document" class="img_ev3q"></p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="entity-links--connect-tasks-like-a-wiki">Entity Links — Connect Tasks Like a Wiki<a href="https://udctl.com/blog/2026/04/27/markdown-features/#entity-links--connect-tasks-like-a-wiki" class="hash-link" aria-label="Direct link to Entity Links — Connect Tasks Like a Wiki" title="Direct link to Entity Links — Connect Tasks Like a Wiki">​</a></h3>
<p>Link to other tasks, notes, expenses, budgets, and accounts directly in your descriptions using custom protocols like <code>task://</code>, <code>note://</code>, and more. The links render as clickable references — click to jump straight to the referenced entity.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/markdown-features/entity-links-rendered.png" alt="Rendered entity links in a task description" class="img_ev3q"></p>
<p>Switch to source mode to see the raw markdown — each link uses a custom protocol like <code>[API Integration Guide](task://189571b0-...)</code>:</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/markdown-features/entity-links-raw.png" alt="Raw markdown showing task:// protocol links" class="img_ev3q"></p>
<p>This turns your task descriptions into a connected wiki, where context flows naturally between related items.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="cli--ai-agent--let-ai-write-your-content">CLI + AI Agent — Let AI Write Your Content<a href="https://udctl.com/blog/2026/04/27/markdown-features/#cli--ai-agent--let-ai-write-your-content" class="hash-link" aria-label="Direct link to CLI + AI Agent — Let AI Write Your Content" title="Direct link to CLI + AI Agent — Let AI Write Your Content">​</a></h3>
<p>Because Markdown is plain text, it's a natural fit for AI agent collaboration. Through the <code>ud</code> CLI, you can let Claude Code, Codex, OpenCode, or any terminal-based AI tool read, write, and update task content directly:</p>
<ul>
<li><strong>Summarize</strong> — have AI read a set of tasks and generate a weekly report or sprint review</li>
<li><strong>Refine &amp; restructure</strong> — turn scattered notes into structured documentation</li>
<li><strong>Classify &amp; tag</strong> — automatically add tags and categories based on content</li>
<li><strong>Batch create</strong> — generate multiple tasks from meeting notes or requirement docs</li>
</ul>
<p>Content pipes directly in:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">cat &lt;&lt;'EOF' | ud apply -f -</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">---</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">title: API Integration Guide</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">status: in-progress</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">tags: [api, backend]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">---</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">## Authentication Flow</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">1. Client sends credentials to `/auth/login`</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">2. Server returns JWT token</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">3. Include in `Authorization: Bearer &lt;token&gt;` header</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">EOF</span><br></span></code></pre></div></div>
<p>AI-generated Markdown renders identically to hand-written Markdown across web, desktop, and shared views. Your content stays portable and version-controllable.</p>
<hr>
<p>Markdown support in UnDercontrol means your tasks can be as detailed and technical as they need to be — without leaving the tool where the work gets tracked. Code blocks, diagrams, tables, and checklists all live alongside your task status, deadlines, and collaboration context.</p>
<p>Try it out — create a task and type <code>/</code> to see what's possible.</p>]]></content:encoded>
            <category>Feature</category>
            <category>markdown</category>
            <category>productivity</category>
        </item>
        <item>
            <title><![CDATA[Remote Workspace — Run AI Agents on Your Machine from the Browser]]></title>
            <link>https://udctl.com/blog/2026/04/25/remote-workspace/</link>
            <guid>https://udctl.com/blog/2026/04/25/remote-workspace/</guid>
            <pubDate>Sat, 25 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[How UnDercontrol's remote workspace feature lets you trigger AI agents like Claude Code on your local machine directly from the desktop app, with real-time progress tracking.]]></description>
            <content:encoded><![CDATA[<p>You write a task description in the web UI. You click a button. Thirty seconds later, an AI agent on your laptop is reading the task, writing code, running tests, and posting progress notes back to the same task page — all while you watch from the browser.</p>
<p>That's Remote Workspace. It bridges the gap between a cloud-based task manager and your local development environment, turning UnDercontrol into a remote control for AI-powered coding agents.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-problem">The Problem<a href="https://udctl.com/blog/2026/04/25/remote-workspace/#the-problem" class="hash-link" aria-label="Direct link to The Problem" title="Direct link to The Problem">​</a></h2>
<p>Most task management tools live entirely in the browser. Your code lives on your machine. When you want an AI agent to work on something, you switch to a terminal, paste context, babysit the process, and manually update the task when it's done. The task tracker and the execution environment are two separate worlds.</p>
<p>Remote Workspace collapses that distance. Your task descriptions become executable instructions. Your browser becomes a control panel. Your machine does the work.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="how-it-works">How It Works<a href="https://udctl.com/blog/2026/04/25/remote-workspace/#how-it-works" class="hash-link" aria-label="Direct link to How It Works" title="Direct link to How It Works">​</a></h2>
<p>The architecture has four pieces:</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/remote-workspace/slide-2.png" alt="Architecture: Web UI connects to Server via REST, Server connects to Daemon via SSE, Daemon spawns AI Agent via PTY" class="img_ev3q"></p>
<p><strong>1. The Electron Desktop App</strong> — runs on your machine and acts as the daemon. When you register your device in the Workspaces page, the app connects to the UnDercontrol server via Server-Sent Events (SSE) and waits for instructions.</p>
<p><strong>2. The Server</strong> — relays commands between the web UI and the daemon. Persists session state, notes, and status updates.</p>
<p><strong>3. The Daemon Core</strong> — the "brain" inside the Electron renderer. It holds auth tokens, manages SSE connections, and drives all API calls. When a workspace session starts, it forwards the init event to the Electron main process.</p>
<p><strong>4. The AI Agent</strong> — a coding tool (like Claude Code) spawned in a pseudo-terminal by the Electron main process. It reads the task, does the work, and reports progress through task notes.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="setting-it-up">Setting It Up<a href="https://udctl.com/blog/2026/04/25/remote-workspace/#setting-it-up" class="hash-link" aria-label="Direct link to Setting It Up" title="Direct link to Setting It Up">​</a></h2>
<p>Open the UnDercontrol desktop app and navigate to the <strong>Workspaces</strong> page. Click <strong>Register this device</strong> — the app detects your machine name and platform automatically. That's it. Your machine is now a daemon, connected and ready.</p>
<p>The Electron app handles everything behind the scenes: daemon registration, SSE connection, heartbeat, and reconnection. No terminal commands needed.</p>
<p>For the AI agent to work, you'll need <strong>Claude Code</strong> installed on your machine:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">npm install -g @anthropic-ai/claude-code</span><br></span></code></pre></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="triggering-a-session">Triggering a Session<a href="https://udctl.com/blog/2026/04/25/remote-workspace/#triggering-a-session" class="hash-link" aria-label="Direct link to Triggering a Session" title="Direct link to Triggering a Session">​</a></h2>
<p>Once your device is registered and online, go to any task in the web UI (or the desktop app itself). Click the globe icon in the actions bar. You'll see a list of your connected daemons — pick one, and the session starts.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/remote-workspace/task-detail.png" alt="Task detail page showing the task description and workspace controls" class="img_ev3q"></p>
<p>The daemon receives the init event, and the Electron main process spawns the AI agent in a PTY window. From this point, everything happens automatically:</p>
<ol>
<li>The agent reads the task description and notes</li>
<li>It plans and executes the work</li>
<li>Progress notes appear on the task in real-time</li>
<li>When done, it updates the task status</li>
</ol>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="watching-it-work">Watching It Work<a href="https://udctl.com/blog/2026/04/25/remote-workspace/#watching-it-work" class="hash-link" aria-label="Direct link to Watching It Work" title="Direct link to Watching It Work">​</a></h2>
<p>This is where it gets satisfying. As the agent runs on your machine, you see its progress in the browser — live.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/remote-workspace/slide-3.png" alt="How it works: five steps from starting the daemon to watching live progress" class="img_ev3q"></p>
<p>Notes stream in as the agent works: what it's reading, what it changed, which files it committed. You get a running log of the entire session without touching the terminal.</p>
<p>The session panel shows:</p>
<ul>
<li><strong>Status</strong> — running, planning, awaiting input, idle, or stopped</li>
<li><strong>Duration</strong> — how long the session has been active</li>
<li><strong>Daemon info</strong> — which machine is doing the work</li>
<li><strong>Notes stream</strong> — real-time updates from the agent</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="interactive-control">Interactive Control<a href="https://udctl.com/blog/2026/04/25/remote-workspace/#interactive-control" class="hash-link" aria-label="Direct link to Interactive Control" title="Direct link to Interactive Control">​</a></h2>
<p>Remote Workspace isn't fire-and-forget. You can interact with the agent while it's running:</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/remote-workspace/slide-4.png" alt="Interactive controls: send instructions, screenshots, interrupt, and stop" class="img_ev3q"></p>
<ul>
<li><strong>Send instructions</strong> — type additional context or redirect the agent mid-task</li>
<li><strong>Take screenshots</strong> — capture the current state of the workspace</li>
<li><strong>Interrupt</strong> — send a Ctrl+C signal to pause execution</li>
<li><strong>Stop</strong> — end the session entirely</li>
</ul>
<p>There's also a prompt system. Save frequently-used instructions as templates, and apply them with one click. If you've set up skills in UnDercontrol, you can reference those too — pipe a skill's content directly to the agent.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-workspaces-dashboard">The Workspaces Dashboard<a href="https://udctl.com/blog/2026/04/25/remote-workspace/#the-workspaces-dashboard" class="hash-link" aria-label="Direct link to The Workspaces Dashboard" title="Direct link to The Workspaces Dashboard">​</a></h2>
<p>The Workspaces page gives you a bird's-eye view of all active sessions and connected daemons.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/remote-workspace/workspaces-dashboard.png" alt="Workspaces dashboard showing active sessions and connected daemons" class="img_ev3q"></p>
<p>Each session card shows the task it's working on, which daemon is running it, how long it's been going, and the latest notes. Daemons are listed with their online/offline status, machine name, platform, and sharing permissions.</p>
<p>You can register your device right from this page — one click and you're online.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="security">Security<a href="https://udctl.com/blog/2026/04/25/remote-workspace/#security" class="hash-link" aria-label="Direct link to Security" title="Direct link to Security">​</a></h2>
<p>Running AI agents remotely raises obvious safety questions. Remote Workspace handles this with a layered blacklist system:</p>
<p><strong>Built-in protections:</strong></p>
<ul>
<li>Tools like <code>bash</code>, <code>sh</code>, <code>zsh</code> are blocked as the implementation tool — the agent runs through a controlled interface, not a raw shell</li>
<li>Destructive commands (<code>rm -rf</code>, <code>mkfs</code>, <code>dd</code>) are blocked at the daemon level</li>
</ul>
<p><strong>Custom configuration:</strong> Drop a YAML file at <code>~/.config/ud/workspace-blacklist.yml</code> to define your own rules:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">blocked_tools</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> bash</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> sh</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">allowed_tools</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> claude</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">blocked_commands</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"rm -rf"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"mkfs"</span><br></span></code></pre></div></div>
<p>The daemon also requires proper authentication — the Electron app uses your logged-in session tokens, so unauthorized access isn't possible without your credentials.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="sharing-daemons">Sharing Daemons<a href="https://udctl.com/blog/2026/04/25/remote-workspace/#sharing-daemons" class="hash-link" aria-label="Direct link to Sharing Daemons" title="Direct link to Sharing Daemons">​</a></h2>
<p>In team setups, you can share a daemon with your group. Set the sharing permissions so teammates can trigger workspace sessions on your machine — useful for shared build servers or dedicated CI machines.</p>
<p>Sharing is controlled through the daemon's permissions: read-only lets others see the daemon status, read-write lets them initiate sessions.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-this-matters">Why This Matters<a href="https://udctl.com/blog/2026/04/25/remote-workspace/#why-this-matters" class="hash-link" aria-label="Direct link to Why This Matters" title="Direct link to Why This Matters">​</a></h2>
<p>Most developer tools treat task management and code execution as separate concerns. You plan in one app, execute in another, and manually bridge the gap with copy-paste and context switching.</p>
<p>Remote Workspace merges them. Your task description <em>is</em> the instruction. Your browser <em>is</em> the control panel. The work happens on your machine, with your tools, in your environment — you just don't have to be in the terminal to start it.</p>
<p><img decoding="async" loading="lazy" src="https://pub-35d77f83ee8a41798bb4b2e1831ac70a.r2.dev/features/blog/remote-workspace/slide-5.png" alt="Security layers: tool blacklist, command blacklist, API key auth, custom config" class="img_ev3q"></p>
<p>For solo developers, it means less context switching. Write the task, trigger the agent, review the output — all in the same interface. For teams, it means a shared queue of work that machines can pick up and execute, with full visibility into what's happening.</p>
<p>The architecture also means you're not locked into any particular AI tool. Today it works with Claude Code; tomorrow it could work with any agent that runs in a terminal. The protocol is simple: receive a task, do the work, report progress.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="getting-started">Getting Started<a href="https://udctl.com/blog/2026/04/25/remote-workspace/#getting-started" class="hash-link" aria-label="Direct link to Getting Started" title="Direct link to Getting Started">​</a></h2>
<ol>
<li>Download the <a href="https://undercontrol.app/" target="_blank" rel="noopener noreferrer">UnDercontrol desktop app</a></li>
<li>Install Claude Code: <code>npm install -g @anthropic-ai/claude-code</code></li>
<li>Open the Workspaces page and click <strong>Register this device</strong></li>
<li>Open any task, click the globe icon, and select your daemon</li>
</ol>
<p>Check the <a href="https://udctl.com/docs/workspace-terminal/">documentation</a> for the full setup guide, including security configuration and troubleshooting.</p>]]></content:encoded>
            <category>Feature</category>
        </item>
    </channel>
</rss>