hermes-local-hands 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hermes_local_hands-0.1.0/.github/workflows/ci.yml +58 -0
- hermes_local_hands-0.1.0/.github/workflows/codeql.yml +38 -0
- hermes_local_hands-0.1.0/.github/workflows/publish.yml +89 -0
- hermes_local_hands-0.1.0/.gitignore +18 -0
- hermes_local_hands-0.1.0/CHANGELOG.md +30 -0
- hermes_local_hands-0.1.0/CONTRIBUTING.md +52 -0
- hermes_local_hands-0.1.0/LICENSE +21 -0
- hermes_local_hands-0.1.0/PKG-INFO +375 -0
- hermes_local_hands-0.1.0/README.md +320 -0
- hermes_local_hands-0.1.0/ROADMAP.md +43 -0
- hermes_local_hands-0.1.0/SECURITY.md +52 -0
- hermes_local_hands-0.1.0/THREAT_MODEL.md +130 -0
- hermes_local_hands-0.1.0/docs/ADR-001-ARCHITECTURE.md +69 -0
- hermes_local_hands-0.1.0/docs/SERVICE.md +113 -0
- hermes_local_hands-0.1.0/pyproject.toml +74 -0
- hermes_local_hands-0.1.0/src/hermes_local_hands/__init__.py +17 -0
- hermes_local_hands-0.1.0/src/hermes_local_hands/__main__.py +6 -0
- hermes_local_hands-0.1.0/src/hermes_local_hands/auth.py +89 -0
- hermes_local_hands-0.1.0/src/hermes_local_hands/cli.py +504 -0
- hermes_local_hands-0.1.0/src/hermes_local_hands/errors.py +33 -0
- hermes_local_hands-0.1.0/src/hermes_local_hands/gitops.py +1201 -0
- hermes_local_hands-0.1.0/src/hermes_local_hands/hermes_config.py +142 -0
- hermes_local_hands-0.1.0/src/hermes_local_hands/mcp_server.py +404 -0
- hermes_local_hands-0.1.0/src/hermes_local_hands/models.py +69 -0
- hermes_local_hands-0.1.0/src/hermes_local_hands/paths.py +134 -0
- hermes_local_hands-0.1.0/src/hermes_local_hands/receipts.py +244 -0
- hermes_local_hands-0.1.0/src/hermes_local_hands/service.py +959 -0
- hermes_local_hands-0.1.0/src/hermes_local_hands/storage.py +840 -0
- hermes_local_hands-0.1.0/tests/test_cli_interface.py +376 -0
- hermes_local_hands-0.1.0/tests/test_core_receipts.py +17 -0
- hermes_local_hands-0.1.0/tests/test_core_service.py +114 -0
- hermes_local_hands-0.1.0/tests/test_gitops_security.py +609 -0
- hermes_local_hands-0.1.0/tests/test_mcp_adapter.py +198 -0
- hermes_local_hands-0.1.0/tests/test_mcp_e2e.py +171 -0
- hermes_local_hands-0.1.0/tests/test_paths_security.py +123 -0
- hermes_local_hands-0.1.0/tests/test_service_workflow.py +670 -0
- hermes_local_hands-0.1.0/tests/test_storage_receipts.py +358 -0
- hermes_local_hands-0.1.0/uv.lock +1791 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
test:
|
|
17
|
+
name: ${{ matrix.os }} / Python ${{ matrix.python-version }}
|
|
18
|
+
runs-on: ${{ matrix.os }}
|
|
19
|
+
strategy:
|
|
20
|
+
fail-fast: false
|
|
21
|
+
matrix:
|
|
22
|
+
os: [ubuntu-latest, macos-latest]
|
|
23
|
+
python-version: ['3.11', '3.12', '3.13']
|
|
24
|
+
steps:
|
|
25
|
+
- name: Check out repository
|
|
26
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
27
|
+
with:
|
|
28
|
+
persist-credentials: false
|
|
29
|
+
- name: Set up Python
|
|
30
|
+
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
31
|
+
with:
|
|
32
|
+
python-version: ${{ matrix.python-version }}
|
|
33
|
+
cache: pip
|
|
34
|
+
- name: Upgrade the environment installer
|
|
35
|
+
run: >-
|
|
36
|
+
python -m pip install --disable-pip-version-check --upgrade
|
|
37
|
+
'pip>=26.2' 'setuptools>=83'
|
|
38
|
+
- name: Install project
|
|
39
|
+
run: python -m pip install --disable-pip-version-check -e '.[dev]'
|
|
40
|
+
- name: Lint
|
|
41
|
+
run: ruff check .
|
|
42
|
+
- name: Check formatting
|
|
43
|
+
run: ruff format --check .
|
|
44
|
+
- name: Audit runtime dependencies
|
|
45
|
+
run: pip-audit --local --skip-editable --progress-spinner off
|
|
46
|
+
- name: Test
|
|
47
|
+
run: pytest --cov=hermes_local_hands --cov-report=term-missing
|
|
48
|
+
- name: Build distributions
|
|
49
|
+
run: python -m build
|
|
50
|
+
- name: Validate package metadata
|
|
51
|
+
run: python -m twine check dist/*
|
|
52
|
+
- name: Install built wheel in a clean environment
|
|
53
|
+
shell: bash
|
|
54
|
+
run: |
|
|
55
|
+
clean_venv="$(mktemp -d)/wheel-smoke"
|
|
56
|
+
python -m venv "$clean_venv"
|
|
57
|
+
"$clean_venv/bin/python" -m pip install --disable-pip-version-check dist/*.whl
|
|
58
|
+
"$clean_venv/bin/hermes-local-hands" --help
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: CodeQL
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
schedule:
|
|
9
|
+
- cron: '23 4 * * 1'
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
security-events: write
|
|
14
|
+
|
|
15
|
+
concurrency:
|
|
16
|
+
group: codeql-${{ github.workflow }}-${{ github.ref }}
|
|
17
|
+
cancel-in-progress: true
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
analyze:
|
|
21
|
+
name: Python
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
timeout-minutes: 15
|
|
24
|
+
steps:
|
|
25
|
+
- name: Check out repository
|
|
26
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
27
|
+
with:
|
|
28
|
+
persist-credentials: false
|
|
29
|
+
- name: Initialize CodeQL
|
|
30
|
+
uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
|
31
|
+
with:
|
|
32
|
+
languages: python
|
|
33
|
+
build-mode: none
|
|
34
|
+
config: |
|
|
35
|
+
paths:
|
|
36
|
+
- src/hermes_local_hands
|
|
37
|
+
- name: Analyze
|
|
38
|
+
uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions: {}
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: publish-${{ github.event.release.tag_name }}
|
|
11
|
+
cancel-in-progress: false
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
build:
|
|
15
|
+
name: Build and verify distributions
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
permissions:
|
|
18
|
+
contents: read
|
|
19
|
+
steps:
|
|
20
|
+
- name: Check out the released tag
|
|
21
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
22
|
+
with:
|
|
23
|
+
ref: ${{ github.event.release.tag_name }}
|
|
24
|
+
persist-credentials: false
|
|
25
|
+
- name: Set up Python
|
|
26
|
+
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
27
|
+
with:
|
|
28
|
+
python-version: "3.13"
|
|
29
|
+
- name: Upgrade the environment installer
|
|
30
|
+
run: >-
|
|
31
|
+
python -m pip install --disable-pip-version-check --upgrade
|
|
32
|
+
'pip>=26.2' 'setuptools>=83'
|
|
33
|
+
- name: Verify release tag and package version
|
|
34
|
+
env:
|
|
35
|
+
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
|
36
|
+
run: |
|
|
37
|
+
python - <<'PY'
|
|
38
|
+
import os
|
|
39
|
+
import tomllib
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
|
|
42
|
+
version = tomllib.loads(
|
|
43
|
+
Path("pyproject.toml").read_text(encoding="utf-8")
|
|
44
|
+
)["project"]["version"]
|
|
45
|
+
expected = f"v{version}"
|
|
46
|
+
if os.environ["RELEASE_TAG"] != expected:
|
|
47
|
+
raise SystemExit(
|
|
48
|
+
f"release tag must be {expected!r}, got {os.environ['RELEASE_TAG']!r}"
|
|
49
|
+
)
|
|
50
|
+
PY
|
|
51
|
+
- name: Install release checks
|
|
52
|
+
run: python -m pip install --disable-pip-version-check -e '.[dev]'
|
|
53
|
+
- name: Validate source
|
|
54
|
+
run: |
|
|
55
|
+
ruff check .
|
|
56
|
+
ruff format --check .
|
|
57
|
+
pytest
|
|
58
|
+
- name: Build distributions
|
|
59
|
+
run: python -m build
|
|
60
|
+
- name: Validate package metadata
|
|
61
|
+
run: python -m twine check dist/*
|
|
62
|
+
- name: Store distributions
|
|
63
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
64
|
+
with:
|
|
65
|
+
name: python-package-distributions
|
|
66
|
+
path: dist/
|
|
67
|
+
if-no-files-found: error
|
|
68
|
+
retention-days: 7
|
|
69
|
+
|
|
70
|
+
publish:
|
|
71
|
+
name: Publish distributions
|
|
72
|
+
if: vars.PYPI_PUBLISH == 'true'
|
|
73
|
+
needs: [build]
|
|
74
|
+
runs-on: ubuntu-latest
|
|
75
|
+
environment:
|
|
76
|
+
name: pypi
|
|
77
|
+
url: https://pypi.org/p/hermes-local-hands
|
|
78
|
+
permissions:
|
|
79
|
+
id-token: write
|
|
80
|
+
steps:
|
|
81
|
+
- name: Retrieve distributions
|
|
82
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
83
|
+
with:
|
|
84
|
+
name: python-package-distributions
|
|
85
|
+
path: dist/
|
|
86
|
+
- name: Publish distributions with attestations
|
|
87
|
+
uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
|
|
88
|
+
with:
|
|
89
|
+
attestations: true
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- No stable protocol, production-security claim, hosted service, or compatibility
|
|
6
|
+
guarantee is made until a release explicitly says otherwise.
|
|
7
|
+
|
|
8
|
+
## 0.1.0 - 2026-09-04
|
|
9
|
+
|
|
10
|
+
Initial alpha release candidate:
|
|
11
|
+
|
|
12
|
+
- loopback-only MCP companion with explicit trusted reverse-proxy host settings;
|
|
13
|
+
- client-scoped workspace grants and narrow status/file-read tools;
|
|
14
|
+
- pending, expiring patch/check requests with local-only approval and rejection;
|
|
15
|
+
- snapshot-only patch/check execution, stale-request protection, and uncertain
|
|
16
|
+
recovery after interrupted execution;
|
|
17
|
+
- anonymous archives, high-entropy staging, and atomic no-replace snapshot
|
|
18
|
+
publication on supported macOS and Linux hosts;
|
|
19
|
+
- exact-ID snapshot cleanup with fd-bound deletion, retained completion markers,
|
|
20
|
+
and explicit incomplete-deletion visibility;
|
|
21
|
+
- signed receipt chain and local verification command;
|
|
22
|
+
- source/build/test/package validation automation.
|
|
23
|
+
- one real two-machine alpha validation over tailnet HTTPS, covering remote
|
|
24
|
+
status/read, a pending patch, local approval, snapshot execution, a linked
|
|
25
|
+
check, and receipt verification.
|
|
26
|
+
|
|
27
|
+
This version is not a sandbox. Approved checks run trusted repository code with
|
|
28
|
+
the local user's normal host and network access and can cause host-side effects.
|
|
29
|
+
Its post-check source observation covers Git-visible checkout status only. It
|
|
30
|
+
has not established broad Hermes/tunnel compatibility or production readiness.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for helping test a safer local companion for Hermes. The project is
|
|
4
|
+
alpha-stage; its protocol, state format, and CLI can change before a stable
|
|
5
|
+
release.
|
|
6
|
+
|
|
7
|
+
## Development setup and required checks
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
python3.11 -m venv .venv
|
|
11
|
+
source .venv/bin/activate
|
|
12
|
+
python -m pip install -e '.[dev]'
|
|
13
|
+
|
|
14
|
+
ruff check .
|
|
15
|
+
ruff format --check .
|
|
16
|
+
pip-audit --local --skip-editable --progress-spinner off
|
|
17
|
+
pytest --cov=hermes_local_hands --cov-report=term-missing
|
|
18
|
+
python -m build
|
|
19
|
+
python -m twine check dist/*
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Run checks against the commit you propose. Do not claim a command passed on a
|
|
23
|
+
platform where you did not run it. CI also installs the built wheel into a clean
|
|
24
|
+
environment; that validates packaging, not a real tunnel or two-host Hermes
|
|
25
|
+
deployment.
|
|
26
|
+
|
|
27
|
+
## Security-sensitive changes
|
|
28
|
+
|
|
29
|
+
Any change to authentication, client/workspace grants, paths, secret handling,
|
|
30
|
+
patch parsing, approvals, request TTL/recovery, receipts, command execution,
|
|
31
|
+
proxy trust, or state persistence requires all of the following:
|
|
32
|
+
|
|
33
|
+
1. A focused test for success and fail-closed behavior.
|
|
34
|
+
2. A [THREAT_MODEL.md](THREAT_MODEL.md) update when the trust boundary changes.
|
|
35
|
+
3. A review of whether output, errors, logs, or receipts could leak local paths,
|
|
36
|
+
credentials, or source.
|
|
37
|
+
4. A clear statement of what remains untrusted. In particular, fixed check
|
|
38
|
+
profiles execute repository code as the local user, are not a sandbox, and
|
|
39
|
+
can cause host-side effects. Git-status observation does not detect effects
|
|
40
|
+
outside the checkout's Git-visible state.
|
|
41
|
+
|
|
42
|
+
Prefer a smaller denied surface over a convenient implicit fallback. Preserve
|
|
43
|
+
the invariant that the remote protocol can neither approve/deny nor write the
|
|
44
|
+
active checkout, merge, push, or expose a raw shell.
|
|
45
|
+
|
|
46
|
+
## Pull requests
|
|
47
|
+
|
|
48
|
+
Describe the user problem, scope, security impact, validation actually run, and
|
|
49
|
+
known limitations. Keep changes narrow. Do not include automatic merge/push,
|
|
50
|
+
credentials, private repositories, private state databases, bearer tokens, or
|
|
51
|
+
real action receipts. Report security problems privately under
|
|
52
|
+
[SECURITY.md](SECURITY.md).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Maurice Mohr
|
|
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,375 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hermes-local-hands
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A consent-gated local companion for remote Hermes agents
|
|
5
|
+
Project-URL: Homepage, https://github.com/mauricemohr88-debug/hermes-local-hands
|
|
6
|
+
Project-URL: Documentation, https://github.com/mauricemohr88-debug/hermes-local-hands#readme
|
|
7
|
+
Project-URL: Issues, https://github.com/mauricemohr88-debug/hermes-local-hands/issues
|
|
8
|
+
Project-URL: Source, https://github.com/mauricemohr88-debug/hermes-local-hands
|
|
9
|
+
Author: Maurice Mohr
|
|
10
|
+
License: MIT License
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2026 Maurice Mohr
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
in the Software without restriction, including without limitation the rights
|
|
17
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
copies or substantial portions of the Software.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
SOFTWARE.
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Keywords: agents,automation,git,hermes,mcp,security
|
|
33
|
+
Classifier: Development Status :: 3 - Alpha
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Operating System :: MacOS
|
|
37
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
41
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
42
|
+
Requires-Python: >=3.11
|
|
43
|
+
Requires-Dist: cryptography<51,>=50
|
|
44
|
+
Requires-Dist: mcp<3,>=2.1
|
|
45
|
+
Requires-Dist: uvicorn<1,>=0.35
|
|
46
|
+
Provides-Extra: dev
|
|
47
|
+
Requires-Dist: build<2,>=1.2; extra == 'dev'
|
|
48
|
+
Requires-Dist: httpx<1,>=0.28; extra == 'dev'
|
|
49
|
+
Requires-Dist: pip-audit<3,>=2.10; extra == 'dev'
|
|
50
|
+
Requires-Dist: pytest-cov<7,>=6; extra == 'dev'
|
|
51
|
+
Requires-Dist: pytest<10,>=9.0.3; extra == 'dev'
|
|
52
|
+
Requires-Dist: ruff<1,>=0.12; extra == 'dev'
|
|
53
|
+
Requires-Dist: twine<7,>=6; extra == 'dev'
|
|
54
|
+
Description-Content-Type: text/markdown
|
|
55
|
+
|
|
56
|
+
# Hermes Local Hands
|
|
57
|
+
|
|
58
|
+
[](https://github.com/mauricemohr88-debug/hermes-local-hands/actions/workflows/ci.yml)
|
|
59
|
+
[](https://github.com/mauricemohr88-debug/hermes-local-hands/actions/workflows/codeql.yml)
|
|
60
|
+
[](https://pypi.org/project/hermes-local-hands/)
|
|
61
|
+
|
|
62
|
+
Hermes Local Hands is an **alpha** local companion for a remote
|
|
63
|
+
[Hermes](https://github.com/NousResearch/hermes-agent) agent. It gives that
|
|
64
|
+
agent a deliberately narrow view of one registered repository, while the
|
|
65
|
+
computer that owns the repository retains the final say over every change and
|
|
66
|
+
check.
|
|
67
|
+
|
|
68
|
+
It solves a concrete split-machine problem: Hermes can reason on a Mac Studio,
|
|
69
|
+
server, or VM, while the developer's source code remains on a different local
|
|
70
|
+
machine. The remote side can inspect approved files and create requests. It
|
|
71
|
+
cannot run a shell, approve or deny a request, write the active checkout,
|
|
72
|
+
merge, or push.
|
|
73
|
+
|
|
74
|
+
> **Security boundary in v0.1.** `workspace_status` and `read_file` are remote
|
|
75
|
+
> inspection tools. `propose_patch` and `request_check` only create a pending,
|
|
76
|
+
> expiring request. Approval and rejection are local-operator actions. Local
|
|
77
|
+
> Hands applies patches and launches checks from an isolated snapshot rather
|
|
78
|
+
> than writing the registered checkout itself. Approved check code still has
|
|
79
|
+
> the local user's normal host and network access and can deliberately modify
|
|
80
|
+
> the registered checkout; this is **not** a sandbox.
|
|
81
|
+
|
|
82
|
+
## Why this now
|
|
83
|
+
|
|
84
|
+
The architecture follows real upstream demand for safer local execution and
|
|
85
|
+
split-runtime workflows, rather than adding a second Hermes runtime. Useful
|
|
86
|
+
primary context is the upstream discussions [#18715](https://github.com/NousResearch/hermes-agent/issues/18715),
|
|
87
|
+
[#42807](https://github.com/NousResearch/hermes-agent/issues/42807), and
|
|
88
|
+
[#16462](https://github.com/NousResearch/hermes-agent/issues/16462), plus the
|
|
89
|
+
related implementation work in [#63966](https://github.com/NousResearch/hermes-agent/pull/63966)
|
|
90
|
+
and [#43045](https://github.com/NousResearch/hermes-agent/pull/43045).
|
|
91
|
+
|
|
92
|
+
## What it does — and does not do
|
|
93
|
+
|
|
94
|
+
| Capability | Remote Hermes can do it | Local operator must do it |
|
|
95
|
+
| --- | --- | --- |
|
|
96
|
+
| See sanitised Git status | Yes, for a granted workspace | Register the workspace and grant the client |
|
|
97
|
+
| Read a text file | Yes, only inside the explicit read allowlist | Define the allowlist |
|
|
98
|
+
| Propose a textual patch | Create a pending request only | Review and approve it |
|
|
99
|
+
| Request a fixed check profile | Create a pending request only | Review and approve it |
|
|
100
|
+
| Apply or test | No | Local Hands launches it in a snapshot; approved code remains host-capable |
|
|
101
|
+
| Reject, merge, push, use a shell | No | Reject is local; merge/push/shell are outside the protocol |
|
|
102
|
+
|
|
103
|
+
Every request is tied to an authenticated client, an allowed workspace, the
|
|
104
|
+
current Git `HEAD`, the workspace-policy hash, an idempotency key scoped to the
|
|
105
|
+
client, and a short TTL. A changed policy or commit invalidates a request.
|
|
106
|
+
Re-registering an existing workspace ID replaces its client grants with exactly
|
|
107
|
+
the new `--client` list; old grants do not follow a changed root or policy.
|
|
108
|
+
Interrupted execution is recorded as **uncertain**, not silently reported as a
|
|
109
|
+
success. The local state store also writes a signed, append-only receipt chain
|
|
110
|
+
for requests and operator decisions.
|
|
111
|
+
|
|
112
|
+
## Install and local-only quickstart
|
|
113
|
+
|
|
114
|
+
Requirements: Python 3.11+ and Git. Install the isolated command with
|
|
115
|
+
[uv](https://docs.astral.sh/uv/) or pipx:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
uv tool install hermes-local-hands
|
|
119
|
+
# Alternative: pipx install hermes-local-hands
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
For development from a source checkout instead:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Use a Python 3.11+ executable; macOS /usr/bin/python3 may still be too old.
|
|
126
|
+
python3.11 -m venv .venv
|
|
127
|
+
source .venv/bin/activate
|
|
128
|
+
python -m pip install -e '.[dev]'
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The example below uses `demo` and only exposes `src` and `tests`; replace the
|
|
132
|
+
absolute path and allowlist with your own deliberate choices.
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
|
|
136
|
+
# Creates private local state and a bearer credential file (mode 0600).
|
|
137
|
+
hermes-local-hands init --client-id hermes-mac-studio
|
|
138
|
+
|
|
139
|
+
# Register one Git repository and fixed, named check profiles.
|
|
140
|
+
hermes-local-hands workspace add \
|
|
141
|
+
--id demo \
|
|
142
|
+
--root /absolute/path/to/repository \
|
|
143
|
+
--read src \
|
|
144
|
+
--read tests \
|
|
145
|
+
--write src \
|
|
146
|
+
--check syntax=/usr/bin/python3,-m,compileall,-q,src \
|
|
147
|
+
--client hermes-mac-studio
|
|
148
|
+
|
|
149
|
+
# Start the listener. It refuses non-loopback bind addresses.
|
|
150
|
+
hermes-local-hands serve --host 127.0.0.1 --port 8741
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`init` stores the token under the private state directory rather than printing
|
|
154
|
+
it. On macOS/Linux the default is
|
|
155
|
+
`~/.local/state/hermes-local-hands/clients/<client-id>.token`; set
|
|
156
|
+
`XDG_STATE_HOME` first to choose another private state location. Read the file
|
|
157
|
+
locally and export it only in the Hermes runtime's private environment:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
export HERMES_LOCAL_HANDS_TOKEN="$(< "$HOME/.local/state/hermes-local-hands/clients/hermes-mac-studio.token")"
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Do not put a real token in a repository, issue, screenshot, shell history, or
|
|
164
|
+
public configuration file.
|
|
165
|
+
|
|
166
|
+
### End-to-end workflow
|
|
167
|
+
|
|
168
|
+
1. The local operator registers a workspace, its explicit read/write allowlist,
|
|
169
|
+
fixed check profiles, and the Hermes client grant.
|
|
170
|
+
`workspace grants <workspace-id>` lists current grants; `workspace revoke
|
|
171
|
+
<workspace-id> <client-id>` removes one and records the decision.
|
|
172
|
+
2. The local operator starts `serve` on loopback only.
|
|
173
|
+
3. A trusted reverse proxy/tunnel terminates HTTPS and forwards to that local
|
|
174
|
+
listener. Local Hands still accepts only the exact proxy host(s) named at
|
|
175
|
+
startup; it does not infer trust from arbitrary forwarded headers.
|
|
176
|
+
4. Hermes calls `workspace_status` or `read_file`, or creates a pending patch
|
|
177
|
+
or check request.
|
|
178
|
+
Patch and check requests require a clean registered checkout both when they
|
|
179
|
+
are created and when they are approved; uncommitted changes are never copied
|
|
180
|
+
into the managed snapshot.
|
|
181
|
+
5. The local operator inspects the request and chooses one of these local-only
|
|
182
|
+
commands:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
hermes-local-hands request list --state pending
|
|
186
|
+
# Default output is a multiline, control-escaped local review.
|
|
187
|
+
hermes-local-hands request show <request-id>
|
|
188
|
+
# Copy the approval_code shown above; it is intentionally request-specific.
|
|
189
|
+
hermes-local-hands request approve <request-id> --confirm <approval-code>
|
|
190
|
+
# or:
|
|
191
|
+
hermes-local-hands request deny <request-id> --reason "not approved"
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Use `request show <request-id> --json` only when escaped machine-readable
|
|
195
|
+
output is needed. The default review prefixes every untrusted patch line and
|
|
196
|
+
escapes terminal/bidirectional controls so a patch cannot visually imitate
|
|
197
|
+
the approval fields.
|
|
198
|
+
|
|
199
|
+
6. An approved operation is revalidated against the recorded `HEAD` and policy,
|
|
200
|
+
then Local Hands applies or launches it from a managed snapshot. Approved
|
|
201
|
+
check code is still host-capable. Inspect the result with
|
|
202
|
+
`hermes-local-hands request status <request-id>` and verify the receipt chain
|
|
203
|
+
locally with `hermes-local-hands receipt-verify`.
|
|
204
|
+
|
|
205
|
+
7. After reviewing a succeeded or failed snapshot, free its retention slot only
|
|
206
|
+
through the exact-ID local deletion gate:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
hermes-local-hands snapshot list
|
|
210
|
+
hermes-local-hands snapshot delete <request-id> --confirm <request-id>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The command records deletion-requested and deletion-completed receipts. It
|
|
214
|
+
refuses pending, executing, missing, symlinked, or mismatched targets and
|
|
215
|
+
never deletes snapshots automatically. An `uncertain` snapshot can be
|
|
216
|
+
removed only after explicit local review and the same exact-ID confirmation.
|
|
217
|
+
A successful deletion empties the already-open request directory in place
|
|
218
|
+
and retains only a tiny completion marker instead of performing a final
|
|
219
|
+
path-based directory removal. Completed marker directories are omitted from
|
|
220
|
+
`snapshot list`; incomplete deletions remain visible there for local
|
|
221
|
+
investigation.
|
|
222
|
+
|
|
223
|
+
For a check request linked to an approved patch, create it locally with the
|
|
224
|
+
patch request ID. The check then uses that patch's approved snapshot rather
|
|
225
|
+
than the mutable active checkout:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
hermes-local-hands check add demo syntax check-after-patch-001 \
|
|
229
|
+
--client hermes-mac-studio \
|
|
230
|
+
--patch-request <approved-patch-request-id>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
That approval creates a **fresh** snapshot at the recorded base commit and
|
|
234
|
+
replays the exact stored patch bytes whose digest was reviewed; it does not
|
|
235
|
+
reuse a mutable previous snapshot. A check result is evidence about that
|
|
236
|
+
snapshot only; it is not a merge, deployment, or production safety claim.
|
|
237
|
+
|
|
238
|
+
## Hermes MCP configuration
|
|
239
|
+
|
|
240
|
+
Generate the configuration fragment rather than hand-copying names:
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
# Local same-machine use:
|
|
244
|
+
hermes-local-hands hermes-config --port 8741
|
|
245
|
+
|
|
246
|
+
# Remote use: pass the exact, already configured HTTPS tunnel endpoint.
|
|
247
|
+
hermes-local-hands hermes-config \
|
|
248
|
+
--endpoint https://mac-studio.example.ts.net/mcp \
|
|
249
|
+
--token-env HERMES_LOCAL_HANDS_TOKEN
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
The generated YAML includes only these five tools:
|
|
253
|
+
|
|
254
|
+
- `workspace_status`
|
|
255
|
+
- `read_file`
|
|
256
|
+
- `propose_patch`
|
|
257
|
+
- `request_check`
|
|
258
|
+
- `request_status`
|
|
259
|
+
|
|
260
|
+
The two inspection tools advertise the MCP `readOnlyHint`. Hermes versions and
|
|
261
|
+
clients may still apply their own approval or policy gate to those tools; the
|
|
262
|
+
hint is useful metadata, not a compatibility guarantee or a bypass.
|
|
263
|
+
|
|
264
|
+
Hermes releases affected by upstream issue
|
|
265
|
+
[#88858](https://github.com/NousResearch/hermes-agent/issues/88858) may still
|
|
266
|
+
prompt for every read-only call while `trust: untrusted` is configured. That is
|
|
267
|
+
a fail-closed Hermes client behaviour, not additional Local Hands authority.
|
|
268
|
+
The upstream fix is tracked in
|
|
269
|
+
[#88372](https://github.com/NousResearch/hermes-agent/pull/88372). Keep the
|
|
270
|
+
generated `untrusted` setting unless you have reviewed the implications of
|
|
271
|
+
changing the client-side trust policy.
|
|
272
|
+
|
|
273
|
+
For a reverse proxy, bind Local Hands only to loopback and name every permitted
|
|
274
|
+
external Host exactly:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
hermes-local-hands serve --host 127.0.0.1 --port 8741 \
|
|
278
|
+
--proxy-host mac-studio.example.ts.net
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
The endpoint generator accepts loopback `http://.../mcp` or an explicit
|
|
282
|
+
non-loopback `https://.../mcp` URL. It rejects embedded credentials, query
|
|
283
|
+
strings, and unsafe schemes. Configure the tunnel's own identity,
|
|
284
|
+
authentication, and HTTPS separately; do not expose port 8741 directly to the
|
|
285
|
+
public Internet.
|
|
286
|
+
|
|
287
|
+
## Safety properties and limits
|
|
288
|
+
|
|
289
|
+
- **Explicit scope:** a client must have a workspace grant; path reads and
|
|
290
|
+
patches must stay inside the workspace allowlist. Secret-looking and binary
|
|
291
|
+
material is not returned as normal text.
|
|
292
|
+
- **No direct mutation:** no remote endpoint approves, denies, executes a
|
|
293
|
+
shell, writes the registered checkout, merges, or pushes.
|
|
294
|
+
- **Snapshot working directory:** accepted patches and checks use a snapshot
|
|
295
|
+
rooted at the recorded commit and do not include uncommitted checkout
|
|
296
|
+
changes. This constrains Local Hands' own file operations, not what approved
|
|
297
|
+
check code can access on the host.
|
|
298
|
+
- **Checks are not sandboxed:** fixed profiles limit what the protocol launches,
|
|
299
|
+
but the selected program still runs as the local user. It can access that
|
|
300
|
+
user's files, network, credentials, services, and can cause host-side
|
|
301
|
+
effects. Only approve profiles and repositories you trust.
|
|
302
|
+
- **Checkout observation is limited:** after a check, Local Hands compares only
|
|
303
|
+
Git-visible checkout status before and after. `same` does not prove that no
|
|
304
|
+
non-Git file, service, network, credential, or other host-side effect
|
|
305
|
+
occurred.
|
|
306
|
+
- **Audit evidence:** retained request/decision events form a signed receipt
|
|
307
|
+
chain. Verification detects edits and reordering within that retained chain;
|
|
308
|
+
without an externally anchored head it cannot detect deletion of a valid
|
|
309
|
+
tail or rollback to an earlier valid database. Receipts also do not prove a
|
|
310
|
+
host was not compromised.
|
|
311
|
+
- **Bounded alpha retention:** v0.1 permits at most 32 open requests per client,
|
|
312
|
+
256 open requests globally, 512 retained requests per client, and 2,048
|
|
313
|
+
retained requests globally. Managed storage also permits at most 128 snapshot
|
|
314
|
+
deletion-marker directories. It never silently deletes requests, snapshots,
|
|
315
|
+
or markers.
|
|
316
|
+
Reviewed terminal snapshots can be removed one at a time through the local
|
|
317
|
+
exact-ID command above, but there is no request-record or marker-prune command
|
|
318
|
+
yet. Reaching a retained-request or deletion-marker cap is a deliberate
|
|
319
|
+
fail-closed stop that needs a later reviewed retention/migration release, not
|
|
320
|
+
a database or filesystem deletion workaround.
|
|
321
|
+
- **Failed creation markers:** snapshot construction uses a private,
|
|
322
|
+
high-entropy `.creating-*` staging directory and atomic no-replace publication.
|
|
323
|
+
If construction fails, Local Hands clears only the directory bound to its open
|
|
324
|
+
descriptor and deliberately does not perform a race-prone path deletion. An
|
|
325
|
+
empty or partially cleared staging marker can therefore remain and counts
|
|
326
|
+
conservatively toward the 32-snapshot limit. v0.1 has no CLI prune operation
|
|
327
|
+
for these internal markers; repeated build failures require a reviewed
|
|
328
|
+
recovery/migration rather than manual deletion while evidence matters.
|
|
329
|
+
|
|
330
|
+
Read [THREAT_MODEL.md](THREAT_MODEL.md) and [SECURITY.md](SECURITY.md) before
|
|
331
|
+
using it with sensitive repositories.
|
|
332
|
+
|
|
333
|
+
## Development validation
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
ruff check .
|
|
337
|
+
ruff format --check .
|
|
338
|
+
pip-audit --local --skip-editable --progress-spinner off
|
|
339
|
+
pytest --cov=hermes_local_hands --cov-report=term-missing
|
|
340
|
+
python -m build
|
|
341
|
+
python -m twine check dist/*
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
CI additionally installs the built wheel into a clean environment. These are
|
|
345
|
+
release checks for the package, not proof of a safe production rollout.
|
|
346
|
+
|
|
347
|
+
On 2026-09-07 one real two-machine alpha flow was completed using Hermes on one
|
|
348
|
+
Mac, this service on another Mac, and HTTPS over a private Tailscale network.
|
|
349
|
+
The run covered unauthenticated rejection, status and file reads, a pending
|
|
350
|
+
patch, local approval, snapshot application, a linked check, and receipt-chain
|
|
351
|
+
verification. This is evidence for that exact environment only; it is not a
|
|
352
|
+
general compatibility, availability, or production-security claim.
|
|
353
|
+
|
|
354
|
+
For a durable local service setup, see [docs/SERVICE.md](docs/SERVICE.md).
|
|
355
|
+
|
|
356
|
+
## Project direction
|
|
357
|
+
|
|
358
|
+
The security core is intended to remain free and open source. There is no paid
|
|
359
|
+
plan, hosted service, customer, or revenue today. Adoption and safety come
|
|
360
|
+
before any optional convenience layer. See [ROADMAP.md](ROADMAP.md) for the
|
|
361
|
+
public, evidence-gated direction.
|
|
362
|
+
|
|
363
|
+
## Contributing and security
|
|
364
|
+
|
|
365
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development expectations and
|
|
366
|
+
[SECURITY.md](SECURITY.md) for private vulnerability reporting. Never put
|
|
367
|
+
credentials, private source, or real action receipts in a public issue or pull
|
|
368
|
+
request.
|
|
369
|
+
|
|
370
|
+
## License
|
|
371
|
+
|
|
372
|
+
Released under the [MIT License](LICENSE).
|
|
373
|
+
|
|
374
|
+
Hermes Local Hands is an independent community project and is not affiliated
|
|
375
|
+
with or endorsed by Nous Research.
|