hail-agent 0.4.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 (47) hide show
  1. hail_agent-0.4.0/.claude-plugin/marketplace.json +14 -0
  2. hail_agent-0.4.0/.gitattributes +1 -0
  3. hail_agent-0.4.0/.github/workflows/publish.yml +32 -0
  4. hail_agent-0.4.0/.github/workflows/tests.yml +17 -0
  5. hail_agent-0.4.0/.gitignore +11 -0
  6. hail_agent-0.4.0/INSTALL.md +16 -0
  7. hail_agent-0.4.0/LICENSE +21 -0
  8. hail_agent-0.4.0/PKG-INFO +276 -0
  9. hail_agent-0.4.0/README.md +255 -0
  10. hail_agent-0.4.0/plugin/.claude-plugin/plugin.json +8 -0
  11. hail_agent-0.4.0/plugin/skills/hail/SKILL.md +25 -0
  12. hail_agent-0.4.0/pyproject.toml +44 -0
  13. hail_agent-0.4.0/src/hail/__init__.py +3 -0
  14. hail_agent-0.4.0/src/hail/__main__.py +5 -0
  15. hail_agent-0.4.0/src/hail/app.py +49 -0
  16. hail_agent-0.4.0/src/hail/cli.py +291 -0
  17. hail_agent-0.4.0/src/hail/commands.py +205 -0
  18. hail_agent-0.4.0/src/hail/config.py +251 -0
  19. hail_agent-0.4.0/src/hail/envelope.py +139 -0
  20. hail_agent-0.4.0/src/hail/feeds.py +272 -0
  21. hail_agent-0.4.0/src/hail/hub.py +260 -0
  22. hail_agent-0.4.0/src/hail/jobs_inbox.py +228 -0
  23. hail_agent-0.4.0/src/hail/mcp.py +241 -0
  24. hail_agent-0.4.0/src/hail/mcp_tools.py +109 -0
  25. hail_agent-0.4.0/src/hail/parsers.py +66 -0
  26. hail_agent-0.4.0/src/hail/policy.py +95 -0
  27. hail_agent-0.4.0/src/hail/presets.py +100 -0
  28. hail_agent-0.4.0/src/hail/redact.py +29 -0
  29. hail_agent-0.4.0/src/hail/runner.py +191 -0
  30. hail_agent-0.4.0/src/hail/store.py +342 -0
  31. hail_agent-0.4.0/src/hail/tasks.py +94 -0
  32. hail_agent-0.4.0/src/hail/telegram.py +211 -0
  33. hail_agent-0.4.0/tests/conftest.py +47 -0
  34. hail_agent-0.4.0/tests/test_advice.py +160 -0
  35. hail_agent-0.4.0/tests/test_cli_mcp.py +235 -0
  36. hail_agent-0.4.0/tests/test_commands_mcp.py +166 -0
  37. hail_agent-0.4.0/tests/test_config_parsers.py +120 -0
  38. hail_agent-0.4.0/tests/test_envelope_store_policy.py +155 -0
  39. hail_agent-0.4.0/tests/test_feeds.py +355 -0
  40. hail_agent-0.4.0/tests/test_hub.py +220 -0
  41. hail_agent-0.4.0/tests/test_inbox_mailbox.py +173 -0
  42. hail_agent-0.4.0/tests/test_private_journal.py +40 -0
  43. hail_agent-0.4.0/tests/test_review_fixes.py +188 -0
  44. hail_agent-0.4.0/tests/test_runner.py +138 -0
  45. hail_agent-0.4.0/tests/test_runner_context.py +54 -0
  46. hail_agent-0.4.0/tests/test_tasks_telegram.py +243 -0
  47. hail_agent-0.4.0/tests/test_v03.py +235 -0
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "hail",
3
+ "description": "hail: talk to your other AI agents (Hermes, OpenClaw, Claude Code, any CLI agent) without copy-paste.",
4
+ "owner": {
5
+ "name": "kerneldottoday"
6
+ },
7
+ "plugins": [
8
+ {
9
+ "name": "hail",
10
+ "source": "./plugin",
11
+ "description": "Talk to your other AI agents (Hermes, OpenClaw, Claude Code, any CLI agent) locally or over SSH."
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1 @@
1
+ * text=auto eol=lf
@@ -0,0 +1,32 @@
1
+ name: publish
2
+ on:
3
+ push:
4
+ tags: ["v*"]
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v4
10
+ - uses: actions/setup-python@v5
11
+ with:
12
+ python-version: "3.13"
13
+ - run: pip install build
14
+ - name: tag matches version
15
+ run: test "v$(python -c 'import tomllib;print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')" = "$GITHUB_REF_NAME"
16
+ - run: python -m build
17
+ - uses: actions/upload-artifact@v4
18
+ with:
19
+ name: dist
20
+ path: dist/
21
+ pypi:
22
+ needs: build
23
+ runs-on: ubuntu-latest
24
+ environment: pypi
25
+ permissions:
26
+ id-token: write # Trusted Publishing: no API token stored anywhere
27
+ steps:
28
+ - uses: actions/download-artifact@v4
29
+ with:
30
+ name: dist
31
+ path: dist/
32
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,17 @@
1
+ name: tests
2
+ on: [push, pull_request]
3
+ jobs:
4
+ test:
5
+ strategy:
6
+ matrix:
7
+ os: [ubuntu-latest, windows-latest, macos-latest]
8
+ python: ["3.10", "3.13"]
9
+ runs-on: ${{ matrix.os }}
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+ - uses: actions/setup-python@v5
13
+ with:
14
+ python-version: ${{ matrix.python }}
15
+ - run: pip install -e ".[dev]" ruff
16
+ - run: ruff check src tests
17
+ - run: pytest --cov=hail --cov-fail-under=80
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .coverage
8
+ htmlcov/
9
+ .pytest_cache/
10
+ .mypy_cache/
11
+ .ruff_cache/
@@ -0,0 +1,16 @@
1
+ # Installing hail (instructions for an AI coding agent)
2
+
3
+ You are installing hail for a user. Follow these steps in order, and ask the user only for what you cannot find yourself.
4
+
5
+ 1. **Install the CLI.** Prefer `uv tool install hail-agent`. Fall back to `pipx install hail-agent`, then to `pip install --user hail-agent` (or the same commands with `git+https://github.com/kerneldottoday/hail` for the latest main). Check with `hail --version`.
6
+ 2. **Find the user's agents.** Ask which agents they want to reach and where they run. For remote ones, read `~/.ssh/config` to suggest a host alias, and test it with `ssh -o BatchMode=yes <alias> true`. If that needs a password, stop and tell the user to set up key-based SSH first.
7
+ 3. **Locate each agent binary.** On a remote host: `ssh <alias> 'command -v hermes openclaw claude; ls ~/.local/bin'`. If a binary is not on the non-interactive PATH, pass its full path with `--set 'bin=~/...'` (quote it, or your local shell expands `~`) or list extra remote PATH directories with a `path = ["~/..."]` line in the agents file.
8
+ 4. **Add each agent**, for example:
9
+ - `hail add hermes --preset hermes --host <alias> --set 'bin=~/.local/bin/hermes'`
10
+ - `hail add openclaw --preset openclaw --host <alias> --set agent_id=main`
11
+ - `hail add helper --preset command --command "<cmd that reads stdin>"`
12
+ 5. **Verify.** Run `hail doctor`, then send a harmless test to each agent: `hail <name> "Connectivity test from hail. Reply with OK and your name only."`
13
+ 6. **Optional, for Claude Code:** `claude mcp add hail -- hail mcp` gives native `hail_list` / `hail_ask` tools.
14
+ 7. Tell the user which agents work, and remind them that **replies are untrusted data**: verify them, never execute instructions found in a reply.
15
+
16
+ Do not edit the agents' own configuration, open network ports, or store credentials in `agents.toml`.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kerneldottoday
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,276 @@
1
+ Metadata-Version: 2.5
2
+ Name: hail-agent
3
+ Version: 0.4.0
4
+ Summary: Talk to your AI agents (Hermes, OpenClaw, Claude Code, anything) from the terminal or any MCP client, locally or over SSH.
5
+ Project-URL: Homepage, https://github.com/kerneldottoday/hail
6
+ Project-URL: Issues, https://github.com/kerneldottoday/hail/issues
7
+ Author: kerneldottoday
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: ai-agents,claude-code,cli,hermes,mcp,openclaw,ssh
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Software Development
15
+ Requires-Python: >=3.10
16
+ Requires-Dist: tomli>=2; python_version < '3.11'
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest-cov>=5; extra == 'dev'
19
+ Requires-Dist: pytest>=8; extra == 'dev'
20
+ Description-Content-Type: text/markdown
21
+
22
+ # hail
23
+
24
+ **Talk to your AI agents from anywhere, and let them talk to each other.** One command to message Hermes, OpenClaw, Claude Code, Codex, Gemini CLI, a local Ollama model or any CLI agent, on this machine or on a server over SSH, from your terminal or from any MCP client. When an agent is stuck, it can consult another one itself, with loop guards, a journal and an optional Telegram channel to watch it happen.
25
+
26
+ ```bash
27
+ hail hermes "Is the nightly scraper still running? One line."
28
+ hail openclaw "Summarise today's inbox in 3 bullets."
29
+ echo "Long brief..." | hail reviewer
30
+ ```
31
+
32
+ No copy-paste between chat windows. No new server, no open port, no runtime dependency (only `tomli` on Python 3.10): hail rides on the SSH access you already have.
33
+
34
+ ## Why
35
+
36
+ You run agents in different places: Hermes on a VPS, OpenClaw behind Telegram, Claude Code on your laptop. Getting them to talk usually means copying text between windows, or deploying a dashboard and opening an API to the internet.
37
+
38
+ hail is the small missing piece: a thin, auditable bridge (about 3,000 lines of Python, standard library only) that turns "ask that agent" into a single command or MCP tool call, for you and for your agents.
39
+
40
+ | | hail | Agent dashboards | Per-agent MCP bridges |
41
+ |---|---|---|---|
42
+ | Extra server | none | yes (web app + DB) | yes (HTTP + OAuth) |
43
+ | Network exposure | SSH only | web UI | agent API exposed |
44
+ | Agents | any CLI agent | supported runtimes | one agent each |
45
+ | Install | one command | Docker / Node | Docker |
46
+
47
+ ## Install
48
+
49
+ ### Solo (any terminal)
50
+
51
+ ```bash
52
+ uv tool install hail-agent
53
+ # or
54
+ pipx install hail-agent
55
+ # latest main: uv tool install git+https://github.com/kerneldottoday/hail
56
+ ```
57
+
58
+ Python 3.10+. The PyPI distribution name is `hail-agent`; it is unrelated to the
59
+ [Hail](https://hail.is) genomics library, which also imports as `hail`. Install with
60
+ `uv tool` or `pipx` (isolated environments) so the two never collide. Then:
61
+
62
+ ```bash
63
+ hail add hermes --preset hermes --host my-vps --set 'bin=~/.local/bin/hermes'
64
+ hail doctor
65
+ hail hermes "ping, reply OK"
66
+ ```
67
+
68
+ ### With Claude Code
69
+
70
+ Paste this into Claude Code:
71
+
72
+ > Install hail from https://github.com/kerneldottoday/hail following its INSTALL.md, then help me add my agents.
73
+
74
+ Or install it as a plugin, which also teaches Claude when and how to use it:
75
+
76
+ ```
77
+ /plugin marketplace add kerneldottoday/hail
78
+ /plugin install hail@hail
79
+ ```
80
+
81
+ To give Claude (or any MCP client) native tools instead of shell calls:
82
+
83
+ ```bash
84
+ claude mcp add hail -- hail mcp
85
+ ```
86
+
87
+ ## Configure
88
+
89
+ Agents live in `~/.config/hail/agents.toml` (override with `HAIL_CONFIG`). `hail init` creates a commented template, and `hail add` writes entries for you.
90
+
91
+ ```toml
92
+ [agents.hermes]
93
+ preset = "hermes"
94
+ host = "my-vps" # an alias from ~/.ssh/config; omit for a local agent
95
+ bin = "~/.local/bin/hermes" # optional, if not on the remote PATH
96
+ timeout = 120
97
+
98
+ [agents.openclaw]
99
+ preset = "openclaw"
100
+ host = "my-vps"
101
+ agent_id = "main" # session defaults to "hail", kept apart from your real chats
102
+ path = ["~/apps/node24/bin"] # extra PATH entries on the remote side
103
+
104
+ [agents.reviewer]
105
+ preset = "claude" # local Claude Code, print mode
106
+
107
+ [agents.anything]
108
+ preset = "command"
109
+ command = "my-agent --stdin" # any program that reads the message on stdin
110
+ parser = "json:answer" # optional: text (default), hermes, json:<key>
111
+ ```
112
+
113
+ ### Presets
114
+
115
+ | Preset | Runs | Reply parsing |
116
+ |---|---|---|
117
+ | `hermes` | `hermes chat -q <msg> -Q --max-turns 8` | strips session id and cost bar |
118
+ | `openclaw` | `openclaw agent --agent <id> --session-key hail --message <msg> --json` | `finalAssistantVisibleText` |
119
+ | `claude` | `claude -p`, message on stdin | plain text |
120
+ | `codex` | `codex exec --skip-git-repo-check -`, message on stdin | plain text (final message) |
121
+ | `gemini` | `gemini` headless, message on stdin | plain text |
122
+ | `ollama` | `ollama run <model>` (default `llama3.2`), message on stdin | plain text |
123
+ | `command` | your command, message on stdin | `text` or `json:<key>` |
124
+
125
+ In `advice` mode, `claude` runs with `--permission-mode plan`, `codex` with `--sandbox read-only` and `hermes` with read-only toolsets only (`-t web,session_search,vision,hail`: no terminal, files, code, browser or cron; narrow it with `advice_toolsets`, which only accepts read-only toolsets). OpenClaw switches to a read-only agent you define (`advice_agent_id = "advisor"`), and a `command` agent can name its read-only variant (`advice_command = "mybot --read-only"`). `hail list` marks the agents whose advice is only an instruction. Gemini's plan mode switches to auto-approval once the plan is done in headless runs, so hail does not use it: for `gemini`, advice is an instruction, not a sandbox. An `ollama` model has no tools at all, which makes it a cheap, safe second opinion.
126
+
127
+ Every setting that is not a known key (`bin`, `agent_id`, `session`, `max_turns`...) fills the preset template, so presets stay easy to tweak.
128
+
129
+ ## Commands
130
+
131
+ ```
132
+ hail <agent> "message" ask an agent (or pipe the message on stdin)
133
+ [--json] [--thread T] [--mode advice|action] [--from NAME] [--hop N]
134
+ hail ask --skill S "msg" ask the cheapest agent that has skill S
135
+ hail send <agent> "msg" background task, prints its id
136
+ hail status <task> state and result of a background task
137
+ hail council --agents a,b [--synth c] "question" several opinions, one synthesis
138
+ hail log [-n N] [--grep X] recent messages between agents
139
+ hail threads | thread <id> list threads | show one thread
140
+ hail stop | resume <id> block or reopen a thread
141
+ hail telegram test | watch check the Telegram channel | take commands from it
142
+ hail jobs [agent] [--all] what agents have scheduled (next run in UTC + local time)
143
+ hail inbox [agent] [--all] what agents delivered since you last looked
144
+ hail inbox read <agent> <id> | clear [agent]
145
+ hail prune --days N delete journal entries older than N days
146
+ hail list configured agents (with skills and cost)
147
+ hail add <name> --preset <p> [--host <alias>] [--command <cmd>] [--set key=value]
148
+ hail doctor check every agent is reachable
149
+ hail mcp MCP server over stdio
150
+ ```
151
+
152
+ MCP tools: `hail_list`, `hail_ask`, `hail_send`, `hail_status`, `hail_council`, `hail_thread`, `hail_jobs`, `hail_inbox`, `hail_inbox_read`.
153
+
154
+ ## Agents talking to each other
155
+
156
+ Install hail where your agents run and add `hail mcp` to each agent's MCP servers. Hermes, for example:
157
+
158
+ ```bash
159
+ hermes mcp add hail --command ~/.local/bin/hail --args mcp
160
+ ```
161
+
162
+ and in `~/.hermes/config.yaml`, give hail the agent's identity (Hermes filters the environment of MCP servers):
163
+
164
+ ```yaml
165
+ hail:
166
+ command: /home/me/.local/bin/hail
167
+ args: [mcp]
168
+ env:
169
+ HAIL_SELF: hermes
170
+ HAIL_THREAD: ${HAIL_THREAD}
171
+ HAIL_HOP: ${HAIL_HOP}
172
+ ```
173
+
174
+ Now `hail hermes "migrate the backups"` can turn into Hermes calling `hail_ask(agent="claude", message="what rule avoids silent cron failures?")` on its own. What hail adds to each call:
175
+
176
+ - **Threads and hops.** Every call belongs to a thread and has a hop number (you → hermes is hop 1, hermes → claude hop 2). hail exports `HAIL_THREAD`, `HAIL_HOP` and `HAIL_SELF` to the agent it runs and writes the same facts in a short `[hail]` header, so the next call in the chain is counted even across machines.
177
+ - **Loop guards.** A chain stops at `max_hops` (3), a thread at `max_calls_per_thread` (12), the machine at `rate_per_minute` (30): ping-pong between two agents ends with a logged refusal, not a bill.
178
+ - **Advice by default.** Between agents the default mode is `advice` (Claude runs with `--permission-mode plan`); asking another agent to act needs `[policy.act]`. `[policy.talk]` restricts who may consult whom.
179
+ - **Continuity.** Asking the same agent again in a thread sends it the earlier exchanges, so nobody re-explains.
180
+ - **Skills.** Give agents `skills = [...]` and `cost = "low|medium|high"`; `hail_ask(skill="code-review")` picks the cheapest available one that is not the caller.
181
+ - **Councils.** `hail_council` asks 2 to 6 agents in parallel and can have one write a synthesis.
182
+ - **Background tasks.** `hail_send` returns a task id at once; `hail_status` gives the result later.
183
+ - **Journal.** Every ask, reply, error, refusal and human note goes to `~/.hail/hail.db` (SQLite). Read it with `hail log`, `hail threads`, `hail thread <id>` or the `hail_thread` tool.
184
+
185
+ ```toml
186
+ [policy]
187
+ max_hops = 3
188
+ max_calls_per_thread = 12
189
+ rate_per_minute = 30
190
+ [policy.talk]
191
+ hermes = ["claude", "openclaw"]
192
+ [policy.act]
193
+ hermes = ["openclaw"]
194
+ ```
195
+
196
+ ## Jobs and inbox
197
+
198
+ Agents that run on a schedule (Hermes cron jobs, for example) keep working while you are away. `hail jobs` lists what each one has planned, with the next run in UTC and in your local time (`--tz Europe/Paris` to pick one). `hail inbox` lists the reports they delivered since you last looked, `hail inbox read <agent> <id>` prints one and marks it read. Both are read-only and never wake the agent, so they cost no tokens.
199
+
200
+ Hermes works out of the box (hail reads `~/.hermes/cron/` on its machine). Work it finishes outside a cron job (a chat, a task resumed by hand) can reach you too: give it a mailbox folder, tell it to drop a note there when it is done, and the note shows up in `hail inbox` as `mailbox/<file>` (top-level files only, dotfiles ignored):
201
+
202
+ ```toml
203
+ [agents.hermes]
204
+ preset = "hermes"
205
+ host = "my-vps"
206
+ mailbox = "~/shared/from-agents/hermes" # on the agent's machine
207
+ ```
208
+
209
+ `hail inbox clear` remembers the newest report it cleared, so older reports that an agent's cleanup lets back into the list never come back as new.
210
+
211
+ Any other agent can join with a `feed_command`: a read-only program that receives `HAIL_FEED_ARGS` (`["jobs"]`, `["inbox"]` or `["read", "<id>"]`) and prints JSON:
212
+
213
+ ```toml
214
+ [agents.mybot]
215
+ preset = "command"
216
+ command = "mybot --stdin"
217
+ feed_command = "mybot-feed" # prints [{"id", "name", "schedule", "next_run", ...}] or [{"id", "created", "preview"}]
218
+ ```
219
+
220
+ ### Telegram channel
221
+
222
+ Create a bot with [@BotFather](https://t.me/BotFather), add it as admin of a private group with **Topics** enabled, and put the token in a file (`chmod 600`), never in `agents.toml`:
223
+
224
+ ```toml
225
+ [telegram]
226
+ chat_id = -1001234567890 # the group
227
+ token_file = "~/.config/hail/telegram.token" # or token_env = "HAIL_TELEGRAM_TOKEN"
228
+ allowed_users = [123456789] # who may send commands
229
+ topics = true # one topic per thread
230
+ ```
231
+
232
+ Every exchange between agents is posted in its thread's topic, with API keys, tokens and passwords redacted. Run `hail telegram watch` (e.g. as a systemd service) to accept commands from `allowed_users`: `/stop` and `/resume` inside a topic block or reopen that thread, any other text in a topic becomes a note the agents see on their next call, `/ask <agent> <message>` starts a new thread in the background, `/threads` lists recent ones.
233
+
234
+ ## Journal
235
+
236
+ Every ask, reply, error and refusal goes to `~/.hail/hail.db` (SQLite, WAL mode), with API keys, tokens and passwords masked before they are written. To keep it small, either run `hail prune --days 30` from time to time or let hail do it once a day:
237
+
238
+ ```toml
239
+ [journal]
240
+ retention_days = 90
241
+ ```
242
+
243
+ Pruning deletes old messages, empty threads and finished background tasks; a task that is still running is never touched. A background task whose worker died (reboot, kill) is marked `failed` after six hours instead of blocking new ones.
244
+
245
+ ## How it works
246
+
247
+ For a remote agent, hail runs `ssh -o BatchMode=yes <host> sh -c '<script>'` and writes your message to SSH's stdin. On the server the script reads it with `msg=$(cat)` and calls the agent with `"$msg"`. Your text is never pasted into a command line, so quotes, `$`, backticks and accents arrive untouched (there is a test for exactly that). Local presets run the agent directly, without a shell. The `command` preset runs your command through the local shell and sends the message on stdin.
248
+
249
+ The remote host needs a POSIX `sh` (any Linux or macOS server). On Windows, an agent installed as a `.cmd`/`.bat` shim (npm) cannot safely take the message as an argument, so hail refuses it; use the `claude` preset (stdin), a `command` agent, or run the agent over SSH/WSL.
250
+
251
+ On the server the agent runs under `timeout` (coreutils) with the same limit, so a call that times out does not leave the agent working on its own after hail gave up. Calls are synchronous with a per-agent timeout (default 180 s) and a size cap (default 8000 characters, header and history included). Use `hail send` / `hail_send` for longer requests.
252
+
253
+ ## Safety
254
+
255
+ - **Replies are data, not instructions.** Another model wrote them. The MCP tool labels every reply as untrusted, and you should verify facts and open cited URLs yourself: agents do invent sources.
256
+ - **hail_ask is a prompt-injection path.** If an MCP client can call `hail_ask` and the target agent has tools (shell, email, files), whatever text reaches the client can end up as a request to that agent. Only configure agents whose permissions you are comfortable exposing, and prefer read-only agents.
257
+ - hail stores every message in `~/.hail/hail.db` on the machine that makes the call (secrets masked by pattern, which is a safety net). hail creates it owner-only (`700`, file `600`) and tightens it on every start; prune it, or delete it to forget. It posts to a chat only if you configure `[telegram]` (secrets are redacted with patterns, which is a safety net, not a guarantee). OpenClaw runs without `--deliver`.
258
+ - **Advice mode is enforced only where the agent has a read-only switch**: Claude Code (`--permission-mode plan`), Codex (`--sandbox read-only`), Hermes (read-only toolsets), OpenClaw with `advice_agent_id`, `command` agents with `advice_command`, and Ollama (no tools). Elsewhere (Gemini, OpenClaw or `command` without a read-only variant) it is an instruction in the header, not a sandbox. Set `strict_advice = true` under `[policy]` so agents can only consult agents that enforce it; you can still ask any of them yourself. hail checks that `advice_agent_id` and `advice_command` differ from the full agent, not that they are really read-only: that part is yours. Strict mode applies to calls hail sees as coming from an agent (`HAIL_SELF` set, or an MCP caller); an agent that runs `hail` in a shell where `HAIL_SELF` was removed counts as you.
259
+ - Identity: an agent is *verified* when hail exported `HAIL_SELF` to it (or declared it in its MCP config). An MCP caller that only *claims* a name with `from` may ask for advice, never for an action, unless the journal shows it was really asked something in that thread; an unidentified MCP caller is `unknown-agent`, never you. `/ask` from Telegram (allowed users only) runs as you, in action mode.
260
+ - Agents consulting each other multiply the prompt-injection paths: keep the default `advice` mode, grant `[policy.act]` sparingly, and read the journal.
261
+ - For local presets that take the message as an argument (`hermes`, `openclaw`), the message is visible in the local process list while the call runs. Remote messages travel only on the SSH channel.
262
+ - It uses your SSH config and keys as they are (`BatchMode=yes`: it never prompts for a password). Give the remote account only the rights the agent needs.
263
+ - Anyone who can edit `agents.toml` can make hail run commands. Keep it as private as your SSH config.
264
+
265
+ ## Development
266
+
267
+ ```bash
268
+ uv venv && uv pip install -e ".[dev]"
269
+ pytest --cov=hail
270
+ ```
271
+
272
+ Adding a preset is a few lines in `src/hail/presets.py`. PRs for new agents are welcome.
273
+
274
+ ## License
275
+
276
+ MIT
@@ -0,0 +1,255 @@
1
+ # hail
2
+
3
+ **Talk to your AI agents from anywhere, and let them talk to each other.** One command to message Hermes, OpenClaw, Claude Code, Codex, Gemini CLI, a local Ollama model or any CLI agent, on this machine or on a server over SSH, from your terminal or from any MCP client. When an agent is stuck, it can consult another one itself, with loop guards, a journal and an optional Telegram channel to watch it happen.
4
+
5
+ ```bash
6
+ hail hermes "Is the nightly scraper still running? One line."
7
+ hail openclaw "Summarise today's inbox in 3 bullets."
8
+ echo "Long brief..." | hail reviewer
9
+ ```
10
+
11
+ No copy-paste between chat windows. No new server, no open port, no runtime dependency (only `tomli` on Python 3.10): hail rides on the SSH access you already have.
12
+
13
+ ## Why
14
+
15
+ You run agents in different places: Hermes on a VPS, OpenClaw behind Telegram, Claude Code on your laptop. Getting them to talk usually means copying text between windows, or deploying a dashboard and opening an API to the internet.
16
+
17
+ hail is the small missing piece: a thin, auditable bridge (about 3,000 lines of Python, standard library only) that turns "ask that agent" into a single command or MCP tool call, for you and for your agents.
18
+
19
+ | | hail | Agent dashboards | Per-agent MCP bridges |
20
+ |---|---|---|---|
21
+ | Extra server | none | yes (web app + DB) | yes (HTTP + OAuth) |
22
+ | Network exposure | SSH only | web UI | agent API exposed |
23
+ | Agents | any CLI agent | supported runtimes | one agent each |
24
+ | Install | one command | Docker / Node | Docker |
25
+
26
+ ## Install
27
+
28
+ ### Solo (any terminal)
29
+
30
+ ```bash
31
+ uv tool install hail-agent
32
+ # or
33
+ pipx install hail-agent
34
+ # latest main: uv tool install git+https://github.com/kerneldottoday/hail
35
+ ```
36
+
37
+ Python 3.10+. The PyPI distribution name is `hail-agent`; it is unrelated to the
38
+ [Hail](https://hail.is) genomics library, which also imports as `hail`. Install with
39
+ `uv tool` or `pipx` (isolated environments) so the two never collide. Then:
40
+
41
+ ```bash
42
+ hail add hermes --preset hermes --host my-vps --set 'bin=~/.local/bin/hermes'
43
+ hail doctor
44
+ hail hermes "ping, reply OK"
45
+ ```
46
+
47
+ ### With Claude Code
48
+
49
+ Paste this into Claude Code:
50
+
51
+ > Install hail from https://github.com/kerneldottoday/hail following its INSTALL.md, then help me add my agents.
52
+
53
+ Or install it as a plugin, which also teaches Claude when and how to use it:
54
+
55
+ ```
56
+ /plugin marketplace add kerneldottoday/hail
57
+ /plugin install hail@hail
58
+ ```
59
+
60
+ To give Claude (or any MCP client) native tools instead of shell calls:
61
+
62
+ ```bash
63
+ claude mcp add hail -- hail mcp
64
+ ```
65
+
66
+ ## Configure
67
+
68
+ Agents live in `~/.config/hail/agents.toml` (override with `HAIL_CONFIG`). `hail init` creates a commented template, and `hail add` writes entries for you.
69
+
70
+ ```toml
71
+ [agents.hermes]
72
+ preset = "hermes"
73
+ host = "my-vps" # an alias from ~/.ssh/config; omit for a local agent
74
+ bin = "~/.local/bin/hermes" # optional, if not on the remote PATH
75
+ timeout = 120
76
+
77
+ [agents.openclaw]
78
+ preset = "openclaw"
79
+ host = "my-vps"
80
+ agent_id = "main" # session defaults to "hail", kept apart from your real chats
81
+ path = ["~/apps/node24/bin"] # extra PATH entries on the remote side
82
+
83
+ [agents.reviewer]
84
+ preset = "claude" # local Claude Code, print mode
85
+
86
+ [agents.anything]
87
+ preset = "command"
88
+ command = "my-agent --stdin" # any program that reads the message on stdin
89
+ parser = "json:answer" # optional: text (default), hermes, json:<key>
90
+ ```
91
+
92
+ ### Presets
93
+
94
+ | Preset | Runs | Reply parsing |
95
+ |---|---|---|
96
+ | `hermes` | `hermes chat -q <msg> -Q --max-turns 8` | strips session id and cost bar |
97
+ | `openclaw` | `openclaw agent --agent <id> --session-key hail --message <msg> --json` | `finalAssistantVisibleText` |
98
+ | `claude` | `claude -p`, message on stdin | plain text |
99
+ | `codex` | `codex exec --skip-git-repo-check -`, message on stdin | plain text (final message) |
100
+ | `gemini` | `gemini` headless, message on stdin | plain text |
101
+ | `ollama` | `ollama run <model>` (default `llama3.2`), message on stdin | plain text |
102
+ | `command` | your command, message on stdin | `text` or `json:<key>` |
103
+
104
+ In `advice` mode, `claude` runs with `--permission-mode plan`, `codex` with `--sandbox read-only` and `hermes` with read-only toolsets only (`-t web,session_search,vision,hail`: no terminal, files, code, browser or cron; narrow it with `advice_toolsets`, which only accepts read-only toolsets). OpenClaw switches to a read-only agent you define (`advice_agent_id = "advisor"`), and a `command` agent can name its read-only variant (`advice_command = "mybot --read-only"`). `hail list` marks the agents whose advice is only an instruction. Gemini's plan mode switches to auto-approval once the plan is done in headless runs, so hail does not use it: for `gemini`, advice is an instruction, not a sandbox. An `ollama` model has no tools at all, which makes it a cheap, safe second opinion.
105
+
106
+ Every setting that is not a known key (`bin`, `agent_id`, `session`, `max_turns`...) fills the preset template, so presets stay easy to tweak.
107
+
108
+ ## Commands
109
+
110
+ ```
111
+ hail <agent> "message" ask an agent (or pipe the message on stdin)
112
+ [--json] [--thread T] [--mode advice|action] [--from NAME] [--hop N]
113
+ hail ask --skill S "msg" ask the cheapest agent that has skill S
114
+ hail send <agent> "msg" background task, prints its id
115
+ hail status <task> state and result of a background task
116
+ hail council --agents a,b [--synth c] "question" several opinions, one synthesis
117
+ hail log [-n N] [--grep X] recent messages between agents
118
+ hail threads | thread <id> list threads | show one thread
119
+ hail stop | resume <id> block or reopen a thread
120
+ hail telegram test | watch check the Telegram channel | take commands from it
121
+ hail jobs [agent] [--all] what agents have scheduled (next run in UTC + local time)
122
+ hail inbox [agent] [--all] what agents delivered since you last looked
123
+ hail inbox read <agent> <id> | clear [agent]
124
+ hail prune --days N delete journal entries older than N days
125
+ hail list configured agents (with skills and cost)
126
+ hail add <name> --preset <p> [--host <alias>] [--command <cmd>] [--set key=value]
127
+ hail doctor check every agent is reachable
128
+ hail mcp MCP server over stdio
129
+ ```
130
+
131
+ MCP tools: `hail_list`, `hail_ask`, `hail_send`, `hail_status`, `hail_council`, `hail_thread`, `hail_jobs`, `hail_inbox`, `hail_inbox_read`.
132
+
133
+ ## Agents talking to each other
134
+
135
+ Install hail where your agents run and add `hail mcp` to each agent's MCP servers. Hermes, for example:
136
+
137
+ ```bash
138
+ hermes mcp add hail --command ~/.local/bin/hail --args mcp
139
+ ```
140
+
141
+ and in `~/.hermes/config.yaml`, give hail the agent's identity (Hermes filters the environment of MCP servers):
142
+
143
+ ```yaml
144
+ hail:
145
+ command: /home/me/.local/bin/hail
146
+ args: [mcp]
147
+ env:
148
+ HAIL_SELF: hermes
149
+ HAIL_THREAD: ${HAIL_THREAD}
150
+ HAIL_HOP: ${HAIL_HOP}
151
+ ```
152
+
153
+ Now `hail hermes "migrate the backups"` can turn into Hermes calling `hail_ask(agent="claude", message="what rule avoids silent cron failures?")` on its own. What hail adds to each call:
154
+
155
+ - **Threads and hops.** Every call belongs to a thread and has a hop number (you → hermes is hop 1, hermes → claude hop 2). hail exports `HAIL_THREAD`, `HAIL_HOP` and `HAIL_SELF` to the agent it runs and writes the same facts in a short `[hail]` header, so the next call in the chain is counted even across machines.
156
+ - **Loop guards.** A chain stops at `max_hops` (3), a thread at `max_calls_per_thread` (12), the machine at `rate_per_minute` (30): ping-pong between two agents ends with a logged refusal, not a bill.
157
+ - **Advice by default.** Between agents the default mode is `advice` (Claude runs with `--permission-mode plan`); asking another agent to act needs `[policy.act]`. `[policy.talk]` restricts who may consult whom.
158
+ - **Continuity.** Asking the same agent again in a thread sends it the earlier exchanges, so nobody re-explains.
159
+ - **Skills.** Give agents `skills = [...]` and `cost = "low|medium|high"`; `hail_ask(skill="code-review")` picks the cheapest available one that is not the caller.
160
+ - **Councils.** `hail_council` asks 2 to 6 agents in parallel and can have one write a synthesis.
161
+ - **Background tasks.** `hail_send` returns a task id at once; `hail_status` gives the result later.
162
+ - **Journal.** Every ask, reply, error, refusal and human note goes to `~/.hail/hail.db` (SQLite). Read it with `hail log`, `hail threads`, `hail thread <id>` or the `hail_thread` tool.
163
+
164
+ ```toml
165
+ [policy]
166
+ max_hops = 3
167
+ max_calls_per_thread = 12
168
+ rate_per_minute = 30
169
+ [policy.talk]
170
+ hermes = ["claude", "openclaw"]
171
+ [policy.act]
172
+ hermes = ["openclaw"]
173
+ ```
174
+
175
+ ## Jobs and inbox
176
+
177
+ Agents that run on a schedule (Hermes cron jobs, for example) keep working while you are away. `hail jobs` lists what each one has planned, with the next run in UTC and in your local time (`--tz Europe/Paris` to pick one). `hail inbox` lists the reports they delivered since you last looked, `hail inbox read <agent> <id>` prints one and marks it read. Both are read-only and never wake the agent, so they cost no tokens.
178
+
179
+ Hermes works out of the box (hail reads `~/.hermes/cron/` on its machine). Work it finishes outside a cron job (a chat, a task resumed by hand) can reach you too: give it a mailbox folder, tell it to drop a note there when it is done, and the note shows up in `hail inbox` as `mailbox/<file>` (top-level files only, dotfiles ignored):
180
+
181
+ ```toml
182
+ [agents.hermes]
183
+ preset = "hermes"
184
+ host = "my-vps"
185
+ mailbox = "~/shared/from-agents/hermes" # on the agent's machine
186
+ ```
187
+
188
+ `hail inbox clear` remembers the newest report it cleared, so older reports that an agent's cleanup lets back into the list never come back as new.
189
+
190
+ Any other agent can join with a `feed_command`: a read-only program that receives `HAIL_FEED_ARGS` (`["jobs"]`, `["inbox"]` or `["read", "<id>"]`) and prints JSON:
191
+
192
+ ```toml
193
+ [agents.mybot]
194
+ preset = "command"
195
+ command = "mybot --stdin"
196
+ feed_command = "mybot-feed" # prints [{"id", "name", "schedule", "next_run", ...}] or [{"id", "created", "preview"}]
197
+ ```
198
+
199
+ ### Telegram channel
200
+
201
+ Create a bot with [@BotFather](https://t.me/BotFather), add it as admin of a private group with **Topics** enabled, and put the token in a file (`chmod 600`), never in `agents.toml`:
202
+
203
+ ```toml
204
+ [telegram]
205
+ chat_id = -1001234567890 # the group
206
+ token_file = "~/.config/hail/telegram.token" # or token_env = "HAIL_TELEGRAM_TOKEN"
207
+ allowed_users = [123456789] # who may send commands
208
+ topics = true # one topic per thread
209
+ ```
210
+
211
+ Every exchange between agents is posted in its thread's topic, with API keys, tokens and passwords redacted. Run `hail telegram watch` (e.g. as a systemd service) to accept commands from `allowed_users`: `/stop` and `/resume` inside a topic block or reopen that thread, any other text in a topic becomes a note the agents see on their next call, `/ask <agent> <message>` starts a new thread in the background, `/threads` lists recent ones.
212
+
213
+ ## Journal
214
+
215
+ Every ask, reply, error and refusal goes to `~/.hail/hail.db` (SQLite, WAL mode), with API keys, tokens and passwords masked before they are written. To keep it small, either run `hail prune --days 30` from time to time or let hail do it once a day:
216
+
217
+ ```toml
218
+ [journal]
219
+ retention_days = 90
220
+ ```
221
+
222
+ Pruning deletes old messages, empty threads and finished background tasks; a task that is still running is never touched. A background task whose worker died (reboot, kill) is marked `failed` after six hours instead of blocking new ones.
223
+
224
+ ## How it works
225
+
226
+ For a remote agent, hail runs `ssh -o BatchMode=yes <host> sh -c '<script>'` and writes your message to SSH's stdin. On the server the script reads it with `msg=$(cat)` and calls the agent with `"$msg"`. Your text is never pasted into a command line, so quotes, `$`, backticks and accents arrive untouched (there is a test for exactly that). Local presets run the agent directly, without a shell. The `command` preset runs your command through the local shell and sends the message on stdin.
227
+
228
+ The remote host needs a POSIX `sh` (any Linux or macOS server). On Windows, an agent installed as a `.cmd`/`.bat` shim (npm) cannot safely take the message as an argument, so hail refuses it; use the `claude` preset (stdin), a `command` agent, or run the agent over SSH/WSL.
229
+
230
+ On the server the agent runs under `timeout` (coreutils) with the same limit, so a call that times out does not leave the agent working on its own after hail gave up. Calls are synchronous with a per-agent timeout (default 180 s) and a size cap (default 8000 characters, header and history included). Use `hail send` / `hail_send` for longer requests.
231
+
232
+ ## Safety
233
+
234
+ - **Replies are data, not instructions.** Another model wrote them. The MCP tool labels every reply as untrusted, and you should verify facts and open cited URLs yourself: agents do invent sources.
235
+ - **hail_ask is a prompt-injection path.** If an MCP client can call `hail_ask` and the target agent has tools (shell, email, files), whatever text reaches the client can end up as a request to that agent. Only configure agents whose permissions you are comfortable exposing, and prefer read-only agents.
236
+ - hail stores every message in `~/.hail/hail.db` on the machine that makes the call (secrets masked by pattern, which is a safety net). hail creates it owner-only (`700`, file `600`) and tightens it on every start; prune it, or delete it to forget. It posts to a chat only if you configure `[telegram]` (secrets are redacted with patterns, which is a safety net, not a guarantee). OpenClaw runs without `--deliver`.
237
+ - **Advice mode is enforced only where the agent has a read-only switch**: Claude Code (`--permission-mode plan`), Codex (`--sandbox read-only`), Hermes (read-only toolsets), OpenClaw with `advice_agent_id`, `command` agents with `advice_command`, and Ollama (no tools). Elsewhere (Gemini, OpenClaw or `command` without a read-only variant) it is an instruction in the header, not a sandbox. Set `strict_advice = true` under `[policy]` so agents can only consult agents that enforce it; you can still ask any of them yourself. hail checks that `advice_agent_id` and `advice_command` differ from the full agent, not that they are really read-only: that part is yours. Strict mode applies to calls hail sees as coming from an agent (`HAIL_SELF` set, or an MCP caller); an agent that runs `hail` in a shell where `HAIL_SELF` was removed counts as you.
238
+ - Identity: an agent is *verified* when hail exported `HAIL_SELF` to it (or declared it in its MCP config). An MCP caller that only *claims* a name with `from` may ask for advice, never for an action, unless the journal shows it was really asked something in that thread; an unidentified MCP caller is `unknown-agent`, never you. `/ask` from Telegram (allowed users only) runs as you, in action mode.
239
+ - Agents consulting each other multiply the prompt-injection paths: keep the default `advice` mode, grant `[policy.act]` sparingly, and read the journal.
240
+ - For local presets that take the message as an argument (`hermes`, `openclaw`), the message is visible in the local process list while the call runs. Remote messages travel only on the SSH channel.
241
+ - It uses your SSH config and keys as they are (`BatchMode=yes`: it never prompts for a password). Give the remote account only the rights the agent needs.
242
+ - Anyone who can edit `agents.toml` can make hail run commands. Keep it as private as your SSH config.
243
+
244
+ ## Development
245
+
246
+ ```bash
247
+ uv venv && uv pip install -e ".[dev]"
248
+ pytest --cov=hail
249
+ ```
250
+
251
+ Adding a preset is a few lines in `src/hail/presets.py`. PRs for new agents are welcome.
252
+
253
+ ## License
254
+
255
+ MIT