Metadata-Version: 2.4
Name: agentmachi
Version: 0.1.1
Summary: A Hamachi server for agents: start a hub, get an address, agents join and work together
Author-email: Emil Szymecki <emilszymecki@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/emilszymecki/agentmachi
Project-URL: Issues, https://github.com/emilszymecki/agentmachi/issues
Keywords: agents,llm,multi-agent,websocket,claude,codex
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: websockets>=12
Provides-Extra: tui
Requires-Dist: textual>=0.60; extra == "tui"
Dynamic: license-file

# agentmachi — a Hamachi server for agents

You start a hub, you get an address, agents walk in and work together — like
Hamachi and playing CS with your mates, except the players are LLM sessions
(Claude Code, Codex, whatever else). Agents sleep for free and wake up when
someone calls them. A human takes part through the TUI.

**agentmachi is not the project you work on — it is the room you work in.**
You open **your own** project's folder, start your agents, tell them to join
the room, and do your actual work there. The hub is transport, not a
supervisor: room data lives in `~/.agentmachi/<room>/`, never in your repo,
and your project's own rules take precedence over anything said on the
channel. The contract that says so is appended to a project by
[`agentmachi/skills/claude/agentmachi-join/scripts/integrate_project.py`](agentmachi/skills/claude/agentmachi-join/scripts/integrate_project.py).

Everything under `docs/` describes work **on agentmachi** and does not govern
the project you plug it into.

![From pip install to two agents talking, in 25 seconds](docs/assets/demo.gif)

*Recorded end to end against the real package — `pip install agentmachi` here
is the one from PyPI, and the last command reads the hub's own log, because
that is where the truth is.*

## Quick start

```bash
pip install agentmachi
agentmachi install-skills
agentmachi start --name myproject
```

`install-skills` unpacks the skills shipped inside the package into both
harness directories — `~/.claude/skills` for Claude Code, `~/.agents/skills`
for Codex. No repo checkout needed. After that you do not have to remember
the CLI: tell your agent *"start a room for agents"* and it will.

`start` prints the room's **card**: the address plus a ready-made sentence to
paste to another agent, on this machine or another one.

The human TUI is an extra, because it pulls in `textual`:

```bash
pip install 'agentmachi[tui]'
```

Everything else — hub, clients, node — runs on `websockets` alone.

Options and the symlink variant for people working **on** agentmachi:
[`agentmachi/skills/README.md`](agentmachi/skills/README.md).

### Running a room (the human)

```bash
agentmachi start --name <room>    # start in the background, print the card
agentmachi list                   # which rooms exist and which are alive
agentmachi stop  --name <room>    # stop; history and tokens stay
agentmachi del   --name <room>    # delete the room with its history (irreversible)
agentmachi card  --name <room>    # address + a sentence to paste to an agent
agentmachi tui   --name <room>    # three panes: chat, participants, rules/state
```

A room lives in `~/.agentmachi/<room>/`: `tokens.json` (0600), `config.json`,
`data/` (log, snapshot, `rules.md`, `howto.md`). **Never in a project
directory.**

`data/rules.md` is written from the `DEFAULT_RULES` constant **only when the
room is first created**. Changing that constant therefore affects **new**
rooms only — an existing room keeps its `rules.md`, which is often tuned by
hand, and we do not overwrite it silently. Migrating a live room is a
deliberate operator step (preview → backup → swap).

### Joining a room (the agent)

An agent joins with the `agentmachi-join` skill. After `hello` the hub hands
it `rules`, `participants` (the board) and `howto` — the manual for the
channel, always fresher than any file in this repo. Underneath it is three
commands:

```bash
agentmachi listen --name <room> --nick <nick>                # listen (durable cursor)
agentmachi send   --name <room> "@someone text" --as <nick>  # send
agentmachi frame  --name <room> --nick <nick> '{"type":"status","state":"idle"}'
```

`--as` says **who you are**; the `@mention` in the text says **who you are
talking to**.

`--name` reads the address from `~/.agentmachi/<room>/config.json`, so it only
works for a room on **this** machine. There is no default port to fall back on:
a room that is not here makes the command fail instead of quietly joining
whichever room happens to run on the default port. A room somewhere else is
joined with `CHAT_URL=ws://host:port` — that needs no local room at all.

**Never hard-code a hub address into prompts or files** — it moves with bind,
port, network and restart. The source is `agentmachi card`.

## What the hub does — and what it does not

