agent-mailbox 0.5.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.
Files changed (44) hide show
  1. agent_mailbox-0.5.0/.github/workflows/ci.yml +22 -0
  2. agent_mailbox-0.5.0/.github/workflows/publish.yml +42 -0
  3. agent_mailbox-0.5.0/.gitignore +9 -0
  4. agent_mailbox-0.5.0/Dockerfile +17 -0
  5. agent_mailbox-0.5.0/LICENSE +21 -0
  6. agent_mailbox-0.5.0/PKG-INFO +308 -0
  7. agent_mailbox-0.5.0/README.es.md +185 -0
  8. agent_mailbox-0.5.0/README.fr.md +185 -0
  9. agent_mailbox-0.5.0/README.md +281 -0
  10. agent_mailbox-0.5.0/README.pt-BR.md +185 -0
  11. agent_mailbox-0.5.0/README.ru.md +185 -0
  12. agent_mailbox-0.5.0/README.zh-CN.md +206 -0
  13. agent_mailbox-0.5.0/docs/architecture-en.html +14786 -0
  14. agent_mailbox-0.5.0/docs/architecture-en.json +176 -0
  15. agent_mailbox-0.5.0/docs/architecture.png +0 -0
  16. agent_mailbox-0.5.0/docs/board-dark.png +0 -0
  17. agent_mailbox-0.5.0/docs/board-light.png +0 -0
  18. agent_mailbox-0.5.0/docs/index.html +134 -0
  19. agent_mailbox-0.5.0/pyproject.toml +47 -0
  20. agent_mailbox-0.5.0/scripts/install-watch-linux.sh +39 -0
  21. agent_mailbox-0.5.0/scripts/install-watch-macos.sh +38 -0
  22. agent_mailbox-0.5.0/scripts/install-watch-windows.ps1 +39 -0
  23. agent_mailbox-0.5.0/scripts/wake-zc.sh +102 -0
  24. agent_mailbox-0.5.0/skills/agent-mailbox/SKILL.md +93 -0
  25. agent_mailbox-0.5.0/src/agent_mailbox/__init__.py +3 -0
  26. agent_mailbox-0.5.0/src/agent_mailbox/__main__.py +8 -0
  27. agent_mailbox-0.5.0/src/agent_mailbox/cleanup.py +177 -0
  28. agent_mailbox-0.5.0/src/agent_mailbox/reap.py +44 -0
  29. agent_mailbox-0.5.0/src/agent_mailbox/server.py +268 -0
  30. agent_mailbox-0.5.0/src/agent_mailbox/store.py +893 -0
  31. agent_mailbox-0.5.0/src/agent_mailbox/watch.py +119 -0
  32. agent_mailbox-0.5.0/src/agent_mailbox/web.py +383 -0
  33. agent_mailbox-0.5.0/src/agent_mailbox/webhook.py +184 -0
  34. agent_mailbox-0.5.0/tests/test_acked_reclaim.py +118 -0
  35. agent_mailbox-0.5.0/tests/test_cleanup.py +131 -0
  36. agent_mailbox-0.5.0/tests/test_compensation.py +193 -0
  37. agent_mailbox-0.5.0/tests/test_dedup.py +222 -0
  38. agent_mailbox-0.5.0/tests/test_e2e_stdio.py +161 -0
  39. agent_mailbox-0.5.0/tests/test_mcp_e2e.py +17 -0
  40. agent_mailbox-0.5.0/tests/test_self_echo.py +170 -0
  41. agent_mailbox-0.5.0/tests/test_store.py +226 -0
  42. agent_mailbox-0.5.0/tests/test_tasks.py +216 -0
  43. agent_mailbox-0.5.0/tests/test_web.py +161 -0
  44. agent_mailbox-0.5.0/tests/test_webhook.py +238 -0
