Random Lines of Code
Ithai’s notes and thoughts about software engineering

Table of Contents

2026-08-22 - ’Oppenheimer’

While I’m still figuring out agentic software development, I do feel I’m getting better and better at it. My real x10 superpower now is becoming a full-scale venture that I will tell more about in a later post.

But this post is about how I use oppenheimer to task my agents with work - dramatically improving my bandwidth and control span.

An oppenheimer kanban board is a folder inside the repo I’m working on, one markdown file per task. A board.toml next to them records which list each task is in and in what order. A task can open into its own sub-board, recursively - that’s the hierarchical part. Claude reads and writes all of it.

Three commands set a board up in a project:

oppenheimer my_project install-skill
oppenheimer my_project install-templates
oppenheimer my_project install-commands

install-skill writes the protocol Claude follows. install-templates writes the task template. install-commands writes /next and /specify. My board has three lists: INBOX, NEXT and ARCHIVE.

Anything I come up with goes into INBOX tagged #spec-needed. I keep oppenheimer in a tmux popup for exactly this, making it super easy to capture things. Claude adds to the same list, running print --open first so it doesn’t file the same task twice.

/specify fills those in. Every task starts from a gherkin template - a Story section for why the task matters, and a Specification section of GIVEN / WHEN / THEN with the WHEN points as checkboxes. Writing GIVEN forces me to say what already exists before I say what should change. I catch a lot of bad tasks right there.

Prioritizing NEXT is mine. /next prints the first open, unclaimed task and Claude claims it. Implementation happens in a git worktree branched off master, always. Closing means capturing the leftovers as new tasks, close --id=<task_id>, and a short summary appended to the task file. Nothing is merged, rebased or pushed unless I ask.

Between that branch and my review sits a secret phase that I save for a later post. Then I code-review, test, approve and move the task to ARCHIVE.

Oppenheimer is on https://github.com/rlofc/oppenheimer and on crates.io. It’s inspired by taskell, for the way a board behaves inside a terminal, and kanban-md, for the idea that something other than me should be able to read and write it.

2026-07-18 - Make if statements clearer

Use:

let clearly_named_super_conditional =
    (one_conditional || other_conditional) && another_conditional;
if clearly_named_super_conditional {
  // your code goes here
}

Instead of:

// Comment explaining the super conditional
if (one_conditional || other_conditional) && another_conditional {
  // your code goes here
}

It costs nothing. Both forms compile to the same assembly under rustc -O, with the conditionals as opaque arguments so nothing folds early:

f:
        or      edi, esi
        test    edi, edx
        je      .LBB0_1
        jmp     qword ptr [rip + work@GOTPCREL]
.LBB0_1:
        ret

In the second example, the comment will at some point grow stale, whereas the super-conditional variable will likely be updated if the condition it holds gets updated.

2026-06-12 - Why and how I use (Doom) Emacs?

It was the mid-1990s and I was coding using Borland C++. I recently completed a Unix course and was slightly confused when introduced to vi. The teacher was overly enthusasitic, so I thought, about this whole modal editing thing. I was super happy using the good old Borland to do my work in.

It took me ten years to get it and start using Vim, but more generically, to the Vim philosophy: lean, modal, and adaptable. This was before trillion dollars worth of inference systems started doing the coding. Efficiently editing text using a keyboard used to matter. It still does to me.

Why (Doom) Emacs then?

Conceptual integrity - Emacs has it as a platform, and all of its packages share it. If you learn the pattern of using and configuring the editor, it will apply to anything you need to do and adjust.

Performance - few editors match the performance I’m getting when it comes to cursor movement, fuzzy matching and LSP integration.

Flexibility - Any “modern” editor capability I needed, I was able to find an Emacs . Want agentic coding, check. Want easymotion on steroids, check. Want a variery of fuzzy matchers, check. Want git integration - one big check. Emacs is polymorphic like no other editor is.

Orgmode - Goes without saying.

How do I use Emacs?

The Evil Way - I use Doom Emacs and Evil to turn Emacs into a superpowered ViM.

Via Terminal - Even though Emacs can really shine using its Gui, I still prefer using it via the terminal using tmux. Using Tmux adds another layer of flexibility and possibilities, and allows me to easily and seamlessly run all my other terminal-based utilities.

Ergonomically - I have a combination of packages to support my ergonomic editing requirements. I use Dimmer to dim unfocused panes. This improves my focus and attention. I use Avy to quickly jump to code positions. It makes my cursor movements faster. I use Whisper to allow my to speak to my editor instead of type when it makes more sense.

My configuration files are on https://github.com/rlofc/dotfiles/tree/master/config/doom. Doom makes it super easy to create a reproducable environment by having only three config files.

My fuzzy macher if hotfuzz. This gives me an fzf like experience for anything I look for:

