## Using Zellij to keep track of multiple agents

Coding agents can take a while to respond, so I run several in parallel. The problem is it is hard to keep track of which terminal is running which task. It's also easy to lose track of which agent is still working, which one is stuck asking a question, and which finished a while ago.

To solve this I use zellij (which is a modern alternative to tmux) and the [opencode-zellij-indicator](https://github.com/aidan-gallagher/opencode-zellij-indicator) plugin (which writes the state of each agent to the zellij tab).

For each agent it shows me what state the agent is in:

- ⏳ working - busy, ignore it for now
- ❓ needs you - blocked on a permission prompt or a question, go unblock it
- 🔔 done, unseen - it finished while you were away, go check the result
- ✅ done, seen - finished, and you've already looked

![Zellij tabs showing the four agent states: needs you, done unseen, done seen, and working](/posts/opencode-tips/tab-states.png)

While an agent is working, the tab also shows how long the task has been running, so I can tell at a glance which agents have been busy the longest.

![A zellij tab showing the elapsed time a task has been running](/posts/opencode-tips/stopwatch.png)

To set this up

1. Install zellij
2. Adding `opencode-zellij-indicator` to `.config/opencode/opencode.json`

```
"plugin": [ "opencode-zellij-indicator"]
```

3. Open OpenCode within zellij

## Zoxide skill to quickly reference directories

[zoxide](https://github.com/ajeetdsouza/zoxide) is a smarter `cd`. It remembers the directories visited and ranks them by "frecency" (frequency + recency), so instead of typing a full path I can jump to a directory with a fragment. For example in bash typing `z 202511` takes me to `~/Code/cf-sonic-buildimage-worktrees/202511`.

I extended that same convenience to OpenCode with a small skill. I just drop `/z <name>` inline anywhere I'd otherwise type a path, and the agent resolves each one with `zoxide query` before acting on the prompt.

Before, I'd spell out the full path to every directory:

```
Look at all patches in /home/aidan/Code/cf-sonic-buildimage-worktrees/202511 and ensure there is a test in /home/aidan/Code/sonic-containerlab covering it.
```

After, the same prompt can just partially name each one:

```
Look at all patches in /z 202511 and ensure there is a test in /z cont covering it.
```

It grabs the best match via `zoxide query <name>`; if it's ambiguous, the agent falls back to `zoxide query --list --score <name>` and picks. No more copy-pasting long paths into the prompt just to point the agent at a repo.

<details>
<summary>Skill definition I use for this</summary>

````text
---
name: zoxide
description: /z is the keyword for resolving fuzzy directory, path, and location references with zoxide. Use when the user writes `/z <name>` to refer to a path by name instead of typing the full path.
---

# Zoxide Paths

When the user writes `/z <name>`, resolve `<name>` with `zoxide query` before using it.

Example:

```text
/z sonic-buildimage
```

Use `zoxide query <query>` for a single best match.

Use `zoxide query --list --score <query>` if the reference is ambiguous.
````

</details>

## Walkthrough command for paginated explanations

When I ask OpenCode to explain some code or a concept, it usually replies with one long block of text. I often read the first couple of paragraphs, reach something I don't understand, and I ask about that. The conversation then follows the new question and I rarely go back to the rest of the original answer. Over a few turns this results in a lot of output tokens wasted as they are never read.

`/walkthrough` avoids this. It is an OpenCode command that turns "explain X" into a walkthrough delivered one section at a time. It first prints an overview: a numbered list of the sections it will cover. I move through it with a few single-key controls:

- `n` -> next section
- `s` -> simplify (explain it like I'm five)
- `d` -> deeper (more technical detail)
- `q` -> quit

Because I only see one small section at a time, follow-up questions stay in context and I don't lose my place. When I am done with a section I hit `n` to continue from where I left off. With no argument it infers the topic from the recent conversation, so I can type `/walkthrough` after any answer I want unpacked. It is good for learning at my own pace.

<details>
<summary>Command definition I use for this</summary>

```text
---
description: Explain a topic interactively, one section at a time
---

# /walkthrough

Explain a topic to the user in a paginated, navigable way.
DO NOT produce a wall of text. You MUST follow the protocol below exactly.

## Topic selection

`$ARGUMENTS` is optional.

- If `$ARGUMENTS` is non-empty -> that is the topic.
- If `$ARGUMENTS` is empty -> look back at the conversation history
  and identify the most recent topic / concept / message that the
  user was asking about or that you were explaining. Use that as
  the topic.
- If `$ARGUMENTS` is empty AND there is no prior conversation,
  ask the user what they want explained, then start.

When the topic was inferred from history (not given as an argument),
state it explicitly on the first line of the overview, e.g.
`Explaining: <topic> (inferred from earlier in this conversation)`
so the user can correct you before continuing.

## Protocol

### Step 1 - Overview (your FIRST and ONLY message right now)

Output:
1. A short title for the topic (with the "inferred" note if applicable).
2. A numbered list of section labels. YOU choose how many sections
   based on the topic - typically 4-10. Each label is one short line.
   Order logically (basics -> advanced).
3. The control hints line:
   `Controls: n: next | b: back | s: simplify | d: deeper | q: quit`

Then STOP. Do not write any sections yet. Wait for the user's reply.

Remember the total number of sections you committed to - call it TOTAL.

### Step 1.5 - Interpreting the reply to the overview

There is no current section yet, so interpret the FIRST reply as:

- `n`, `next`, or empty -> begin: show section 1
- a section number      -> jump straight to that section
- `q`, `quit`           -> abandon the walkthrough (no wrap-up needed)
- anything else         -> treat as a correction or refinement of the
                           topic / section plan. Regenerate the
                           overview accordingly, then wait again

### Step 2 - Sections (one per turn, on demand)

On each user reply, interpret it as a control:

- `n`, `next`, or empty -> show the NEXT section
- `b`, `back`           -> show the PREVIOUS section. If already on
                           section 1, stay on it and say there is no
                           previous section
- a section number      -> jump to that section
- `s`, `simplify`       -> re-explain the CURRENT section as if to a
                           5-year-old: plain words, analogies, no jargon
- `d`, `deeper`         -> re-explain the CURRENT section in more
                           technical depth, with examples
- `q`, `quit`           -> end the walkthrough now: give the 3-line
                           wrap-up and stop
- anything else         -> treat as a question about the current
                           section; answer briefly, then re-print
                           the footer

`s` and `d` do NOT advance the counter. `b` moves it back by one; a
numbered jump sets the counter to that section.

When showing a section:
- Heading: `## Section <N>/<TOTAL>: <label>`
- Body: a few short paragraphs - long enough to be useful, short
  enough to read in one sitting (longer with examples for `d`,
  shorter and simpler for `s`)
- Footer: `[<N>/<TOTAL>] n: next | b: back | s: simplify | d: deeper | q: quit`

Then STOP. Do not chain into the next section.

After the final section is shown and the user sends `n`, give a
3-line wrap-up and end the walkthrough session.

## Hard rules

- ONE section per message. Never two.
- Always end with the controls footer until the wrap-up.
- Section count is fixed at overview time - do not add or drop sections later.
```

</details>

## Voice input

Since AI harnesses take natural language as input, I can use voice dictation instead of typing. The benefit is that dictating is faster and less mentally tiring, so I can give far more context than I would bother to type out. It doesn't replace typing but it's definitely a useful tool to pile on context quickly.

I'm on Ubuntu, so the tool I use is [voxtype](https://voxtype.io), which works well on Linux. It types into any app - notes, chat, whatever - but in practice I almost only use it for OpenCode. It runs a local speech model on my own machine, so nothing gets sent to the cloud.

It's a small model, so it really only knows everyday English. It's accurate enough, but trips on the odd word - usually a similar-sounding one, most often a technical term outside its vocabulary. It rarely matters though: the LLM spots the wrong word and works out what I actually meant from context.

## Personalisation

OpenCode reads `~/.config/opencode/AGENTS.md` at the start of every session. I add instructions to this file to customize OpenCode's behaviour for me.

I keep it short - just personal preferences I want on no matter the task, such as my documentation workflow (writing notes into my own second brain), being aware of voice-dictation slips, opening links in Chrome automatically rather than making me copy and paste, etc.

## Presentation

Coding agents are great at creating HTML files and I think they are much easier for humans to consume ([read more](https://claude.com/blog/using-claude-code-the-unreasonable-effectiveness-of-html)). After a significant code change I ask OpenCode to generate a HTML file to walkthrough the changes - I find this helps with the review and I can also attach the final HTML file to my MR to help my colleagues review the change.