file 001english

omp: from zero to /vibe

date
playtime
00:13:00
location
ompai-agentstoolingterminal

Install, theme, providers, models and /vibe: a walkthrough of my omp setup on macOS, Linux and Windows.

First real post here, and it’s the tutorial I wish I’d had: going from zero to my omp (oh-my-pi) setup, configured and good-looking, step by step. Everything was checked against omp’s docs and CLI (18.12.5 here; the Linux path was tested in a clean Ubuntu 24.04 container, which installed 18.12.6).

StepmacOSLinux (Debian/Ubuntu)Windows
Package managerHomebrewaptwinget
TerminalWarp (mine)Warp or any terminal with a Nerd FontWindows Terminal (or Warp)
Nerd Fontbrew install --caskzip into ~/.local/share/fonts + fc-cachezip → Install
Shell themeOh My Zsh + p10kOh My Zsh + p10kinside WSL2 only
ompbrew install can1357/tap/ompcurl -fsSL https://omp.sh/install | shirm https://omp.sh/install.ps1 | iex

What a harness is

If you’ve only used LLMs through a chat window, this is the missing piece. A model on its own is text in, text out. It doesn’t read your repository, doesn’t run commands, doesn’t remember last week. The harness is the program around it that turns it into an agent:

  • The agent loop. The model asks for a tool call, the harness runs it and feeds the result back, and this repeats until the task is done.
  • The tools. Reading and editing files, a shell, search, a browser, MCP servers.
  • What goes into the context. The system prompt, project rules (AGENTS.md), skills, memory, file contents, and compaction when the context fills up.
  • Permissions and approvals. What runs on its own and what needs your “ok”.
  • Sessions and history. Picking up where you left off.
  • Sub-agents and orchestration. Splitting work across several agents (in omp, that includes /vibe).
  • Model routing. A strong model to think, a cheap one for the grunt work.

Think of a console: the model is the CPU; the harness is everything else, the controller, the memory card and the disc drive.

In practice, the agent acts instead of suggesting: it runs the command and reads the output, no copy-pasting on your end. It keeps state across a long task. And rules, skills and project context make the agent follow your method every time, without you pasting the same instructions into every conversation. Parallel work becomes sub-agents or workers. In my experience, the same model feels like a different one depending on the harness’s tools and how it handles context.

What it does not do: it doesn’t replace judgement, and it doesn’t make a bad model good.

AspectChatHarness
Runs commandsnoyes, with configurable approval
Edits filesyou copy and pastestraight to disk
Project rulesyou paste them every timeloaded automatically
Parallel agentsnosub-agents and workers
Model choicewhatever the app offersany provider, one per job

Examples of the category: Claude Code, Codex CLI, opencode, Aider, Cursor’s agent, and omp.

Why omp

For me, omp wins by putting all of that in one place: dozens of providers (via environment variables, OAuth login or any compatible gateway), local models detected automatically, model roles with a thinking level per role, sub-agents you can customise in markdown, /vibe, skills, memory across sessions, MCP, approval modes, secret obfuscation, isolated profiles and resumable sessions. And it looks good in the terminal, which is half of this post.

1. Prerequisites and terminal

omp needs no runtime: Homebrew and the official scripts ship a prebuilt binary. Bun (≥ 1.3.14, per the README) only matters if you’d rather install with bun install -g @oh-my-pi/pi-coding-agent.

The terminal has to accept a custom font. On macOS I use Warp, which also has Linux and Windows builds. On Windows, the sensible default is Windows Terminal.

macOS (Homebrew, official command from brew.sh, and Warp)

BASH
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install --cask warp

Linux (the basics the next steps use; terminal: Warp from its site, or whatever you already use)

BASH
sudo apt update && sudo apt install -y curl git unzip fontconfig

Windows (PowerShell; Windows Terminal is also on the Microsoft Store)

POWERSHELL
winget install --id Microsoft.WindowsTerminal -e

2. Nerd Font