(setq completion-styles '(hotfuzz))

My cursor jumping is done using avy - and specifically, ’avy-goto-char-timer’ - letting me selectivly filter words and have less jumping shortcuts to choose from:

(use-package avy
  :config
  (setq avy-timeout-seconds 0.4)
)
(map! :after avy :vn "SPC j" #'avy-goto-char-timer)

Two more Evil tweaks earn their keep every day. The first makes _ a word character, so w, * and diw treat foo_bar as one thing instead of three. The second stops a visual-mode paste from replacing my register with whatever I just pasted over:

(add-hook 'prog-mode-hook #'(lambda () (modify-syntax-entry ?_ "w")))
(with-eval-after-load 'evil
    (defalias #'forward-evil-word #'forward-evil-symbol)
    (setq-default evil-symbol-word-search t))
(setq evil-kill-on-visual-paste nil)

Neither is worth much alone. Together they remove a few dozen small annoyances a day.

Running in the terminal isn’t free. You lose the fringe, so Emacs falls back to drawing a $ continuation marker and a plain | window divider. I get most of it back by hand - a real box-drawing divider, diff markers in the margin instead of the fringe and no $ at all:

(set-display-table-slot standard-display-table 'vertical-border ?│)
(set-display-table-slot standard-display-table 0 ?\ )
(use-package diff-hl
  :custom ((diff-hl-draw-borders nil)
           (diff-hl-margin-symbols-alist
            '((insert . "▐") (delete . "▐") (change . "▐")
              (unknown . "▐") (ignored . "▐")))))

Add corfu-terminal and Doom’s tty module and the terminal version gets close enough to the Gui that I stopped noticing the difference.

Dimmer needed more tuning than anything else here. Out of the box it dims popups too - corfu, which-key, posframe, lsp-ui-doc - and that just looks broken. Most of my dimmer block is a list of predicates telling it what to leave alone.

Whisper runs locally. The base model is good enough for prose and I hand it half the cores so the rest of the machine stays usable:

(use-package whisper
  :config
  (setq whisper-install-directory "/files/bin/whisper/"
        whisper-model "base"
        whisper-language "en"
        whisper-use-threads (/ (num-processors) 2)))
(global-set-key (kbd "<f3>") 'whisper-run)

F3 starts and stops it, in normal mode and inside vterm. I use it for commit messages and for prose. I don’t dictate code.

Now about those inference systems. I use gptel, and the part I reach for isn’t the chat window - it’s a set of rewrite verbs sitting on the SPC k leader key:

(defun gptel-rewrite-refactor ()
  (interactive)
  (setq gptel--rewrite-message
        "Refactor the following code, prefering readability and then performance:")
  (gptel--suffix-rewrite))

(map! :leader
      :prefix ("k" . "AI agents")
      :desc "refactor" "r" #'gptel-rewrite-refactor
      :desc "explain"  "e" #'gptel-rewrite-explain
      :desc "document" "d" #'gptel-rewrite-document
      :desc "custom"   "k" #'gptel-rewrite-with-instruction)

(setq gptel-rewrite-default-action 'merge)

There are eight of these - refactor, optimize, implement, explain, evolve, document, specify and one that just asks me what I want. I select a region, press SPC k r and the suggestion arrives as a merge conflict.

That last setting is the one that matters. Nothing is applied behind my back. I read both sides in smerge and resolve it like any other conflict, making code review easy.

This blog is also generated via Emacs.

Wanting to document my tools and workflows, I looked for static site generators I could use. Turns up Emacs with Orgmode works great. All I needed to do is use Orgmode HTML exporter, embed any images I use, and add a CSS file. Done.

Thirty years after Borland, I’m still editing text in a terminal. The teacher was right.

2026-05-11 - ’Capsules’

I will usually avoid installing too many things on my host OS. My host will have just the basic tools I need. Keeping things minimal helps with upgrades, security and recovery.

When I want to have a task specific workspace, I use Capsules to build a container and run things inside it.

’Capsules’ works like Distrobox but it is, by-default, more isolated. When you use distrobox, you normally get your home folder shared with the distrobox you enter. I dont like it.

Capsules will want you to bootstrap a volume for your container files.

It starts with a folder. I make a directory somewhere on my host, cd into it and run capsules init debian-dev. This copies my blueprint into a .capsules folder and builds the image. The folder I’m standing in is now the capsule’s volume. When I later run capsules create my-capsule, that same folder is mounted into the container as /files, and my home inside the container becomes /files/home/$USER. Both paths come from ~/.config/capsules/capsules.toml:

capsule_volume_dir = "/files"
capsule_home_dir = "home"

This is the part I care about. The capsule has a home directory, it just isn’t mine. I can browse it, back it up or delete the whole thing without touching my host home. When I’m done with a task, the workspace is a folder I can remove.

Total isolation would be annoying, though. I want the same Emacs and tmux setup in every capsule I work in. So in most cases I’ll mount a few host folders when creating:

capsules create my-capsule \
  --volume ~/.config:/files/home/$USER/.config \
  --volume ~/.fonts:/files/home/$USER/.fonts

This way I keep one set of dotfiles instead of one per container. If I tweak my Doom configuration inside a capsule, the change lands on my host, and every other capsule gets it too. Fonts are the same story. I’d rather not install them again per image.

It doesn’t happen often, but sometimes I get the run arguments wrong. I create a capsule, work in it for two weeks, then realize I need another folder mounted. Doing this by hand means losing everything I installed in the container. So capsules has recreate:

capsules recreate my-capsule --with-volumes \
  --volume /more/of/your/projects:/more/of/your/projects

It commits the running container to a new image, removes the old container and starts a fresh one from that commit using the new arguments. --with-volumes carries over the extra mounts the container already had, so I only name the new one. init.sh is not run again - whatever it did is already in the commit.

Podman 4.4 added Quadlets - systemd unit files that describe a container and let systemd manage it. Right now capsules keeps the run arguments inside the container it created, which is the reason recreate has to exist at all. With quadlets those arguments would live in a file I could edit and version, and adding a mount would be editing that file and restarting a unit. I think this is where capsules should go. I haven’t looked into it properly yet.

I’m likely the only user of capsules and that’s fine. It still lives on https://github.com/rlofc/capsules and on crates.io, because the point of the whole thing is getting a working environment back. A fresh host, cargo install capsules, my blueprints, and I’m where I was.