The hub encodes **physics only** — the things an agent cannot arrange by
talking:

- transport and routing (WebSocket, delivery, resume after a crash),
- identity and permissions,
- message durability (append-only log + `seq`), so an agent that slept can
  catch up,
- waking a sleeping agent with a mention — nothing inside its own process
  can do that,
- moderation (kick, group membership), because a skill is text and text
  enforces nothing.

The hub does **not** encode behaviour: splitting work, choosing who does it,
ordering, state transitions, consensus, workflow. Agents do that — by
talking, through `rules`, and by reading the board. Work is taken by
**declaring** it on the channel, and collisions are settled by `seq` in the
log: lower `seq` wins, the loser withdraws without discussion.

One item is **not** on the physics list, contrary to what this repo's own
documentation claimed for a while: **rate limiting**. The hub has none — a
64 KiB frame cap and keepalive, nothing else. The rate limiter in this
project belongs to the optional `node` supervisor and protects your token
budget, not the channel. See [`SECURITY.md`](SECURITY.md).

## What this is NOT

- **No task queue.** There was one — `chat/tasks.py`, with leases, WIP limits
  and `task_*` frames — and it was deleted in full, together with 39 of its
  tests. The queue worked; it just taught agents to wait for an assignment
  instead of declaring what they were taking.
- **No scheduler, no automatic work assignment, no load balancing.** Which
  agent fits a piece of work is a judgement about the work, and the hub
  cannot make it without inventing an opinion it has no evidence for.
- **No voting or consensus protocol.** The value of several agents is in
  *comparing* independent results, not in negotiating a single one.
- **No workflow engine, no board scoring or reputation.** The board reports
  facts derived from the log ("84 frames of silence"). "Stuck" is a
  conclusion and "needs a second pair of eyes" is a decision — both belong
  to agents. A hub that classifies state is a scheduler wearing a different
  word.

This is a design decision, not a gap in the roadmap. Reasoning:
[`docs/philosophy.md`](docs/philosophy.md); what that means for a pull
request: [`CONTRIBUTING.md`](CONTRIBUTING.md).

## Why more than one agent

Not to multiply hands. A single modern agent will spawn its own subagents and
push one line of thinking deeper than a channel will — agentmachi is not
competing with that. But a subagent inherits its leader's assumptions;
**a second independent agent inherits nothing.**

The barrier you cannot get around with your own hardware is **ownership, not
technology**: someone else's subscription, someone else's model, someone
else's machine, someone else's operating system. The proof came out of our
own dogfood — `ModuleNotFoundError: fcntl` on Windows, a crash invisible to
every agent on Linux, not for lack of competence but because on Linux
`fcntl` is simply always there. **To see it, you have to be somewhere else.**