A Nerd Font is a programming font patched with thousands of extra icons: the git logo, folders, powerline arrows, spinners. omp uses those icons when you turn on symbolPreset: nerd, its status line has a nerd preset, and the Powerlevel10k prompt in step 3 relies on them too. Without the font, the icons turn into little boxes.

I use JetBrains Mono Nerd Font, in the NL variant (no ligatures), whose family name is JetBrainsMonoNL Nerd Font.

macOS

BASH
brew install --cask font-jetbrains-mono-nerd-font

Linux (the nerd-fonts project’s manual method; fc-list confirms it)

BASH
mkdir -p ~/.local/share/fonts
curl -fsSLo /tmp/JetBrainsMono.zip \
  https://github.com/ryanoasis/nerd-fonts/releases/latest/download/JetBrainsMono.zip
unzip -oq /tmp/JetBrainsMono.zip -d ~/.local/share/fonts/JetBrainsMono
fc-cache -fv
fc-list | grep -i "JetBrainsMonoNL Nerd Font"

On Arch, the official package is sudo pacman -S ttf-jetbrains-mono-nerd.

Windows (download JetBrainsMono.zip from github.com/ryanoasis/nerd-fonts/releases/latest, extract it, select the .ttf files and right-click → Install). Then, in Windows Terminal’s settings.json (Settings with Shift held), set the font for every profile under profiles.defaults:

JSON
{
  "profiles": {
    "defaults": {
      "font": { "face": "JetBrainsMonoNL Nerd Font", "size": 13 }
    }
  }
}

If you use WSL2, install the font on Windows: Windows Terminal draws the icons, not Linux.

Pick it in the terminal. In Warp (any OS), choose JetBrainsMonoNL Nerd Font in the appearance settings. On macOS my ~/.warp/settings.toml looks like this:

TOML
[appearance.text]
font_family = "JetBrainsMonoNL Nerd Font"
font_size = 13.0

[appearance.themes]
theme = "dark"

[appearance.cursor]
cursor_display_type = "bar"

In any other terminal (iTerm2, Ghostty, Kitty, GNOME Terminal…) the principle is the same: the terminal font has to be a Nerd Font.

3. The shell’s look (optional)

I use Oh My Zsh with the Powerlevel10k theme. omp doesn’t need it, but it’s part of how my terminal looks. Zsh, Oh My Zsh and p10k are Unix-shell things: on Windows they work inside WSL2, not in PowerShell.

macOS: zsh is already the default shell; nothing to install.

Linux (and Windows inside WSL2: install it with wsl --install in an administrator PowerShell, reboot, open Ubuntu and continue from here)

BASH
sudo apt install -y zsh git curl
chsh -s "$(which zsh)"   # takes effect on your next login

macOS, Linux and WSL2: from here on it’s the same. Oh My Zsh, then Powerlevel10k as its theme, with the commands from the official READMEs:

BASH
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git \
  "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k"

In ~/.zshrc, switch the theme (I keep only the git plugin):

BASH
ZSH_THEME="powerlevel10k/powerlevel10k"
plugins=(git)

Open a new terminal; the wizard starts on its own. If it doesn’t, run p10k configure. My picks were nerdfont-v3 + powerline, small icons, rainbow style, 24h clock, angled separators, two lines, transient prompt and instant prompt. The wizard writes everything to ~/.p10k.zsh. In Warp, the p10k prompt only shows up with honor_ps1 = true under [terminal.input].

4. Installing omp

macOS (how I install it)

BASH
brew install can1357/tap/omp

Linux (official script; installs to ~/.local/bin, which has to be on your PATH)

BASH
curl -fsSL https://omp.sh/install | sh
export PATH="$HOME/.local/bin:$PATH"   # put this in ~/.zshrc or ~/.bashrc

Windows (PowerShell, official script; inside WSL2, use the Linux command)

POWERSHELL
irm https://omp.sh/install.ps1 | iex

On any OS, check with omp --version. omp setup runs the onboarding wizard; after that, just run omp inside a project. Everything user-level lives in ~/.omp/agent/ (omp config path shows where): config.yml, models.yml, mcp.json, .env, skills/, agents/.

