Skip to main content
Glama
README.md
# ohjaamo

Run several coding agents side by side in one terminal, and let them talk to
each other.

`ohjaamo` is Finnish for the cabin you steer from. It is two small things: a
launcher that puts N agents into a tmux layout and gives each one a name, and
an MCP server that gives them a shared mailbox, a claim list and a log.

No dependencies. One file for the launcher, one for the server.

```
ohjaamo up 2              # two agents, side by side
ohjaamo up 3 --director   # three workers and a director that assigns work
ohjaamo board             # see the state from outside
```

## Why not just open two terminals

You can, and that is the point at which the problem shows up. Two agents
working on the same repository find the same bug separately, fix the same
file at the same time, and neither can tell that it happened. The cost is not
confusion, it is the same hour spent twice.

What they need is not chat. It is three facts they can both see:

- **who is here** and what they are working on
- **what is already claimed**, so nobody starts work someone else is doing
- **what has been learned**, including the dead ends

That is the whole tool.

## The tools an agent gets

| tool | what it does |
| --- | --- |
| `peers` | who else is in this session, their role and directory |
| `send` | put a message in someone's mailbox, or `"all"` |
| `inbox` | read what is addressed to you; reading marks it read |
| `claim` | take one piece of work so nobody else starts it |
| `release` | give it back when you are done |
| `note` | record a finding, a decision or a dead end in the shared log |
| `board` | everything at once: peers, claims, recent log |

A message goes into a mailbox, not onto someone's screen. Writing into
another terminal's prompt is possible and tempting, and it interrupts
whatever that agent was thinking. A mailbox is read at a moment that suits
the reader, which is the difference between a colleague and an alarm.

## The director

`--director` adds one more pane whose job is to read the board and hand out
work. It has no special tools: a director is a peer that assigns with `send`
and watches with `board`. That is deliberate. The moment coordination needs
privileged tools, the tool has opinions about how you work, and those
opinions are wrong for somebody.

A prompt that works to start it:

> You are the director. Do not edit files. Read `board`, decide what should
> happen next, assign one concrete task per worker with `send`, and check
> `inbox` for their answers. When a worker reports a finding, write it to the
> log with `note` so it survives.

## Install

Not on npm yet. Clone and link:

```
git clone https://github.com/justusbernerdev/ohjaamo.git
cd ohjaamo && npm link
```

Requires Node 20+, tmux, and an agent CLI on your PATH. It defaults to
`claude`; use `--agent` for anything else that runs in a terminal.

`ohjaamo up` writes an `.mcp.json` into the working directory so the agents
find the server. It is written per project rather than into your global
config on purpose: a coordination channel that follows you into unrelated
work is a channel you switch off.

## Moving between panes

Mouse mode is turned on for the session, so clicking a pane switches to it.
It is set on the session and not in your config, because changing someone's
tmux everywhere is not this tool's business.

| | |
| --- | --- |
| click a pane | switch to it |
| `ctrl-b` then an arrow | switch without the mouse |
| `ctrl-b z` | make the current pane full screen, and back |
| `ctrl-b d` | leave the session running and detach |

Each pane is labelled with the name its agent answers to, because four
identical prompts and a message addressed to "b" is otherwise a guessing
game.

## Joining from outside

The panes are not the only participants. Any terminal, including the one you
are typing in right now, can reach the session without opening a pane:

```
ohjaamo send a "start with the smallest list"
ohjaamo note "the phone column on that source is the platform switchboard"
ohjaamo board
```

You appear in the log as `human`, or as whatever `--as` says. A note you
cannot leave is a note that does not get left.

## The mistake worth knowing about

If you build something like this yourself, this is the hour you will lose:
**a stdio MCP server that keeps its state in memory cannot work here.** Each
client starts its own copy of the process, so two terminals talk to two
different servers, and every message is delivered to nobody. It looks like it
works. Nothing arrives.

`ohjaamo` therefore keeps everything in files under `~/.ohjaamo/<session>/`:
append-only JSONL for mail and the log, whole-file JSON behind a lock for
peers and claims. The alternative is an HTTP or WebSocket server that all
clients connect to, which is the right answer once the agents are on
different machines.

## State on disk

```
~/.ohjaamo/<session>/
  peers.json          who is here, updated on every tool call
  claims.json         key -> { peer, at, note }
  log.jsonl           append-only shared findings
  mail/<peer>.jsonl   one mailbox per peer
  mail/<peer>.jsonl.cursor
```

Claims older than two hours are treated as abandoned and can be taken over.
An agent stopped with ctrl-c in the middle of work would otherwise hold a
claim until someone deleted a file by hand.

Nothing is sent anywhere. The whole thing is local files and one tmux
session.

## Status

Version 0.1, new and lightly used. Built on macOS with tmux 3.6 and Claude
Code, tested with two peers passing messages, colliding on a claim and
reading the board. Everything else is untested. Open an issue if it breaks,
and say what you were running.

Next, in the order it matters: an HTTP transport so peers can live on
different machines, and npm.

MIT.