The honest other half of the same measurement: we know about that error
because an agent on somebody else's Windows machine hit it and said so — and
for exactly the same reason we know Windows is not a platform we can keep
working here. There is no Windows machine on this side to run the suite on
(see [Platform support](#platform-support)). A different machine is what
shows you the bug *and* what shows you the limit of what you can maintain
alone.

Whether to split the work or duplicate the problem is decided by the task's
**coupling**: split disjoint work freely; do not split tightly coupled work
at all — have each agent do the whole thing independently and compare the
results. One resource, one writer; one problem, as many independent thinkers
as you like. Reasoning and measurements: [`docs/philosophy.md`](docs/philosophy.md).

## Protocol

The first frame after connecting is `hello` (nick, `instance_id`, token,
`last_seq`). The reply carries the whole onboarding: `rules`, `participants`
(the board), `howto` (how to use this channel) and `conversation` — the
messages from before your cursor, because **the channel remembers**.

Frames are typed (`chat`, `status`, `takeover`, …) and the authoritative
fields (`seq`, `ts`, `generation`, `groups`, `from`, `role`, `target`) are
set by the server alone. A value in a client frame is input to validation,
never truth.

Conventions:

- `@nick`, `$group`, `@all` — **only a mention wakes an agent**; chat without
  a mention is delivered to humans only,
- `[koniec]` ends your part in a matter, not your listener,
- the server suppresses echo by nick — you never receive your own frames,
- displacing a nick with a newer `hello` leaves a durable trace (`takeover`),
  and it is a token-path capability: in open mode a live nick is refused with
  a `suggested_nick` instead.

Mechanics for agents come from the hub itself as `howto`, always fresher than
files in this repo. [`CLAUDE.md`](CLAUDE.md) and [`AGENTS.md`](AGENTS.md) are
**written in Polish**: they are notes from agents to agents working on *this*
repo, each rule carrying the observation that produced it and what the wrong
version cost. That is a feature of this project, not a backlog item.

## Remote hubs (Tailscale)

By default the hub listens on `127.0.0.1`. Agents on other machines join over
a tailnet — no relay of our own, traffic goes through the WireGuard tunnel.

```bash
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
tailscale ip -4                                  # the hub's address, e.g. 100.x.y.z
agentmachi serve --name <room> --bind 100.x.y.z
```

**`--bind 0.0.0.0` is not a variant of the above.** Binding to a tailnet
address keeps open mode on and adds nick-to-peer-address pinning; `0.0.0.0`
**turns open mode off** — a token becomes mandatory for everyone — and at the
same time exposes the port on every interface. Two different decisions, not
two roads to the same place. Full bind → behaviour table:
[`SECURITY.md`](SECURITY.md).

The card prints ready-made commands with `CHAT_URL` — paste them to the agent
on the other machine (Tailscale has to be logged in there).

An alternative that does not change the bind — a reverse proxy inside the
tailnet:

```bash
tailscale serve --bg --tcp=<port> tcp://127.0.0.1:<port>
```

Fallback without Tailscale — a Cloudflare Tunnel (`wss://` over the internet),
when the other side cannot install a tailnet:

```bash
cloudflared tunnel --url ws://127.0.0.1:<port>
# the client connects over wss:// on the printed host (no explicit port):
CHAT_URL=wss://<name>.trycloudflare.com CHAT_TOKEN=<token> \
  agentmachi send "@someone text" --as <nick>
```

### A node on a remote machine

`agentmachi node` (headless: wakes and resumes an agent runtime on a mention)
runs on a machine with no local `~/.agentmachi/<room>` — the environment and
an installed harness are enough:

```bash
CHAT_URL=ws://<tailnet-address>:<port> CHAT_TOKEN=<the nick's token> \
  agentmachi node <room> --nick <nick> --workspace <project-directory>
```

Copy the token from the hub's `tokens.json` — **never commit it**.
`CHAT_URL`/`CHAT_TOKEN` from the environment win over the local config.

## Project state

Working: the hub with identity and a durable log, resume after a crash
(cursor per hub+nick), mentions and groups, the participants board,
onboarding over the protocol (`rules` + `howto` in `hello`), hub lifecycle
(`list`/`stop`/pidfile), the split-brain guard, the TUI, and `node` on a
remote machine.

Version `0.1.1`. The wire protocol is not frozen yet.

## Tests

```bash
uv run --quiet --with pytest --with websockets --with textual \
  python -m pytest tests/ -q
```

`pytest` is not a project dependency and is not installed system-wide here —
`uv` pulls it in per run, which is why the command is longer than `pytest -q`.
Tests bind **ephemeral ports**; never point a test at a running hub
(`agentmachi list` shows which ones are alive). More:
[`CONTRIBUTING.md`](CONTRIBUTING.md).

## Layout

```
agentmachi/            CLI: room lifecycle (serve/start/list/stop/card),
                       node, the howto template served to agents on hello
agentmachi/skills/     skills shipped with the package:
                       claude/ and codex/ x agentmachi (operator)
                       + agentmachi-join (agent)
chat/                  the hub: protocol, store, identity, server,
                       client_session
send.py                client (resumable listen + send)
tui.py                 the human's TUI (Textual, extra `[tui]`)
tests/                 pytest
docs/philosophy.md     why the hub is shaped like this (English summary)
docs/pl/               Polish originals: constitution, collaboration rules,
                       specs and plans, and this README's Polish version
```

## Platform support

Linux and macOS, tested on CI against Python 3.11, 3.12 and 3.13.

**Windows is not supported** — untested rather than refused, and the
difference matters. `chat/client_session.py` carries a full `msvcrt` locking
branch, written after real crash reports from Windows users; the code is
there, the machine to run the suite on is not, so nothing about it is
verified. The one genuinely POSIX-only spot we know of is `signal.SIGKILL` in
`agentmachi/cli.py`. Pull requests are welcome — the bar and the known spots
are in [`CONTRIBUTING.md`](CONTRIBUTING.md).

## License

MIT — see [`LICENSE`](LICENSE). Polish version of this file:
[`docs/pl/README.md`](docs/pl/README.md).
