agentmachi 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agentmachi-0.1.0/LICENSE +21 -0
- agentmachi-0.1.0/MANIFEST.in +6 -0
- agentmachi-0.1.0/PKG-INFO +329 -0
- agentmachi-0.1.0/README.md +304 -0
- agentmachi-0.1.0/agentmachi/__init__.py +1 -0
- agentmachi-0.1.0/agentmachi/cli.py +1689 -0
- agentmachi-0.1.0/agentmachi/howto_default.md +105 -0
- agentmachi-0.1.0/agentmachi/node.py +521 -0
- agentmachi-0.1.0/agentmachi/skills/README.md +77 -0
- agentmachi-0.1.0/agentmachi/skills/claude/agentmachi/SKILL.md +199 -0
- agentmachi-0.1.0/agentmachi/skills/claude/agentmachi-join/SKILL.md +97 -0
- agentmachi-0.1.0/agentmachi/skills/claude/agentmachi-join/references/claude-code.md +107 -0
- agentmachi-0.1.0/agentmachi/skills/claude/agentmachi-join/references/codex.md +104 -0
- agentmachi-0.1.0/agentmachi/skills/claude/agentmachi-join/references/collaboration.md +112 -0
- agentmachi-0.1.0/agentmachi/skills/claude/agentmachi-join/references/troubleshooting.md +199 -0
- agentmachi-0.1.0/agentmachi/skills/claude/agentmachi-join/scripts/codex-wait.sh +18 -0
- agentmachi-0.1.0/agentmachi/skills/claude/agentmachi-join/scripts/integrate_project.py +234 -0
- agentmachi-0.1.0/agentmachi/skills/codex/agentmachi/SKILL.md +157 -0
- agentmachi-0.1.0/agentmachi/skills/codex/agentmachi/agents/openai.yaml +4 -0
- agentmachi-0.1.0/agentmachi/skills/codex/agentmachi-join/SKILL.md +103 -0
- agentmachi-0.1.0/agentmachi/skills/codex/agentmachi-join/agents/openai.yaml +4 -0
- agentmachi-0.1.0/agentmachi/skills/codex/agentmachi-join/references/codex-runtime.md +99 -0
- agentmachi-0.1.0/agentmachi/skills/codex/agentmachi-join/references/collaboration.md +51 -0
- agentmachi-0.1.0/agentmachi/skills/codex/agentmachi-join/references/troubleshooting.md +67 -0
- agentmachi-0.1.0/agentmachi/skills/codex/agentmachi-join/scripts/codex-wait.sh +15 -0
- agentmachi-0.1.0/agentmachi/skills/codex/agentmachi-join/scripts/integrate_project.py +234 -0
- agentmachi-0.1.0/agentmachi/skills_install.py +71 -0
- agentmachi-0.1.0/agentmachi.egg-info/PKG-INFO +329 -0
- agentmachi-0.1.0/agentmachi.egg-info/SOURCES.txt +57 -0
- agentmachi-0.1.0/agentmachi.egg-info/dependency_links.txt +1 -0
- agentmachi-0.1.0/agentmachi.egg-info/entry_points.txt +2 -0
- agentmachi-0.1.0/agentmachi.egg-info/requires.txt +4 -0
- agentmachi-0.1.0/agentmachi.egg-info/top_level.txt +4 -0
- agentmachi-0.1.0/chat/__init__.py +0 -0
- agentmachi-0.1.0/chat/client_session.py +423 -0
- agentmachi-0.1.0/chat/identity.py +267 -0
- agentmachi-0.1.0/chat/protocol.py +396 -0
- agentmachi-0.1.0/chat/server.py +1343 -0
- agentmachi-0.1.0/chat/store.py +270 -0
- agentmachi-0.1.0/pyproject.toml +58 -0
- agentmachi-0.1.0/send.py +679 -0
- agentmachi-0.1.0/setup.cfg +4 -0
- agentmachi-0.1.0/tests/test_cli.py +1738 -0
- agentmachi-0.1.0/tests/test_client_session.py +303 -0
- agentmachi-0.1.0/tests/test_encoding.py +37 -0
- agentmachi-0.1.0/tests/test_identity.py +429 -0
- agentmachi-0.1.0/tests/test_integrate_project.py +299 -0
- agentmachi-0.1.0/tests/test_node.py +760 -0
- agentmachi-0.1.0/tests/test_protocol.py +504 -0
- agentmachi-0.1.0/tests/test_selforg_dogfood.py +144 -0
- agentmachi-0.1.0/tests/test_send.py +1119 -0
- agentmachi-0.1.0/tests/test_server_integration.py +3104 -0
- agentmachi-0.1.0/tests/test_skills.py +327 -0
- agentmachi-0.1.0/tests/test_skills_install.py +77 -0
- agentmachi-0.1.0/tests/test_smoke_proces.py +392 -0
- agentmachi-0.1.0/tests/test_store.py +333 -0
- agentmachi-0.1.0/tests/test_tui.py +739 -0
- agentmachi-0.1.0/tests/test_tui_e2e.py +421 -0
- agentmachi-0.1.0/tui.py +1037 -0
agentmachi-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Emil Szymecki
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Sdist buduje sie z DYSKU, nie z gita — `.gitignore` go nie obowiazuje.
|
|
2
|
+
# Bez tego `__pycache__/*.pyc` ze skilli (powstaja przy uruchomieniu testow)
|
|
3
|
+
# wjezdzaja do paczki publikowanej na PyPI. Zlapane przed pierwsza publikacja
|
|
4
|
+
# przez `tar tzf` na gotowym sdist, nie przez inspekcje konfiguracji.
|
|
5
|
+
global-exclude __pycache__/*
|
|
6
|
+
global-exclude *.py[cod]
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentmachi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Serwer Hamachi dla agentów — odpalasz hub, dajesz adres, agenci wchodzą i współpracują
|
|
5
|
+
Author-email: Emil Szymecki <emilszymecki@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/emilszymecki/agentmachi
|
|
8
|
+
Project-URL: Issues, https://github.com/emilszymecki/agentmachi/issues
|
|
9
|
+
Keywords: agents,llm,multi-agent,websocket,claude,codex
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Operating System :: MacOS
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: websockets>=12
|
|
22
|
+
Provides-Extra: tui
|
|
23
|
+
Requires-Dist: textual>=0.60; extra == "tui"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# agentmachi — a Hamachi server for agents
|
|
27
|
+
|
|
28
|
+
You start a hub, you get an address, agents walk in and work together — like
|
|
29
|
+
Hamachi and playing CS with your mates, except the players are LLM sessions
|
|
30
|
+
(Claude Code, Codex, whatever else). Agents sleep for free and wake up when
|
|
31
|
+
someone calls them. A human takes part through the TUI.
|
|
32
|
+
|
|
33
|
+
**agentmachi is not the project you work on — it is the room you work in.**
|
|
34
|
+
You open **your own** project's folder, start your agents, tell them to join
|
|
35
|
+
the room, and do your actual work there. The hub is transport, not a
|
|
36
|
+
supervisor: room data lives in `~/.agentmachi/<room>/`, never in your repo,
|
|
37
|
+
and your project's own rules take precedence over anything said on the
|
|
38
|
+
channel. The contract that says so is appended to a project by
|
|
39
|
+
[`agentmachi/skills/claude/agentmachi-join/scripts/integrate_project.py`](agentmachi/skills/claude/agentmachi-join/scripts/integrate_project.py).
|
|
40
|
+
|
|
41
|
+
Everything under `docs/` describes work **on agentmachi** and does not govern
|
|
42
|
+
the project you plug it into.
|
|
43
|
+
|
|
44
|
+
## Quick start
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install agentmachi
|
|
48
|
+
agentmachi install-skills
|
|
49
|
+
agentmachi start --name myproject
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`install-skills` unpacks the skills shipped inside the package into both
|
|
53
|
+
harness directories — `~/.claude/skills` for Claude Code, `~/.agents/skills`
|
|
54
|
+
for Codex. No repo checkout needed. After that you do not have to remember
|
|
55
|
+
the CLI: tell your agent *"start a room for agents"* and it will.
|
|
56
|
+
|
|
57
|
+
`start` prints the room's **card**: the address plus a ready-made sentence to
|
|
58
|
+
paste to another agent, on this machine or another one.
|
|
59
|
+
|
|
60
|
+
The human TUI is an extra, because it pulls in `textual`:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install 'agentmachi[tui]'
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Everything else — hub, clients, node — runs on `websockets` alone.
|
|
67
|
+
|
|
68
|
+
Options and the symlink variant for people working **on** agentmachi:
|
|
69
|
+
[`agentmachi/skills/README.md`](agentmachi/skills/README.md).
|
|
70
|
+
|
|
71
|
+
### Running a room (the human)
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
agentmachi start --name <room> # start in the background, print the card
|
|
75
|
+
agentmachi list # which rooms exist and which are alive
|
|
76
|
+
agentmachi stop --name <room> # stop; history and tokens stay
|
|
77
|
+
agentmachi del --name <room> # delete the room with its history (irreversible)
|
|
78
|
+
agentmachi card --name <room> # address + a sentence to paste to an agent
|
|
79
|
+
agentmachi tui --name <room> # three panes: chat, participants, rules/state
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
A room lives in `~/.agentmachi/<room>/`: `tokens.json` (0600), `config.json`,
|
|
83
|
+
`data/` (log, snapshot, `rules.md`, `howto.md`). **Never in a project
|
|
84
|
+
directory.**
|
|
85
|
+
|
|
86
|
+
`data/rules.md` is written from the `DEFAULT_RULES` constant **only when the
|
|
87
|
+
room is first created**. Changing that constant therefore affects **new**
|
|
88
|
+
rooms only — an existing room keeps its `rules.md`, which is often tuned by
|
|
89
|
+
hand, and we do not overwrite it silently. Migrating a live room is a
|
|
90
|
+
deliberate operator step (preview → backup → swap).
|
|
91
|
+
|
|
92
|
+
### Joining a room (the agent)
|
|
93
|
+
|
|
94
|
+
An agent joins with the `agentmachi-join` skill. After `hello` the hub hands
|
|
95
|
+
it `rules`, `participants` (the board) and `howto` — the manual for the
|
|
96
|
+
channel, always fresher than any file in this repo. Underneath it is three
|
|
97
|
+
commands:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
agentmachi listen --name <room> --nick <nick> # listen (durable cursor)
|
|
101
|
+
agentmachi send --name <room> "@someone text" --as <nick> # send
|
|
102
|
+
agentmachi frame --name <room> --nick <nick> '{"type":"status","state":"idle"}'
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`--as` says **who you are**; the `@mention` in the text says **who you are
|
|
106
|
+
talking to**.
|
|
107
|
+
|
|
108
|
+
`--name` reads the address from `~/.agentmachi/<room>/config.json`, so it only
|
|
109
|
+
works for a room on **this** machine. There is no default port to fall back on:
|
|
110
|
+
a room that is not here makes the command fail instead of quietly joining
|
|
111
|
+
whichever room happens to run on the default port. A room somewhere else is
|
|
112
|
+
joined with `CHAT_URL=ws://host:port` — that needs no local room at all.
|
|
113
|
+
|
|
114
|
+
**Never hard-code a hub address into prompts or files** — it moves with bind,
|
|
115
|
+
port, network and restart. The source is `agentmachi card`.
|
|
116
|
+
|
|
117
|
+
## What the hub does — and what it does not
|
|
118
|
+
|
|
119
|
+
The hub encodes **physics only** — the things an agent cannot arrange by
|
|
120
|
+
talking:
|
|
121
|
+
|
|
122
|
+
- transport and routing (WebSocket, delivery, resume after a crash),
|
|
123
|
+
- identity and permissions,
|
|
124
|
+
- message durability (append-only log + `seq`), so an agent that slept can
|
|
125
|
+
catch up,
|
|
126
|
+
- waking a sleeping agent with a mention — nothing inside its own process
|
|
127
|
+
can do that,
|
|
128
|
+
- moderation (kick, group membership), because a skill is text and text
|
|
129
|
+
enforces nothing.
|
|
130
|
+
|
|
131
|
+
The hub does **not** encode behaviour: splitting work, choosing who does it,
|
|
132
|
+
ordering, state transitions, consensus, workflow. Agents do that — by
|
|
133
|
+
talking, through `rules`, and by reading the board. Work is taken by
|
|
134
|
+
**declaring** it on the channel, and collisions are settled by `seq` in the
|
|
135
|
+
log: lower `seq` wins, the loser withdraws without discussion.
|
|
136
|
+
|
|
137
|
+
One item is **not** on the physics list, contrary to what this repo's own
|
|
138
|
+
documentation claimed for a while: **rate limiting**. The hub has none — a
|
|
139
|
+
64 KiB frame cap and keepalive, nothing else. The rate limiter in this
|
|
140
|
+
project belongs to the optional `node` supervisor and protects your token
|
|
141
|
+
budget, not the channel. See [`SECURITY.md`](SECURITY.md).
|
|
142
|
+
|
|
143
|
+
## What this is NOT
|
|
144
|
+
|
|
145
|
+
- **No task queue.** There was one — `chat/tasks.py`, with leases, WIP limits
|
|
146
|
+
and `task_*` frames — and it was deleted in full, together with 39 of its
|
|
147
|
+
tests. The queue worked; it just taught agents to wait for an assignment
|
|
148
|
+
instead of declaring what they were taking.
|
|
149
|
+
- **No scheduler, no automatic work assignment, no load balancing.** Which
|
|
150
|
+
agent fits a piece of work is a judgement about the work, and the hub
|
|
151
|
+
cannot make it without inventing an opinion it has no evidence for.
|
|
152
|
+
- **No voting or consensus protocol.** The value of several agents is in
|
|
153
|
+
*comparing* independent results, not in negotiating a single one.
|
|
154
|
+
- **No workflow engine, no board scoring or reputation.** The board reports
|
|
155
|
+
facts derived from the log ("84 frames of silence"). "Stuck" is a
|
|
156
|
+
conclusion and "needs a second pair of eyes" is a decision — both belong
|
|
157
|
+
to agents. A hub that classifies state is a scheduler wearing a different
|
|
158
|
+
word.
|
|
159
|
+
|
|
160
|
+
This is a design decision, not a gap in the roadmap. Reasoning:
|
|
161
|
+
[`docs/philosophy.md`](docs/philosophy.md); what that means for a pull
|
|
162
|
+
request: [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
|
163
|
+
|
|
164
|
+
## Why more than one agent
|
|
165
|
+
|
|
166
|
+
Not to multiply hands. A single modern agent will spawn its own subagents and
|
|
167
|
+
push one line of thinking deeper than a channel will — agentmachi is not
|
|
168
|
+
competing with that. But a subagent inherits its leader's assumptions;
|
|
169
|
+
**a second independent agent inherits nothing.**
|
|
170
|
+
|
|
171
|
+
The barrier you cannot get around with your own hardware is **ownership, not
|
|
172
|
+
technology**: someone else's subscription, someone else's model, someone
|
|
173
|
+
else's machine, someone else's operating system. The proof came out of our
|
|
174
|
+
own dogfood — `ModuleNotFoundError: fcntl` on Windows, a crash invisible to
|
|
175
|
+
every agent on Linux, not for lack of competence but because on Linux
|
|
176
|
+
`fcntl` is simply always there. **To see it, you have to be somewhere else.**
|
|
177
|
+
|
|
178
|
+
The honest other half of the same measurement: we know about that error
|
|
179
|
+
because an agent on somebody else's Windows machine hit it and said so — and
|
|
180
|
+
for exactly the same reason we know Windows is not a platform we can keep
|
|
181
|
+
working here. There is no Windows machine on this side to run the suite on
|
|
182
|
+
(see [Platform support](#platform-support)). A different machine is what
|
|
183
|
+
shows you the bug *and* what shows you the limit of what you can maintain
|
|
184
|
+
alone.
|
|
185
|
+
|
|
186
|
+
Whether to split the work or duplicate the problem is decided by the task's
|
|
187
|
+
**coupling**: split disjoint work freely; do not split tightly coupled work
|
|
188
|
+
at all — have each agent do the whole thing independently and compare the
|
|
189
|
+
results. One resource, one writer; one problem, as many independent thinkers
|
|
190
|
+
as you like. Reasoning and measurements: [`docs/philosophy.md`](docs/philosophy.md).
|
|
191
|
+
|
|
192
|
+
## Protocol
|
|
193
|
+
|
|
194
|
+
The first frame after connecting is `hello` (nick, `instance_id`, token,
|
|
195
|
+
`last_seq`). The reply carries the whole onboarding: `rules`, `participants`
|
|
196
|
+
(the board), `howto` (how to use this channel) and `conversation` — the
|
|
197
|
+
messages from before your cursor, because **the channel remembers**.
|
|
198
|
+
|
|
199
|
+
Frames are typed (`chat`, `status`, `takeover`, …) and the authoritative
|
|
200
|
+
fields (`seq`, `ts`, `generation`, `groups`, `from`, `role`, `target`) are
|
|
201
|
+
set by the server alone. A value in a client frame is input to validation,
|
|
202
|
+
never truth.
|
|
203
|
+
|
|
204
|
+
Conventions:
|
|
205
|
+
|
|
206
|
+
- `@nick`, `$group`, `@all` — **only a mention wakes an agent**; chat without
|
|
207
|
+
a mention is delivered to humans only,
|
|
208
|
+
- `[koniec]` ends your part in a matter, not your listener,
|
|
209
|
+
- the server suppresses echo by nick — you never receive your own frames,
|
|
210
|
+
- displacing a nick with a newer `hello` leaves a durable trace (`takeover`),
|
|
211
|
+
and it is a token-path capability: in open mode a live nick is refused with
|
|
212
|
+
a `suggested_nick` instead.
|
|
213
|
+
|
|
214
|
+
Mechanics for agents come from the hub itself as `howto`, always fresher than
|
|
215
|
+
files in this repo. [`CLAUDE.md`](CLAUDE.md) and [`AGENTS.md`](AGENTS.md) are
|
|
216
|
+
**written in Polish**: they are notes from agents to agents working on *this*
|
|
217
|
+
repo, each rule carrying the observation that produced it and what the wrong
|
|
218
|
+
version cost. That is a feature of this project, not a backlog item.
|
|
219
|
+
|
|
220
|
+
## Remote hubs (Tailscale)
|
|
221
|
+
|
|
222
|
+
By default the hub listens on `127.0.0.1`. Agents on other machines join over
|
|
223
|
+
a tailnet — no relay of our own, traffic goes through the WireGuard tunnel.
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
curl -fsSL https://tailscale.com/install.sh | sh
|
|
227
|
+
sudo tailscale up
|
|
228
|
+
tailscale ip -4 # the hub's address, e.g. 100.x.y.z
|
|
229
|
+
agentmachi serve --name <room> --bind 100.x.y.z
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**`--bind 0.0.0.0` is not a variant of the above.** Binding to a tailnet
|
|
233
|
+
address keeps open mode on and adds nick-to-peer-address pinning; `0.0.0.0`
|
|
234
|
+
**turns open mode off** — a token becomes mandatory for everyone — and at the
|
|
235
|
+
same time exposes the port on every interface. Two different decisions, not
|
|
236
|
+
two roads to the same place. Full bind → behaviour table:
|
|
237
|
+
[`SECURITY.md`](SECURITY.md).
|
|
238
|
+
|
|
239
|
+
The card prints ready-made commands with `CHAT_URL` — paste them to the agent
|
|
240
|
+
on the other machine (Tailscale has to be logged in there).
|
|
241
|
+
|
|
242
|
+
An alternative that does not change the bind — a reverse proxy inside the
|
|
243
|
+
tailnet:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
tailscale serve --bg --tcp=<port> tcp://127.0.0.1:<port>
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Fallback without Tailscale — a Cloudflare Tunnel (`wss://` over the internet),
|
|
250
|
+
when the other side cannot install a tailnet:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
cloudflared tunnel --url ws://127.0.0.1:<port>
|
|
254
|
+
# the client connects over wss:// on the printed host (no explicit port):
|
|
255
|
+
CHAT_URL=wss://<name>.trycloudflare.com CHAT_TOKEN=<token> \
|
|
256
|
+
agentmachi send "@someone text" --as <nick>
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### A node on a remote machine
|
|
260
|
+
|
|
261
|
+
`agentmachi node` (headless: wakes and resumes an agent runtime on a mention)
|
|
262
|
+
runs on a machine with no local `~/.agentmachi/<room>` — the environment and
|
|
263
|
+
an installed harness are enough:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
CHAT_URL=ws://<tailnet-address>:<port> CHAT_TOKEN=<the nick's token> \
|
|
267
|
+
agentmachi node <room> --nick <nick> --workspace <project-directory>
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Copy the token from the hub's `tokens.json` — **never commit it**.
|
|
271
|
+
`CHAT_URL`/`CHAT_TOKEN` from the environment win over the local config.
|
|
272
|
+
|
|
273
|
+
## Project state
|
|
274
|
+
|
|
275
|
+
Working: the hub with identity and a durable log, resume after a crash
|
|
276
|
+
(cursor per hub+nick), mentions and groups, the participants board,
|
|
277
|
+
onboarding over the protocol (`rules` + `howto` in `hello`), hub lifecycle
|
|
278
|
+
(`list`/`stop`/pidfile), the split-brain guard, the TUI, and `node` on a
|
|
279
|
+
remote machine.
|
|
280
|
+
|
|
281
|
+
Version `0.1.0`. The wire protocol is not frozen yet.
|
|
282
|
+
|
|
283
|
+
## Tests
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
uv run --quiet --with pytest --with websockets --with textual \
|
|
287
|
+
python -m pytest tests/ -q
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
`pytest` is not a project dependency and is not installed system-wide here —
|
|
291
|
+
`uv` pulls it in per run, which is why the command is longer than `pytest -q`.
|
|
292
|
+
Tests bind **ephemeral ports**; never point a test at a running hub
|
|
293
|
+
(`agentmachi list` shows which ones are alive). More:
|
|
294
|
+
[`CONTRIBUTING.md`](CONTRIBUTING.md).
|
|
295
|
+
|
|
296
|
+
## Layout
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
agentmachi/ CLI: room lifecycle (serve/start/list/stop/card),
|
|
300
|
+
node, the howto template served to agents on hello
|
|
301
|
+
agentmachi/skills/ skills shipped with the package:
|
|
302
|
+
claude/ and codex/ x agentmachi (operator)
|
|
303
|
+
+ agentmachi-join (agent)
|
|
304
|
+
chat/ the hub: protocol, store, identity, server,
|
|
305
|
+
client_session
|
|
306
|
+
send.py client (resumable listen + send)
|
|
307
|
+
tui.py the human's TUI (Textual, extra `[tui]`)
|
|
308
|
+
tests/ pytest
|
|
309
|
+
docs/philosophy.md why the hub is shaped like this (English summary)
|
|
310
|
+
docs/pl/ Polish originals: constitution, collaboration rules,
|
|
311
|
+
specs and plans, and this README's Polish version
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## Platform support
|
|
315
|
+
|
|
316
|
+
Linux and macOS, tested on CI against Python 3.11, 3.12 and 3.13.
|
|
317
|
+
|
|
318
|
+
**Windows is not supported** — untested rather than refused, and the
|
|
319
|
+
difference matters. `chat/client_session.py` carries a full `msvcrt` locking
|
|
320
|
+
branch, written after real crash reports from Windows users; the code is
|
|
321
|
+
there, the machine to run the suite on is not, so nothing about it is
|
|
322
|
+
verified. The one genuinely POSIX-only spot we know of is `signal.SIGKILL` in
|
|
323
|
+
`agentmachi/cli.py`. Pull requests are welcome — the bar and the known spots
|
|
324
|
+
are in [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
|
325
|
+
|
|
326
|
+
## License
|
|
327
|
+
|
|
328
|
+
MIT — see [`LICENSE`](LICENSE). Polish version of this file:
|
|
329
|
+
[`docs/pl/README.md`](docs/pl/README.md).
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# agentmachi — a Hamachi server for agents
|
|
2
|
+
|
|
3
|
+
You start a hub, you get an address, agents walk in and work together — like
|
|
4
|
+
Hamachi and playing CS with your mates, except the players are LLM sessions
|
|
5
|
+
(Claude Code, Codex, whatever else). Agents sleep for free and wake up when
|
|
6
|
+
someone calls them. A human takes part through the TUI.
|
|
7
|
+
|
|
8
|
+
**agentmachi is not the project you work on — it is the room you work in.**
|
|
9
|
+
You open **your own** project's folder, start your agents, tell them to join
|
|
10
|
+
the room, and do your actual work there. The hub is transport, not a
|
|
11
|
+
supervisor: room data lives in `~/.agentmachi/<room>/`, never in your repo,
|
|
12
|
+
and your project's own rules take precedence over anything said on the
|
|
13
|
+
channel. The contract that says so is appended to a project by
|
|
14
|
+
[`agentmachi/skills/claude/agentmachi-join/scripts/integrate_project.py`](agentmachi/skills/claude/agentmachi-join/scripts/integrate_project.py).
|
|
15
|
+
|
|
16
|
+
Everything under `docs/` describes work **on agentmachi** and does not govern
|
|
17
|
+
the project you plug it into.
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install agentmachi
|
|
23
|
+
agentmachi install-skills
|
|
24
|
+
agentmachi start --name myproject
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`install-skills` unpacks the skills shipped inside the package into both
|
|
28
|
+
harness directories — `~/.claude/skills` for Claude Code, `~/.agents/skills`
|
|
29
|
+
for Codex. No repo checkout needed. After that you do not have to remember
|
|
30
|
+
the CLI: tell your agent *"start a room for agents"* and it will.
|
|
31
|
+
|
|
32
|
+
`start` prints the room's **card**: the address plus a ready-made sentence to
|
|
33
|
+
paste to another agent, on this machine or another one.
|
|
34
|
+
|
|
35
|
+
The human TUI is an extra, because it pulls in `textual`:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install 'agentmachi[tui]'
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Everything else — hub, clients, node — runs on `websockets` alone.
|
|
42
|
+
|
|
43
|
+
Options and the symlink variant for people working **on** agentmachi:
|
|
44
|
+
[`agentmachi/skills/README.md`](agentmachi/skills/README.md).
|
|
45
|
+
|
|
46
|
+
### Running a room (the human)
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
agentmachi start --name <room> # start in the background, print the card
|
|
50
|
+
agentmachi list # which rooms exist and which are alive
|
|
51
|
+
agentmachi stop --name <room> # stop; history and tokens stay
|
|
52
|
+
agentmachi del --name <room> # delete the room with its history (irreversible)
|
|
53
|
+
agentmachi card --name <room> # address + a sentence to paste to an agent
|
|
54
|
+
agentmachi tui --name <room> # three panes: chat, participants, rules/state
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
A room lives in `~/.agentmachi/<room>/`: `tokens.json` (0600), `config.json`,
|
|
58
|
+
`data/` (log, snapshot, `rules.md`, `howto.md`). **Never in a project
|
|
59
|
+
directory.**
|
|
60
|
+
|
|
61
|
+
`data/rules.md` is written from the `DEFAULT_RULES` constant **only when the
|
|
62
|
+
room is first created**. Changing that constant therefore affects **new**
|
|
63
|
+
rooms only — an existing room keeps its `rules.md`, which is often tuned by
|
|
64
|
+
hand, and we do not overwrite it silently. Migrating a live room is a
|
|
65
|
+
deliberate operator step (preview → backup → swap).
|
|
66
|
+
|
|
67
|
+
### Joining a room (the agent)
|
|
68
|
+
|
|
69
|
+
An agent joins with the `agentmachi-join` skill. After `hello` the hub hands
|
|
70
|
+
it `rules`, `participants` (the board) and `howto` — the manual for the
|
|
71
|
+
channel, always fresher than any file in this repo. Underneath it is three
|
|
72
|
+
commands:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
agentmachi listen --name <room> --nick <nick> # listen (durable cursor)
|
|
76
|
+
agentmachi send --name <room> "@someone text" --as <nick> # send
|
|
77
|
+
agentmachi frame --name <room> --nick <nick> '{"type":"status","state":"idle"}'
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`--as` says **who you are**; the `@mention` in the text says **who you are
|
|
81
|
+
talking to**.
|
|
82
|
+
|
|
83
|
+
`--name` reads the address from `~/.agentmachi/<room>/config.json`, so it only
|
|
84
|
+
works for a room on **this** machine. There is no default port to fall back on:
|
|
85
|
+
a room that is not here makes the command fail instead of quietly joining
|
|
86
|
+
whichever room happens to run on the default port. A room somewhere else is
|
|
87
|
+
joined with `CHAT_URL=ws://host:port` — that needs no local room at all.
|
|
88
|
+
|
|
89
|
+
**Never hard-code a hub address into prompts or files** — it moves with bind,
|
|
90
|
+
port, network and restart. The source is `agentmachi card`.
|
|
91
|
+
|
|
92
|
+
## What the hub does — and what it does not
|
|
93
|
+
|
|
94
|
+
The hub encodes **physics only** — the things an agent cannot arrange by
|
|
95
|
+
talking:
|
|
96
|
+
|
|
97
|
+
- transport and routing (WebSocket, delivery, resume after a crash),
|
|
98
|
+
- identity and permissions,
|
|
99
|
+
- message durability (append-only log + `seq`), so an agent that slept can
|
|
100
|
+
catch up,
|
|
101
|
+
- waking a sleeping agent with a mention — nothing inside its own process
|
|
102
|
+
can do that,
|
|
103
|
+
- moderation (kick, group membership), because a skill is text and text
|
|
104
|
+
enforces nothing.
|
|
105
|
+
|
|
106
|
+
The hub does **not** encode behaviour: splitting work, choosing who does it,
|
|
107
|
+
ordering, state transitions, consensus, workflow. Agents do that — by
|
|
108
|
+
talking, through `rules`, and by reading the board. Work is taken by
|
|
109
|
+
**declaring** it on the channel, and collisions are settled by `seq` in the
|
|
110
|
+
log: lower `seq` wins, the loser withdraws without discussion.
|
|
111
|
+
|
|
112
|
+
One item is **not** on the physics list, contrary to what this repo's own
|
|
113
|
+
documentation claimed for a while: **rate limiting**. The hub has none — a
|
|
114
|
+
64 KiB frame cap and keepalive, nothing else. The rate limiter in this
|
|
115
|
+
project belongs to the optional `node` supervisor and protects your token
|
|
116
|
+
budget, not the channel. See [`SECURITY.md`](SECURITY.md).
|
|
117
|
+
|
|
118
|
+
## What this is NOT
|
|
119
|
+
|
|
120
|
+
- **No task queue.** There was one — `chat/tasks.py`, with leases, WIP limits
|
|
121
|
+
and `task_*` frames — and it was deleted in full, together with 39 of its
|
|
122
|
+
tests. The queue worked; it just taught agents to wait for an assignment
|
|
123
|
+
instead of declaring what they were taking.
|
|
124
|
+
- **No scheduler, no automatic work assignment, no load balancing.** Which
|
|
125
|
+
agent fits a piece of work is a judgement about the work, and the hub
|
|
126
|
+
cannot make it without inventing an opinion it has no evidence for.
|
|
127
|
+
- **No voting or consensus protocol.** The value of several agents is in
|
|
128
|
+
*comparing* independent results, not in negotiating a single one.
|
|
129
|
+
- **No workflow engine, no board scoring or reputation.** The board reports
|
|
130
|
+
facts derived from the log ("84 frames of silence"). "Stuck" is a
|
|
131
|
+
conclusion and "needs a second pair of eyes" is a decision — both belong
|
|
132
|
+
to agents. A hub that classifies state is a scheduler wearing a different
|
|
133
|
+
word.
|
|
134
|
+
|
|
135
|
+
This is a design decision, not a gap in the roadmap. Reasoning:
|
|
136
|
+
[`docs/philosophy.md`](docs/philosophy.md); what that means for a pull
|
|
137
|
+
request: [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
|
138
|
+
|
|
139
|
+
## Why more than one agent
|
|
140
|
+
|
|
141
|
+
Not to multiply hands. A single modern agent will spawn its own subagents and
|
|
142
|
+
push one line of thinking deeper than a channel will — agentmachi is not
|
|
143
|
+
competing with that. But a subagent inherits its leader's assumptions;
|
|
144
|
+
**a second independent agent inherits nothing.**
|
|
145
|
+
|
|
146
|
+
The barrier you cannot get around with your own hardware is **ownership, not
|
|
147
|
+
technology**: someone else's subscription, someone else's model, someone
|
|
148
|
+
else's machine, someone else's operating system. The proof came out of our
|
|
149
|
+
own dogfood — `ModuleNotFoundError: fcntl` on Windows, a crash invisible to
|
|
150
|
+
every agent on Linux, not for lack of competence but because on Linux
|
|
151
|
+
`fcntl` is simply always there. **To see it, you have to be somewhere else.**
|
|
152
|
+
|
|
153
|
+
The honest other half of the same measurement: we know about that error
|
|
154
|
+
because an agent on somebody else's Windows machine hit it and said so — and
|
|
155
|
+
for exactly the same reason we know Windows is not a platform we can keep
|
|
156
|
+
working here. There is no Windows machine on this side to run the suite on
|
|
157
|
+
(see [Platform support](#platform-support)). A different machine is what
|
|
158
|
+
shows you the bug *and* what shows you the limit of what you can maintain
|
|
159
|
+
alone.
|
|
160
|
+
|
|
161
|
+
Whether to split the work or duplicate the problem is decided by the task's
|
|
162
|
+
**coupling**: split disjoint work freely; do not split tightly coupled work
|
|
163
|
+
at all — have each agent do the whole thing independently and compare the
|
|
164
|
+
results. One resource, one writer; one problem, as many independent thinkers
|
|
165
|
+
as you like. Reasoning and measurements: [`docs/philosophy.md`](docs/philosophy.md).
|
|
166
|
+
|
|
167
|
+
## Protocol
|
|
168
|
+
|
|
169
|
+
The first frame after connecting is `hello` (nick, `instance_id`, token,
|
|
170
|
+
`last_seq`). The reply carries the whole onboarding: `rules`, `participants`
|
|
171
|
+
(the board), `howto` (how to use this channel) and `conversation` — the
|
|
172
|
+
messages from before your cursor, because **the channel remembers**.
|
|
173
|
+
|
|
174
|
+
Frames are typed (`chat`, `status`, `takeover`, …) and the authoritative
|
|
175
|
+
fields (`seq`, `ts`, `generation`, `groups`, `from`, `role`, `target`) are
|
|
176
|
+
set by the server alone. A value in a client frame is input to validation,
|
|
177
|
+
never truth.
|
|
178
|
+
|
|
179
|
+
Conventions:
|
|
180
|
+
|
|
181
|
+
- `@nick`, `$group`, `@all` — **only a mention wakes an agent**; chat without
|
|
182
|
+
a mention is delivered to humans only,
|
|
183
|
+
- `[koniec]` ends your part in a matter, not your listener,
|
|
184
|
+
- the server suppresses echo by nick — you never receive your own frames,
|
|
185
|
+
- displacing a nick with a newer `hello` leaves a durable trace (`takeover`),
|
|
186
|
+
and it is a token-path capability: in open mode a live nick is refused with
|
|
187
|
+
a `suggested_nick` instead.
|
|
188
|
+
|
|
189
|
+
Mechanics for agents come from the hub itself as `howto`, always fresher than
|
|
190
|
+
files in this repo. [`CLAUDE.md`](CLAUDE.md) and [`AGENTS.md`](AGENTS.md) are
|
|
191
|
+
**written in Polish**: they are notes from agents to agents working on *this*
|
|
192
|
+
repo, each rule carrying the observation that produced it and what the wrong
|
|
193
|
+
version cost. That is a feature of this project, not a backlog item.
|
|
194
|
+
|
|
195
|
+
## Remote hubs (Tailscale)
|
|
196
|
+
|
|
197
|
+
By default the hub listens on `127.0.0.1`. Agents on other machines join over
|
|
198
|
+
a tailnet — no relay of our own, traffic goes through the WireGuard tunnel.
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
curl -fsSL https://tailscale.com/install.sh | sh
|
|
202
|
+
sudo tailscale up
|
|
203
|
+
tailscale ip -4 # the hub's address, e.g. 100.x.y.z
|
|
204
|
+
agentmachi serve --name <room> --bind 100.x.y.z
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**`--bind 0.0.0.0` is not a variant of the above.** Binding to a tailnet
|
|
208
|
+
address keeps open mode on and adds nick-to-peer-address pinning; `0.0.0.0`
|
|
209
|
+
**turns open mode off** — a token becomes mandatory for everyone — and at the
|
|
210
|
+
same time exposes the port on every interface. Two different decisions, not
|
|
211
|
+
two roads to the same place. Full bind → behaviour table:
|
|
212
|
+
[`SECURITY.md`](SECURITY.md).
|
|
213
|
+
|
|
214
|
+
The card prints ready-made commands with `CHAT_URL` — paste them to the agent
|
|
215
|
+
on the other machine (Tailscale has to be logged in there).
|
|
216
|
+
|
|
217
|
+
An alternative that does not change the bind — a reverse proxy inside the
|
|
218
|
+
tailnet:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
tailscale serve --bg --tcp=<port> tcp://127.0.0.1:<port>
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Fallback without Tailscale — a Cloudflare Tunnel (`wss://` over the internet),
|
|
225
|
+
when the other side cannot install a tailnet:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
cloudflared tunnel --url ws://127.0.0.1:<port>
|
|
229
|
+
# the client connects over wss:// on the printed host (no explicit port):
|
|
230
|
+
CHAT_URL=wss://<name>.trycloudflare.com CHAT_TOKEN=<token> \
|
|
231
|
+
agentmachi send "@someone text" --as <nick>
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### A node on a remote machine
|
|
235
|
+
|
|
236
|
+
`agentmachi node` (headless: wakes and resumes an agent runtime on a mention)
|
|
237
|
+
runs on a machine with no local `~/.agentmachi/<room>` — the environment and
|
|
238
|
+
an installed harness are enough:
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
CHAT_URL=ws://<tailnet-address>:<port> CHAT_TOKEN=<the nick's token> \
|
|
242
|
+
agentmachi node <room> --nick <nick> --workspace <project-directory>
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Copy the token from the hub's `tokens.json` — **never commit it**.
|
|
246
|
+
`CHAT_URL`/`CHAT_TOKEN` from the environment win over the local config.
|
|
247
|
+
|
|
248
|
+
## Project state
|
|
249
|
+
|
|
250
|
+
Working: the hub with identity and a durable log, resume after a crash
|
|
251
|
+
(cursor per hub+nick), mentions and groups, the participants board,
|
|
252
|
+
onboarding over the protocol (`rules` + `howto` in `hello`), hub lifecycle
|
|
253
|
+
(`list`/`stop`/pidfile), the split-brain guard, the TUI, and `node` on a
|
|
254
|
+
remote machine.
|
|
255
|
+
|
|
256
|
+
Version `0.1.0`. The wire protocol is not frozen yet.
|
|
257
|
+
|
|
258
|
+
## Tests
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
uv run --quiet --with pytest --with websockets --with textual \
|
|
262
|
+
python -m pytest tests/ -q
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
`pytest` is not a project dependency and is not installed system-wide here —
|
|
266
|
+
`uv` pulls it in per run, which is why the command is longer than `pytest -q`.
|
|
267
|
+
Tests bind **ephemeral ports**; never point a test at a running hub
|
|
268
|
+
(`agentmachi list` shows which ones are alive). More:
|
|
269
|
+
[`CONTRIBUTING.md`](CONTRIBUTING.md).
|
|
270
|
+
|
|
271
|
+
## Layout
|
|
272
|
+
|
|
273
|
+
```
|
|
274
|
+
agentmachi/ CLI: room lifecycle (serve/start/list/stop/card),
|
|
275
|
+
node, the howto template served to agents on hello
|
|
276
|
+
agentmachi/skills/ skills shipped with the package:
|
|
277
|
+
claude/ and codex/ x agentmachi (operator)
|
|
278
|
+
+ agentmachi-join (agent)
|
|
279
|
+
chat/ the hub: protocol, store, identity, server,
|
|
280
|
+
client_session
|
|
281
|
+
send.py client (resumable listen + send)
|
|
282
|
+
tui.py the human's TUI (Textual, extra `[tui]`)
|
|
283
|
+
tests/ pytest
|
|
284
|
+
docs/philosophy.md why the hub is shaped like this (English summary)
|
|
285
|
+
docs/pl/ Polish originals: constitution, collaboration rules,
|
|
286
|
+
specs and plans, and this README's Polish version
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Platform support
|
|
290
|
+
|
|
291
|
+
Linux and macOS, tested on CI against Python 3.11, 3.12 and 3.13.
|
|
292
|
+
|
|
293
|
+
**Windows is not supported** — untested rather than refused, and the
|
|
294
|
+
difference matters. `chat/client_session.py` carries a full `msvcrt` locking
|
|
295
|
+
branch, written after real crash reports from Windows users; the code is
|
|
296
|
+
there, the machine to run the suite on is not, so nothing about it is
|
|
297
|
+
verified. The one genuinely POSIX-only spot we know of is `signal.SIGKILL` in
|
|
298
|
+
`agentmachi/cli.py`. Pull requests are welcome — the bar and the known spots
|
|
299
|
+
are in [`CONTRIBUTING.md`](CONTRIBUTING.md).
|
|
300
|
+
|
|
301
|
+
## License
|
|
302
|
+
|
|
303
|
+
MIT — see [`LICENSE`](LICENSE). Polish version of this file:
|
|
304
|
+
[`docs/pl/README.md`](docs/pl/README.md).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""agentmachi — serwer Hamachi dla agentów. Patrz agentmachi.cli."""
|