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.
- hail_agent-0.4.0/.claude-plugin/marketplace.json +14 -0
- hail_agent-0.4.0/.gitattributes +1 -0
- hail_agent-0.4.0/.github/workflows/publish.yml +32 -0
- hail_agent-0.4.0/.github/workflows/tests.yml +17 -0
- hail_agent-0.4.0/.gitignore +11 -0
- hail_agent-0.4.0/INSTALL.md +16 -0
- hail_agent-0.4.0/LICENSE +21 -0
- hail_agent-0.4.0/PKG-INFO +276 -0
- hail_agent-0.4.0/README.md +255 -0
- hail_agent-0.4.0/plugin/.claude-plugin/plugin.json +8 -0
- hail_agent-0.4.0/plugin/skills/hail/SKILL.md +25 -0
- hail_agent-0.4.0/pyproject.toml +44 -0
- hail_agent-0.4.0/src/hail/__init__.py +3 -0
- hail_agent-0.4.0/src/hail/__main__.py +5 -0
- hail_agent-0.4.0/src/hail/app.py +49 -0
- hail_agent-0.4.0/src/hail/cli.py +291 -0
- hail_agent-0.4.0/src/hail/commands.py +205 -0
- hail_agent-0.4.0/src/hail/config.py +251 -0
- hail_agent-0.4.0/src/hail/envelope.py +139 -0
- hail_agent-0.4.0/src/hail/feeds.py +272 -0
- hail_agent-0.4.0/src/hail/hub.py +260 -0
- hail_agent-0.4.0/src/hail/jobs_inbox.py +228 -0
- hail_agent-0.4.0/src/hail/mcp.py +241 -0
- hail_agent-0.4.0/src/hail/mcp_tools.py +109 -0
- hail_agent-0.4.0/src/hail/parsers.py +66 -0
- hail_agent-0.4.0/src/hail/policy.py +95 -0
- hail_agent-0.4.0/src/hail/presets.py +100 -0
- hail_agent-0.4.0/src/hail/redact.py +29 -0
- hail_agent-0.4.0/src/hail/runner.py +191 -0
- hail_agent-0.4.0/src/hail/store.py +342 -0
- hail_agent-0.4.0/src/hail/tasks.py +94 -0
- hail_agent-0.4.0/src/hail/telegram.py +211 -0
- hail_agent-0.4.0/tests/conftest.py +47 -0
- hail_agent-0.4.0/tests/test_advice.py +160 -0
- hail_agent-0.4.0/tests/test_cli_mcp.py +235 -0
- hail_agent-0.4.0/tests/test_commands_mcp.py +166 -0
- hail_agent-0.4.0/tests/test_config_parsers.py +120 -0
- hail_agent-0.4.0/tests/test_envelope_store_policy.py +155 -0
- hail_agent-0.4.0/tests/test_feeds.py +355 -0
- hail_agent-0.4.0/tests/test_hub.py +220 -0
- hail_agent-0.4.0/tests/test_inbox_mailbox.py +173 -0
- hail_agent-0.4.0/tests/test_private_journal.py +40 -0
- hail_agent-0.4.0/tests/test_review_fixes.py +188 -0
- hail_agent-0.4.0/tests/test_runner.py +138 -0
- hail_agent-0.4.0/tests/test_runner_context.py +54 -0
- hail_agent-0.4.0/tests/test_tasks_telegram.py +243 -0
- 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,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`.
|
hail_agent-0.4.0/LICENSE
ADDED
|
@@ -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
|