<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Adam Tait</title>
  <link href="https://adamtait.com/atom.xml" rel="self"/>
  <link href="https://adamtait.com"/>
  <updated>2026-07-19T14:38:08+00:00</updated>
  <id>https://adamtait.com</id>
  <author>
    <name>Adam Tait</name>
  </author>
  <entry>
    <id>https://adamtait.com/using-remote-agent.html</id>
    <link href="https://adamtait.com/using-remote-agent.html"/>
    <title>Using Remote Agent</title>
    <updated>2026-07-15T23:59:59+00:00</updated>
    <content type="html"><![CDATA[<p><a href='https://github.com/adamtait/remote-agent'>Remote Agent</a> (also see <a href='/remote-agent.html'>intro post</a>) was intentionally designed to be powerful, giving you (near) complete control over the tool. Great power can also be overwhelming and I've had feedback that a practical usage guide would aid in getting started. </p><p>Since designing and building Remote Agent, I've logged about 4 months of experience with it across different projects and organizations. The goal here is to share some practical tips for using Remote Agent and highlight important decisions you'll face when installing and managing Remote Agent configurations. Hope it helps!</p><h1>Local vs Remote</h1><p>Since the beginning of 2026, I've been using combinations Claude Code, Codex and Antigravity (agy) on all my software projects. I only want to run those agent in remote dev environments, so I have Remote Agent configurations for everything and I do very little development locally. Some exceptions include writing first drafts of prose, some risky deploys and one-time IAM role setups. I give my Remote Agents appropriately scoped IAM roles to my Google Cloud or AWS live environments, which I'll discuss more later.</p><h1>User vs Project level</h1><p>The single most important decision is how to organize your projects into workstations and Remote Agent configurations. <em>When should you separate projects on different workstations?</em> </p><p>The heuristic I've been using is when billing or permissions need separation. <em>Do I need billing or IAM roles / permissions that shouldn't be co-located?</em> </p><p>Your decisions here have real cost implications, particularly if you span across Google Cloud projects or regions. The dominant cost isn't the workstation — it's the Workstation Cluster control plane, which bills ~$5/day (~$144/month) per cluster, 24/7, whether or not any workstation is awake. You need a cluster per region, so I co-locate different Remote Agent configurations into a common Google Cloud project and region to keep the cluster count down. The <a href='/remote-agent.html'>intro post</a> has a full cost breakdown in its appendix.</p><p>For my personal tools and experiments, I maintain a single workstation configuration which is a good balance of value to price for me.</p><p>I have not yet hit a machine performance limit even running 10+ agents concurrently on <code>e2-standard-2</code> machines — they spend most of their time waiting on tokens from the model API, so CPU is rarely the bottleneck. That means I rarely need bigger machines or many concurrent ones. Others will have different mileage, and when they do, bumping <code>machine&#95;type</code> to a bigger machine is a one-line config change.</p><h1>Enterprise and Team Support</h1><p>Remote Agent was designed to support groups of people working on the same project with shared billing, configuration and secrets. Your project-level configuration is what's shared and user-level gives each individual personalization. If you've defined a shared Google Cloud project or an API key secret for Claude Code in your project-level config, then anyone who can build a workstation from that config inherits those secrets. That's the point for a team — and the risk. Scope your project-level config to people you'd trust with its secrets.</p><p>Remote Agent creates a Google Cloud Service Account (SA) designed with least-privilege and resource-scoped (per-secret and per-bucket bindings rather than broad project roles). You're welcome to change the existing role or separately create a role for your Remote Agent workstation.</p><p>VPC egress control matters to most enterprises, and Google Cloud supports it well. Remote Agent ships with the standard Cloud Workstation Cluster resources which you can control in your Google Cloud project.</p><p>If you're interested in how secrets are managed, <a href='https://github.com/adamtait/remote-agent/blob/main/docs/auth_and_security.md'>more details are available here</a> or you can point your favorite terminal agent at the code :)</p><h1>Connecting and Shells</h1><p>Out of the box, Remote Agent wraps <code>gcloud</code> to give you SSH access (as <code>ra connect</code>) which drops you into a login shell. I found that ssh is generally stable but on an unstable connection can... be unstable, even after tweaking keep-alives. The <a href='https://github.com/adamtait/ra-plugins/tree/main/et'>EternalTerminal plugin</a> greatly improves connection stability and often allows the connection to recover even through your laptop suspending. If you're thinking about building your own plugin, it's also a great example of plugins adding their own commands as you'll connect to your workstation using <code>ra plugin et connect --workstation personal</code>.</p><p>I like <a href='https://github.com/adamtait/ra-plugins/tree/main/tmux'>tmux</a> for managing shells and running terminal agents concurrently. Once installed, tmux should automatically start a new session and drop you into it.</p><p>Personally, I have a user-level <a href='https://github.com/adamtait/dotfiles/blob/main/home/dot_config/ra/workstation-startup.d/executable_250_dotfiles-install.sh'>workstation-startup.d/ script</a> that installs <a href='https://github.com/adamtait/dotfiles/tree/main'>my dotfiles repo</a>. It runs only on the first workstation startup (thanks to a file marker) but you might also choose to bake it into the image.</p><h1>GitHub</h1><p>All my repos are added to the <a href='https://github.com/adamtait/ra-plugins/tree/main/github'>github plugin</a> configuration like so:</p><pre><code class="lang-yaml">plugins:
    github:
        auth&#95;provider: pat
        enabled: true
        repo&#95;urls:
            - git@github.com:adamtait/remote-agent.git
            - git@github.com:adamtait/ra-plugins.git
</code></pre><p>I keep my github plugin and repo list at the project-level. Since I combine all my personal github repos in a single workstation configuration, I maintain a repo just to hold my personal project-level ra configs. In other projects, I keep the ra configuration in the same git repo as the project itself.</p><h1>Terminal Agents</h1><p>Claude Code (Anthropic), Codex (OpenAI), Antigravity (Google) are all currently supported via <a href='https://github.com/adamtait/ra-plugins'>plugins</a>. </p><p>Having Remote Agent manage your auth keys makes connecting to a new workstation easy. Claude Code authentication is simplest as Anthropic supports persistent OAuth and API keys. Codex uses a web-based auth flow that generates tokens which can be stored (or you can supply an API key directly). Currently, Antigravity supports no type of persistent keys, so you'll need to login separately to each Remote Agent workstation.</p><p>Specifically for Claude Code, there's also the <a href='https://github.com/adamtait/ra-plugins/tree/main/claude-sync'>claude-sync</a> plugin which persists all your Claude Code sessions in a Google Cloud Storage Bucket. Periodically, it pulls and merges all your sessions across all your machines. Very helpful for Claude Code to understand other concurrent sessions or for reflecting on past sessions. In keeping with the principle of full control, you can install the plugin at the user or project level.</p><h1>Web-based Tools and Tunnels</h1><p>Google's <a href='https://docs.cloud.google.com/workstations/docs/preconfigured-base-images'><code>code-oss</code></a> default base image comes baked with VS Code web. <code>ra url</code> will get you a link to open in your browser for access. You're welcome to change the base image in your Dockerfile configuration overrides. I've found that having VS Code open in my browser helps me quickly verify changes to files from the terminal agents.</p><p>You can run your own webservers from your workstation and tunnel locally to connect. Two examples at time of writing:</p><ul><li><a href='https://github.com/adamtait/ra-plugins/tree/main/antigravity'>Antigravity</a> supports its native IDE, tunneling to the workstation for code and environment access.</li><li><a href='https://github.com/adamtait/ra-plugins/tree/main/claude-science'>Claude Science (preview)</a> runs a webserver locally, which requires a tunnel to reach.</li></ul><h1>Customizations and Creating Plugins</h1><p>There are many ways to configure Remote Agent so <a href='https://github.com/adamtait/remote-agent/blob/main/docs/extensions_and_plugins.md'>this doc</a> details how to think about customizations. I would recommend adding scripts to your user-level configuration (~/.config/ra/) first before creating a heavier plugin. If you do decide to create a plugin, <a href='https://github.com/adamtait/remote-agent/blob/main/docs/authoring-plugins.md'>authoring your own plugins</a> is a great resource.</p><h1>Conclusion</h1><p>I designed Remote Agent using the principles of <em>complete power</em>, <em>declarative configuration</em> and <em>sane defaults</em>. If you find any rough edges or have ideas for how to make your use better or just want to talk it through, please drop me an email (see footer).</p>]]></content>
  </entry>
  <entry>
    <id>https://adamtait.com/remote-agent.html</id>
    <link href="https://adamtait.com/remote-agent.html"/>
    <title>Introducing Remote Agent</title>
    <updated>2026-07-03T23:59:59+00:00</updated>
    <content type="html"><![CDATA[<h1>TLDR</h1><p>Launch of <strong><a href='https://github.com/adamtait/remote-agent'>Remote Agent</a></strong>, a cli tool for managing remote developer environments so you can <strong>run your CLI coding agents securely on a throwaway Google Cloud Workstation, not your laptop</strong>.</p><p><a href='https://github.com/adamtait/remote-agent'>Remote Agent</a> (<code>ra</code>) provisions a browser- and SSH-accessible Google Cloud Workstation, attaches your credentials through GCP Secret Manager, and drops you into a persistent dev environment where an agent can read untrusted issues and run shell commands without ever touching your personal admin ~/.aws/credentials or your email session cookies. It's a single Go binary that wraps gcloud — just bring your terminal agent OAuth/API keys.</p><h1>Setting the Scene</h1><p>Software development has changed, and so should our development environments. While running AI agents with full access to your local machine feels empowering, it presents significant risks, including identity theft and accidental data destruction.</p><p>This post introduces a new tool for running terminal agents in the cloud. There are two primary benefits; isolation of your personal data and long-running sessions. You can find the new tool at <a href='https://github.com/adamtait/remote-agent'>github.com/adamtait/remote-agent</a></p><p>Agents and AI models have reached a tipping point around trust. For the past few years, models would unpredictably suggest foolish actions such as deleting a database or <code>rm -rf /</code>. They were untrusted. Today, models are much more capable of complex reasoning and reliable in carrying it out. Many users now want to employ AI models to do more (see the <a href='https://github.com/openclaw/openclaw'>OpenClaw</a> movement). But as we hand agents more power, the downside grows right alongside the capability.</p><h2>With Great Power Comes Great Responsibility</h2><p>Your personal computer is deeply personal. You access your email and social media accounts there. You do your banking and pay your taxes. Maybe you journal and record your private thoughts. You are probably careful not to share your personal computer account with other people. When we talk about identity theft, we’re often talking about information available from your personal computer.</p><p>Enter a powerful but unpredictable new productivity tool. Even without explicit permission and CLI command classifiers (see Claude Code auto mode), the risk that an LLM-as-agent could grab your local credentials and post them on the dark web is non-trivial. A prompt injection hidden in a GitHub issue or a branch name can make an agent run an attacker's commands <em>inside</em> its container — enough to <a href='https://www.shipsafecli.com/blog/docker-authz-chatgpt-dns-codex-branch-injection-ai-container-security'>steal a GitHub OAuth token</a> or read your <code>.env</code> and <a href='https://www.cequence.ai/blog/ai/even-the-best-ai-agents-leak-secrets-prompt-injection-is-why/'><code>&#126;/.aws/credentials</code></a>. Even a local VM container is no guarantee. Frontier models have been shown to <a href='https://blaxel.ai/blog/container-escape'>discover and execute container escapes on their own</a>. As one security writeup put it: <a href='https://www.toxsec.com/p/ai-sandbox-escape'>"Docker doesn't survive a model that's read its own CVE feed."</a></p><h2>Humble Beginnings</h2><p>You might ask &#45; how did we land on such a powerful default access point for AI agents? Early models started as chat bots (think ChatGPT) but modern LLM-based agents for software engineering were popularized by Cursor-like IDE integrations. The thinking was that developers live in their IDE and there are clear extension points (though many new extensions were added to VS Code for Github Copilot). Command line interface (CLI) or terminal agents came later, as reasoning models became capable. Builders reached for the terminal because it's ubiquitous, powerful, and (relatively) easy to build on. Many developers have turned a blind eye to these risks, driven by the desire to reduce productivity friction.</p><h2>Friction-less</h2><p>Beyond the risks, running agents (not to mention LLMs) on your personal machine is inconvenient. You have to keep your laptop open for the agents to keep running. One developer reportedly keeps a laptop open on the passenger seat of their car to monitor agents while stopped.</p><p>As agents run tools, they burn your machine's resources — CPU, memory, network. Your machine feels slow and you can't do that internet search or join a meeting. Peter Steinberger (creator of OpenClaw) got so tired of local agents heating up his CPU and slowing his whole machine that he built <a href='https://newsletter.pragmaticengineer.com/p/impressions-from-visiting-openai'>Crabbox</a> to run them in the cloud instead.</p><h2>Developer Environments Live On</h2><p>While many have proclaimed the death of code, it's primarily the human-hand authoring of code in programming languages that has changed since the rise of LLM-based agents. Software engineers continue to design, architect and verify code changes that they delegate to agents. Many continue to use IDEs as they have been well designed for reviewing and verifying (in addition to authoring).</p><p>In my own work, GitHub's Pull Requests and IDEs (Cursor, Antigravity, VS Code, Emacs) are under heavy use in reviewing the changes and iterating.</p><h2>Trust in the Cloud</h2><p>With few exceptions, builders in big tech (Google, Meta, etc…) are working on cloud developer environments and have been for years. These organizations employ dedicated developer experience teams to maintain robust environments, enabling high-quality, secure workflows.</p><p>Fortunately, Google Cloud's Workstations are very similar to the environments that Google engineers work in. A VM running Google's code-oss container image with enough (configurable) power for developing most applications. The code-oss container comes baked with VS Code (web) so you can use a familiar IDE remotely (alternatively you can also tunnel Google's Antigravity from your local machine). For terminal agents, you can <code>ssh</code> into the machine and run them yourself.</p><p>Running on Google Cloud (aka GCP), you've got access to anything your organization or you personally need for safe and productive software development work. Further, you can use enterprise-trusted IAM and private VPC networks to control access and limit risks. </p><p>Google Cloud comes with a powerful but less user friendly permissions system (IAM). Fortunately, you can reduce the downside by using your favorite terminal agent to setup and maintain fine grained permissions access to your personal data (GMail, Drive, Docs) and GCP resources.</p><h2>Alternatives</h2><p>Most of the AI Labs and popular agent tools have solutions to run agents remotely.</p><ul><li><strong>Cursor Cloud</strong> — Customization includes Dockerfile and environment config files. Management of secrets. <a href='https://cursor.com/cloud'>&#91;ref&#93;</a></li><li><strong>Codex Web</strong> — Customization with setup scripts. Internet access is either on/off. <a href='https://developers.openai.com/codex/cloud'>&#91;ref&#93;</a></li><li><strong>Claude Code Web</strong> &#45; Claude Code but in a delegated cloud environment with limited customization. Best for scheduled tasks. <a href='https://code.claude.com/docs/en/claude-code-on-the-web'>&#91;ref&#93;</a></li><li><strong>Claude Managed Agents</strong> — Pre-built, configurable agent harness that runs in managed infrastructure but system environment (agent tools) run on BYO compute. You create and maintain the machines for Claude to call into. <a href='https://platform.claude.com/docs/en/managed-agents/overview'>&#91;ref&#93;</a></li><li><strong>Devin</strong> — Agent in a hosted environment (no local cli agent). Declarative environment configuration and secrets management. VPN configuration for accessing private networks. <a href='https://docs.devin.ai/onboard-devin/environment'>&#91;ref&#93;</a></li><li><strong>Google Jules</strong> — Agent in a hosted, sandboxed environment (no local cli agent). Customization with setup scripts and baked images. <a href='https://jules.google/docs/'>&#91;ref&#93;</a></li></ul><p>Some distinguishing points:</p><ul><li>None want to be responsible for running arbitrary machine images, so customization is limited.</li><li>Network ingress/egress controls are lacking. Each has chosen defaults which users must abide.</li></ul><h1>Introducing Remote Agent</h1><p>The objective of <a href='https://github.com/adamtait/remote-agent'>Remote Agent</a> is to ensure modern, AI-focused developers derive maximum value from terminal agents in a safe, remote, persistent environment that mirrors their own development environment. The design is simple and extensible &#45; built on enterprise cloud development environments. Use the terminal agents you already know and love, and do it safely.</p><h2>Getting Started</h2><p>Clone <a href='https://github.com/adamtait/remote-agent'>adamtait/remote-agent</a> and <code>make install</code>.</p><ol><li><code>ra setup</code> and you get your own container and cloud configurations</li><li><code>ra create</code> creates a <a href='https://cloud.google.com/blog/products/application-development/introducing-cloud-workstations'>Cloud Workstation on Google Cloud</a></li><li><code>ra connect</code> connects via ssh to your workstation.</li></ol><p><strong>Prerequisites</strong> (all shelled out to, so they must be on your <code>PATH</code>):</p><pre><code class="lang-bash">gcloud auth login     # authenticated gcloud CLI, on a project with billing enabled
docker --version      # docker with buildx &#40;cross-arch image builds&#41;
git --version         # used to clone plugins
</code></pre><ul><li><a href='https://docs.cloud.google.com/sdk/docs/install-sdk'>gcloud install</a></li><li>docker install: <a href='https://docs.docker.com/get-started/introduction/get-docker-desktop/'>desktop</a> or <a href='https://docs.docker.com/engine/install/'>engine</a></li></ul><h2>Customization</h2><p>Remote Agent was designed with two types of extensions and the goal of allowing near complete customization of the packaging and live environment. The two types are <strong>configuration</strong> and <strong>plugins</strong>.</p><p><strong>Configuration</strong> should be project or user-specific. You intend to keep these customizations scoped and do not need re-use.</p><p><strong>Plugins</strong> are canonical and easily sharable.</p><p>The mental model of how Remote Agent works is the same for both configuration and plugins, which are simply collections of Dockerfile scripts, workstation startup and shell profile scripts. Configuration for a project is the source of truth (highest precedence) followed by the user-level configuration. Plugin scripts are included verbatim in either project or user-level configuration (after installation).</p><p>Configuration is centralized in two locations, and standard CLI tools ensure everything remains discoverable for both you and your agents. You & your agents also have complete power to fix or customize with local changes, then later decide to contribute upstream.</p><h3>Configuration</h3><p>Remote Agent's extension mode shares some design cues from Claude Code such as a user-level configuration (kept in <code>&#126;/.config/ra</code>) and a project-level configuration (stored in a local <code>.ra/</code>). The project-level config can be sparse and overrides the user-level configuration. The user-level configuration contains the full Dockerfile and startup scripts so you can directly modify the scaffold that creates your remote development environment.</p><h3>Plugins</h3><p>Plugins can be installed either from a remote GitHub repo or a local directory. Follow the prompts with <code>ra plugin install ...</code>. The README has <a href='https://github.com/adamtait/ra-plugins#creating-a-plugin'>instructions for creating your own plugins</a>, which LLMs can easily follow.</p><p>Versioning should be handled by your code; Remote Agent does not include a package manager. When a plugin is installed, the scaffolded scripts are verbatim copied into the project/user configuration directory.</p><p>The <a href='https://github.com/adamtait/ra-plugins'>adamtait/ra-plugins</a> repo comes with some batteries included plugins:</p><ul><li>github</li><li>tmux</li><li>eternal terminal</li><li>claude code</li><li>codex</li><li>antigravity cli (agy)</li></ul><p>If you're in the Claude ecosystem, consider installing <a href='https://github.com/adamtait/ra-plugins/tree/main/claude-sync'>claude-sync</a> which constantly syncs and merges your Claude conversation history across your workstations. Amazing for sharing conversation status across different agents or doing a single usage review across all your agent sessions. Conversation history is persistently stored in a Google Cloud Storage Bucket, which is created when you install the plugin. Plugin can be installed at either the user or project level.</p><h2>Secrets</h2><p>Many remote services (like most cli agents) require authentication credentials. Remote Agent is designed to collect and store your secrets securely in Google Cloud. When you install a plugin requiring a secret, the CLI collects it and sends it directly to Google Cloud Secret Manager.</p><p>When provisioning a new machine, Remote Agent creates a Google Service Account which can access the secrets and associates the Service Account with the Cloud Workstation. Secrets are not baked into the container image and only accessed when the workstation runs.</p><h2>Costs</h2><p>The real cost isn't the workstation — it's the cluster. To run any Cloud Workstation you first need a Workstation Cluster Control Plane, and it bills ~$5 / day 24/7, whether or not a single workstation is awake. Auto-sleep and auto-shutdown do nothing for it; the only way to stop the charge is to delete the cluster (<code>ra teardown</code>). The <a href='https://docs.cloud.google.com/workstations/docs/architecture'>Cluster resource includes a VPC network and gateway</a> and stores/secures persistent disks, so it lives on even after you delete every Workstation.</p><p>By comparison the workstation itself is rounding error: a single <code>e2-standard-2</code> adds about $1.34 / day of running charges, and drops to $0 the moment it auto-sleeps. Consolidate to one shared cluster and the control-plane fee stays flat no matter how many workstations you run under it. See the appendix for the full breakdown.</p><h1>Conclusion</h1><p>Your laptop is where your life lives. It's the worst possible place to run something that reads untrusted text and executes code on your behalf. The engineers building the biggest systems in the world stopped developing on their local machines years ago — and the tooling to do the same for your agents is (now) easily available.</p><p>Remote Agent gives you the control, maintains strong security, uses full developer environments, limits cost, frees you from your laptop and allows you to leverage your existing LLM subscription plans directly.</p><p>Download it from <a href='https://github.com/adamtait/remote-agent'>adamtait/remote-agent</a>.</p><h2>Appendix: Cost</h2><p>I had Claude report on my own usage by reviewing my Google Cloud billing data. After reading and starting to edit it, I decided it was written well enough to include verbatim. Enjoy &#45;</p><h3>Configured workstation specs</h3><ul><li><strong>Machine:</strong> <code>e2-standard-2</code> (2 vCPU / 8 GB) in <code>us-central1</code></li><li><strong>Disk:</strong> 50 GB <code>pd-balanced</code> (persistent home disk)</li><li><strong>Auto-sleep:</strong> 3 h idle · <strong>Auto-shutdown:</strong> 10 h max continuous run</li></ul><h3>Cost while a workstation is <em>running</em></h3><p>Billed per-minute only while running; all three components stop when it auto-sleeps.</p><table><thead><tr><th style='text-align:left'>Component</th><th style='text-align:left'>Rate</th><th style='text-align:left'>Per running hour</th></tr></thead><tbody><tr><td style='text-align:left'>Workstations management fee</td><td style='text-align:left'>$0.05 / vCPU-hr × 2 vCPU</td><td style='text-align:left'>$0.100</td></tr><tr><td style='text-align:left'>Compute Engine VM (e2-standard-2)</td><td style='text-align:left'>~$0.067 / hr</td><td style='text-align:left'>$0.067</td></tr><tr><td style='text-align:left'><strong>Total</strong></td><td style='text-align:left'></td><td style='text-align:left'><strong>~$0.167 / running hour</strong></td></tr></tbody></table><p><strong>Average for a typical work day</strong> (~8 running hours — active coding plus short idle tails before the 3 h auto-sleep kicks in):</p><table><thead><tr><th style='text-align:left'>Scenario</th><th style='text-align:left'>Running hrs</th><th style='text-align:left'>Compute &#43; mgmt cost</th></tr></thead><tbody><tr><td style='text-align:left'>Light day</td><td style='text-align:left'>4 h</td><td style='text-align:left'>~$0.67</td></tr><tr><td style='text-align:left'><strong>Typical day</strong></td><td style='text-align:left'><strong>8 h</strong></td><td style='text-align:left'><strong>~$1.34</strong></td></tr><tr><td style='text-align:left'>Heavy (hits 10 h shutdown cap)</td><td style='text-align:left'>10 h</td><td style='text-align:left'>~$1.67</td></tr></tbody></table><p>A <strong>typical work day ≈ $1.30–1.35</strong> in running charges, plus the base below.</p><h3>Base cost — standing resources, <em>no running workstations</em></h3><p>These bill continuously whether or not any workstation is awake.</p><table><thead><tr><th style='text-align:left'>Resource</th><th style='text-align:left'>Size</th><th style='text-align:left'>Monthly</th><th style='text-align:left'>Per day</th></tr></thead><tbody><tr><td style='text-align:left'><code>pd-balanced</code> home disk</td><td style='text-align:left'>50 GB × $0.10/GiB-mo</td><td style='text-align:left'>$5.00</td><td style='text-align:left'>$0.164</td></tr><tr><td style='text-align:left'>Artifact Registry (Docker image)</td><td style='text-align:left'>~3 GB × $0.10/GB-mo</td><td style='text-align:left'>~$0.25</td><td style='text-align:left'>~$0.008</td></tr><tr><td style='text-align:left'>Secret Manager</td><td style='text-align:left'>~5–8 versions (partly free-tier)</td><td style='text-align:left'>$0–0.36</td><td style='text-align:left'>~$0.01</td></tr><tr><td style='text-align:left'>GCS claude-sync bucket</td><td style='text-align:left'><1 GB standard</td><td style='text-align:left'>~$0.05</td><td style='text-align:left'>~$0.002</td></tr><tr><td style='text-align:left'><strong>Workstations cluster control plane</strong></td><td style='text-align:left'><strong>$0.20/cluster-hr × 24 h</strong></td><td style='text-align:left'><strong>$144.00 / cluster</strong></td><td style='text-align:left'><strong>$4.80 / cluster</strong></td></tr><tr><td style='text-align:left'><strong>Total base (1 cluster)</strong></td><td style='text-align:left'></td><td style='text-align:left'><strong>~$149/mo</strong></td><td style='text-align:left'><strong>~$5.0 / day</strong></td></tr></tbody></table><p><strong>The cluster control plane fee dominates everything else.</strong> It is a fixed $0.20 per cluster per hour, billed 24/7 regardless of whether any workstation is running — auto-sleep and auto-shutdown do nothing for it. At one cluster that's <strong>~$144/month ($4.80/day)</strong>; observed June billing averaged <strong>~$9/day</strong>, consistent with <strong>two clusters</strong> in us-central1 (2 × $4.80 = $9.60/day). The only way to stop this charge is to delete the cluster itself. By comparison the 50 GB disk (~$0.16/day) and everything else are rounding error, and the VM/management fees are $0 while asleep.</p><h3>Bottom line</h3><ul><li><strong>Base (idle) cost:</strong> <strong>~$5/day per cluster</strong> (~$149/month) — almost entirely the $4.80/day control plane fee; the 50 GB disk and other storage add only ~$0.19/day.</li><li><strong>Typical work day (8 running hrs, 1 cluster):</strong> ~$1.34 running &#43; ~$5.0 base ≈ <strong>~$6.3/day</strong>.</li><li><strong>Rough monthly</strong> (~22 working days, 1 cluster): ~$29 running &#43; ~$149 base ≈ <strong>~$178/month</strong>.</li><li><strong>The single biggest lever is cluster count.</strong> Each extra 24/7 cluster adds ~$144/month for zero usage. Consolidate to one shared cluster (a cluster holds many configs and workstations), and delete the cluster entirely during long idle stretches (<code>ra</code> recreates it in a few minutes).</li><li><strong>Use <code>ra teardown</code> to stop the fee.</strong> <code>ra teardown</code> deletes ra-provisioned resources (workstations → config → <strong>cluster</strong> → service account/IAM → secrets → Artifact Registry repo) in one command; removing the cluster is what actually ends the control-plane charge. Run <code>ra teardown --dry-run</code> first to preview, then <code>ra teardown</code>. Because the cluster (and SA/repo/secrets) are shared across projects, teardown deletes them only when the project you run it from is the <strong>last config on the cluster</strong>; pass <code>--delete-shared</code> to force removal while other projects still use it. GCS buckets (synced data) are preserved unless you pass <code>--delete-buckets</code>.</li></ul><h3>Caveats</h3><ul><li>The control plane fee is per <strong>cluster</strong>, not per workstation or per config — many workstations under one cluster share the single $0.20/hr charge.</li><li>E2 machines don't get sustained-use discounts — list price applies.</li><li>Bumping <code>machine&#95;type</code> to <code>e2-standard-8</code> for heavy builds roughly quadruples running cost (~$0.67/running hr → ~$5.4 for an 8 h day).</li><li>For <em>actual</em> spend: enable the Cloud Billing API and set up a BigQuery billing export (or <code>gcloud billing</code>), or grant an account with <code>billing.viewer</code>.</li></ul><h3>Sources</h3><ul><li><a href='https://cloud.google.com/workstations/pricing'>Cloud Workstations pricing</a></li><li><a href='https://cloud.google.com/compute/all-pricing'>Compute Engine VM pricing</a></li><li><a href='https://www.economize.cloud/resources/gcp/pricing/compute-engine/e2-standard-2/'>e2-standard-2 rate</a></li><li><a href='https://cloud.google.com/compute/disks-image-pricing'>Persistent Disk pricing</a></li><li><a href='https://cloud.google.com/artifact-registry/pricing'>Artifact Registry pricing</a></li><li><a href='https://github.com/mattpocock/sandcastle'>Sandcastle</a>: a local sandboxing tool for agents</li></ul>]]></content>
  </entry>
  <entry>
    <id>https://adamtait.com/think-twice-write-once.html</id>
    <link href="https://adamtait.com/think-twice-write-once.html"/>
    <title>Think Twice, Write Once</title>
    <updated>2023-08-17T23:59:59+00:00</updated>
    <content type="html"><![CDATA[<p>In the popular imagination, the software engineer is a romantic hero who sits down at a glowing screen, cracks their knuckles, and immediately starts typing lines of code at a furious pace. They don't think; they just <em>build</em>. </p><p>In the real world, this is a recipe for building a system that runs beautifully, passes its tests, and solves entirely the wrong problem. </p><p>Rich Hickey’s talks have long been a guide for developers trying to escape this trap. He is famous for advocating "hammock time"—the practice of stepping away from the keyboard, closing your eyes, and actually thinking about the problem before writing a single line of code. </p><p>Hickey’s talk, <em><a href='https://www.youtube.com/watch?v=c5QF2HjHLSE'>Design in Practice</a></em>, is a masterclass in how to formalize this process. It is the practical companion to his philosophical talks, offering a concrete structure for recording design decisions without drowning in corporate bureaucracy. It also serves as a beautiful elaboration on Architecture Decision Records (ADRs), a concept first popularized by <a href='https://www.cognitect.com/blog/2011/11/15/documenting-architecture-decisions'>Mike Nygard's 2011 post</a>, which I still reference regularly.</p><p>Here are my key takeaways and reflections on the talk.</p><h2>Why Design? (Or: The Cognitive Savings Account)</h2><p>If you ask the average developer why they don't write design documents, they'll tell you they don't have time. They need to ship features. </p><p>But design isn't about satisfying auditors or writing massive Word documents that no one reads. Design is a cognitive savings account. The goals of formalizing design are intensely practical:</p><ul><li><strong>Reducing Context-Switching Costs:</strong> We all have "hammock time," but when we wake up or get interrupted, how long does it take to rebuild that mental state? A good design record lets you return to your context in minutes instead of hours.</li><li><strong>Onboarding Others:</strong> It allows teammates to step into your mental frame without needing a three-hour whiteboard session.</li><li><strong>Smaller, More General Solutions:</strong> When you design on the fly, you write code for specific edge cases as you hit them. When you design upfront, you find general abstractions that solve entire classes of problems at once. The final code is almost always smaller.</li></ul><h2>The Power of the Socratic Method</h2><p>Hickey emphasizes that design is essentially a Socratic dialogue with yourself. It requires detaching your ego from your ideas. The goal isn't to prove your favorite solution is right; it's to discover the objective truth of the system.</p><p>He suggests framing your progress around four fundamental questions:</p><ol><li><strong>Where are you at?</strong> (Your current state and constraints).</li><li><strong>Where are you going?</strong> (The target objective).</li><li><strong>What do you know?</strong> (Your facts and assumptions).</li><li><strong>What do you need to know?</strong> (Your unknowns and research spikes).</li></ol><p>By continuously asking these questions, you engage in <em>reflective inquiry</em>—being aware of your own cognitive state. </p><p>Part of this discipline is linguistic precision. If two developers use the same word to mean different things, design fails. Hickey recommends compiling a <strong>glossary</strong> early in the process. Force yourself to define your terms. If you can't explain what a term means in two sentences, you don't understand it yet.</p><h2>The 6-Phase Lifecycle of Design</h2><p>To turn these philosophical ideas into practice, Hickey breaks the software design and development process into six distinct, sequential phases:</p><pre><code class="lang-mermaid">graph TD
    A&#91;1. Describe&#93; --&gt; B&#91;2. Diagnose&#93;
    B --&gt; C&#91;3. Delimit&#93;
    C --&gt; D&#91;4. Direction&#93;
    D --&gt; E&#91;5. Design&#93;
    E --&gt; F&#91;6. Dev&#93;
    E -.-&gt;|Backtrack if needed| C
</code></pre><h3>1. Describe (The What)</h3><p>Write down the situation as you hear it. Focus entirely on describing symptoms, not solutions. How big is the problem? What is the business impact? If a database is slow, don't say "we need Redis." Say "queries on the user table are taking 800ms, delaying checkout."</p><h3>2. Diagnose (The Why)</h3><p>Accumulate hypotheses. There is never just one reason for a bug or bottleneck. List them all and use the scientific method to test them one by one. Prove your hypotheses with data, not vibes.</p><h3>3. Delimit (The Scope)</h3><p>If you do steps 1 and 2 correctly, you will discover that you are facing a Hydra of multiple, interconnected problems. You cannot solve them all. Delimiting means drawing a hard boundary around the <em>single</em> problem you are going to solve right now. </p><h3>4. Direction (The Strategy)</h3><p>List high-level approaches and evaluate their trade-offs. Hickey suggests putting these in a decision matrix: columns are your approaches, and rows are your criteria (e.g., development time, operational cost, legal compliance). This is where bad ideas go to die, cheaply and quickly, before they cost a single line of code.</p><h3>5. Design (The Tactics)</h3><p>Once you have chosen a direction, map out the implementation path. Choose your libraries, database schemas, and API contracts. If you discover during this phase that your assumptions were wrong, backtrack. It is infinitely cheaper to rewrite a design document than to refactor a production database.</p><h3>6. Dev (The Execution)</h3><p>Actually write the code. If you did phases 1 through 5 correctly, this should be the easiest, most mechanical part of the entire process. You aren't guessing or exploring; you are simply executing a well-defined blueprint.</p><h2>The Takeaway</h2><p>Writing code is fun. Thinking is hard. </p><p>But as software systems grow in complexity, the developers who succeed are not the ones who type the fastest. They are the ones who know how to sit in the hammock, ask the right questions, and write their decisions down.</p><p><em>For more details, check out the <a href='https://www.youtube.com/watch?v=c5QF2HjHLSE'>video of the talk</a> or read the <a href='https://github.com/matthiasn/talk-transcripts/blob/master/Hickey_Rich/DesignInPractice.md'>full transcript</a>.</em></p>]]></content>
  </entry>
  <entry>
    <id>https://adamtait.com/parsing-and-grammars.html</id>
    <link href="https://adamtait.com/parsing-and-grammars.html"/>
    <title>The Seductive Trap of the Hand-Written Parser</title>
    <updated>2022-12-11T23:59:59+00:00</updated>
    <content type="html"><![CDATA[<p>Every developer, at some point in their career, falls into the same seductive trap. You have some string input—maybe a custom config file format, a DSL for layout, or a set of nested parenthetical commands. You need to parse it. </p><p>You think: <em>"I don't need a heavy grammar engine. I'll just write a quick regular expression and a split loop. It'll take ten minutes."</em></p><p>And it does. And it works beautifully. You feel like a wizard. </p><p>But then, the requirements change. You need to support nested brackets. You need to handle escaped quotes. Suddenly, that "quick regex" turns into a recursive, state-holding, back-tracking monster. You spend your weekends stepping through 300 lines of nested <code>if/else</code> statements, muttering dark oaths under your breath. You are trapped by the sunk-cost fallacy, unwilling to throw away the subsystem you’ve spent weeks debugging.</p><p>A hand-written parser is a financial loan shark: it offers incredibly easy terms upfront, followed by ruinous interest rates later.</p><h2>Decomplecting the Problem</h2><p>If we want to build a better parser, we have to follow the classic Rich Hickey advice: <em>let's decomplect it</em>. We must untangle the three distinct duties that a parser implicitly performs:</p><ol><li><strong>Lexing (The Tokenizer):</strong> Slicing raw characters into meaningful words, or "tokens." For example, in Javascript, the characters <code>f</code>, <code>u</code>, <code>n</code>, <code>c</code>, <code>t</code>, <code>i</code>, <code>o</code>, <code>n</code> are recognized collectively as a single keyword token.</li><li><strong>Parsing (The Structurer):</strong> Taking the flat stream of tokens and arranging them into a hierarchical tree (an Abstract Syntax Tree, or AST) that reflects the recursive nature of the language.</li><li><strong>Evaluating (The Actor):</strong> Walking the AST and executing the actual logic or translating it into another format.</li></ol><p>Historically, computer scientists separated these concerns into specialized tools. In 1975, Mike Lesk and Eric Schmidt (who would later become the CEO of Google) wrote <strong>Lex</strong>, a lexical analyzer generator. It was typically paired with <strong>Yacc</strong> ("Yet Another Compiler-Compiler"), a parser generator. </p><p>For decades, CompSci students (including myself, fifteen years ago) were forced to learn these tools. It was dry, theoretical, and slightly painful. But the professors had a point: formal grammars are vastly superior to custom procedural parsing code.</p><h2>The Modern Alternative: ANTLR 4</h2><p>Today, we don't have to use Lex and Yacc. We have tools like <strong>ANTLR 4</strong> (Another Tool for Language Recognition), which combines lexer and parser definitions into a single, clean grammar file.</p><p>Here is a simple ANTLR 4 grammar for the ubiquitous CSV (comma-separated values) format:</p><pre><code class="lang-antlr">csvFile: hdr row+ ;
hdr    : row ;
row    : field &#40;',' field&#41;&#42; ' '? '\r'? '\n' ;
field  : TEXT
       | STRING
       |
       ;

TEXT   : &#126;&#91;, &quot;\r\n&#93;+ ;
STRING : '&quot;' &#40;'&quot;&quot;'|&#126;'&quot;'&#41;&#42; '&quot;' ;
</code></pre><p>In ANTLR, lexer rules (which define tokens) begin with an uppercase letter (<code>TEXT</code>, <code>STRING</code>), while parser rules (which define structure) begin with a lowercase letter (<code>csvFile</code>, <code>hdr</code>, <code>row</code>, <code>field</code>). </p><p>Look at how declarative this is. You don't write loops or search algorithms. You simply describe what a CSV file <em>is</em>. The generator takes this description and outputs highly optimized parsing code in Java, Clojure, Python, or JavaScript.</p><h2>The Ambiguity Cliff</h2><p>The trickiest part of designing your own language is <em>ambiguity</em>. An ambiguous language is one where a single string of characters can match more than one parser rule.</p><p>Consider this naive grammar for links:</p><pre><code class="lang-antlr">link: '&#91;&#91;' STRING '&#93;&#93;' ;
alias: '&#91;' STRING '&#93;&#40;' STRING '&#41;' ;
STRING: &#91;a-zA-Z0-9 &#93;+ ;
</code></pre><p>What happens if the parser encounters <code>&#91;&#91;alias&#93;&#40;target&#41;&#93;</code>? </p><p>Because both rules start with the open bracket <code>&#91;</code>, the parser has to guess which path to follow. Without sufficient lookahead, it may commit to the <code>link</code> rule, proceed to look for <code>&#93;&#93;</code>, fail to find it, and throw a syntax error.</p><p>To avoid this, you should design your language syntax to be structurally unambiguous from the start. If that isn't possible, you can resort to lookahead markers or design your parser rules to explicitly allow optional characters to resolve the conflict. For example, using lookahead checks like ANTLR’s semantic predicates to check what characters follow.</p><h2>Further Reading</h2><p>If you want to escape the cycle of hand-written parser grief, I highly recommend:</p><ul><li><strong><a href='https://www.antlr.org/'>ANTLR 4</a>:</strong> Extremely powerful, performant, and well-supported.</li><li><strong><a href='https://github.com/aphyr/clj-antlr'>clj-antlr</a>:</strong> A Clojure wrapper by @aphyr, who ran excellent performance comparisons against Instaparse.</li><li><strong><a href='https://pragprog.com/titles/tpantlr2/the-definitive-antlr-4-reference/'>The Definitive ANTLR 4 Reference</a>:</strong> The bible for language design.</li></ul>]]></content>
  </entry>
  <entry>
    <id>https://adamtait.com/static-site-generator.html</id>
    <link href="https://adamtait.com/static-site-generator.html"/>
    <title>Seek Simplicity and Distrust It</title>
    <updated>2020-11-29T23:59:59+00:00</updated>
    <content type="html"><![CDATA[<p>Admitting to building your own static site generator is a bit like admitting you enjoy pressure-washing videos. People look at you with a mix of mild fascination and concern, wondering why you aren't doing something more productive. Yet here we are. In my previous post, I defended the philosophy of technical self-reliance. Now, let’s talk about how this beast actually functions.</p><p>Most modern static site generators are built on a premise I find fundamentally flawed: they exist to translate text-based markup languages (usually Markdown) into HTML and CSS. If you're a non-technical writer, this is fine. But if you already speak fluent HTML and CSS, Markdown is a restrictive abstraction. </p><p>I don't want to write Markdown. I want direct, programmatic access to the full expressiveness of the web's native formats. I want a tool that engages me in the complexity of code re-use through simple composition, without burying me in a pile of YAML front-matter and nested templating engines.</p><h2>The Weapon of Choice: Code as Data</h2><p>Clojure is uniquely suited for building domain-specific languages because of Lisp’s defining superpower: <em>homoiconicity</em> (the code is represented as data structures that the language itself can manipulate).</p><p>Instead of parsing string-based HTML templates, we can represent HTML elements and inline CSS directly as native Clojure vectors and maps. The Clojure ecosystem already has a brilliant library for this called <strong>Hiccup</strong>. Here is what Hiccup looks like in action:</p><pre><code class="lang-clojure">user=&gt; &#40;html &#91;:span {:class &quot;foo&quot;} &quot;bar&quot;&#93;&#41;
&quot;&lt;span class=\&quot;foo\&quot;&gt;bar&lt;/span&gt;&quot;
</code></pre><p>Notice how clean that is. There are no trailing slash errors, no closing tag syntax, and no string interpolation bugs. It is just structured data.</p><h2>Composition via Aerodynamics</h2><p>Once your markup is represented as data, the problem of templating resolves into a problem of data composition. </p><p>To manage our site's assets and configuration, I pulled in <strong>Aero</strong>, a small Clojure library for parsing configuration files (written in EDN, Clojure's data format). Aero is excellent because it allows you to define custom tag literals. In Lisp terms, think of tag literals as compiler hooks that evaluate data structures at read-time.</p><p>By using Aero, we can write our entire website's layout and pages in static <code>.edn</code> files, version them with Git, and guarantee absolute idempotency. The compiler reads the data, evaluates our custom tags, renders the Hiccup to HTML/CSS, and writes it to the disk.</p><p>Let’s look at the asset configuration for this website's home page:</p><pre><code class="lang-clojure">{
 :type :html
 :slug &quot;/index.html&quot;
 :content #template &#91;&quot;pages/index.edn&quot; &#91;:content&#93; {:path &quot;/&quot;}&#93;
}
</code></pre><p>The heart of our composition engine is the custom <code>#template</code> tag literal. When Aero's reader encounters <code>#template</code>, it treats it like a function call. It takes three arguments:</p><ol><li><strong><code>&quot;pages/index.edn&quot;</code></strong>: The path to the template definition.</li><li><strong><code>&#91;:content&#93;</code></strong>: A pull vector (similar to Clojure's <code>get-in</code>) to extract the exact portion of the evaluated data we want.</li><li><strong><code>{:path &quot;/&quot;}</code></strong>: A map of input variables passed into the template's rendering context.</li></ol><p>Here is a look inside <code>pages/index.edn</code>:</p><pre><code class="lang-clojure">{
 :color #include &quot;../styles/color.edn&quot;
 :content
   #template &#91;&quot;../components/layout.edn&quot;
              &#91;:content&#93;
              {:title &quot;home&quot;
               :body #ref &#91;:body&#93;}&#93;
 :body
   &#91;:div
     {:style
       #css {:display :flex
             :flex-direction :column
             :justify-content :flex-start
             :align-items :flex-start}}
   &#91;:div
     {:style
       #css {:font-size &quot;50px&quot;
             :font-weight 700
             :color #ref &#91;:color :yellow&#93;}}
   &quot;Hi. I'm Adam Tait.&quot;&#93;&#93;
}
</code></pre><p>Notice how we compose the layout. The <code>:content</code> key calls the layout template, passing the <code>:body</code> of our home page as an input variable via the <code>#ref</code> literal. The <code>#css</code> tag translates Clojure maps into inline style strings. It is simple, explicit, and lacks any magical "black box" behavior.</p><h2>Simplicity and Its Discontents</h2><p>In his famous talk, <em><a href='https://github.com/matthiasn/talk-transcripts/blob/master/Hickey_Rich/AreWeThereYet.md'>Are We There Yet?</a></em>, Rich Hickey makes a crucial distinction between "simple" (unentangled) and "easy" (near at hand). </p><p>Most modern web frameworks are <em>easy</em>—you run <code>npx create-next-app</code> and you instantly have a working site. But they are not <em>simple</em>. They bring along a staggering amount of incidental complexity like Webpack configurations, Babel steps, node_modules folders the size of small planets—that you must eventually debug.</p><p>The mathematician Alfred North Whitehead once wrote:<blockquote><p> "Seek simplicity, and distrust it."  </p></blockquote>Our custom EDN/Hiccup approach does not hide complexity; it lays it bare. There is no magic under the hood. You have direct access to the raw building blocks of the web, structured as immutable data. </p><p>Is this a poor abstraction? For many, yes. It requires you to write Lisp syntax to build a webpage. But by building a tool that builds the generator, I created a system I deeply understand. When a bug occurs, I don't file a GitHub issue; I just fix my code.</p>]]></content>
  </entry>
  <entry>
    <id>https://adamtait.com/build-your-own-software.html</id>
    <link href="https://adamtait.com/build-your-own-software.html"/>
    <title>Build Your Own Software</title>
    <updated>2020-11-24T23:59:59+00:00</updated>
    <content type="html"><![CDATA[<p>The prevailing, unquestioned orthodoxy of modern software engineering is simple: <em>never build what you can buy, rent, or copy-paste from GitHub.</em> If you dare to suggest writing your own library, let alone your own static site generator, you will be met with the kind of looks usually reserved for people who suggest returning to the gold standard. </p><p>Yet, I built this blog using a completely custom, handcrafted static site generator. Why? Because I am deeply, unapologetically biased toward building my own software systems. </p><p>In this post, I want to mount a defense of what the industry disparagingly calls Not Invented Here (NIH) syndrome. The next post will cover the actual technical details of what I built. For now, let’s talk about the <em>why</em>.</p><h2>The Hidden Costs of Other People's Land</h2><p>In software, dependencies are like renting an apartment. It looks great on paper—someone else handles the plumbing and maintains the roof. But eventually, you discover the landlord has some deeply eccentric house rules, the plumbing is made of papier-mâché, and they can raise the rent or evict you on a whim. </p><p>My architectural thinking has been heavily shaped by <a href='https://github.com/matthiasn/talk-transcripts/tree/master/Hickey_Rich'>Rich Hickey's talks</a> (for a quick digest of his philosophy, Daniel Higginbotham’s <a href='http://www.flyingmachinestudios.com/programming/the-unofficial-guide-to-rich-hickeys-brain/'>unofficial guide</a> is fantastic). Hickey’s core lesson is that we should possess a healthy fear of systems we don't understand, written by people whose goals and biases we don't share.</p><p>Every time you pull in a heavy third-party framework or outsource a capability to a SaaS provider, you inherit their future roadmap, their bugs, and their licensing decisions. We’ve all been there: you invest weeks learning a complex new tool, only to hit a wall because its core abstraction makes your simple use-case incredibly difficult. Suddenly, instead of building your product, you're spelunking through the GitHub issues of a foreign repository, praying for a workaround.</p><p>With SaaS, the risks are even more immediate. You aren't just relying on their code; you're relying on their network stability, their database team, and their business model. When their server goes down, your system goes down with it, and you're left with no recourse but to refresh their status page.</p><h2>The Cognitive Trap (Or: This is Water)</h2><p>There is a deeper, more insidious risk to depending on other people's abstractions: they capture your mind.</p><p>In his famous graduation speech, <a href='https://fs.blog/2012/04/david-foster-wallace-this-is-water/'>"This is Water"</a>, David Foster Wallace tells the story of two young fish swimming along who meet an older fish. The older fish nods and says, "Morning, boys. How's the water?" The two young fish swim on for a bit, and then eventually one looks at the other and goes, "What the hell is water?"</p><p>The longer and deeper you submerge your mind in a specific framework or tool, the more your thinking begins to resemble the limits of that environment. If you only write React, every problem looks like a component lifecycle issue. If you only use static site generators built around markdown, you begin to accept markdown's limitations as the natural laws of web publishing. You forget that other classes of design and entirely different paradigms of simplicity even exist.</p><h2>Ken Thompson is Modern, Again</h2><p>At this stage in my career, I’ve stopped chasing the industry's flavor-of-the-month framework. Instead, I find myself returning to <a href='https://en.wikipedia.org/wiki/Unix_philosophy'>Ken Thompson's UNIX philosophy</a>: build small, sharp, reliable tools that do one thing well and compose together.</p><p>Earlier in my career, I wasted countless hours learning broad but shallow frameworks, chasing the latest hypetrain only to watch it derail a year later. Mastery doesn't come from memorizing the APIs of ephemeral libraries. Mastery comes from deep, compounding knowledge of fundamental principles. </p><p>Building your own small tools is slow in the short term, but it carries a massive long-term dividend. The more small tools you construct, the larger your mental library of design patterns and clean code becomes. You can compose these simple components to solve massive, complex problems without inheriting the incidental complexity of a monolithic framework.</p><p>Software mastery isn't about writing perfect code; it's about practicing the art of composition. And to compose effectively, you must understand the notes you're playing.</p><h2>Further Reading</h2><p>If you want to dive deeper into the intellectual foundations of this approach, I highly recommend:</p><ul><li><strong><a href='https://github.com/matthiasn/talk-transcripts/tree/master/Hickey_Rich'>Rich Hickey's Talk Transcripts</a>:</strong> A masterclass in software design, simplicity, and state.</li><li><strong><a href='http://www.flyingmachinestudios.com/programming/the-unofficial-guide-to-rich-hickeys-brain/'>The Unofficial Guide to Rich Hickey's Brain</a>:</strong> Daniel Higginbotham's excellent synthesis of Hickey's core philosophy.</li></ul>]]></content>
  </entry>
</feed>