A provider. OAuth (subscription) is the same everywhere: omp login and pick from the list (inside a session, /login). For an API key, keep it out of your shell history:

macOS and Linux (works in zsh and bash)

BASH
printf 'API key: ' && read -rs K && printf '\nANTHROPIC_API_KEY=%s\n' "$K" >> ~/.omp/agent/.env && unset K
chmod 600 ~/.omp/agent/.env
omp models   # what's available now, grouped by provider

Windows (PowerShell 7.1+; stores a user environment variable that applies to new terminals)

POWERSHELL
$k = Read-Host 'API key' -MaskInput
[Environment]::SetEnvironmentVariable('ANTHROPIC_API_KEY', $k, 'User')
Remove-Variable k

omp reads OPENAI_API_KEY, GEMINI_API_KEY, OPENROUTER_API_KEY, DEEPSEEK_API_KEY and many more; omp --help lists the main ones.

5. Making omp look good

From here to the end, everything is the same on all three OSes: it’s omp config, not OS config. The look lives in ~/.omp/agent/config.yml. These are the values I changed from the defaults:

YAML
# ~/.omp/agent/config.yml — appearance
theme:
  dark: dark-dracula # built-in theme (default: titanium)
symbolPreset: nerd # Nerd Font icons (default: unicode)
statusLine:
  preset: nerd # icons in the status line (default: default)
  contextLine: annotated # annotated context usage
composer:
  shape: box # editor in a box (default: band)
display:
  showTokenUsage: true # tokens per reply
  showTurnTime: true # total time per turn, tool calls included

Or from the CLI, one key at a time (the name has to match the schema exactly):

BASH
omp config set theme.dark dark-dracula
omp config set symbolPreset nerd
omp config set statusLine.preset nerd
omp config set statusLine.contextLine annotated
omp config set composer.shape box
omp config set display.showTokenUsage true
omp config set display.showTurnTime true

All of this is also in /settings inside a session, if you prefer a menu. The other statusLine.preset options are default, minimal, compact, full, ascii and custom. The separator lives in statusLine.separator (powerline, powerline-thin, slash, pipe, block, none, ascii). Custom themes are JSON files in ~/.omp/agent/themes/.

Expected result: when you open omp, the editor sits in a box, the status line shows the model, folder and git state with icons, and the colours are Dracula’s. If you see little boxes, the font from step 2 isn’t selected.

6. Settings worth changing beyond looks

Mine, all of them off the defaults:

KeyValueWhat it does
defaultThinkingLevelmaxdefault reasoning depth (default: high)
temperature0.7sampling temperature (default: the provider’s)
memory.backendlocalextracts lessons from sessions and injects a summary into the next one (default: off)
autolearn.enabledtruelets the agent create and update managed skills
autolearn.autoContinuetrueruns a private lesson-capture turn when it stops; costs tokens
model.loopGuard.enabledfalseturns off automatic loop detection on reasoning and prose
model.toolCallLoopGuard.threshold20identical calls in a row before it steps in (default: 5)
bash.allowCompoundCommandstrueevaluates each command in an && chain separately
browser.headlessfalseshows the window when the agent uses the browser
computer.enabledtrueenables desktop control (screenshots, input)

Two I recommend to everyone, even though they’re not on my list:

  • secrets.enabled: true (default false): credential-looking env vars and known patterns (JWTs, Bearer headers, cloud keys) become placeholders in the text sent to the provider and are restored right before a tool runs. It doesn’t catch short values or the static prompt.
  • tools.approvalMode: the default is yolo (everything runs). If you’re starting out, write approves reads and writes and asks before executing. Sub-agents always run in yolo; the approval that counts is the parent’s.

7. Providers and models

omp works with roles, set under modelRoles in config.yml:

RoleUsed for
defaultthe main conversation
smollight tasks; /vibe fast workers
slowheavy reasoning
planplan mode (/plan)
tasksub-agents and /vibe good workers
visionimage analysis
commitomp commit
tiny / memorysession titles and the memory pipeline

