<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Diogo Moreira</title>
    <link>https://diogomr.com</link>
    <description>Writing about software, products, and the ideas that shape how we build things.</description>
    
    <item>
      <title>How I Run Pi in a Docker Sandbox</title>
      <link>https://diogomr.com/posts/docker-sandboxes-pi/</link>
      <guid>https://diogomr.com/posts/docker-sandboxes-pi/</guid>
      <pubDate>Fri, 08 May 2026 00:00:00 GMT</pubDate>
      
      <content:encoded><![CDATA[<p>I’ve been wanting to try <a href="https://pi.dev/">pi.dev</a> for a while, but I’ve been a little hesitant to run it directly on my
machine due to its lack of built-in permission system and the possibility of it running a destructive command
without my approval.<sup><a href="#user-content-fn-security-note" id="user-content-fnref-security-note" data-footnote-ref="" aria-describedby="footnote-label">1</a></sup></p>
<p>Naturally I asked ChatGPT what my options were to containerize or run the agent in some kind of sandbox. To my surprise
there was no consensus in the community on what the ideal approach is here. My guess is that there’s so much variance in
personal preferences, security concerns or even company-mandated policies that there’s no general-purpose approach that
can be recommended to everyone.</p>
<p>These were the requirements I had in mind while looking for an approach:</p>
<ul>
<li>I prefer running the software I’m developing locally, with as little indirection as possible</li>
<li>I want to use the agent as a companion on my computer, and review and edit its output directly in my editor of
choice</li>
<li>I don’t want to allow the agent to independently install software on my computer without my explicit approval</li>
<li>I don’t want the agent to access any environment variables or credentials on my machine that are unrelated to the
project it’s working on</li>
<li>I want the agent to be able to build, run tests and run the application it is working on. This will almost always mean
that it needs to be able to run Docker containers (e.g. to run a database)</li>
</ul>
<h2 id="landing-on-docker-sandboxes">Landing on Docker Sandboxes</h2>
<p>After searching for a bunch of approaches I found <a href="https://www.docker.com/products/docker-sandboxes/">Docker Sandboxes</a>
to meet all my requirements. Docker Sandboxes are purposely built for running AI agents and
their <a href="https://docs.docker.com/ai/sandboxes/security/">security model</a> ensures my computer will be safe if Pi ever goes
rogue.<sup><a href="#user-content-fn-sandboxes-note" id="user-content-fnref-sandboxes-note" data-footnote-ref="" aria-describedby="footnote-label">2</a></sup></p>
<p>Pi is not one of the pre-packaged agents for Docker Sandboxes (at least at the time of writing) but it’s quite trivial
to work around that by creating a <a href="https://docs.docker.com/ai/sandboxes/customize/kits/">custom kit</a>.
Here’s <a href="https://github.com/docker/sbx-kits-contrib/tree/main/pi">one</a> provided by the community. The usage is very
simple and that will be enough to get you started and safely experiment with Pi.</p>
<h2 id="finding-the-edges">Finding the edges</h2>
<p>The community kit is fine to experiment with the agent for a bit, but I quickly started hitting the edges of the sandbox
when using it in real projects.</p>
<p>The main problems were:</p>
<ul>
<li>I wanted <code>mise</code> available in every sandbox and not have to install it every time I created a new sandbox.</li>
<li>I wanted to start Pi from any subdirectory in a repo, but still mount the repo root (or else there’s no access to
<code>.git</code>, for example).</li>
<li>I wanted Pi to reuse the same config, prompts, extensions, and <code>AGENTS.md</code> across sandboxes.</li>
<li>I wanted the sandbox to stay open if Pi crashed or exited, so I could debug interactively.</li>
<li>I did not want to rely on the <code>AGENTS.md</code> that Docker Sandboxes ships with, which is polluting the context with
troubleshooting instructions for the sandbox itself.</li>
</ul>
<p>So I decided to create a custom kit that addressed all of the above.</p>
<h3 id="custom-kit">Custom kit</h3>
<p>The kit uses the Docker variant of the shell template, to ensure Pi can run Docker containers.
It installs the system packages Pi needs, installs a system Node.js to install Pi, and mise for managing project tools.</p>
<details class="collapsible-section">
<summary>
<h4 id="kitspispecyaml">kits/pi/spec.yaml</h4>
</summary>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="yaml"><code><span class="line"><span style="color:#85E89D">schemaVersion</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"1"</span></span>
<span class="line"><span style="color:#85E89D">kind</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">agent</span></span>
<span class="line"><span style="color:#85E89D">name</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">pi</span></span>
<span class="line"><span style="color:#85E89D">displayName</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">pi</span></span>
<span class="line"><span style="color:#85E89D">description</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"Pi coding agent with mise, running on Docker shell sandbox image"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#85E89D">agent</span><span style="color:#E1E4E8">:</span></span>
<span class="line"><span style="color:#85E89D">  image</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"docker/sandbox-templates:shell-docker"</span></span>
<span class="line"><span style="color:#6A737D">  # Commented out `aiFilename` to prevent this image from including a base AGENTS.md that is too wordy</span></span>
<span class="line"><span style="color:#6A737D">  # aiFilename: AGENTS.md</span></span>
<span class="line"><span style="color:#85E89D">  persistence</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">persistent</span></span>
<span class="line"><span style="color:#85E89D">  entrypoint</span><span style="color:#E1E4E8">:</span></span>
<span class="line"><span style="color:#85E89D">    run</span><span style="color:#E1E4E8">:</span></span>
<span class="line"><span style="color:#E1E4E8">      - </span><span style="color:#9ECBFF">/usr/local/bin/sbx-pi-entrypoint</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># Cannot use memory because `aiFilename` is commented out</span></span>
<span class="line"><span style="color:#6A737D"># memory:</span></span>
<span class="line"></span>
<span class="line"><span style="color:#85E89D">environment</span><span style="color:#E1E4E8">:</span></span>
<span class="line"><span style="color:#85E89D">  variables</span><span style="color:#E1E4E8">:</span></span>
<span class="line"><span style="color:#85E89D">    LANG</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"C.UTF-8"</span></span>
<span class="line"><span style="color:#85E89D">    LC_ALL</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"C.UTF-8"</span></span>
<span class="line"><span style="color:#6A737D">    # The sandbox proxy enables Node's env proxy support, which emits a noisy</span></span>
<span class="line"><span style="color:#6A737D">    # experimental Undici warning on every npm/node invocation. Suppress only</span></span>
<span class="line"><span style="color:#6A737D">    # that warning code so other Node warnings remain visible.</span></span>
<span class="line"><span style="color:#85E89D">    NODE_OPTIONS</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"--disable-warning=UNDICI-EHPA"</span></span>
<span class="line"><span style="color:#6A737D">    # Host-mounted Pi config directory. The sbx-pi wrapper mounts this path by</span></span>
<span class="line"><span style="color:#6A737D">    # default so Pi can load shared settings, AGENTS.md, extensions, skills,</span></span>
<span class="line"><span style="color:#6A737D">    # prompts, themes, etc. Override together with PI_SBX_AGENT_DIR if needed.</span></span>
<span class="line"><span style="color:#85E89D">    PI_CODING_AGENT_DIR</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"/path/to/agent/pi"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#85E89D">commands</span><span style="color:#E1E4E8">:</span></span>
<span class="line"><span style="color:#85E89D">  startup</span><span style="color:#E1E4E8">:</span></span>
<span class="line"><span style="color:#E1E4E8">    - </span><span style="color:#85E89D">description</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"Update Pi on sandbox startup"</span></span>
<span class="line"><span style="color:#85E89D">      user</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"1000"</span></span>
<span class="line"><span style="color:#85E89D">      command</span><span style="color:#E1E4E8">:</span></span>
<span class="line"><span style="color:#E1E4E8">        - </span><span style="color:#9ECBFF">/bin/bash</span></span>
<span class="line"><span style="color:#E1E4E8">        - </span><span style="color:#9ECBFF">-lc</span></span>
<span class="line"><span style="color:#E1E4E8">        - </span><span style="color:#F97583">|</span></span>
<span class="line"><span style="color:#9ECBFF">          set +eu</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9ECBFF">          pi update</span></span>
<span class="line"><span style="color:#85E89D">  install</span><span style="color:#E1E4E8">:</span></span>
<span class="line"><span style="color:#E1E4E8">    - </span><span style="color:#85E89D">description</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"Install base packages needed for mise, npm packages, and native builds"</span></span>
<span class="line"><span style="color:#85E89D">      user</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"0"</span></span>
<span class="line"><span style="color:#85E89D">      command</span><span style="color:#E1E4E8">: </span><span style="color:#F97583">|</span></span>
<span class="line"><span style="color:#9ECBFF">        set -eu</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9ECBFF">        export DEBIAN_FRONTEND=noninteractive</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9ECBFF">        apt-get update &#x26;&#x26; apt-get install -y --no-install-recommends \</span></span>
<span class="line"><span style="color:#9ECBFF">          ca-certificates \</span></span>
<span class="line"><span style="color:#9ECBFF">          curl \</span></span>
<span class="line"><span style="color:#9ECBFF">          gnupg</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9ECBFF">        install -d -m 0755 /etc/apt/keyrings</span></span>
<span class="line"><span style="color:#9ECBFF">        curl --fail --location --show-error --silent https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \</span></span>
<span class="line"><span style="color:#9ECBFF">          | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg</span></span>
<span class="line"><span style="color:#9ECBFF">        echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_24.x nodistro main" \</span></span>
<span class="line"><span style="color:#9ECBFF">          > /etc/apt/sources.list.d/nodesource.list</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9ECBFF">        apt-get update &#x26;&#x26; apt-get install -y --no-install-recommends \</span></span>
<span class="line"><span style="color:#9ECBFF">          git \</span></span>
<span class="line"><span style="color:#9ECBFF">          openssh-client \</span></span>
<span class="line"><span style="color:#9ECBFF">          build-essential \</span></span>
<span class="line"><span style="color:#9ECBFF">          nodejs \</span></span>
<span class="line"><span style="color:#9ECBFF">          fd-find \</span></span>
<span class="line"><span style="color:#9ECBFF">          pkg-config \</span></span>
<span class="line"><span style="color:#9ECBFF">          python3 \</span></span>
<span class="line"><span style="color:#9ECBFF">          python3-pip \</span></span>
<span class="line"><span style="color:#9ECBFF">          unzip \</span></span>
<span class="line"><span style="color:#9ECBFF">          xz-utils</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9ECBFF">        if command -v fdfind >/dev/null 2>&#x26;1 &#x26;&#x26; ! command -v fd >/dev/null 2>&#x26;1; then</span></span>
<span class="line"><span style="color:#9ECBFF">          ln -s /usr/bin/fdfind /usr/local/bin/fd</span></span>
<span class="line"><span style="color:#9ECBFF">        fi</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9ECBFF">        # Keep package indexes for live/interactive sandboxes so later ad-hoc</span></span>
<span class="line"><span style="color:#9ECBFF">        # apt-get install commands do not require another apt-get update.</span></span>
<span class="line"><span style="color:#9ECBFF">        apt-get clean</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8">    - </span><span style="color:#85E89D">description</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"Install mise"</span></span>
<span class="line"><span style="color:#85E89D">      user</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"1000"</span></span>
<span class="line"><span style="color:#85E89D">      command</span><span style="color:#E1E4E8">: </span><span style="color:#F97583">|</span></span>
<span class="line"><span style="color:#9ECBFF">        set -eu</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9ECBFF">        mkdir -p /home/agent/.local/bin /home/agent/.config/mise</span></span>
<span class="line"><span style="color:#9ECBFF">        curl --fail --location --show-error --silent https://mise.run | sh</span></span>
<span class="line"><span style="color:#E1E4E8">    - </span><span style="color:#85E89D">description</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"Install sandbox entrypoint wrapper"</span></span>
<span class="line"><span style="color:#85E89D">      user</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"0"</span></span>
<span class="line"><span style="color:#85E89D">      command</span><span style="color:#E1E4E8">: </span><span style="color:#F97583">|</span></span>
<span class="line"><span style="color:#9ECBFF">        set -eu</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9ECBFF">        cat > /usr/local/bin/sbx-pi-entrypoint &#x3C;&#x3C;'EOF'</span></span>
<span class="line"><span style="color:#9ECBFF">        #!/usr/bin/env bash</span></span>
<span class="line"><span style="color:#9ECBFF">        set +e</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9ECBFF">        # Configure Git SSH signing from the forwarded SSH agent at runtime.</span></span>
<span class="line"><span style="color:#9ECBFF">        # The SSH agent socket is available in interactive runs, but may not be</span></span>
<span class="line"><span style="color:#9ECBFF">        # available when Docker Sandboxes executes kit startup commands.</span></span>
<span class="line"><span style="color:#9ECBFF">        signing_key="$(ssh-add -L 2>/dev/null | head -n 1 || true)"</span></span>
<span class="line"><span style="color:#9ECBFF">        if [ -n "$signing_key" ]; then</span></span>
<span class="line"><span style="color:#9ECBFF">          git config --global commit.gpgsign true</span></span>
<span class="line"><span style="color:#9ECBFF">          git config --global tag.gpgsign true</span></span>
<span class="line"><span style="color:#9ECBFF">          git config --global gpg.format ssh</span></span>
<span class="line"><span style="color:#9ECBFF">          git config --global user.signingkey "key::${signing_key}"</span></span>
<span class="line"><span style="color:#9ECBFF">        else</span></span>
<span class="line"><span style="color:#9ECBFF">          echo "No SSH public key available from ssh-add; skipping Git SSH signing config." >&#x26;2</span></span>
<span class="line"><span style="color:#9ECBFF">        fi</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9ECBFF">        # Start Pi in the directory where sbx-pi was invoked on the host</span></span>
<span class="line"><span style="color:#9ECBFF">        original_pwd="${PI_SBX_ORIGINAL_PWD:-}"</span></span>
<span class="line"><span style="color:#9ECBFF">        if [ -n "$original_pwd" ] &#x26;&#x26; [ -d "$original_pwd" ]; then</span></span>
<span class="line"><span style="color:#9ECBFF">          cd "$original_pwd"</span></span>
<span class="line"><span style="color:#9ECBFF">        fi</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9ECBFF">        # Pi runs commands non-interactively, so use mise shims for the</span></span>
<span class="line"><span style="color:#9ECBFF">        # inherited environment. Install the active project toolset after cd so</span></span>
<span class="line"><span style="color:#9ECBFF">        # .tool-versions/mise.toml in the project is detected.</span></span>
<span class="line"><span style="color:#9ECBFF">        eval "$(/home/agent/.local/bin/mise activate bash --shims)"</span></span>
<span class="line"><span style="color:#9ECBFF">        mise install --yes || true</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9ECBFF">        pi "$@"</span></span>
<span class="line"><span style="color:#9ECBFF">        status=$?</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9ECBFF">        echo</span></span>
<span class="line"><span style="color:#9ECBFF">        echo "pi exited with status ${status}; dropping into sandbox shell."</span></span>
<span class="line"><span style="color:#9ECBFF">        echo "Run 'exit' to close the sandbox session."</span></span>
<span class="line"><span style="color:#9ECBFF">        echo</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9ECBFF">        # If Pi exits and we drop into a shell inside the sandbox, put that</span></span>
<span class="line"><span style="color:#9ECBFF">        # shell in the same original directory too.</span></span>
<span class="line"><span style="color:#9ECBFF">        if [ -n "${original_pwd:-}" ] &#x26;&#x26; [ -d "$original_pwd" ]; then</span></span>
<span class="line"><span style="color:#9ECBFF">          cd "$original_pwd"</span></span>
<span class="line"><span style="color:#9ECBFF">        fi</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9ECBFF">        # Prefer normal PATH activation for the fallback interactive shell.</span></span>
<span class="line"><span style="color:#9ECBFF">        eval "$(/home/agent/.local/bin/mise activate bash)"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9ECBFF">        exec /bin/bash -l</span></span>
<span class="line"><span style="color:#9ECBFF">        EOF</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9ECBFF">        chmod 755 /usr/local/bin/sbx-pi-entrypoint</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8">    - </span><span style="color:#85E89D">description</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"Install Pi globally"</span></span>
<span class="line"><span style="color:#85E89D">      user</span><span style="color:#E1E4E8">: </span><span style="color:#9ECBFF">"1000"</span></span>
<span class="line"><span style="color:#85E89D">      command</span><span style="color:#E1E4E8">: </span><span style="color:#F97583">|</span></span>
<span class="line"><span style="color:#9ECBFF">        set -eu</span></span>
<span class="line"></span>
<span class="line"><span style="color:#9ECBFF">        npm install -g @earendil-works/pi-coding-agent</span></span>
<span class="line"><span style="color:#9ECBFF">        pi --version || true</span></span></code></pre>
</details>
<h3 id="pi-config">Pi config</h3>
<p>I created a shared folder that holds the Pi configuration files. Every sandbox mounts it at the same absolute path, so
Pi uses the same settings, prompts, extensions, and instructions no matter which project I am working on.</p>
<p>My <code>AGENTS.md</code> is intentionally short and describes the sandbox-specific assumptions:</p>
<details class="collapsible-section">
<summary>
<h4 id="agentsmd">AGENTS.md</h4>
</summary>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="md"><code><span class="line"><span style="color:#79B8FF;font-weight:bold">## Environment</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8">You run in a Docker sandbox with internet access. The workspace is mounted at its host path.</span></span>
<span class="line"><span style="color:#79B8FF">`sudo`</span><span style="color:#E1E4E8"> is passwordless. Docker is available; nested containers are isolated in the microVM.</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8">Prefer </span><span style="color:#79B8FF">`mise`</span><span style="color:#E1E4E8"> for project tooling and expose build/test/run commands as mise tasks; use </span><span style="color:#79B8FF">`hk`</span><span style="color:#E1E4E8"> for git hooks. For one-off</span></span>
<span class="line"><span style="color:#E1E4E8">tools, prefer ephemeral execution, e.g. </span><span style="color:#79B8FF">`uv run --with &#x3C;pkg>`</span><span style="color:#E1E4E8">, instead of system installs.</span></span>
<span class="line"></span>
<span class="line"><span style="color:#79B8FF;font-weight:bold">## Work style</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8">Ask clarifying questions only when needed to avoid likely rework.</span></span>
<span class="line"><span style="color:#E1E4E8">Fix root causes rather than symptoms.</span></span>
<span class="line"><span style="color:#E1E4E8">For bugs, reproduce the issue first when practical; prefer regression tests when the repo supports them.</span></span>
<span class="line"><span style="color:#E1E4E8">Before finishing, verify the result; if verification is unclear, ask the user.</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8">Do not revert unexpected user/agent changes. Work around them; if they block you, ask before stashing or reverting.</span></span>
<span class="line"><span style="color:#E1E4E8">Ask before destructive actions such as deleting large paths, resetting history, or discarding changes.</span></span>
<span class="line"></span>
<span class="line"><span style="color:#79B8FF;font-weight:bold">## Communication</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8">Be direct. Say when you do not know something or when a request seems wrong.</span></span>
<span class="line"><span style="color:#E1E4E8">When asking for guidance, present options and a recommendation.</span></span>
<span class="line"><span style="color:#E1E4E8">Use ASCII diagrams when they make complex flows clearer.</span></span>
<span class="line"></span>
<span class="line"><span style="color:#79B8FF;font-weight:bold">## Security</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8">Treat secrets, tokens, credentials, private keys, and </span><span style="color:#79B8FF">`.env`</span><span style="color:#E1E4E8"> files as sensitive.</span></span>
<span class="line"><span style="color:#E1E4E8">Prefer local files and official docs before internet searches. Do not pipe remote scripts to shells without inspection.</span></span></code></pre>
</details>
<h3 id="sandbox-launcher-script">Sandbox launcher script</h3>
<p>To make life a bit easier I created a launcher script for sandboxes that hides the complexity of creating the
sandbox, mounting the required directories, ensuring the git repository root is also mounted and that existing sandboxes
are reused. I turned this script into a shell alias (<code>spi</code>, as in sandboxed-pi) that I’m able to run from any directory.</p>
<details class="collapsible-section">
<summary>
<h4 id="sbxbinsbx-pi">sbx/bin/sbx-pi</h4>
</summary>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="bash"><code><span class="line"><span style="color:#6A737D">#!/usr/bin/env bash</span></span>
<span class="line"><span style="color:#79B8FF">set</span><span style="color:#79B8FF"> -euo</span><span style="color:#9ECBFF"> pipefail</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># Run the pi Docker Sandbox kit from anywhere.</span></span>
<span class="line"><span style="color:#6A737D"># Defaults to this dotfiles checkout's kit, but can be overridden with PI_SBX_KIT.</span></span>
<span class="line"><span style="color:#E1E4E8">KIT</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"${</span><span style="color:#E1E4E8">PI_SBX_KIT</span><span style="color:#F97583">:-/</span><span style="color:#E1E4E8">path</span><span style="color:#F97583">/</span><span style="color:#E1E4E8">to</span><span style="color:#F97583">/</span><span style="color:#E1E4E8">sbx</span><span style="color:#F97583">/</span><span style="color:#E1E4E8">kits</span><span style="color:#F97583">/</span><span style="color:#E1E4E8">pi</span><span style="color:#9ECBFF">}"</span></span>
<span class="line"><span style="color:#E1E4E8">AGENT_NAME</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"${</span><span style="color:#E1E4E8">PI_SBX_NAME</span><span style="color:#F97583">:-</span><span style="color:#E1E4E8">pi</span><span style="color:#9ECBFF">}"</span></span>
<span class="line"><span style="color:#E1E4E8">ORIGINAL_PWD</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">$PWD</span><span style="color:#9ECBFF">"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># If invoked from inside a Git repository, use the repository root as the</span></span>
<span class="line"><span style="color:#6A737D"># primary workspace so the whole repo is mounted into the sandbox. This script</span></span>
<span class="line"><span style="color:#6A737D"># does not cd, so the host shell remains wherever sbx-pi was invoked from. The</span></span>
<span class="line"><span style="color:#6A737D"># sandbox entrypoint cd's back to ORIGINAL_PWD before starting Pi.</span></span>
<span class="line"><span style="color:#F97583">if</span><span style="color:#E1E4E8"> GIT_ROOT</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"$(</span><span style="color:#B392F0">git</span><span style="color:#79B8FF"> -C</span><span style="color:#9ECBFF"> "</span><span style="color:#E1E4E8">$ORIGINAL_PWD</span><span style="color:#9ECBFF">" rev-parse </span><span style="color:#79B8FF">--show-toplevel</span><span style="color:#F97583"> 2></span><span style="color:#9ECBFF">/dev/null)"</span><span style="color:#E1E4E8">; </span><span style="color:#F97583">then</span></span>
<span class="line"><span style="color:#E1E4E8">  WORKSPACE_DIR</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">$GIT_ROOT</span><span style="color:#9ECBFF">"</span></span>
<span class="line"><span style="color:#F97583">else</span></span>
<span class="line"><span style="color:#E1E4E8">  WORKSPACE_DIR</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">$ORIGINAL_PWD</span><span style="color:#9ECBFF">"</span></span>
<span class="line"><span style="color:#F97583">fi</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8">PROJECT_NAME</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"$(</span><span style="color:#B392F0">basename</span><span style="color:#9ECBFF"> "</span><span style="color:#E1E4E8">$WORKSPACE_DIR</span><span style="color:#9ECBFF">")"</span></span>
<span class="line"><span style="color:#E1E4E8">SANDBOX_NAME</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"${</span><span style="color:#E1E4E8">PI_SBX_SANDBOX_NAME</span><span style="color:#F97583">:-</span><span style="color:#9ECBFF">${</span><span style="color:#E1E4E8">AGENT_NAME</span><span style="color:#9ECBFF">}</span><span style="color:#E1E4E8">-</span><span style="color:#9ECBFF">${</span><span style="color:#E1E4E8">PROJECT_NAME</span><span style="color:#9ECBFF">}}"</span></span>
<span class="line"><span style="color:#E1E4E8">PI_SBX_AGENT_DIR</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"${</span><span style="color:#E1E4E8">PI_SBX_AGENT_DIR</span><span style="color:#F97583">:-/</span><span style="color:#E1E4E8">path</span><span style="color:#F97583">/</span><span style="color:#E1E4E8">to</span><span style="color:#F97583">/</span><span style="color:#E1E4E8">pi-agent</span><span style="color:#9ECBFF">}"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">if</span><span style="color:#F97583"> !</span><span style="color:#79B8FF"> command</span><span style="color:#79B8FF"> -v</span><span style="color:#9ECBFF"> sbx</span><span style="color:#F97583"> ></span><span style="color:#9ECBFF">/dev/null</span><span style="color:#F97583"> 2>&#x26;1</span><span style="color:#E1E4E8">; </span><span style="color:#F97583">then</span></span>
<span class="line"><span style="color:#79B8FF">  echo</span><span style="color:#9ECBFF"> "sbx-pi: 'sbx' command not found in PATH"</span><span style="color:#F97583"> >&#x26;2</span></span>
<span class="line"><span style="color:#79B8FF">  exit</span><span style="color:#79B8FF"> 127</span></span>
<span class="line"><span style="color:#F97583">fi</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">if</span><span style="color:#E1E4E8"> [ </span><span style="color:#F97583">!</span><span style="color:#F97583"> -f</span><span style="color:#9ECBFF"> "</span><span style="color:#E1E4E8">$KIT</span><span style="color:#9ECBFF">/spec.yaml"</span><span style="color:#E1E4E8"> ]; </span><span style="color:#F97583">then</span></span>
<span class="line"><span style="color:#79B8FF">  echo</span><span style="color:#9ECBFF"> "sbx-pi: kit not found: </span><span style="color:#E1E4E8">$KIT</span><span style="color:#9ECBFF">"</span><span style="color:#F97583"> >&#x26;2</span></span>
<span class="line"><span style="color:#79B8FF">  echo</span><span style="color:#9ECBFF"> "Set PI_SBX_KIT to the directory containing spec.yaml."</span><span style="color:#F97583"> >&#x26;2</span></span>
<span class="line"><span style="color:#79B8FF">  exit</span><span style="color:#79B8FF"> 1</span></span>
<span class="line"><span style="color:#F97583">fi</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8">args</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">$AGENT_NAME</span><span style="color:#9ECBFF">"</span><span style="color:#9ECBFF"> --kit</span><span style="color:#9ECBFF"> "</span><span style="color:#E1E4E8">$KIT</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># Keep the Git repository root as the primary workspace when called from inside</span></span>
<span class="line"><span style="color:#6A737D"># a repo, otherwise keep the caller's current directory. Extra workspaces follow</span></span>
<span class="line"><span style="color:#6A737D"># it, so the sandbox starts in the project rather than in the shared Pi config</span></span>
<span class="line"><span style="color:#6A737D"># directory.</span></span>
<span class="line"><span style="color:#E1E4E8">args</span><span style="color:#F97583">+=</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">$WORKSPACE_DIR</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># Mount the shared Pi config directory as an additional workspace. Docker</span></span>
<span class="line"><span style="color:#6A737D"># Sandboxes mount workspaces at their absolute host paths, matching the</span></span>
<span class="line"><span style="color:#6A737D"># PI_CODING_AGENT_DIR configured in the kit.</span></span>
<span class="line"><span style="color:#6A737D">#</span></span>
<span class="line"><span style="color:#6A737D"># Set PI_SBX_AGENT_DIR= to disable this automatic mount, or point it at a</span></span>
<span class="line"><span style="color:#6A737D"># different host-side Pi config directory if you also override the kit env var.</span></span>
<span class="line"><span style="color:#F97583">if</span><span style="color:#E1E4E8"> [ </span><span style="color:#F97583">-n</span><span style="color:#9ECBFF"> "</span><span style="color:#E1E4E8">$PI_SBX_AGENT_DIR</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8"> ]; </span><span style="color:#F97583">then</span></span>
<span class="line"><span style="color:#F97583">  if</span><span style="color:#E1E4E8"> [ </span><span style="color:#F97583">!</span><span style="color:#F97583"> -d</span><span style="color:#9ECBFF"> "</span><span style="color:#E1E4E8">$PI_SBX_AGENT_DIR</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8"> ]; </span><span style="color:#F97583">then</span></span>
<span class="line"><span style="color:#79B8FF">    echo</span><span style="color:#9ECBFF"> "sbx-pi: shared Pi config directory not found: </span><span style="color:#E1E4E8">$PI_SBX_AGENT_DIR</span><span style="color:#9ECBFF">"</span><span style="color:#F97583"> >&#x26;2</span></span>
<span class="line"><span style="color:#79B8FF">    echo</span><span style="color:#9ECBFF"> "Set PI_SBX_AGENT_DIR= to disable, or create the directory."</span><span style="color:#F97583"> >&#x26;2</span></span>
<span class="line"><span style="color:#79B8FF">    exit</span><span style="color:#79B8FF"> 1</span></span>
<span class="line"><span style="color:#F97583">  fi</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8">  args</span><span style="color:#F97583">+=</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">$PI_SBX_AGENT_DIR</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#F97583">fi</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8">args</span><span style="color:#F97583">+=</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"</span><span style="color:#79B8FF">$@</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B392F0">set_sandbox_env</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#B392F0">  sbx</span><span style="color:#9ECBFF"> exec</span><span style="color:#79B8FF"> -d</span><span style="color:#9ECBFF"> "</span><span style="color:#E1E4E8">$SANDBOX_NAME</span><span style="color:#9ECBFF">"</span><span style="color:#9ECBFF"> bash</span><span style="color:#79B8FF"> -lc</span><span style="color:#9ECBFF"> '</span></span>
<span class="line"><span style="color:#9ECBFF">    set -e</span></span>
<span class="line"><span style="color:#9ECBFF">    original_pwd=$1</span></span>
<span class="line"><span style="color:#9ECBFF">    file=/etc/sandbox-persistent.sh</span></span>
<span class="line"><span style="color:#9ECBFF">    tmp=$(mktemp)</span></span>
<span class="line"><span style="color:#9ECBFF">    if [ -f "$file" ]; then</span></span>
<span class="line"><span style="color:#9ECBFF">      grep -Ev "^export (PI_SBX_ORIGINAL_PWD|GIT_DISCOVERY_ACROSS_FILESYSTEM)=" "$file" > "$tmp" || true</span></span>
<span class="line"><span style="color:#9ECBFF">    fi</span></span>
<span class="line"><span style="color:#9ECBFF">    printf "export PI_SBX_ORIGINAL_PWD=%q\n" "$original_pwd" >> "$tmp"</span></span>
<span class="line"><span style="color:#9ECBFF">    printf "export GIT_DISCOVERY_ACROSS_FILESYSTEM=1\n" >> "$tmp"</span></span>
<span class="line"><span style="color:#9ECBFF">    cat "$tmp" > "$file"</span></span>
<span class="line"><span style="color:#9ECBFF">    rm -f "$tmp"</span></span>
<span class="line"><span style="color:#9ECBFF">  '</span><span style="color:#9ECBFF"> bash</span><span style="color:#9ECBFF"> "</span><span style="color:#E1E4E8">$ORIGINAL_PWD</span><span style="color:#9ECBFF">"</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># If the sandbox already exists, run it by name. Recent sbx versions reject</span></span>
<span class="line"><span style="color:#6A737D"># passing workspace arguments to an existing sandbox.</span></span>
<span class="line"><span style="color:#F97583">if</span><span style="color:#B392F0"> sbx</span><span style="color:#9ECBFF"> ls</span><span style="color:#79B8FF"> --quiet</span><span style="color:#F97583"> |</span><span style="color:#B392F0"> grep</span><span style="color:#79B8FF"> -qxF</span><span style="color:#9ECBFF"> "</span><span style="color:#E1E4E8">$SANDBOX_NAME</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">; </span><span style="color:#F97583">then</span></span>
<span class="line"><span style="color:#B392F0">  set_sandbox_env</span></span>
<span class="line"><span style="color:#E1E4E8">  existing_args</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">$SANDBOX_NAME</span><span style="color:#9ECBFF">"</span><span style="color:#9ECBFF"> --kit</span><span style="color:#9ECBFF"> "</span><span style="color:#E1E4E8">$KIT</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#E1E4E8">  existing_args</span><span style="color:#F97583">+=</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"</span><span style="color:#79B8FF">$@</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#79B8FF">  exec</span><span style="color:#9ECBFF"> sbx</span><span style="color:#9ECBFF"> run</span><span style="color:#9ECBFF"> "${</span><span style="color:#E1E4E8">existing_args</span><span style="color:#9ECBFF">[</span><span style="color:#F97583">@</span><span style="color:#9ECBFF">]}"</span></span>
<span class="line"><span style="color:#F97583">fi</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8">create_args</span><span style="color:#F97583">=</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"${</span><span style="color:#E1E4E8">args</span><span style="color:#9ECBFF">[</span><span style="color:#F97583">@</span><span style="color:#9ECBFF">]}"</span><span style="color:#9ECBFF"> --name</span><span style="color:#9ECBFF"> "</span><span style="color:#E1E4E8">$SANDBOX_NAME</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">)</span></span>
<span class="line"><span style="color:#B392F0">sbx</span><span style="color:#9ECBFF"> create</span><span style="color:#9ECBFF"> "${</span><span style="color:#E1E4E8">create_args</span><span style="color:#9ECBFF">[</span><span style="color:#F97583">@</span><span style="color:#9ECBFF">]}"</span></span>
<span class="line"><span style="color:#B392F0">set_sandbox_env</span></span>
<span class="line"><span style="color:#79B8FF">exec</span><span style="color:#9ECBFF"> sbx</span><span style="color:#9ECBFF"> run</span><span style="color:#9ECBFF"> "</span><span style="color:#E1E4E8">$SANDBOX_NAME</span><span style="color:#9ECBFF">"</span><span style="color:#79B8FF"> --kit</span><span style="color:#9ECBFF"> "</span><span style="color:#E1E4E8">$KIT</span><span style="color:#9ECBFF">"</span><span style="color:#9ECBFF"> "</span><span style="color:#79B8FF">$@</span><span style="color:#9ECBFF">"</span></span></code></pre>
</details>
<h2 id="final-thoughts">Final thoughts</h2>
<p>I had to iterate a lot in order to land on this configuration of sandboxes and I haven’t yet used them in very complex
projects. The good news is that with an agent like Pi it’s really simple to state your vision and let it build the
necessary scaffolding to make your life easier. The project is still in Early Access so it’s likely that it will keep
getting improved to a point where a lot of this is not necessary.</p>
<p>As for Pi, it’s really as great as they say! You can use a Codex subscription to get frontier model tokens at a
discount, but you get to use your custom-built harness. That might not sound like much. After all, won’t the Claude Code
or Codex team be much better at building a harness than yourself? They might be better at creating a general-purpose
one, but if you don’t care about MCPs, subagents, remote access, plan-mode, you’re paying a context penalty on all your
tasks for features you never use. <sup><a href="#user-content-fn-pi-note" id="user-content-fnref-pi-note" data-footnote-ref="" aria-describedby="footnote-label">3</a></sup></p>
<section data-footnotes="" class="footnotes"><h2 class="sr-only" id="footnote-label">Footnotes</h2>
<ol>
<li id="user-content-fn-security-note">
<p>In the meantime I’ve come to realize that even agent harnesses such as Claude Code or Codex only give
you the impression of security with their permission systems. You can attempt to block destructive commands such as
<code>rm -rf /</code> but if you allow Bash or Python scripting those can be bypassed. One can argue that it’s on you to review the
scripts before allowing them to run, but I think that defeats the purpose of using coding agents. <a href="#user-content-fnref-security-note" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-sandboxes-note">
<p>The security model of the sandboxes is a good read. A sufficiently <em>motivated</em> agent will find a way
of breaking out of a simple Docker container but that is impossible with a microVM. <a href="#user-content-fnref-sandboxes-note" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref">↩</a></p>
</li>
<li id="user-content-fn-pi-note">
<p>On that topic, I really recommend this quite short talk called <a href="https://www.youtube.com/watch?v=fdbXNWkpPMY">A love letter to Pi by Lucas Meijer
</a>. <a href="#user-content-fnref-pi-note" data-footnote-backref="" aria-label="Back to reference 3" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>]]></content:encoded>
    </item>
    <item>
      <title>Naval on AI and Engineers</title>
      <link>https://diogomr.com/posts/naval-ai-engineers/</link>
      <guid>https://diogomr.com/posts/naval-ai-engineers/</guid>
      <pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate>
      
      <content:encoded><![CDATA[<p>Naval’s <a href="https://nav.al/ai">latest podcast about AI</a> is really insightful. I particularly appreciated his take on the impact of AI on the future of software engineering, as it is quite contrary to the doomer scenarios I often read about.</p>
<p>The prediction that AI will lead to software engineers being replaced — presumably, by product managers, designers, or anyone able to articulate creative thoughts to an LLM — has been around for a while, and as models get better, the prediction only seems to get more popular.</p>
<p>Naval predicts otherwise. Software engineers will be able to use AI to increase their productivity and will be able to leverage the tooling around it like no one else. They’ll run circles around anyone that doesn’t know how to code or never maintained a complex application for the long term. That will get reflected in the quality of the end product. Naval has a great line on that: “there’s no demand for average”. The best apps will win over all the vibe-coded alternatives.</p>
<p>And then he goes even further. Not only will software engineers not get replaced, they will be the ones doing the replacement. AI as a lever can increase their productivity by so much that they’ll be able to go after even more industries.</p>
<p>I think Naval is right. But while this view is optimistic for the future of engineers in the industry, it also makes it very clear how important it is to turn AI into a productivity multiplier. Engineers that are skeptical of AI will have no edge over everyone else using the ever improving models.</p>]]></content:encoded>
    </item>
    <item>
      <title>macOS setup for software development</title>
      <link>https://diogomr.com/posts/macos-setup/</link>
      <guid>https://diogomr.com/posts/macos-setup/</guid>
      <pubDate>Sat, 27 Dec 2025 00:00:00 GMT</pubDate>
      
      <content:encoded><![CDATA[<p>I’ve been using Linux as my daily-driver OS for professional software development since 2019.</p>
<p>In 2021 I had a short, company-mandated stint with macOS, and it went badly enough that I swore I’d never touch it
again.
Looking back, most of it was self-inflicted: I tried to force my Linux workflow and keyboard shortcuts into macOS,
used an external keyboard whose layout was not Mac-compatible and then made it worse by remapping keys like <code>Cmd</code> and
<code>Ctrl</code> in an attempt to bring some familiarity back. I also really, really missed a tiling window manager.
Unsurprisingly the whole thing didn’t work and I switched back to Linux as soon as I could.</p>
<p>By the beginning of 2025, a few things had changed:</p>
<ul>
<li>I stopped caring about using a tiling window manager. My setup made context-switching <em>too</em> easy: one shortcut to
change workspaces and suddenly I’m “taking a quick look” on slack whenever something gets hard or boring.</li>
<li>The performance difference between my laptop and the Macbooks was very noticeable: running a linter on my company’s
main monorepo was taking 2-3x more time than
on my colleagues’ Macbooks.</li>
<li>I missed the ecosystem: my AirPods never quite worked on Linux (to be fair, I didn’t fight it much), some apps like
Slack or Spotify were clearly second class on Linux</li>
<li>LLMs were clearly an industry changing technology and apps like ChatGPT or Claude did not exist on Linux. Although in
retrospect these apps weren’t that important I really didn’t want to miss out on the bleeding edge of the industry and
Linux was clearly not being treated as a first class target of app developers.</li>
</ul>
<p>So I switched to a MacBook Pro M4. And, annoyingly for my past self, it’s been great: everything is fast, the screen is
so good that I ditched my external monitor, and the keyboard + trackpad combo is excellent.</p>
<p>The biggest difference this time is that I didn’t try to “fix” macOS into Linux. No key remaps, no forcing it. I just
learned the shortcuts and everything clicked right away.</p>
<p>As the saying goes: <strong>never</strong> say never.</p>
<h2 id="setup">Setup</h2>
<ul>
<li>Install <a href="https://ghostty.org/">Ghostty</a> terminal emulator.</li>
<li>Install Xcode Command Line Tools with <code>xcode-select --install</code></li>
<li>Install <a href="https://brew.sh/">Homebrew</a></li>
<li><a href="https://manual.raycast.com/hotkey">Disable Spotlight hotkey</a>. Install <a href="https://www.raycast.com/">Raycast</a>.</li>
<li>Install <a href="https://github.com/ohmyzsh/ohmyzsh">ohmyzsh</a> to manage zsh (default macOS shell)</li>
<li>Install <a href="https://github.com/jimeh/zsh-peco-history">zsh-peco-history</a> to improve shell history search using
<code>Ctrl+R</code>.<sup><a href="#user-content-fn-peco-history" id="user-content-fnref-peco-history" data-footnote-ref="" aria-describedby="footnote-label">1</a></sup></li>
<li>Install <a href="https://mise.jdx.dev/">mise</a> to manage different versions of tooling (node, python, ruby, etc)</li>
<li>Install Jetbrain’s <a href="https://www.jetbrains.com/toolbox-app/">toolbox app</a> to manage all IDE’s I use</li>
<li>Install <a href="https://claude.com/product/claude-code">claude code</a>.</li>
<li>Setup zsh aliases.<sup><a href="#user-content-fn-aliases" id="user-content-fnref-aliases" data-footnote-ref="" aria-describedby="footnote-label">2</a></sup></li>
<li>Setup <a href="https://docs.github.com/en/authentication/connecting-to-github-with-ssh">ssh for Github</a> and create
a <a href="https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key">GPG signing key</a></li>
<li>Setup macOS keyboard with the help of <a href="https://mac-key-repeat.zaymon.dev/">https://mac-key-repeat.zaymon.dev/</a>
<ul>
<li><code>defaults write -g InitialKeyRepeat -int 12</code></li>
<li><code>defaults write -g KeyRepeat -int 1</code></li>
</ul>
</li>
<li>Change Finder settings
<ul>
<li><code>chflags nohidden ~/Library</code> to show Library folder</li>
<li><code>defaults write com.apple.finder AppleShowAllFiles -boolean true</code> to show hidden files</li>
<li><code>defaults write com.apple.finder ShowPathbar -bool true</code> to show path bar</li>
</ul>
</li>
</ul>
<section data-footnotes="" class="footnotes"><h2 class="sr-only" id="footnote-label">Footnotes</h2>
<ol>
<li id="user-content-fn-peco-history">
<p><code>zsh-peco-history</code> config:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#6A737D"># .zshrc</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8">HISTSIZE</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">10000000</span></span>
<span class="line"><span style="color:#E1E4E8">SAVEHIST</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">10000000</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B392F0">setopt</span><span style="color:#9ECBFF"> EXTENDED_HISTORY</span><span style="color:#6A737D">          # Write the history file in the ":start:elapsed;command" format.</span></span>
<span class="line"><span style="color:#B392F0">setopt</span><span style="color:#9ECBFF"> INC_APPEND_HISTORY</span><span style="color:#6A737D">        # Write to the history file immediately, not when the shell exits.</span></span>
<span class="line"><span style="color:#B392F0">setopt</span><span style="color:#9ECBFF"> SHARE_HISTORY</span><span style="color:#6A737D">             # Share history between all sessions.</span></span>
<span class="line"><span style="color:#B392F0">setopt</span><span style="color:#9ECBFF"> HIST_EXPIRE_DUPS_FIRST</span><span style="color:#6A737D">    # Expire duplicate entries first when trimming history.</span></span>
<span class="line"><span style="color:#B392F0">setopt</span><span style="color:#9ECBFF"> HIST_IGNORE_DUPS</span><span style="color:#6A737D">          # Don't record an entry that was just recorded again.</span></span>
<span class="line"><span style="color:#B392F0">setopt</span><span style="color:#9ECBFF"> HIST_IGNORE_ALL_DUPS</span><span style="color:#6A737D">      # Delete old recorded entry if new entry is a duplicate.</span></span>
<span class="line"><span style="color:#B392F0">setopt</span><span style="color:#9ECBFF"> HIST_FIND_NO_DUPS</span><span style="color:#6A737D">         # Do not display a line previously found.</span></span>
<span class="line"><span style="color:#B392F0">setopt</span><span style="color:#9ECBFF"> HIST_IGNORE_SPACE</span><span style="color:#6A737D">         # Don't record an entry starting with a space.</span></span>
<span class="line"><span style="color:#B392F0">setopt</span><span style="color:#9ECBFF"> HIST_SAVE_NO_DUPS</span><span style="color:#6A737D">         # Don't write duplicate entries in the history file.</span></span>
<span class="line"><span style="color:#B392F0">setopt</span><span style="color:#9ECBFF"> HIST_REDUCE_BLANKS</span><span style="color:#6A737D">        # Remove superfluous blanks before recording entry.</span></span></code></pre>
<a href="#user-content-fnref-peco-history" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a>
</li>
<li id="user-content-fn-aliases">
<p>I make heavy use of aliases on my <code>~/.zshrc</code> file to help me navigate to common directories or open projects
on the appropriate IDE. Some examples:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#F97583">export</span><span style="color:#E1E4E8"> EDITOR</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">'vim'</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># .zshrc</span></span>
<span class="line"><span style="color:#F97583">alias</span><span style="color:#E1E4E8"> ezshrc</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">$EDITOR</span><span style="color:#E1E4E8"> $HOME</span><span style="color:#9ECBFF">/.zshrc"</span></span>
<span class="line"><span style="color:#F97583">alias</span><span style="color:#E1E4E8"> szshrc</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"source </span><span style="color:#E1E4E8">$HOME</span><span style="color:#9ECBFF">/.zshrc"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># projects</span></span>
<span class="line"><span style="color:#F97583">export</span><span style="color:#E1E4E8"> PROJECTS_HOME</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">$HOME</span><span style="color:#9ECBFF">/projects"</span></span>
<span class="line"><span style="color:#F97583">export</span><span style="color:#E1E4E8"> AOC</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">$PROJECTS_HOME</span><span style="color:#9ECBFF">/aoc-2022"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">alias</span><span style="color:#E1E4E8"> aoc</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"cd </span><span style="color:#E1E4E8">$AOC</span><span style="color:#9ECBFF">"</span></span>
<span class="line"><span style="color:#F97583">alias</span><span style="color:#E1E4E8"> iaoc</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"launch_rubymine </span><span style="color:#E1E4E8">$AOC</span><span style="color:#9ECBFF">"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># functions</span></span>
<span class="line"><span style="color:#B392F0">launch_rubymine</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#B392F0">        nohup</span><span style="color:#E1E4E8"> $HOME</span><span style="color:#9ECBFF">/bin/rubymine</span><span style="color:#FFAB70"> $1</span><span style="color:#F97583"> ></span><span style="color:#9ECBFF"> /dev/null</span><span style="color:#F97583"> 2>&#x26;1</span><span style="color:#E1E4E8"> &#x26;</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>I can type <code>ezshrc</code> from anywhere in my terminal to edit my zsh config file and then <code>szshrc</code> to source it. <a href="#user-content-fnref-aliases" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>]]></content:encoded>
    </item>
    <item>
      <title>Making impossible states impossible</title>
      <link>https://diogomr.com/posts/making-impossible-states-impossible/</link>
      <guid>https://diogomr.com/posts/making-impossible-states-impossible/</guid>
      <pubDate>Sat, 24 May 2025 00:00:00 GMT</pubDate>
      <description>Beyond a programming language's type system</description>
      <content:encoded><![CDATA[<p>Early in my software development journey I came across a <a href="https://www.youtube.com/watch?v=IcgmSRJHu_8">talk</a> by Richard Feldman’s titled “Make impossible states impossible.” His talk explored how a programming language’s type system could be leveraged to make impossible states impossible to represent.</p>
<p>That idea stuck with me and, over the years, I’ve been applying it way beyond the type system. It has helped me build and maintain resilient systems, preventing bugs that would otherwise be much harder to find.</p>
<h2 id="impossible-states">Impossible states</h2>
<p>An impossible state is a state that should have no way of being represented according to your system’s requirement.</p>
<p>Take these two basic requirements, for example:</p>
<ul>
<li>All users in my system have an email</li>
<li>An order cannot be shipped if no payment has been confirmed</li>
</ul>
<p>Which results in the following impossible states:</p>
<ul>
<li>User exists in the system without email</li>
<li>An order has been shipped without payment confirmation</li>
</ul>
<p>How can we design a system so that these theoretically impossible states are indeed impossible?</p>
<h2 id="database-schema-design">Database schema design</h2>
<p>When defining a relational database table schema, we need to choose the types of the columns, define primary keys, foreign keys, column nullability, and constraints. These are all tools we can use to make our model conform to the requirements.
If all users of our application are expected to have an email but the <code>users</code> table has a nullable <code>email</code> column then the requirements are not properly enforced. A theoretical impossible state is effectively possible.</p>
<p>What if the “Sign Up” flow is validating the email ensuring that no users can be created without an email?</p>
<p>There are a few assumptions in that line of thinking:</p>
<ol>
<li>There are no bugs in that flow that could allow users to register without an email</li>
<li>There is no bug in dealing with the payload the client sends to the application</li>
<li>No one will delete the email in a different application flow after it is created</li>
<li>No one will access the database and manually delete it</li>
</ol>
<p>If the <code>email</code> column is <strong>not</strong> nullable then all the above <em>can</em> still happen, but they will result in an error, we’ll be alerted and fix whatever hole allowed the error to happen. We’ll catch bugs earlier and make it <em>impossible</em> to have users without emails in our system.</p>
<p>The alternative scenario, where we don’t catch these issues immediately, is that we now have an <em>impossible state</em> in our system that we’re not yet aware of. We’ll have all kinds of flows built with the assumption that an email will always be present which will break when they run.</p>
<p>But when will that happen? If we’re lucky, it’ll happen soon. Otherwise, it can happen months down the line, and we’ll be left wondering how that happened. Was there a fault in the sign-up flow at the time the user was created? Is the fault still present or has it been fixed in the meantime?
How do we remediate this? Can we delete the user, force them to fill an email, or will the system need to handle users without emails from now on?</p>
<h2 id="application-code">Application code</h2>
<p>Here’s a very simplified implementation of an <code>attemptDelivery</code> method, that moves a package status from <code>preparing</code> to <code>in_transit</code>.</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">type</span><span style="color:#B392F0"> PackageStatus</span><span style="color:#F97583"> =</span><span style="color:#9ECBFF"> "preparing"</span><span style="color:#F97583"> |</span><span style="color:#9ECBFF"> "in_transit"</span><span style="color:#F97583"> |</span><span style="color:#9ECBFF"> "delivered"</span><span style="color:#E1E4E8">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">interface</span><span style="color:#B392F0"> Package</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#FFAB70">  status</span><span style="color:#F97583">:</span><span style="color:#B392F0"> PackageStatus</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#B392F0">  update</span><span style="color:#E1E4E8">(</span><span style="color:#FFAB70">fields</span><span style="color:#F97583">:</span><span style="color:#E1E4E8"> { </span><span style="color:#FFAB70">status</span><span style="color:#F97583">:</span><span style="color:#B392F0"> PackageStatus</span><span style="color:#E1E4E8"> })</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> void</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">function</span><span style="color:#B392F0"> attemptDelivery</span><span style="color:#E1E4E8">(</span><span style="color:#FFAB70">pkg</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Package</span><span style="color:#E1E4E8">)</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> void</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583">  switch</span><span style="color:#E1E4E8"> (package.status) {</span></span>
<span class="line"><span style="color:#F97583">    case</span><span style="color:#9ECBFF"> "preparing"</span><span style="color:#E1E4E8">:</span></span>
<span class="line"><span style="color:#E1E4E8">      package.</span><span style="color:#B392F0">update</span><span style="color:#E1E4E8">({ status: </span><span style="color:#9ECBFF">"in_transit"</span><span style="color:#E1E4E8"> });</span></span>
<span class="line"><span style="color:#F97583">      break</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#F97583">    case</span><span style="color:#9ECBFF"> "in_transit"</span><span style="color:#E1E4E8">:</span></span>
<span class="line"><span style="color:#6A737D">      // No-op</span></span>
<span class="line"><span style="color:#F97583">      break</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#F97583">    case</span><span style="color:#9ECBFF"> "delivered"</span><span style="color:#E1E4E8">:</span></span>
<span class="line"><span style="color:#F97583">      throw</span><span style="color:#F97583"> new</span><span style="color:#B392F0"> Error</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"Cannot attempt delivery of already delivered package"</span><span style="color:#E1E4E8">);</span></span>
<span class="line"><span style="color:#E1E4E8">  }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>This implementation does a good job of declaring its assumptions: instead of assuming the package’s <code>status</code> will be <code>preparing</code> it exhausts all the possible <code>status</code> values and deals with them accordingly.
It even documents an <em>impossible state</em>: the <code>attemptDelivery</code> action should never be called for packages that already have been delivered. We could argue the same should apply to <code>in_transit</code> but making the operation <a href="https://en.wikipedia.org/wiki/Idempotence">idempotent</a> seems reasonable.
By raising an error, this state is getting flagged as an issue which can be investigated and fixed.</p>
<p>While this approach handles all the possible statuses as of now, what would happen if a <code>waiting_payment</code> status was to be added in the future?
An <em>impossible state</em> is now permitted to go through. The operation that attempts a delivery will <strong>not</strong> fail for a package that was not yet paid, which is problematic.
A good way to defend against such a scenario is to always exhaust the switch statement options and use the <code>default</code> branch to raise awareness of <em>impossible</em> states:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">function</span><span style="color:#B392F0"> attemptDelivery</span><span style="color:#E1E4E8">(</span><span style="color:#FFAB70">package</span><span style="color:#F97583">:</span><span style="color:#B392F0"> Package</span><span style="color:#E1E4E8">)</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> void</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583">  switch</span><span style="color:#E1E4E8"> (package.status) {</span></span>
<span class="line"><span style="color:#F97583">    case</span><span style="color:#9ECBFF"> "preparing"</span><span style="color:#E1E4E8">:</span></span>
<span class="line"><span style="color:#E1E4E8">      package.</span><span style="color:#B392F0">update</span><span style="color:#E1E4E8">({ status: </span><span style="color:#9ECBFF">"in_transit"</span><span style="color:#E1E4E8"> });</span></span>
<span class="line"><span style="color:#F97583">      break</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#F97583">    case</span><span style="color:#9ECBFF"> "in_transit"</span><span style="color:#E1E4E8">:</span></span>
<span class="line"><span style="color:#6A737D">      // No-op</span></span>
<span class="line"><span style="color:#F97583">      break</span><span style="color:#E1E4E8">;</span></span>
<span class="line"><span style="color:#F97583">    case</span><span style="color:#9ECBFF"> "delivered"</span><span style="color:#E1E4E8">:</span></span>
<span class="line"><span style="color:#F97583">      throw</span><span style="color:#F97583"> new</span><span style="color:#B392F0"> Error</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"Cannot attempt delivery of already delivered package"</span><span style="color:#E1E4E8">);</span></span>
<span class="line"><span style="color:#F97583">    default</span><span style="color:#E1E4E8">:</span></span>
<span class="line"><span style="color:#F97583">      throw</span><span style="color:#F97583"> new</span><span style="color:#B392F0"> Error</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">`Impossible state: status ${</span><span style="color:#E1E4E8">package</span><span style="color:#9ECBFF">.</span><span style="color:#E1E4E8">status</span><span style="color:#9ECBFF">} not recognized`</span><span style="color:#E1E4E8">);</span></span>
<span class="line"><span style="color:#E1E4E8">  }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>The same can be said for if-else scenarios. Say we have a <code>package</code> that has either a <code>tracking_id</code> or an <code>external_tracking_url</code>.</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">function</span><span style="color:#B392F0"> trackingUrl</span><span style="color:#E1E4E8">()</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> string</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583">  if</span><span style="color:#E1E4E8"> (package.trackingId) {</span></span>
<span class="line"><span style="color:#F97583">    return</span><span style="color:#B392F0"> getTrackingUrl</span><span style="color:#E1E4E8">(package.trackingId);</span></span>
<span class="line"><span style="color:#E1E4E8">  } </span><span style="color:#F97583">else</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583">    return</span><span style="color:#B392F0"> externalTrackingUrl</span><span style="color:#E1E4E8">();</span></span>
<span class="line"><span style="color:#E1E4E8">  }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>What happens when both <code>tracking_id</code> and <code>external_tracking_url</code> are not present? The assumption is that one will exist, but that might not always be true.
Exhausting all the options via <code>else if</code> is the more defensive approach protecting against <em>impossible</em> states.</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="typescript"><code><span class="line"><span style="color:#F97583">function</span><span style="color:#B392F0"> trackingUrl</span><span style="color:#E1E4E8">()</span><span style="color:#F97583">:</span><span style="color:#79B8FF"> string</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583">  if</span><span style="color:#E1E4E8"> (package.trackingId) {</span></span>
<span class="line"><span style="color:#F97583">    return</span><span style="color:#B392F0"> getTrackingUrl</span><span style="color:#E1E4E8">(package.trackingId);</span></span>
<span class="line"><span style="color:#E1E4E8">  } </span><span style="color:#F97583">else</span><span style="color:#F97583"> if</span><span style="color:#E1E4E8"> (</span><span style="color:#B392F0">externalTrackingUrl</span><span style="color:#E1E4E8">()) {</span></span>
<span class="line"><span style="color:#F97583">    return</span><span style="color:#B392F0"> externalTrackingUrl</span><span style="color:#E1E4E8">();</span></span>
<span class="line"><span style="color:#E1E4E8">  } </span><span style="color:#F97583">else</span><span style="color:#E1E4E8"> {</span></span>
<span class="line"><span style="color:#F97583">    throw</span><span style="color:#F97583"> new</span><span style="color:#B392F0"> Error</span><span style="color:#E1E4E8">(</span><span style="color:#9ECBFF">"Impossible state: both tracking_id and external_tracking_url are missing"</span><span style="color:#E1E4E8">);</span></span>
<span class="line"><span style="color:#E1E4E8">  }</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<h2 id="closing-thoughts">Closing thoughts</h2>
<p>These examples are some of the patterns I regularly use to prevent those impossible states as early as possible.<br>
Still, these are as basic as it gets. Complex software systems have lots of intertwined requirements, which makes it much more difficult to avoid impossible states.</p>
<p>I usually ask these questions when designing new flows:</p>
<ul>
<li>“What invalid states could this flow be allowing?”</li>
<li>“Which assumptions do I currently have that aren’t properly enforced?”</li>
</ul>
<p>I find them good prompts to help me spot edge cases. Addressing those edge cases makes the software I build far more resilient.</p>
<p>Bugs still happen all the time. I just find them much sooner. And it always makes me smile when I see an alert with a message starting with “Impossible state”, because it means I just prevented a bunch of trouble down the line.</p>]]></content:encoded>
    </item>
    <item>
      <title>Notes from the book Corporate Rebels</title>
      <link>https://diogomr.com/posts/corporate-rebels/</link>
      <guid>https://diogomr.com/posts/corporate-rebels/</guid>
      <pubDate>Wed, 27 Sep 2023 00:00:00 GMT</pubDate>
      
      <content:encoded><![CDATA[<h2 id="chapter-1-from-profit-to-purpose--values">Chapter 1: From Profit To Purpose &#x26; Values</h2>
<h3 id="pioneering-practices">Pioneering Practices</h3>
<ol>
<li>Have a bold purpose</li>
<li>Get the message to everyone</li>
<li>Hire for culture, train for skills</li>
<li>Measure impact, track progress, share it widely</li>
<li>Put your money where your mouth is</li>
</ol>
<h3 id="notes">Notes</h3>
<p>Chapter mostly focused on Patagonia and how they put purpose above profits and how that drives a lot of their business decisions (sustainable materials, donating to non-profits, etc.).
Argues that employees are more engaged/driven when working for companies with clear purposes instead of just maximizing profits.</p>
<h2 id="chapter-2-from-hierarchical-pyramid-to-network-of-teams">Chapter 2: From Hierarchical Pyramid to Network of Teams</h2>
<h3 id="pioneering-practices-1">Pioneering Practices</h3>
<ol>
<li>Inverted pyramid</li>
<li>Autonomous teams in pyramid</li>
<li>Flat organization with autonomous teams</li>
<li>Network of teams</li>
<li>Ecosystem of mini companies</li>
</ol>
<h3 id="notes-1">Notes</h3>
<ul>
<li>Haier (chinese home appliances/electronics company) organizes their 70,000 employess into 4,000 mini-companies</li>
<li>Handelsbanken (Netherlands bank) gives complete autonomy to local branches: as far as allowing decisions over which products are offered, how they’re marketed or priced</li>
</ul>
<h2 id="chapter-3-from-directive-to-supportive-leadership">Chapter 3: From Directive to Supportive Leadership</h2>
<h3 id="pioneering-practices-2">Pioneering Practices</h3>
<ol>
<li>Beware of HiPPos (highest paid person opinions)</li>
<li>Destroy the ivory tower</li>
<li>Evaluate your manager</li>
<li>Split managers</li>
<li>Choose your leader</li>
</ol>
<h3 id="notes-2">Notes</h3>
<ul>
<li>“Park your hierarchy at the door” when going into a meeting</li>
<li>Drop status symbols (corner office, reserved parking space)</li>
</ul>
<h2 id="chapter-4-from-plan--predict-to-experiment--adapt">Chapter 4: From Plan &#x26; Predict to Experiment &#x26; Adapt</h2>
<h3 id="pioneering-practices-3">Pioneering Practices</h3>
<ol>
<li>Ruthlessly experiment</li>
<li>Kill the budget cycle</li>
<li>Create a “safe-to-try” environment</li>
<li>Crowdsource experiments</li>
<li>Rebel Time (10% hacking time at Spotify, 20% time at Google)</li>
</ol>
<h3 id="notes-3">Notes</h3>
<ul>
<li>“I let go of the naive belief that the world in which we work can be predicted or that every detail can be planned” - Koldo Saratxaga, K2K</li>
<li>Some Spotify squads have Fail Walls where they post their failures and learnings.</li>
<li>“We would rather spend out time and energy on adjusting, and quickly recovering, than on futile attempts to predict the future.” - Katarina Berg, Spotify</li>
</ul>
<h2 id="chapter-5-from-rules--control-to-freedom--trust">Chapter 5: From Rules &#x26; Control to Freedom &#x26; Trust</h2>
<h3 id="pioneering-practices-4">Pioneering Practices</h3>
<ol>
<li>Design your own workplace</li>
<li>Results-based working</li>
<li>Remove control mechanisms</li>
<li>Peer Review</li>
<li>Self-Setting Salaries</li>
</ol>
<h3 id="notes-4">Notes</h3>
<ul>
<li>Many agree that around 3% of the workforce is likely to take advantage of the system. Rules implemented to keep the 3% in line stifle the productivity, autonomy and joy of the other 97%.</li>
<li>One of the main reasons of burnout is the lack of autonomy and control over your own work.</li>
</ul>
<h2 id="chapter-6-from-centralised-to-distributed-authority">Chapter 6: From Centralised to Distributed Authority</h2>
<h3 id="pioneering-practices-5">Pioneering Practices</h3>
<ol>
<li>Map decision-making</li>
<li>Change the language</li>
<li>Push authority down</li>
<li>Pre-approval</li>
<li>Advice process</li>
</ol>
<h3 id="notes-5">Notes</h3>
<ul>
<li>Pre-approval: Leader/manager approves something in advance, before employee made actual decision or found solution. Employee has full autonomy as long as decision fits within pre-defined boundaries: time, cost, requirements.</li>
<li>Advice process:
<ul>
<li>Someone tries to solve a problem or make a decision</li>
<li>They seek advice from people with expertise or experience</li>
<li>Advice can be accepted or rejected. You have the final say.</li>
<li>All involved are informed of the decision taken</li>
</ul>
</li>
<li>Consensus is overrated. It’ll slow you down and the solution will end up being a compromise that no one is happy with.</li>
</ul>
<h2 id="chapter-7-from-secrecy-to-radical-transparency">Chapter 7: From Secrecy to Radical Transparency</h2>
<h3 id="pioneering-practices-6">Pioneering Practices</h3>
<ol>
<li>Open communication</li>
<li>Openness as the default</li>
<li>Transparent performance and goals</li>
<li>Open-book management</li>
<li>Salary Transparency</li>
</ol>
<h3 id="notes-6">Notes</h3>
<ul>
<li>All information is public (internally) unless there’s a very good reason not to</li>
</ul>
<h2 id="chapter-8-from-job-descriptions-to-talent--mastery">Chapter 8: From Job Descriptions to Talent &#x26; Mastery</h2>
<h3 id="pioneering-practices-7">Pioneering Practices</h3>
<ol>
<li>Identify talents</li>
<li>Job crafting by combining roles</li>
<li>Unlimited training</li>
<li>Self-selected mentors</li>
<li>Internal project marketplace</li>
</ol>]]></content:encoded>
    </item>
    <item>
      <title>My (old) work computer setup [Linux]</title>
      <link>https://diogomr.com/posts/setup/</link>
      <guid>https://diogomr.com/posts/setup/</guid>
      <pubDate>Wed, 26 Jul 2023 00:00:00 GMT</pubDate>
      
      <content:encoded><![CDATA[<h2 id="os">OS</h2>
<p>Whichever latest LTS version of <a href="https://ubuntu.com/">Ubuntu</a>.</p>
<h2 id="terminal">Terminal</h2>
<p>I use <a href="https://gnome-terminator.org/">Terminator</a> as my terminal.</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#B392F0">sudo</span><span style="color:#9ECBFF"> apt</span><span style="color:#9ECBFF"> install</span><span style="color:#9ECBFF"> terminator</span></span></code></pre>
<h2 id="shell">Shell</h2>
<p>zsh is my preferred shell. <a href="https://github.com/ohmyzsh/ohmyzsh">ohmyzsh</a> manages its configuration.</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#B392F0">sudo</span><span style="color:#9ECBFF"> apt</span><span style="color:#9ECBFF"> install</span><span style="color:#9ECBFF"> zsh</span></span></code></pre>
<h3 id="history">History</h3>
<p>I use <a href="https://github.com/jimeh/zsh-peco-history">zsh-peco-history</a> to improve shell history search using <code>Ctrl+R</code>, with the following configuration:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#6A737D"># .zshrc</span></span>
<span class="line"></span>
<span class="line"><span style="color:#E1E4E8">HISTSIZE</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">10000000</span></span>
<span class="line"><span style="color:#E1E4E8">SAVEHIST</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">10000000</span></span>
<span class="line"></span>
<span class="line"><span style="color:#B392F0">setopt</span><span style="color:#9ECBFF"> EXTENDED_HISTORY</span><span style="color:#6A737D">          # Write the history file in the ":start:elapsed;command" format.</span></span>
<span class="line"><span style="color:#B392F0">setopt</span><span style="color:#9ECBFF"> INC_APPEND_HISTORY</span><span style="color:#6A737D">        # Write to the history file immediately, not when the shell exits.</span></span>
<span class="line"><span style="color:#B392F0">setopt</span><span style="color:#9ECBFF"> SHARE_HISTORY</span><span style="color:#6A737D">             # Share history between all sessions.</span></span>
<span class="line"><span style="color:#B392F0">setopt</span><span style="color:#9ECBFF"> HIST_EXPIRE_DUPS_FIRST</span><span style="color:#6A737D">    # Expire duplicate entries first when trimming history.</span></span>
<span class="line"><span style="color:#B392F0">setopt</span><span style="color:#9ECBFF"> HIST_IGNORE_DUPS</span><span style="color:#6A737D">          # Don't record an entry that was just recorded again.</span></span>
<span class="line"><span style="color:#B392F0">setopt</span><span style="color:#9ECBFF"> HIST_IGNORE_ALL_DUPS</span><span style="color:#6A737D">      # Delete old recorded entry if new entry is a duplicate.</span></span>
<span class="line"><span style="color:#B392F0">setopt</span><span style="color:#9ECBFF"> HIST_FIND_NO_DUPS</span><span style="color:#6A737D">         # Do not display a line previously found.</span></span>
<span class="line"><span style="color:#B392F0">setopt</span><span style="color:#9ECBFF"> HIST_IGNORE_SPACE</span><span style="color:#6A737D">         # Don't record an entry starting with a space.</span></span>
<span class="line"><span style="color:#B392F0">setopt</span><span style="color:#9ECBFF"> HIST_SAVE_NO_DUPS</span><span style="color:#6A737D">         # Don't write duplicate entries in the history file.</span></span>
<span class="line"><span style="color:#B392F0">setopt</span><span style="color:#9ECBFF"> HIST_REDUCE_BLANKS</span><span style="color:#6A737D">        # Remove superfluous blanks before recording entry.</span></span></code></pre>
<h3 id="aliases">Aliases</h3>
<p>I make heavy use of aliases on my <code>~/.zshrc</code> file to help me navigate to common directories or open projects on the appropriate IDE. Some examples:</p>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="shell"><code><span class="line"><span style="color:#F97583">export</span><span style="color:#E1E4E8"> EDITOR</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">'vim'</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># .zshrc</span></span>
<span class="line"><span style="color:#F97583">alias</span><span style="color:#E1E4E8"> ezshrc</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">$EDITOR</span><span style="color:#E1E4E8"> $HOME</span><span style="color:#9ECBFF">/.zshrc"</span></span>
<span class="line"><span style="color:#F97583">alias</span><span style="color:#E1E4E8"> szshrc</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"source </span><span style="color:#E1E4E8">$HOME</span><span style="color:#9ECBFF">/.zshrc"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># projects</span></span>
<span class="line"><span style="color:#F97583">export</span><span style="color:#E1E4E8"> PROJECTS_HOME</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">$HOME</span><span style="color:#9ECBFF">/projects"</span></span>
<span class="line"><span style="color:#F97583">export</span><span style="color:#E1E4E8"> AOC</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"</span><span style="color:#E1E4E8">$PROJECTS_HOME</span><span style="color:#9ECBFF">/aoc-2022"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F97583">alias</span><span style="color:#E1E4E8"> aoc</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"cd </span><span style="color:#E1E4E8">$AOC</span><span style="color:#9ECBFF">"</span></span>
<span class="line"><span style="color:#F97583">alias</span><span style="color:#E1E4E8"> iaoc</span><span style="color:#F97583">=</span><span style="color:#9ECBFF">"launch_rubymine </span><span style="color:#E1E4E8">$AOC</span><span style="color:#9ECBFF">"</span></span>
<span class="line"></span>
<span class="line"><span style="color:#6A737D"># functions</span></span>
<span class="line"><span style="color:#B392F0">launch_rubymine</span><span style="color:#E1E4E8">() {</span></span>
<span class="line"><span style="color:#B392F0">        nohup</span><span style="color:#E1E4E8"> $HOME</span><span style="color:#9ECBFF">/bin/rubymine</span><span style="color:#FFAB70"> $1</span><span style="color:#F97583"> ></span><span style="color:#9ECBFF"> /dev/null</span><span style="color:#F97583"> 2>&#x26;1</span><span style="color:#E1E4E8"> &#x26;</span></span>
<span class="line"><span style="color:#E1E4E8">}</span></span></code></pre>
<p>I can type <code>ezshrc</code> from anywhere in my terminal to edit my zsh config file and then <code>szshrc</code> to source it.</p>
<p>I can also type <code>aoc</code> to access the root of my <a href="https://adventofcode.com/">Advent of Code</a> project or <code>iaoc</code> to open the project in <a href="https://www.jetbrains.com/ruby/">RubyMine</a>.</p>
<h2 id="window-manager">Window Manager</h2>
<p><a href="https://i3wm.org/">i3wm</a> is my preferred tiling window manager.
<a href="https://regolith-desktop.com/">Regolith</a> provides a bunch of sane defaults for i3 on Ubuntu, so that’s I use in order to simplify my setup.</p>
<p>Regolith <a href="https://regolith-desktop.com/docs/using-regolith/install/">install instructions</a>.</p>
<h3 id="regolith-config">Regolith Config</h3>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="text"><code><span class="line"><span># ~/.config/regolith3/i3/config.d/40_workspace-config</span></span>
<span class="line"><span></span></span>
<span class="line"><span># Custom Bindings</span></span>
<span class="line"><span>bindsym $mod+m move workspace to output left</span></span>
<span class="line"><span>bindsym Ctrl+Shift+Print exec --no-startup-id maim --select | xclip -selection clipboard -t image/png</span></span>
<span class="line"><span></span></span>
<span class="line"><span># Workspace assignment</span></span>
<span class="line"><span></span></span>
<span class="line"><span>## Notion Chromium App</span></span>
<span class="line"><span>assign [instance="crx_jpebhcbfinglhpgbfclggnfppgbkklnh"] $ws8</span></span>
<span class="line"><span></span></span>
<span class="line"><span>assign [class="thunderbird"] $ws1</span></span>
<span class="line"><span>assign [class="Slack"] $ws3</span></span>
<span class="line"><span>assign [class="Google-chrome"] $ws4</span></span>
<span class="line"><span>assign [class="jetbrains-rubymine"] $ws5</span></span>
<span class="line"><span>assign [class="jetbrains-idea"] $ws6</span></span>
<span class="line"><span>assign [class="jetbrains-datagrip"] $ws7</span></span>
<span class="line"><span></span></span>
<span class="line"><span># workaround for spotify</span></span>
<span class="line"><span>for_window [class="Spotify"] move container to workspace $ws9</span></span></code></pre>
<pre class="astro-code github-dark" style="background-color:#24292e;color:#e1e4e8; overflow-x: auto;" tabindex="0" data-language="text"><code><span class="line"><span># ~/.config/regolith3/Xresources</span></span>
<span class="line"><span></span></span>
<span class="line"><span>gtk.font_name: Ubuntu</span></span>
<span class="line"><span>gtk.monospace_font_name: Ubuntu Mono</span></span>
<span class="line"><span>i3-wm.font: Ubuntu Mono</span></span>
<span class="line"><span></span></span>
<span class="line"><span>wm.workspace.01.name: 1: 1: Email</span></span>
<span class="line"><span>wm.workspace.02.name: 2: 2: Terminal</span></span>
<span class="line"><span>wm.workspace.03.name: 3: 3: Slack</span></span>
<span class="line"><span>wm.workspace.04.name: 4: 4: Chrome</span></span>
<span class="line"><span>wm.workspace.05.name: 5: 5: IntelliJ</span></span>
<span class="line"><span>wm.workspace.06.name: 6: 6: RubyMine</span></span>
<span class="line"><span>wm.workspace.07.name: 7: 7: Datagrip</span></span>
<span class="line"><span>wm.workspace.08.name: 8: 8: Notion</span></span>
<span class="line"><span>wm.workspace.09.name: 9: 9: Spotify</span></span>
<span class="line"><span>wm.workspace.10.name: 10: 10:</span></span>
<span class="line"><span>wm.workspace.11.name: 11: 11:</span></span>
<span class="line"><span>wm.workspace.12.name: 12: 12:</span></span>
<span class="line"><span>wm.workspace.13.name: 13: 13:</span></span>
<span class="line"><span>wm.workspace.14.name: 14: 14:</span></span>
<span class="line"><span>wm.workspace.15.name: 15: 15:</span></span>
<span class="line"><span>wm.workspace.16.name: 16: 16:</span></span>
<span class="line"><span>wm.workspace.17.name: 17: 17:</span></span>
<span class="line"><span>wm.workspace.18.name: 18: 18:</span></span>
<span class="line"><span>wm.workspace.19.name: 19: 19:</span></span></code></pre>
<h2 id="asdf">asdf</h2>
<p>I use <a href="https://asdf-vm.com/">asdf</a> to manage different versions of tools (Ruby, Python, Node, etc) on my machine.</p>
<h2 id="ide">IDE</h2>
<p>I use Jetbrain’s <a href="https://www.jetbrains.com/toolbox-app/">toolbox app</a> to manage all IDE’s I use.</p>]]></content:encoded>
    </item>
  </channel>
</rss>