Metadata-Version: 2.4
Name: hermes-pubky
Version: 0.2.2
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Rust
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Dist: pyyaml>=6,<7
Requires-Dist: pytest>=8 ; extra == 'test'
Requires-Dist: pyyaml>=6 ; extra == 'test'
Provides-Extra: test
License-File: LICENSE
Summary: Run a Hermes agent whose saved state lives on your own Pubky homeserver
Keywords: hermes,pubky,agent,portable,homeserver
Author: Marcos Carlomagno
License-Expression: MIT
Requires-Python: >=3.11, <3.14
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Homepage, https://github.com/MCarlomagno/hermes-pubky
Project-URL: Issues, https://github.com/MCarlomagno/hermes-pubky/issues
Project-URL: Repository, https://github.com/MCarlomagno/hermes-pubky

# hermes-pubky

Run a [Hermes](https://github.com/NousResearch/hermes) agent whose saved state
lives on your own [Pubky](https://github.com/pubky/pubky-core) homeserver. The
laptop becomes a working copy, but the agent belongs to you.

## Why

Hermes keeps everything it knows in one directory on one machine: `SOUL.md`,
your memories, its learned skills, your conversations. New laptop, fresh
container, throwaway VM, and the agent starts from nothing. The usual
alternative is a memory SaaS, which makes your agent portable by moving it into
someone else's account.

This is a third option. The homeserver holds the authoritative copy of the
agent, a local Hermes reconstructs a working copy, runs it, and saves changes
back. What travels:

- instructions (`SOUL.md`), user and agent memories
- learned skills and their assets
- allowlisted settings
- the conversation database, including tool calls, compaction and rewind history
- a designated workspace: notes, references, outputs

What stays on the machine: API keys, OAuth sessions, the Pubky grant itself, and
anything else machine-specific.

## Install

```bash
uv pip install hermes-pubky==0.2.2
hermes-pubky agent init default
hermes-pubky run default
```

Run these commands in your activated Hermes environment. Needs Python 3.11–3.13,
Hermes 0.21.3 with conversation schema 30, and a Pubky homeserver on v0.11
or later. This release was verified against Hermes commit
`a51143fbbe6ddbc0c7f403d0579c4d75504c6793`; use that checkout as described
under Development below. Hermes is distributed from source, not through PyPI.
`agent init` asks Pubky Ring to authorize one scoped capability, then
publishes the agent's first checkpoint.

On a second computer:

```bash
uv pip install hermes-pubky==0.2.2
hermes-pubky agent attach pubky://<owner>/priv/hermes.pubky.app/v2/agents/default/head.json
hermes-pubky run default
```

Nothing is copied between machines. `attach` downloads the instructions,
memories, skills, settings and conversation database; workspace documents are
fetched when the agent asks for them. Every run saves a checkpoint when it ends,
and the next run on any machine starts from the newest one.

### Hermes plugin packaging

`plugin/` is the directory-plugin wrapper for catalog distribution. It declares
the pinned `hermes-pubky` package dependency, which includes the native Pubky
extension, and adds `/pubky` setup help when enabled. Managed agents still
start through `hermes-pubky run`; enabling the companion does not sync an
ordinary Hermes profile or select its memory provider.

Once this directory is published in the repository, install it with:

```bash
hermes plugins install MCarlomagno/hermes-pubky/plugin
hermes plugins enable hermes-pubky
```

Current Hermes installers install the pinned dependency from
`plugin/pyproject.toml` into the Hermes environment. If dependency installation
is disabled or fails, install it explicitly with the pip command above.
The installer also displays [setup instructions](plugin/after-install.md).

The catalog entry is a **setup helper**. Running a managed agent syncs its
instructions, memories, skills, conversation database snapshots, portable
configuration, and workspace to your remote Pubky homeserver. The homeserver
operator can read that data; it is not end-to-end encrypted.

For a catalog submission, set `subdir: plugin` and `requires_hermes: "==0.21.3"`.
Keep the wrapper's manifest version, project version, and exact package
dependency aligned with the published release. The wrapper never downloads or
updates code at import time.

### Upgrading an existing agent

Stop the agent and sync its latest work with the old runtime before upgrading.
Install hermes-pubky 0.2.2 in the verified Hermes 0.21.3 environment on every
computer that runs the agent. Schema-22 conversations from Hermes 0.19.0 are
upgraded using Hermes' own migrations on a staged copy during capture or restore.
The next checkpoint saves schema 30; old checkpoints remain in history.
An upgrade failure leaves the working database unchanged. Other unverified
schemas are refused. After the new checkpoint is saved, use the newer runtime
on every device; hermes-pubky 0.2.1 cannot read schema 30.

## Commands

```text
hermes-pubky run <id> [--resume SESSION] [--offline] [--query TEXT]

hermes-pubky agent init <id> [--from-hermes-home PATH] [--workspace PATH]
hermes-pubky agent attach <pubky-uri>
hermes-pubky agent list
hermes-pubky agent status <id> [--json] [--offline]
hermes-pubky agent login <id>
hermes-pubky agent logout <id>
hermes-pubky agent sync <id> [--prefer local|remote]
hermes-pubky agent files <id> list|fetch|import|remove
hermes-pubky agent history <id>
hermes-pubky agent restore <id> <snapshot-id>

hermes-pubky template init|inspect|publish <directory>
hermes-pubky template adopt <agent-id> <pubky-uri>
hermes-pubky template update <agent-id>
```

`agent sync` seals whatever changed locally, including files added by hand or
with `agent files import`, then publishes everything pending. `agent logout`
revokes the grant at the homeserver and says so only when that succeeded.

Exit codes: 0 done, 1 usage, 2 saved locally but not yet on the homeserver,
3 conflict, 4 authorization needed, 5 quota, 6 integrity or unsupported runtime.

## What it can reach

`agent init` requests one capability:

```text
/priv/hermes.pubky.app/v2/agents/<id>/:rw
```

Read and write inside that agent's own directory. No root capability, no access
to your other apps. The launcher also refuses paths outside that directory
locally, before a request leaves the process, and keeps the grant out of the
Hermes child's environment entirely.

Publishing a template needs its own separate capability under `/pub/`. An
agent's grant can never publish.

A grant holds one session at a time. Authorize each computer with its own
`agent login` rather than copying a grant between machines, or each will keep
signing the other out.

## Read this before you store anything

Your homeserver operator can read your agent. `/priv` is access-controlled, not
encrypted. If you self-host, that operator is you, otherwise assume whoever runs
your homeserver can read your instructions, memories and conversations.
Credentials are never uploaded, but a conversation can contain anything you or a
tool put in it.

One writer at a time. Read from as many machines as you like, but stop and sync
on one before writing from another. If the homeserver's checkpoint moved while
this machine has unsaved work, `run` stops before starting the agent, installs
nothing over your work, and asks you to choose with `agent sync --prefer
local|remote`; whichever side you drop is preserved under `recovery/` first.

Offline works. Startup seals anything a previous run left unsaved, then refreshes
from the homeserver with a five-second budget. Offline runs chain their
checkpoints, survive restarts, and publish in order at the next sync, retrying
with backoff capped at 60 seconds. A run that could not reach the homeserver
exits 2 and says `saved locally; not yet saved to homeserver`. A run whose
conversation database cannot be captured exits 6 and does not claim anything
was saved.

A cold attach downloads the conversation database in full. It is streamed and
reassembled on disk, never held in memory, but a large history takes time.
Workspace documents stay remote until requested.

History is kept. Older checkpoints remain recoverable and count against your
homeserver quota. Removing a file drops it from the current inventory, not from
history; this is not secure erasure.

Not in 0.2: multi-device merge, client-side encryption, portable credentials,
semantic retrieval, Windows wheels, and harnesses other than the verified
Hermes 0.21.3 runtime.

## How it fits together

```text
hermes-pubky run <id>
  -> read and verify the remote checkpoint
  -> materialize a dedicated HERMES_HOME and workspace
  -> start Hermes as a child, with HERMES_HOME set before it imports
       -> the in-process plugin reports changes and serves two workspace tools
  -> the supervisor owns the lock, the journal and every network call
  -> final capture and sync after the child exits
```

Storage is content-addressed. A checkpoint is an immutable snapshot document
naming immutable objects; only `head.json` is mutable, and it moves last, after
every object it names has been verified as recorded. The homeserver offers no
conditional write, so this is ordering, not a lock: concurrent writers remain
unsupported and are detected rather than merged.

Locally, the journal tracks one *base*: the checkpoint the working copy
corresponds to. Sealing a checkpoint advances it, installing one advances it,
publishing never does. That is what lets a second run continue the first and
offline runs form a chain.

```text
/priv/hermes.pubky.app/v2/agents/<id>/{head,snapshots/*,objects/*}.json
/pub/hermes.pubky.app/v2/templates/<id>/{head,snapshots/*,objects/*}.json
```

Markdown is stored as Markdown and JSON as JSON, so what is on your homeserver
is readable there. Files over 1 MiB, and the database always, are split into
1 MiB chunks.

## Development

```bash
uv venv --python 3.11 .venv
git clone https://github.com/NousResearch/hermes-agent.git /tmp/hermes-pubky-runtime
git -C /tmp/hermes-pubky-runtime checkout a51143fbbe6ddbc0c7f403d0579c4d75504c6793
uv pip install --python .venv/bin/python maturin pytest -e /tmp/hermes-pubky-runtime
PYO3_PYTHON="$PWD/.venv/bin/python" .venv/bin/maturin develop

.venv/bin/pytest        # Python tests; the Hermes ones skip if it is absent
cargo test --lib        # Rust unit tests
```

`tests/test_supervisor.py` runs the whole launcher against a fake homeserver
and a stand-in child: two runs in a row, an attach on a second machine, offline
chains, a moved head over local edits, a corrupt database, a failed restore.
Those are the scenarios that matter; the rest of the suite guards the wire
format and the policy boundaries.

The end-to-end and acceptance suites need PostgreSQL for the homeserver and the
well-known testnet ports free:

```bash
docker run -d --name hermes-pubky-pg \
  -e POSTGRES_USER=test_user -e POSTGRES_PASSWORD=test_pass -e POSTGRES_DB=postgres \
  -p 5432:5432 postgres:18-alpine

export TEST_PUBKY_CONNECTION_STRING="postgres://test_user:test_pass@localhost:5432/postgres?pubky-test=true"
cargo test --test v2 -- --ignored --test-threads=1
```

A real managed Hermes turn, against a local fake model so no credentials are
needed:

```bash
python scripts/check_managed_hermes_integration.py
```

Verify the catalog wrapper in a fresh environment using the candidate wheel:

```bash
maturin build --release --out dist
python scripts/check_plugin_install.py --hermes-source /tmp/hermes-pubky-runtime --wheel-dir dist
```

The full recovery scenario through the shipped command line, one agent across
two machines, ending with the grant revoked (so each run needs a fresh fixture):

```bash
cargo run --example testnet_fixture > /tmp/fixture.json &
HERMES_PUBKY_TESTNET=1 python scripts/check_managed_handoff.py --fixture /tmp/fixture.json
```

Pinned upstream versions, changed only as a deliberate compatibility decision
with tests to match:

| | |
| --- | --- |
| Hermes | Hermes 0.21.3 at the verified commit above, conversation schema 30 |
| Pubky SDK | v0.11.0 commit `6a14bdb8fa2e30ef4e4b241fcdd3992c453d2378` |

`Cargo.lock` is committed. `tests/test_hermes_contract.py` holds the upstream
contract as executable tests and fails if the installed package drifts from it.

## License

MIT