A :level suffix sets the thinking level per role (off, minimal, low, medium, high, xhigh, max, auto): default: deepseek/deepseek-v4-flash:max. Unset smol and slow inherit default. To switch: /model opens the hub, Ctrl+P cycles through the role models, Shift+Tab cycles the thinking level, and omp --model opus does fuzzy matching.

Your own gateway (any OpenAI- or Anthropic-compatible endpoint) goes in ~/.omp/agent/models.yml:

YAML
providers:
  my-gateway:
    baseUrl: https://gateway.example/v1
    api: openai-completions # or anthropic-messages, openai-responses…
    apiKey: MY_GATEWAY_API_KEY # name of an env var; omp uses its value
    authHeader: true # Authorization: Bearer <key>
    models:
      - id: vendor/big-model # the id the endpoint expects
        name: Big Model
        reasoning: true
        thinking: { mode: effort, efforts: [low, high, max], defaultLevel: high }
        input: [text, image]
        contextWindow: 1048576
        maxTokens: 131072

apiKey means “env var name or literal”: if it matches a non-empty variable, that value is used. A YAML error makes omp skip the whole file, so always check with omp models my-gateway. And before declaring a provider, run omp models <name>: if it’s already built in, all you need is the credential.

What I use:

  • default role: deepseek/deepseek-v4-flash:max, through the built-in DeepSeek provider. Everything that inherits default (the fast workers, for example) runs on it.
  • Main session and /vibe director: Claude Opus (anthropic/claude-opus-5-5). omp lets you switch just the session’s model without touching the default role: Alt+P picks a temporary model for the session, and omp --model opus does the same from the command line.
  • A gateway in models.yml with DeepSeek V4.1 Flash, Nemotron 3.5 Super VL 120B and GLM-5.3 Flash, all with a 1M context, their effort ladders declared and costs filled in so omp can track spend.

8. /vibe

/vibe turns the session into a director. The director keeps only read, an optional todo and five control tools; the searching, editing and running is done by persistent workers in the background, each with its own transcript.

/vibe diagram: the director spawns fast and good workers, which work in the workspace; the director verifies by reading the files
The director reads and decides; the workers do.
TEXT
/vibe                       # enter the mode
/vibe migrate the parser    # enter and send the first directive
/vibe                       # exit (shuts the workers down)
TierAgentRoleUse for
fastsonic@smolmechanical execution, volume, drafts
goodtask@taskjudgement, design, reviewing fast output

The director uses vibe_spawn (creates a worker with a brief and a tier), vibe_send (steers or sends the next step), vibe_wait, vibe_kill and vibe_list. You just talk to it in plain language.

To pick the tier models, the docs suggest aliases:

YAML
task:
  agentModelOverrides:
    sonic: '@fast_worker'
    task: '@good_worker'
modelRoles:
  fast_worker: my-gateway/vendor/fast-model
  good_worker: my-gateway/vendor/big-model:high

That override applies to every sonic and task sub-agent, not just /vibe. I didn’t configure any of it: fast inherits default (DeepSeek V4 Flash) and good falls back to the director’s active model (Opus). Cheap grunt work, expensive judgement.

How to brief. A worker can’t see the director’s conversation. Give it files, constraints and an acceptance criterion you can check:

TEXT
Goal: cursor pagination on GET /api/orders.
Files: src/api/orders.ts, tests/orders.test.ts. Don't touch anything else.
Acceptance: `npm test -- orders` passes, with a new test for the last page.
Deliver: the files you touched and the test output.

How to verify. The docs say it plainly: a worker finishing means the turn ended, not that it got things right. The director reads the touched files and the full output at agent://<id>. One worker per workstream, fast and good in parallel, and good reviewing fast when it matters.

Pitfalls: exiting /vibe shuts down every worker; it doesn’t mix with plan mode or goal mode; while it’s on, you can’t start, delete, fork or move the session. This post, by the way, was drafted by an omp worker in a director session.