@@ -0,0 +1,22 @@
1
+ name: CI
2
+ on:
3
+ push: { branches: [main] }
4
+ pull_request:
5
+ jobs:
6
+ test:
7
+ runs-on: ${{ matrix.os }}
8
+ env:
9
+ PYTHONUTF8: "1"
10
+ PYTHONIOENCODING: utf-8
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ os: [ubuntu-latest, macos-latest, windows-latest]
15
+ python: ["3.11", "3.13"]
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: actions/setup-python@v5
19
+ with: { python-version: "${{ matrix.python }}" }
20
+ - run: pip install -e ".[dev]"
21
+ - run: ruff check src tests
22
+ - run: pytest
@@ -0,0 +1,42 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-python@v5
15
+ with:
16
+ python-version: "3.12"
17
+ - name: Install build tools
18
+ run: |
19
+ python -m pip install --upgrade pip
20
+ pip install build
21
+ - name: Build sdist and wheel
22
+ run: python -m build
23
+ - uses: actions/upload-artifact@v4
24
+ with:
25
+ name: dist
26
+ path: dist/
27
+
28
+ publish:
29
+ needs: build
30
+ runs-on: ubuntu-latest
31
+ environment: pypi
32
+ permissions:
33
+ # Trusted Publisher 必需:OIDC token 而非 API token,无需邮箱验证密码
34
+ id-token: write
35
+ steps:
36
+ - uses: actions/download-artifact@v4
37
+ with:
38
+ name: dist
39
+ path: dist/
40
+ - name: Publish to PyPI
41
+ uses: pypa/gh-action-pypi-publish@release/v1
42
+ # 无 username/password —— 走 Trusted Publisher(GitHub OIDC)
@@ -0,0 +1,9 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.egg-info/
4
+ .pytest_cache/
5
+ .test-mail/
6
+ dist/
7
+ .env
8
+ .test-mail2/
9
+ .mimosa/
@@ -0,0 +1,17 @@
1
+ # agent-mailbox MCP server — starts over stdio by default.
2
+ # Glama introspection: server must start and respond to MCP initialize over stdio.
3
+ FROM python:3.12-slim
4
+
5
+ WORKDIR /app
6
+
7
+ COPY pyproject.toml README.md ./
8
+ COPY src ./src
9
+
10
+ RUN pip install --no-cache-dir .
11
+
12
+ # Default mail root inside container; mount a volume to persist.
13
+ ENV AGENT_MAIL_HOME=/data
14
+ VOLUME ["/data"]
15
+
16
+ # stdio transport (default). Glama runs this and speaks MCP over stdio.
17
+ ENTRYPOINT ["agent-mailbox"]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NoFox Team
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,308 @@
1
+ Metadata-Version: 2.5
2
+ Name: agent-mailbox
3
+ Version: 0.5.0
4
+ Summary: Give every local AI agent its own mailbox — one MCP server, register once, message any agent on this machine.
5
+ Project-URL: Homepage, https://github.com/polaris-smart/agent-mailbox
6
+ Project-URL: Issues, https://github.com/polaris-smart/agent-mailbox/issues
7
+ Author: polaris-smart
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: agent,local-first,mailbox,mcp,message,multi-agent
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Communications :: Email
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: mcp>=2.1
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest-timeout>=2.4; extra == 'dev'
24
+ Requires-Dist: pytest>=8; extra == 'dev'
25
+ Requires-Dist: ruff>=0.6; extra == 'dev'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # agent-mailbox
29
+
30
+ [![polaris-smart/agent-mailbox MCP server](https://glama.ai/mcp/servers/polaris-smart/agent-mailbox/badges/score.svg)](https://glama.ai/mcp/servers/polaris-smart/agent-mailbox)
31
+ [![npm](https://img.shields.io/npm/v/agent-mailbox)](https://www.npmjs.com/package/agent-mailbox)
32
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
33
+
34
+ **Give every AI agent its own mailbox.** One stdio MCP server. Zero daemons. One JSON file per message. Plus a built-in task board: cards wake their assignee when they move, and a zero-dependency web kanban for the human.
35
+
36
+ > **📊 Production-proven**: 1,787 messages across 5 agents (Claude Code, Hermes, Codex-based, webhook wake) in 17 days of daily multi-agent software development — 105 messages/day, zero data loss.
37
+
38
+ Other docs: [中文](README.zh-CN.md) · [Español](README.es.md) · [Português](README.pt-BR.md) · [Français](README.fr.md) · [Русский](README.ru.md)
39
+
40
+ ## Why not just use MCP / Slack / raw files?
41
+
42
+ | Approach | Cross-CLI | Async | Wake-up | Human board | Deps |
43
+ |---|---|---|---|---|---|
44
+ | **agent-mailbox** | ✅ any MCP host | ✅ inbox persists | ✅ webhook + task cards | ✅ built-in kanban | **0** |
45
+ | Raw MCP tools | ❌ per-CLI sessions | ❌ lost on restart | ❌ | ❌ | — |
46
+ | Slack/Discord bot | ✅ | ✅ | ✅ | ❌ | API tokens, rate limits, cloud dependency |
47
+ | Shared files + conventions | ✅ | ⚠️ ad-hoc | ❌ manual | ❌ | your own locking code |
48
+
49
+ The gap agent-mailbox fills: **agents on different CLIs, on the same machine, messaging each other asynchronously — with delivery guarantees and a human-visible board — without a single dependency.**
50
+
51
+ > 🆕 **v0.3.0 — Task board**: agents now share a task surface on the same mail root. 3 new MCP tools (12 total), a zero-dependency drag-and-drop board (`--web`), and every move messages the assignee. ⚠️ **Upgrade note:** restart your agent session to pick up the new tools. → [Task board](#task-board)
52
+
53
+ ---
54
+
55
+ ## The problem
56
+
57
+ Run several AI agents on one machine — Claude Code, Hermes, your own scripts — and they have no way to leave each other messages. Agents overlap, wait on each other, or you end up copy-pasting between their windows like a human switchboard.
58
+
59
+ ## The fix
60
+
61
+ A mailbox is a directory of plain JSON files:
62
+
63
+ ```
64
+ ~/.agent-mail/
65
+ registry.json agent_id → {owner, description, created_at}
66
+ inbox/HS/20260905-….json one file per message
67
+ archive/HS/…
68
+ tasks.json the task board ({"next
69
+
70
+
71
+ A mailbox is a directory of plain JSON files:
72
+
73
+ ```
74
+ ~/.agent-mail/
75
+ registry.json agent_id → {owner, description, created_at}
76
+ inbox/HS/20260905-….json one file per message
77
+ archive/HS/…
78
+ tasks.json the task board ({"next_id", "tasks": {id: card}})
79
+ ```
80
+
81
+ Agents read and write it through a small stdio MCP server. No broker process, no ports, no database, no network by default. Any number of MCP host processes share one mail root safely (file-lock guarded).
82
+
83
+ ![agent-mailbox architecture](docs/architecture.png)
84
+
85
+ ## Quick start
86
+
87
+ **Prerequisites** — one-time: install [uv](https://docs.astral.sh/uv/) (`curl -LsSf https://astral.sh/uv/install.sh | sh` on macOS/Linux, or `powershell -c "irm https://astral.sh/uv/install.ps1 | iex"` on Windows). `uvx` runs everything else; nothing else to install.
88
+
89
+ ### 1 · Register the server with your MCP host
90
+
91
+ Claude Code:
92
+
93
+ ```bash
94
+ claude mcp add agent-mailbox -- uvx --from git+https://github.com/polaris-smart/agent-mailbox agent-mailbox
95
+ ```
96
+
97
+ Any MCP host (generic JSON):
98
+
99
+ ```json
100
+ {
101
+ "mcpServers": {
102
+ "agent-mailbox": {
103
+ "command": "uvx",
104
+ "args": ["--from", "git+https://github.com/polaris-smart/agent-mailbox", "agent-mailbox"]
105
+ }
106
+ }
107
+ }
108
+ ```
109
+
110
+ Tip: set `AGENT_MAIL_ID=HS` (or whichever id) in the agent's environment and every tool becomes self-addressed — no need to pass `agent_id` on each call.
111
+
112
+ ### 2 · Agents register once
113
+
114
+ ```json
115
+ { "tool": "mailbox_register", "arguments": { "agent_id": "HS", "owner": "Hermes", "description": "PM & QA" } }
116
+ ```
117
+
118
+ Registration is idempotent. Every registered agent is immediately addressable by everyone — including a human `boss` id you can read yourself.
119
+
120
+ ### 3 · Send, check, reply
121
+
122
+ ```json
123
+ { "tool": "mailbox_send", "arguments": { "to": "HS", "subject": "deploy ready", "body": "v0.1.0 is staged, please verify." } }
124
+ { "tool": "mailbox_check", "arguments": {} }
125
+ { "tool": "mailbox_reply", "arguments": { "msg_id": "20260905-…-hs", "body": "verified, marked done." } }
126
+ ```
127
+
128
+ `mailbox_check` fetches pending messages and marks them `acked`. Lifecycle: `pending → acked → done`, then optionally archived. A message is one JSON file you can `cat` — the boss reads the inbox directly.
129
+
130
+ ### 4 · Wait instead of poll
131
+
132
+ `mailbox_wait` blocks (long-poll) until a message arrives — call it as the last action of a turn:
133
+
134
+ ```json
135
+ { "tool": "mailbox_wait", "arguments": { "timeout_seconds": 25 } }
136
+ ```
137
+
138
+ ## Task board
139
+
140
+ Task cards live in `<mail-root>/tasks.json` (plain JSON, same file lock as the mail). The state machine is strict: `todo→doing→review→done`, non-adjacent moves rejected unless `force=True`; `done` is terminal. Creating or moving a card sends the assignee a normal mailbox message (`[task#t-12 → review] …`) — so board motion wakes the owning agent through the existing inbox, no polling, no webhooks. Self-assigned moves stay silent, and `notify=False` opts out.
141
+
142
+ **Web board (for the human).** `agent-mailbox --web 8643` serves a zero-dependency kanban (stdlib `http.server` + one embedded HTML page, no framework) on `127.0.0.1`. Four lanes mirror the state machine; drag a card between adjacent lanes to move it, or create cards from the form; one toggle switches light/dark themes. Auth is a bearer token — set `AGENT_MAIL_WEB_TOKEN` for a stable one, or a fresh token is printed at boot (open `http://127.0.0.1:8643/?token=…`). The board acts as agent `boss`: every card you create or drag still auto-messages the assignee, so even human drag-and-drop wakes the right agent. The page auto-refreshes every 5 seconds.
143
+
144
+ ## For AI agents (hand this section to your agent)
145
+
146
+ If you are an AI agent reading this because your user said "install agent-mailbox": do exactly this, no questions needed.
147
+
148
+ 1. Check `uvx --version`; if missing, install uv with the Prerequisites command above for the OS you are running on.
149
+ 2. Register the MCP server with your host app — use the generic JSON above (step 1) or your host's native mechanism.
150
+ 3. Pick an identity: any id matching `[A-Za-z0-9_-]` up to 64 chars. Set `AGENT_MAIL_ID=<id>` in your environment, then call `mailbox_register` once (it is idempotent).
151
+ 4. Verify: call `mailbox_whoami` — you should appear in the registry. You are now addressable by every other agent on this machine.
152
+ 5. Report your id back to your user so they can send you mail at `~/.agent-mail/inbox/<id>/`.
153
+
154
+ Read English even if your user speaks another language — the commands are identical everywhere.
155
+
156
+ ## Works with your agent CLI
157
+
158
+ Anything that speaks MCP stdio can host a mailbox — which is all of them. Register per your CLI's config:
159
+
160
+ | Agent CLI | How to register |
161
+ |-----------|-----------------|
162
+ | Claude Code | `claude mcp add agent-mailbox -- uvx --from git+https://github.com/polaris-smart/agent-mailbox agent-mailbox` |
163
+ | Gemini CLI | `~/.gemini/settings.json` → `"mcpServers": { … }` (same JSON as Quick start) |
164
+ | Qwen Code | same as Gemini CLI (`~/.qwen/settings.json`) |
165
+ | Codex CLI | `~/.codex/config.toml` → `[mcp_servers.agent-mailbox]` with `command` / `args` |
166
+ | OpenCode | `opencode.json` → `"mcp": { "agent-mailbox": { "type": "local", "command": ["uvx", "--from", "git+https://github.com/polaris-smart/agent-mailbox", "agent-mailbox"] } }` |
167
+ | Hermes / Ark CLI / veCLI / OpenClaw / any MCP host | same generic JSON — point `command` at the `uvx` line above |
168
+
169
+ Then set `AGENT_MAIL_ID` for that CLI's sessions and `mailbox_register` once. Agents on the same machine can now message each other **across different CLIs** — a Claude Code agent and a Gemini CLI agent share the same mail root with zero extra setup.
170
+
171
+ ## Waking a sleeping agent (one config line)
172
+
173
+ If the receiving agent isn't even running, `mailbox_send` itself can POST every new message to a webhook the moment it lands — no daemon, no polling, no extra process:
174
+
175
+ ```json
176
+ // ~/.agent-mail/webhook.json (chmod 600)
177
+ { "url": "http://localhost:8644/webhooks/agent-mailbox", "secret": "…" }
178
+ ```
179
+
180
+ Generate the secret yourself once: `openssl rand -hex 32`. Omit it for unsigned posts (fine for local testing; your receiver decides whether to require it).
181
+
182
+ Your host's webhook handler receives:
183
+
184
+ ```json
185
+ { "event": "agent_mailbox_new_message", "event_type": "agent_mailbox_new_message", "message": { "id": "…", "from": "ZC", "to": "HS", "subject": "…", "body": "…" } }
186
+ ```
187
+
188
+ …wakes the agent, and the agent calls `mailbox_check` on arrival. That is the whole integration.
189
+
190
+ - Signed `X-Hub-Signature-256: sha256=<hmac>` (GitHub scheme — accepted by Hermes gateway and most webhook consumers).
191
+ - The signature style is configurable via `AGENT_MAIL_SIGNATURE_STYLE`: `github` (default, `X-Hub-Signature-256: sha256=<hex>`) / `generic` (`X-Webhook-Signature: <hex>`, bare hex) / `slack` (`X-Slack-Signature: v0=<hex>`; this webhook emits no `ts` field, so receivers must NOT verify against the full Slack `v0:ts:body` base string).
192
+ - The target is pinned: http/https only, loopback/private addresses by default, redirects refused, system proxy bypassed.
193
+ - Env vars `AGENT_MAIL_WEBHOOK_URL` / `AGENT_MAIL_WEBHOOK_SECRET` override the file. Unset → fully offline.
194
+ - The config file is resolved **per mail root** (the store's own `webhook.json`), so a `MailStore(root=…)` built on a scratch root can never wake the production gateway. `webhook.json` in the default home still covers normal use.
195
+ - Every delivered mail is also appended to `<mail-root>/sent.log` (one JSONL line: id/from/to/subject/created_at) under the same lock as the write — a webhook notification with no matching `sent.log` line never was a mail.
196
+
197
+ ### Self-echo protection (on by default)
198
+
199
+ A notification whose sender equals its target (self-echo, from == to) is **not delivered** by default: the letter still lands on disk, `mailbox_list` / `mailbox_check` are unaffected — the sender just isn't woken by its own send. The drop is audited in `sent.log` as `echo_suppressed: true`, so "there was a notification but no mail"-style disputes stay one grep away. Set `notify_self_echo: true` in the mail root's `config.json` (or env `AGENT_MAIL_NOTIFY_SELF_ECHO`) to restore delivery — restored self-echo notifications carry an `[echo] ` subject prefix (the stored letter keeps its original subject, so the prefix is regex-strippable).
200
+
201
+ ### Duplicate suppression (v0.5.0, delivery-side)
202
+
203
+ Every letter stores a `semantic_hash` — `sha256(norm(subject) + "\0" + norm(prose) + "\0" + code_regions.join("\0"))`. Prose is NFKC/NFC normalized with whitespace folded; fenced ` ``` ` code regions are lifted out first and hashed **raw, in their original order** (zero folding — reordering code must not collapse to one hash). Legacy letters without the field never match (old mail is never back-filled).
204
+
205
+ `mailbox_send` / `mailbox_broadcast` dedupe by default: if the target inbox already holds a same-hash letter in a non-terminal state (`pending`/`acked`) inside the dedup window, no new mail is created. **How callers notice**: that recipient's entry in the result carries `"deduped": true` and `"existing_id"` — nothing lands on disk, nothing is appended to `sent.log`, and no webhook fires (zero side effects). `"count"` counts only letters that actually landed. Pass `dedupe: false` to exempt a send; replies (`mailbox_reply`) are exempt by design. Only inboxes are consulted, never archives — a same-hash letter that is `done` or archived never blocks a re-send.
206
+
207
+ - **Scope boundary (by design)**: normalization keeps timestamps verbatim, so periodic jobs whose bodies embed dates naturally hash differently — A does **not** stop them. A prevents same-semantics repeat wakes; periodic-task replay protection relies on the B compensation flow (below) plus `dedupe: false`.
208
+ - **Expected, not a bug**: once the dedup window passes, a re-send goes through, so the queue can legitimately hold two same-hash non-terminal letters (the older one is still being handled).
209
+ - **铁1 config coupling**: the reclaim window must stay strictly below the dedup window — `reap_ttl` (default 3600s) **<** `dedup_ttl` (default 86400s = 24h), both settable in `<mail-root>/config.json`. Violating configs fail loudly (`MailboxError`) at load time instead of silently distorting the windows.
210
+
211
+ ### Half-done handling: compensation + reclaim (v0.5.0)
212
+
213
+ An agent that claims mail (`check` → `acked`) and dies leaves it invisible to a pending-only drain. v0.5 closes the loop three ways:
214
+
215
+ - **Two-phase handled_log**: the handling layer records `intent` when it starts and `outcome` when finished via `MailStore.record_handled(agent_id, msg_id, action)` — the store is the single writer (mail-root flock), so `handled_log` stays append-only and auditable across sessions. `set_status(done)` comes last.
216
+ - **Compensation table**: `MailStore.resume_plan(agent_id, msg_id)` reads only the log ("which segment did it reach?") and returns `resume`: `process` (pending, or acked with no records at all — claimed then crashed before the intent — same treatment: handle normally from the intent), `replay` (intent without outcome: idempotently redo the handling body), `finalize` (intent + outcome recorded but not `done` yet: only the status flip remains), `skip` (terminal).
217
+ - **Stale-`acked` reclaim**: `python -m agent_mailbox.reap --agent ID --ttl 7200` flips `acked` mail older than the TTL back to `pending` with a `reclaimed` entry in `handled_log` (acked → reclaimed → done stays auditable end to end). Defaults: TTL 3600s in the store, 7200s in the wake script. A letter whose newest `intent` is fresher than 30 minutes is deferred — someone is actively on it. Reclaim is a manual maintenance operation: nothing in the library calls it automatically; the deployed wake script is the only wired caller.
218
+ - **Wake wiring**: `scripts/wake-zc.sh` reaps **before** counting pending on every loop iteration (fail-open with an explicit log line) and carries a **circuit breaker**: after N consecutive no-progress drain rounds it latches (`~/.agent-mail/wake-zc.breaker`, auto-expires after 6h) and stops launching drain turns — backoff stretches the interval, the breaker stops the bleeding.
219
+
220
+ ## The tools
221
+
222
+ | Tool | Notes |
223
+ |------|-------|
224
+ | `mailbox_register(agent_id, owner?, description?)` | claim a mailbox; idempotent |
225
+ | `mailbox_send(to, subject, body, priority?)` | `to` = one id, a list, or `"all"`; `dedupe?` (default true) suppresses same-hash repeats (see above) |
226
+ | `mailbox_check(agent_id?, mark?)` | fetch pending (→ `acked`) |
227
+ | `mailbox_reply(msg_id, body)` | routes back to the original sender (dedupe-exempt) |
228
+ | `mailbox_list(agent_id?, status?)` | list messages, optional status filter |
229
+ | `mailbox_done(msg_id)` | mark handled |
230
+ | `mailbox_broadcast(subject, body)` | to every registered agent; `dedupe?` per recipient |
231
+ | `mailbox_whoami()` | directory of agents + mail root |
232
+ | `mailbox_wait(agent_id?, timeout_seconds?)` | long-poll for new mail |
233
+ | `task_create(title, assignee, due?)` | create a task card (starts `todo`); assignee auto-messaged |
234
+ | `task_move(task_id, status, assignee?, note?, force?)` | move along `todo→doing→review→done` (skips need `force`); moving a card auto-messages its owner |
235
+ | `task_list(assignee?, status?)` | list task cards, optional filters |
236
+
237
+ Identity: pass `agent_id` explicitly, or set `AGENT_MAIL_ID` once per agent.
238
+
239
+ ## Optional: desktop notifications for humans
240
+
241
+ A companion watcher prints every new message as a JSON line and fires desktop notifications (macOS / Linux / Windows). It is never on the agent wake-up path — agents don't need it:
242
+
243
+ ```bash
244
+ uvx --from git+https://github.com/polaris-smart/agent-mailbox agent-mailbox-watch --notify boss
245
+ ```
246
+
247
+ Run it as a service on your platform:
248
+
249
+ | Platform | Install | Verify |
250
+ |----------|---------|--------|
251
+ | macOS (launchd) | `scripts/install-watch-macos.sh --notify boss` | `tail -f ~/.agent-mail/watch.log` |
252
+ | Linux (systemd user) | `scripts/install-watch-linux.sh …` | `journalctl --user -u agent-mailbox-watch -f` |
253
+ | Windows (schtasks) | `scripts\install-watch-windows.ps1` | `schtasks /Query /TN AgentMailboxWatch /V` |
254
+
255
+ ## Maintenance: cleaning up test residue (cleanup)
256
+
257
+ ```bash
258
+ python -m agent_mailbox.cleanup --dry-run # list only (the default behaviour)
259
+ python -m agent_mailbox.cleanup --dry-run --root ~/.agent-mail
260
+ python -m agent_mailbox.cleanup --yes # actually delete: explicit --yes + interactive confirmation
261
+ ```
262
+
263
+ Scans the mail root and lists **suspected test residue**: agent `inbox/` / `archive/` directories missing from registry.json, `NEWBIE` / `WBTEST`-style test-named directories, and orphan letters (stray files directly under `inbox/` / `archive/`, unparseable JSON, `*.tmp` left by an interrupted atomic write). Each finding prints path + size + reason; `--dry-run` (and the flagless default) deletes nothing; `--yes` deletes for real but requires typing `yes` to confirm. Registered-but-test-named directories are reported as review-only and never deleted. The `--yes` capability shipped with v0.4.0 — actually running it against a production mail root remains an explicit operator (boss) approval, separate from the release.
264
+
265
+ ## Design
266
+
267
+ - **Local-first** — plain JSON files under `~/.agent-mail/`. No SMTP, no IMAP, no domain, no cloud relay, no network by default.
268
+ - **Register-once addressing** — `mailbox_register("HS")` is all it takes; every registered agent is immediately addressable by everyone.
269
+ - **Zero external dependencies** — only `mcp`. The store is one Python file with `flock`-guarded atomic writes; multiple MCP host processes share one mail root safely.
270
+ - **Human-readable** — every message is a small JSON file you can `cat`. The boss reads the inbox directly.
271
+ - **Honors existing identities** — set `AGENT_MAIL_ID` in each agent's environment and its tools become self-addressed.
272
+
273
+ ## Security notes
274
+
275
+ - Mail root lives in your home directory; messages never leave the machine unless you opt into the webhook, which is pinned to loopback/private targets by default.
276
+ - Agent ids are strictly validated (`[A-Za-z0-9_-]`, ≤64 chars) — no path traversal.
277
+ - The store is append-oriented with atomic writes and file locks; a crashed writer cannot corrupt the registry.
278
+ - Webhook payloads are HMAC-signed; verifiers should compare with a constant-time function.
279
+ - For tamper-evidence, signed receipts (ed25519) are on the roadmap.
280
+
281
+ ## Development
282
+
283
+ ```bash
284
+ git clone https://github.com/polaris-smart/agent-mailbox && cd agent-mailbox
285
+ uv venv && uv pip install -e ".[dev]"
286
+ pytest
287
+ ```
288
+
289
+ ## Upgrading
290
+
291
+ Upgrade with `uv tool upgrade agent-mailbox` (or re-pull however you installed it).
292
+
293
+ ⚠️ **Restart your agent session (or reconnect the MCP client) after upgrading** — MCP tool lists are enumerated at session start, so new tools (12 now, was 9) only appear after a restart. No config changes needed; `tasks.json` is created automatically on first use.
294
+
295
+ ## Roadmap
296
+
297
+ - **v0.5.0** (current) — lifecycle hardening from the 2026-09-13 incidents (task `t-6`): **duplicate suppression** (delivery-side `semantic_hash`, same-hash non-terminal repeats within a 24h window return `{"deduped": true, "existing_id"}` with zero side effects; `dedupe: false` exempts; code-fence-raw hashing, inbox-only scope, hash→inbox index); **half-done compensation** (`record_handled` two-phase intent/outcome API as the single `handled_log` writer + `resume_plan` four-row table: process / replay / finalize / skip); **stale-`acked` reclaim** shipped earlier as `reap_stale_acked` / `python -m agent_mailbox.reap` now wired into the wake loop (reap first, count second, fail-open) with the 铁1 coupling enforced — `reap_ttl` (3600s) must stay strictly below `dedup_ttl` (24h), violations fail loudly; **wake circuit breaker** — N consecutive no-progress drain rounds latch a breaker file and stop launching turns (backoff stretches the interval, the breaker stops the bleeding).
298
+ - **v0.5.x (open)** — still tracked from the t-6 reviews, not in this release: `status filtering` (ask for "pending or acked" views), `identity binding` (bind MCP callers to `AGENT_MAIL_ID` against foreign checks; today's model is local trust — anyone on the machine can read any box), `wake routing` (gateway subscription `to`-filter; lives outside this repo), `unread_count` in webhook payloads, claim semantics for `mailbox_wait` (P1–P4 from the 09-13 forensics).
299
+ - **v0.4.0** — feature batch: configurable webhook signature style (`AGENT_MAIL_SIGNATURE_STYLE`: github default / generic / slack); self-echo protection (notifications where sender == target are dropped by default, audited as `echo_suppressed` in `sent.log`; `notify_self_echo` / `AGENT_MAIL_NOTIFY_SELF_ECHO` restores delivery with an `[echo] ` subject prefix on the notification while the letter keeps its subject); new `cleanup --dry-run` maintenance command (scans for test residue, lists without deleting, `--yes` deletes after confirmation).
300
+ - **v0.3.1** — patch batch: reply subjects no longer pile up `Re: Re:` (first reply, re-replies, and mixed-case prefixes all normalize to a single `Re:`); web board tokens use constant-time comparison (`hmac.compare_digest`) and persist across reboots (`~/.agent-mail/web_token`, mode 0600, `AGENT_MAIL_WEB_TOKEN` env always wins); `sent.log` auto-rotates one generation past 10 MB (to `sent.log.1`).
301
+ - **v0.3.0** — task board + web kanban: `task_create` / `task_move` / `task_list` with a strict todo→doing→review→done state machine; creating or moving a card auto-messages the assignee, so board motion wakes agents with zero polling. `--web 8643` serves a token-protected zero-dependency kanban UI where human drag-and-drop goes through the same wake-up path. Messages + tasks + wake-up + board, still zero dependencies.
302
+ - **v0.5.0+** — maybe: deeper kanban integrations (Kaneo as reference/competitor). Under discussion.
303
+ - **Next** — federation: streamable HTTP transport for agents on other machines (Tailscale/LAN friendly); signed receipts (ed25519) for tamper-evident delivery.
304
+ - **v1.0.0** — cross-organization bridge: local threads reach agents on other machines and organizations over standard email infrastructure, with the same mailbox lifecycle.
305
+
306
+ ## License
307
+
308
+ MIT
@@ -0,0 +1,185 @@
1
+ # agent-mailbox
2
+
3
+ [![polaris-smart/agent-mailbox MCP server](https://glama.ai/mcp/servers/polaris-smart/agent-mailbox/badges/score.svg)](https://glama.ai/mcp/servers/polaris-smart/agent-mailbox)
4
+
5
+ **Un buzón propio para cada agente de IA local.** Un servidor MCP por stdio. Cero demonios. Un archivo JSON por mensaje. Más un tablero de tareas integrado: las tarjetas despiertan a su responsable al moverse, y un kanban web sin dependencias para el humano.
6
+
7
+ 📖 **Docs**: [English](README.md) · [中文](README.zh-CN.md) · [Español](README.es.md) · [Português](README.pt-BR.md) · [Français](README.fr.md) · [Русский](README.ru.md)
8
+
9
+ > 🆕 **v0.3.0 — Tablero de tareas**: los agentes comparten ahora una superficie de tareas sobre la misma raíz de correo. 3 herramientas MCP nuevas (12 en total), un tablero de arrastrar y soltar sin dependencias (`--web`), y cada movimiento avisa al responsable. ⚠️ **Nota de actualización**: reinicia tu sesión de agente para cargar las herramientas nuevas. → [Tablero de tareas](#tablero-de-tareas)
10
+
11
+ ---
12
+
13
+ ## El problema
14
+
15
+ Ejecutar varios agentes de IA en una misma máquina — Claude Code, Hermes, tus propios scripts — y no tienen forma de dejarse mensajes. Se quedan esperándose, o terminas copiando y pegando entre ventanas como un operador humano.
16
+
17
+ ## La solución
18
+
19
+ Un buzón es un directorio de archivos JSON simples:
20
+
21
+ ```
22
+ ~/.agent-mail/
23
+ registry.json agent_id → {owner, description, created_at}
24
+ inbox/HS/20260905-….json un archivo por mensaje
25
+ archive/HS/…
26
+ tasks.json el tablero de tareas ({"next_id", "tasks": {id: tarjeta}})
27
+ ```
28
+
29
+ Los agentes lo leen y escriben a través de un pequeño servidor MCP por stdio. Sin proceso intermediario, sin puertos, sin base de datos, sin red por defecto. Cualquier número de hosts MCP comparten una misma raíz de correo de forma segura (con bloqueo de archivos).
30
+
31
+ ## Inicio rápido
32
+
33
+ **Requisitos previos** — solo una vez: instala [uv](https://docs.astral.sh/uv/) (`curl -LsSf https://astral.sh/uv/install.sh | sh` en macOS/Linux, o `powershell -c "irm https://astral.sh/uv/install.ps1 | iex"` en Windows). `uvx` ejecuta todo lo demás; no hay nada más que instalar.
34
+
35
+ ### 1 · Registra el servidor en tu host MCP
36
+
37
+ Claude Code:
38
+
39
+ ```bash
40
+ claude mcp add agent-mailbox -- uvx --from git+https://github.com/polaris-smart/agent-mailbox agent-mailbox
41
+ ```
42
+
43
+ Cualquier host MCP (JSON genérico):
44
+
45
+ ```json
46
+ {
47
+ "mcpServers": {
48
+ "agent-mailbox": {
49
+ "command": "uvx",
50
+ "args": ["--from", "git+https://github.com/polaris-smart/agent-mailbox", "agent-mailbox"]
51
+ }
52
+ }
53
+ }
54
+ ```
55
+
56
+ Consejo: define `AGENT_MAIL_ID=HS` (o el id que quieras) en el entorno del agente y todas las herramientas quedan autodireccionadas — sin pasar `agent_id` en cada llamada.
57
+
58
+ ### 2 · Los agentes se registran una vez
59
+
60
+ ```json
61
+ { "tool": "mailbox_register", "arguments": { "agent_id": "HS", "owner": "Hermes", "description": "PM & QA" } }
62
+ ```
63
+
64
+ El registro es idempotente. Todo agente registrado es direccionable de inmediato por todos — incluido un id humano `boss` que puedes leer tú mismo.
65
+
66
+ ### 3 · Enviar, revisar, responder
67
+
68
+ ```json
69
+ { "tool": "mailbox_send", "arguments": { "to": "HS", "subject": "deploy ready", "body": "v0.1.0 preparada, por favor verifica." } }
70
+ { "tool": "mailbox_check", "arguments": {} }
71
+ { "tool": "mailbox_reply", "arguments": { "msg_id": "20260905-…-hs", "body": "verificado, marcado done." } }
72
+ ```
73
+
74
+ `mailbox_check` trae los mensajes pendientes y los marca `acked`. Ciclo de vida: `pending → acked → done`, y luego se archivan opcionalmente. Cada mensaje es un JSON que puedes `cat` — el jefe lee la bandeja directamente.
75
+
76
+ ### 4 · Esperar en vez de sondear
77
+
78
+ `mailbox_wait` se bloquea (long-poll) hasta que llega un mensaje — llámalo como última acción del turno:
79
+
80
+ ```json
81
+ { "tool": "mailbox_wait", "arguments": { "timeout_seconds": 25 } }
82
+ ```
83
+
84
+ ## Tablero de tareas
85
+
86
+ Las tarjetas viven en `<raíz de correo>/tasks.json` (JSON plano, el mismo bloqueo de archivos que el correo). La máquina de estados es estricta: `todo→doing→review→done`; los saltos no adyacentes se rechazan salvo `force=True`, y `done` es terminal. Crear o mover una tarjeta envía al responsable un mensaje normal del buzón (`[task#t-12 → review] …`) — el movimiento del tablero despierta al agente por la bandeja existente, sin sondeos ni webhooks. Los movimientos que uno se hace a sí mismo permanecen en silencio, y `notify=False` los desactiva.
87
+
88
+ **Tablero web (para el humano).** `agent-mailbox --web 8643` sirve un kanban sin dependencias (`http.server` de stdlib + una sola página HTML embebida, sin frameworks) en `127.0.0.1`. Cuatro columnas reflejan la máquina de estados; arrastra una tarjeta entre columnas adyacentes para moverla, o crea tarjetas desde el formulario, con una sola alternancia entre tema claro y oscuro. La autenticación es un token bearer — define `AGENT_MAIL_WEB_TOKEN` para uno fijo, o se genera e imprime uno nuevo en cada arranque (abre `http://127.0.0.1:8643/?token=…`). El tablero actúa como agente `boss`: cada tarjeta que crees o arrastres sigue avisando al responsable — cada movimiento le envía un mensaje. La página se refresca cada 5 segundos.
89
+
90
+ ## Despertar a un agente dormido (una línea de configuración)
91
+
92
+ Si el agente receptor ni siquiera está en ejecución, `mailbox_send` puede hacer POST de cada mensaje nuevo a un webhook en el instante en que aterriza — sin demonios, sin sondeo, sin procesos extra:
93
+
94
+ ```json
95
+ // ~/.agent-mail/webhook.json (chmod 600)
96
+ { "url": "http://localhost:8644/webhooks/agent-mailbox", "secret": "…" }
97
+ ```
98
+
99
+ Genera el secreto una vez: `openssl rand -hex 32`. Omítelo para POSTs sin firmar (suficiente para pruebas locales; exigir la verificación lo decide el receptor).
100
+
101
+ El manejador de webhooks del host recibe:
102
+
103
+ ```json
104
+ { "event": "agent_mailbox_new_message", "event_type": "agent_mailbox_new_message", "message": { "id": "…", "from": "ZC", "to": "HS", "subject": "…", "body": "…" } }
105
+ ```
106
+
107
+ …despierta al agente, y el agente llama a `mailbox_check` al llegar. Esa es toda la integración.
108
+
109
+ - Firmado con `X-Hub-Signature-256: sha256=<hmac>` (esquema GitHub — aceptado por Hermes gateway y la mayoría de consumidores de webhooks).
110
+ - El destino queda fijado: solo http/https, direcciones loopback/privadas por defecto, redirecciones rechazadas, proxy del sistema omitido.
111
+ - Las variables de entorno `AGENT_MAIL_WEBHOOK_URL` / `AGENT_MAIL_WEBHOOK_SECRET` tienen prioridad sobre el archivo. Sin configurar → totalmente offline.
112
+
113
+ ## Las herramientas
114
+
115
+ | Herramienta | Notas |
116
+ |-------------|-------|
117
+ | `mailbox_register(agent_id, owner?, description?)` | reclama un buzón; idempotente |
118
+ | `mailbox_send(to, subject, body, priority?)` | `to` = un id, una lista, o `"all"` |
119
+ | `mailbox_check(agent_id?, mark?)` | trae pendientes (→ `acked`) |
120
+ | `mailbox_reply(msg_id, body)` | enruta de vuelta al remitente original |
121
+ | `mailbox_list(agent_id?, status?)` | lista mensajes, filtro opcional por estado |
122
+ | `mailbox_done(msg_id)` | marca como atendido |
123
+ | `mailbox_broadcast(subject, body)` | a todos los agentes registrados |
124
+ | `mailbox_whoami()` | directorio de agentes + raíz de correo |
125
+ | `mailbox_wait(agent_id?, timeout_seconds?)` | long-poll de correo nuevo |
126
+ | `task_create(title, assignee, due?)` | crea una tarjeta de tarea (arranca en `todo`); avisa al responsable |
127
+ | `task_move(task_id, status, assignee?, note?, force?)` | avanza por `todo→doing→review→done` (los saltos requieren `force`); mover una tarjeta avisa a su responsable |
128
+ | `task_list(assignee?, status?)` | lista tarjetas de tarea, filtros opcionales |
129
+
130
+ ## Opcional: notificaciones de escritorio para humanos
131
+
132
+ Un watcher complementario imprime cada mensaje nuevo como línea JSON y lanza notificaciones de escritorio (macOS / Linux / Windows). Nunca está en la ruta de despertar de agentes — los agentes no lo necesitan:
133
+
134
+ ```bash
135
+ uvx --from git+https://github.com/polaris-smart/agent-mailbox agent-mailbox-watch --notify boss
136
+ ```
137
+
138
+ | Plataforma | Instalar | Verificar |
139
+ |------------|----------|-----------|
140
+ | macOS (launchd) | `scripts/install-watch-macos.sh --notify boss` | `tail -f ~/.agent-mail/watch.log` |
141
+ | Linux (systemd user) | `scripts/install-watch-linux.sh …` | `journalctl --user -u agent-mailbox-watch -f` |
142
+ | Windows (schtasks) | `scripts\install-watch-windows.ps1` | `schtasks /Query /TN AgentMailboxWatch /V` |
143
+
144
+ ## Diseño
145
+
146
+ - **Local-first** — archivos JSON simples bajo `~/.agent-mail/`. Sin SMTP, sin IMAP, sin dominio, sin relé en la nube, sin red por defecto.
147
+ - **Direccionamiento con un registro** — `mailbox_register("HS")` es todo lo que hace falta; todo agente registrado es direccionable por todos.
148
+ - **Cero dependencias externas** — solo `mcp`. El almacén es un archivo Python con escrituras atómicas protegidas por `flock`.
149
+ - **Legible por humanos** — cada mensaje es un JSON pequeño que puedes `cat`. El jefe lee la bandeja directamente.
150
+ - **Respeta identidades existentes** — define `AGENT_MAIL_ID` en el entorno de cada agente y sus herramientas quedan autodireccionadas.
151
+
152
+ ## Notas de seguridad
153
+
154
+ - La raíz de correo vive en tu directorio personal; los mensajes nunca salen de la máquina salvo que actives el webhook, fijado por defecto a destinos loopback/privados.
155
+ - Los ids de agente se validan estrictamente (`[A-Za-z0-9_-]`, ≤64 caracteres) — sin path traversal.
156
+ - El almacén es orientado a anexión con escrituras atómicas y bloqueos; un escritor caído no corrompe el registro.
157
+ - Los payloads del webhook van firmados con HMAC; los verificadores deben usar comparación en tiempo constante.
158
+ - Los recibos firmados (ed25519) están en el roadmap.
159
+
160
+ ## Desarrollo
161
+
162
+ ```bash
163
+ git clone https://github.com/polaris-smart/agent-mailbox && cd agent-mailbox
164
+ uv venv && uv pip install -e ".[dev]"
165
+ pytest
166
+ ```
167
+
168
+ ## Actualización
169
+
170
+ Actualiza con `uv tool upgrade agent-mailbox` (o vuelve a instalar según tu método original).
171
+
172
+ ⚠️ **Tras actualizar, reinicia tu sesión de agente (o reconecta el cliente MCP)** — la lista de herramientas MCP se enumera al iniciar la sesión, así que las herramientas nuevas (12 ahora, antes 9) solo aparecen tras un reinicio. No hay que cambiar ninguna configuración; `tasks.json` se crea automáticamente al primer uso.
173
+
174
+ ## Roadmap
175
+
176
+ - **v0.4.0** (actual) — lote de funcionalidades: estilo de firma de webhook configurable (`AGENT_MAIL_SIGNATURE_STYLE`: github por defecto / generic / slack); protección contra auto-eco (las notificaciones donde remitente == destinatario no se entregan por defecto y se auditan como `echo_suppressed` en `sent.log`; `notify_self_echo` / `AGENT_MAIL_NOTIFY_SELF_ECHO` restaura la entrega con un prefijo `[echo] ` en el asunto de la notificación mientras la carta conserva el suyo); nuevo comando de mantenimiento `cleanup --dry-run` (escanea residuos de prueba, lista sin borrar, `--yes` borra tras confirmación).
177
+ - **v0.3.1** — lote de parches: los asuntos de respuesta ya no acumulan `Re: Re:` (primera respuesta, re-respuestas y prefijos con mayúsculas/minúsculas mixtas se normalizan a un solo `Re:`); los tokens del tablero web usan comparación en tiempo constante (`hmac.compare_digest`) y persisten entre reinicios (`~/.agent-mail/web_token`, modo 0600, el env `AGENT_MAIL_WEB_TOKEN` siempre gana); `sent.log` rota automáticamente una generación pasados los 10 MB (a `sent.log.1`).
178
+ - **v0.3.0** — tablero de tareas + kanban web: `task_create` / `task_move` / `task_list` con una estricta máquina de estados todo→doing→review→done; crear o mover una tarjeta avisa automáticamente al responsable, así el movimiento del tablero despierta agentes sin ningún sondeo. `--web 8643` sirve una interfaz kanban sin dependencias protegida por token donde el arrastre humano pasa por la misma ruta de despertar. Mensajes + tareas + despertar + tablero, cero dependencias.
179
+ - **v0.5.0+** — quizá: integraciones kanban más profundas (Kaneo como referencia/competidor). En discusión.
180
+ - **Siguiente** — federación: transporte HTTP streamable para agentes en otras máquinas (amigable con Tailscale/LAN); recibos firmados (ed25519) para entrega a prueba de manipulación.
181
+ - **v1.0.0** — puente entre organizaciones: los hilos locales alcanzan agentes en otras máquinas y organizaciones sobre infraestructura de email estándar, con el mismo ciclo de vida del buzón.
182
+
183
+ ## Licencia
184
+
185
+ MIT