9. Skills, MCP and AGENTS.md

  • AGENTS.md: omp loads ~/.omp/agent/AGENTS.md, .omp/AGENTS.md, and a plain AGENTS.md searched from the current directory up to the repo root (it reads CLAUDE.md too). Keep it short: hard rules, and where everything else lives.
  • Skills: a directory with a SKILL.md (name and description in the frontmatter). Only the name and description go into the prompt; the body loads on demand (skill://<name>). Yours live in ~/.omp/agent/skills/; with autolearn, the agent writes its own into ~/.omp/agent/managed-skills/. I have about 30 of mine (vulnerability classes, methodology, triage, report writing) and dozens of managed ones.
  • MCP: ~/.omp/agent/mcp.json, with ${VAR} expanded. I use Burp Suite through PortSwigger’s official MCP Server extension (local SSE, by default at http://127.0.0.1:9876), and a private knowledge base, a Python MCP server over my notes:
JSON
{
  "mcpServers": {
    "burp": { "type": "sse", "url": "http://127.0.0.1:9876" },
    "kb": {
      "type": "stdio",
      "command": "${HOME}/kb/.venv/bin/python",
      "args": ["${HOME}/kb/server.py"],
      "env": { "KB_PATH": "${HOME}/kb/data" }
    }
  }
}

10. A taste: bug bounty

For large programs I open several terminals running omp, each with one role, and they talk only through the filesystem. I call it the pack: an orch owns scope and the queue, attackers a1..aN take one host at a time, and a triage validates candidates. Every shared write goes through a bash script, bin/hb:

TEXT
~/hunting/
├── PROTOCOL.md            # the contract: roles, lease, rate, proof
├── bin/hb                 # the bus
├── scope/<program>/       # eligible.txt, deny.txt
└── state/
    ├── queue.jsonl        # append-only queue
    ├── claims/<host>/     # lease via atomic mkdir (owner + epoch)
    ├── findings.jsonl     # everything starts as "candidate"
    ├── dead-ends.md       # verified negatives + the control used
    ├── intel.md           # reusable patterns: "[tag] fact → action"
    └── loot/F-0001/       # request.txt, response.txt

An attacker’s loop:

BASH
HOST=$(bin/hb next a1) || exit 0     # atomic lease on the next host
bin/hb inscope "$HOST" || { bin/hb release "$HOST" a1; exit 3; }
grep -i "$HOST" state/dead-ends.md state/intel.md   # don't repeat dead tests
ffuf -u "https://$HOST/FUZZ" -w wordlists/base.txt -ac -rate 20 -t 10 -o "recon/$HOST/ffuf.json"
bin/hb finding a1 "$HOST" idor "https://$HOST/api/orders?id=2" "BOLA on /orders" "200 with account B's order"
bin/hb done "$HOST" a1 findings "1 candidate"

The guardrails are executable, not promises from the model:

  • Scope: hb inscope exits 0, 2 or 3; deny beats allow; unlisted hosts don’t get touched.
  • Rate limits: an explicit cap per tool, manual requests at 1/s per host, and a burst of 403/429 pauses the host for 10 minutes.
  • Proof before claims: without request and response in state/loot/, it’s only a candidate. Triage reproduces from scratch with a negative control, on a model from a different family.
  • Human review: the agent can draft the report, but the manual reproduction, the review and the submission are mine.

That deserves a post of its own. Next time.

Wrapping up

A harness won’t think for you, but it makes your method run the same way every time, in a terminal that’s nice to look at. Recap:

  1. A terminal with a configurable font: Warp (macOS/Linux/Windows) or Windows Terminal.
  2. JetBrainsMono Nerd Font: brew install --cask on macOS, zip + fc-cache on Linux, zip → Install on Windows; selected in the terminal.
  3. (Optional) zsh + Oh My Zsh + Powerlevel10k, p10k configure (on Windows, inside WSL2).
  4. omp: brew install can1357/tap/omp, curl -fsSL https://omp.sh/install | sh or irm https://omp.sh/install.ps1 | iex; then omp setup and omp login or an API key.
  5. Theme and icons: theme.dark, symbolPreset, statusLine.preset, composer.shape.
  6. Memory, secrets and approvals.
  7. Model roles and, if you need it, a gateway in models.yml.
  8. /vibe once the basics are boringly reliable.