Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ web/package-lock.json linguist-generated=true
Dockerfile text eol=lf
*.dockerfile text eol=lf
docker/entrypoint.sh text eol=lf
# Vendored CGP schema in the pmoves_bootstrap package: keep LF so the
# SHA-256 byte-compare against the canonical PMOVES.AI copy doesn't
# produce a false-positive drift signal on Windows checkouts.
pmoves_bootstrap/cgp_schema/*.json text eol=lf
3 changes: 3 additions & 0 deletions .mailmap
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,6 @@ xinbenlv <zzn+pa@zzn.im> <zzn+pa@zzn.im>
SaulJWu <saul.jj.wu@gmail.com> <saul.jj.wu@gmail.com>
angelos <angelos@oikos.lan.home.malaiwah.com> <angelos@oikos.lan.home.malaiwah.com>
MestreY0d4-Uninter <241404605+MestreY0d4-Uninter@users.noreply.github.com> <MestreY0d4-Uninter@users.noreply.github.com>

# === PMOVES Mavis (local agent; commit author for the harness v0 fork consumer PRs) ===
Mavis <Mavis@users.noreply.github.com> <Mavis@pmoves.local>
195 changes: 195 additions & 0 deletions pmoves_bootstrap/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# PMOVES Bootstrap Consumer for Hermes (`pmoves_bootstrap/`)

The Hermes-side of the Mavis harness v0 (3-repo coordinated slice). A
non-breaking consumer of the `pmoves.bootstrap/v1` CGP that the
PMOVES.AI side writes (see `pmoves/tools/load_bootstrap.py` and
`pmoves/contracts/schemas/pmoves-bootstrap/v1.schema.json` in the
PMOVES.AI repo).

## Why this exists

The harness v0 (PMOVES.AI PR #2477) is a 3-repo coordinated slice:

1. **PMOVES.AI** — the writer, owns the CGP schema + example, the
orchestrator, the BPM cron, and the canonical `load_bootstrap.py`.
2. **PMOVES-hermes-agent** (this fork) — the agent runtime. Loads
the CGP at session init, registers PMOVES tools alongside
Hermes's native tools, and (in a future slice) subscribes to
`pmoves.agent.task.v1` to pick up Mavis-orchestrator tasks.
3. **PMOVES-pinokio** — the app launcher. Reads the CGP when
launching a PMOVES-tagged Pinokio app.

This fork's role is the heaviest of the three: read the CGP, expose
PMOVES tools as first-class in the agent's toolset, and (optionally)
participate in the multi-agent orchestrator loop via NATS.

## What this slice ships

- `pmoves_bootstrap/__init__.py` — public surface
- `pmoves_bootstrap/loader.py` — CGP reader (YAML + JSON, validates
against the vendored JSON Schema, returns a typed `Bootstrap`).
Mirrors the PMOVES.AI side's `load_bootstrap.py` API.
- `pmoves_bootstrap/tools_bridge.py` — registers PMOVES tools in
the session. Resolves each `bootstrap.tools` entry against the
v0 tool registry (Python scripts + CLI binaries). The session init
code merges these into Hermes's active toolset.
- `pmoves_bootstrap/subscriber.py` — the optional NATS subscriber
stub. v0 is a no-op (no `nats-py` in Hermes's core deps); the
dataclasses (`TaskEnvelope`, `ResultEnvelope`) document the wire
contract so a future slice can wire it in without changing the
surface.
- `pmoves_bootstrap/cgp_schema/v1.schema.json` — vendored copy of
the PMOVES.AI schema
- `pmoves_bootstrap/cgp_schema/example.cgp.yaml` — vendored YAML
example (Hermes has `pyyaml` in its core deps, so YAML is the
natural format)
- `tests/test_pmoves_bootstrap.py` - 33 tests, 9 test groups
- `pmoves_bootstrap/README.md` — this file

## Non-breaking contract

The CGP is a **manifest**, not a config replacement. The consumer
fork is required to honor the 6 constraints baked into the CGP:

| Constraint | What it means for Hermes |
|------------|-------------------------|
| `no-override-existing-config` | Hermes's own config (`cli-config.yaml`, `hermes_state`) is never replaced by the CGP |
| `tagged-services-are-advisory` | The `services` block (Tailscale, RustDesk, Hostinger, Cloudflare) is a hint — missing services are skipped, not failed |
| `no-chit-bypass` | State-changing actions still go through `pmoves-chit-sign`, not directly through the CGP |
| `no-force-push` | Lane rule (this fork's PRs use rebase, never raw `--force`) |
| `no-ci-bypass` | Lane rule (no `--admin` to skip CI; admin merge override is OK for already-green PRs) |
| `preserve-existing-tools` | Hermes's existing toolset (`toolsets.py`) is preserved; the bridge adds PMOVES tools alongside, not in place of |

The non-breaking test pair:

- **No CGP present** → `load_bootstrap()` returns the stub Bootstrap
(empty tools, empty services, all 6 constraints). `register_pmoves_tools()`
is a no-op (no tools added). `subscribe()` is a no-op. Hermes runs
as it does today.
- **CGP present** → the CGP is validated against the vendored schema,
the PMOVES tools are registered alongside Hermes's native tools,
the optional NATS subscriber can pick up Mavis-orchestrator tasks.

## Public API

```python
from pmoves_bootstrap import (
load_bootstrap, # the CGP reader
stub_bootstrap, # the no-CGP fallback Bootstrap
export_env, # export PMOVES_BOOTSTRAP_* env vars
register_pmoves_tools, # the tools_bridge
subscribe, # the optional NATS subscriber
Bootstrap, Identity, Meta, BootstrapError,
)

bs = load_bootstrap() # real or stub
export_env(bs) # process.env gets PMOVES_BOOTSTRAP_*
result = register_pmoves_tools(bs=bs) # BridgeResult(registered, skipped, disabled)
status = subscribe(target="hermes") # SubscriberStatus (always safe; disabled in v0)
```

## Resolution order (4 sources, 1 default)

In priority order, the first one that yields a parseable CGP wins:

1. `path` arg (file path, YAML or JSON detected by content)
2. `source` arg (raw YAML/JSON string)
3. `PMOVES_BOOTSTRAP_CGP` env var (raw) or `PMOVES_BOOTSTRAP_CGP_PATH` env var (file path)
4. The vendored example at `pmoves_bootstrap/cgp_schema/example.cgp.yaml`

If none of the above yield a CGP, the stub Bootstrap is returned.

## Why YAML (in addition to JSON)

Hermes has `pyyaml==6.0.3` and `ruamel.yaml==0.18.17` already in its
core dependencies (see `pyproject.toml`). The loader accepts both
YAML and JSON, with YAML preferred when the content is YAML-shaped
(no leading `{` or `[`). The PMOVES.AI side writes the canonical
CGP in YAML for human editing; the Hermes side reads YAML natively
without a conversion step. JSON is supported for `PMOVES_BOOTSTRAP_CGP`
raw-string env vars (env vars are awkward for multi-line YAML).

## Why no `nats-py` in the v0 subscriber

`nats-py` is not in Hermes's core dependencies. Adding it would be a
meaningful change to `pyproject.toml` (the deps list warns against
adding new packages — see the "Mini Shai-Hulud" comment on the
existing `dependencies` list).

The v0 subscriber is a STUB: `subscribe()` always returns a
`SubscriberStatus` with `enabled=False` and a clear `reason`. The
dataclasses (`TaskEnvelope`, `ResultEnvelope`) document the wire
contract so a future slice that adds `nats-py` can wire it in
without changing the public surface.

To enable the real subscriber in a future slice:

1. Add `nats-py` to `pyproject.toml`'s `dependencies` list
(exact-pinned per the existing pattern, e.g. `nats-py==2.10.0`).
2. Implement the `subscribe()` body with a real nats-py loop.
3. Set `PMOVES_SUBSCRIBER_ENABLED=true` in the session env.

The wire contract:

- Tasks arrive on `pmoves.agent.task.v1`, filtered by
`TaskEnvelope.target == "hermes"`.
- Results go out on `pmoves.agent.result.v1` as a `ResultEnvelope`.
- BPM events arrive on `pmoves.bpm.phase.v1` and
`pmoves.bpm.pomodoro.v1` (the orchestrator publishes these for
observability; the subscriber doesn't respond to them, but a
future slice might).

## Tests

```bash
pytest tests/test_pmoves_bootstrap.py -v
```

33 tests, 9 test groups:

- A. LoadFromExampleTests (5)
- B. LoadFromSourceTests (4)
- C. ValidationFailureTests (5)
- D. StubFallbackTests (2)
- E. ExportEnvTests (3)
- F. TypedAccessorTests (3)
- G. ToolsBridgeTests (6)
- H. SubscriberTests (3)
- I. Constants and subject surfaces (2)

## What this slice does NOT do (intentional, follow-up)

- **Wiring into `run_agent.py` / `cli.py`** — the actual integration
point in Hermes's session lifecycle is a follow-up. v0 ships the
package; the operator (or a future slice) wires `load_bootstrap()`,
`export_env()`, and `register_pmoves_tools()` into the session
init code.
- **Real `nats-py` subscriber** — see "Why no `nats-py` in the v0
subscriber" above.
- **CHIT trail signing** — the `no-chit-bypass` constraint is
honored by the loader's behavior (the stub carries it, the real
CGP carries the operator's set), but no actual CHIT signing code
lives in this package. The Mavis orchestrator side does the
signing.
- **Per-session tool allow-list** — a future slice can extend
`register_pmoves_tools()` with a per-identity allow-list (e.g.
`minimax` gets all 10 tools, `critic` gets only `web_search` and
`web_fetch`). v0 trusts the operator's CGP as-is.

## Cross-fork plan

This is the Hermes-side of the 3-repo Mavis harness v0 slice.
The other two PRs are:

1. `POWERFULMOVES/PMOVES.AI` PR #2477 — the writer (load_bootstrap.py
+ orchestrator.py + bpm_cron.py + 56/56 tests)
2. `POWERFULMOVES/PMOVES-pinokio` PR `feat/pmoves-app-launcher` —
the app launcher (pmoves_loader.js + example app + 24/24 tests)

All three read the same `v1.schema.json`; the schema is the contract
that ties the three forks together.

## License

Same as the upstream NousResearch hermes-agent fork (MIT, per LICENSE
in repo root).
89 changes: 89 additions & 0 deletions pmoves_bootstrap/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"""PMOVES bootstrap CGP consumer for the PMOVES-hermes-agent fork.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Include the new package in installed distributions

The repository's pyproject.toml has an explicit [tool.setuptools.packages.find] allow-list that does not include pmoves_bootstrap or pmoves_bootstrap.*, and no package-data entry ships cgp_schema/*.json or *.yaml. Consequently, source-checkout tests can import this package, but normal wheel/sdist installations omit the module and its required schema/example files, making the feature unavailable with ModuleNotFoundError; add the package to discovery and include its data files in both wheel and sdist configuration.

Useful? React with 👍 / 👎.


The Hermes-side of the Mavis harness v0 (3-repo coordinated). A
non-breaking consumer of the ``pmoves.bootstrap/v1`` CGP that the
PMOVES.AI side writes (see ``pmoves/tools/load_bootstrap.py`` and
``pmoves/contracts/schemas/pmoves-bootstrap/v1.schema.json`` in the
PMOVES.AI repo).

This package is a NEW addition to the Hermes fork. It does not
modify any existing Hermes files (``cli.py``, ``run_agent.py``,
``toolsets.py``, etc.). The non-breaking test pair:

- No CGP present -> ``load_bootstrap()`` returns the stub
Bootstrap. ``register_pmoves_tools()`` is a no-op. ``subscribe()``
is a no-op. Hermes runs as it does today.
- CGP present -> the CGP is validated against the vendored
schema, the PMOVES tools are registered alongside Hermes's native
tools, the optional NATS subscriber can pick up Mavis-orchestrator
tasks (when nats-py is in scope, future slice).

Public surface:

- ``load_bootstrap`` - the CGP reader (loader.py)
- ``stub_bootstrap`` - the no-CGP fallback Bootstrap
- ``export_env`` - export the Bootstrap as PMOVES_BOOTSTRAP_* env vars
- ``register_pmoves_tools`` - the tools_bridge (tools_bridge.py)
- ``subscribe`` - the optional NATS subscriber (subscriber.py)
- ``Bootstrap`` / ``Identity`` / ``Meta`` / ``BootstrapError`` - the typed shapes

CGP profile: ``pmoves.bootstrap/v1``
Canonical spec: PMOVES.AI's ``pmoves/docs/PMOVESCHIT/CGP_v1.0_SPECIFICATION.md``
Schema (vendored): ``pmoves_bootstrap/cgp_schema/v1.schema.json``
"""
from .loader import (
PROFILE,
SCHEMA_PATH,
EXAMPLE_PATH,
BootstrapError,
Bootstrap,
Identity,
Meta,
load_bootstrap,
stub_bootstrap,
export_env,
)
from .tools_bridge import (
PMOVES_TOOL_REGISTRY,
BridgeResult,
register_pmoves_tools,
)
from .subscriber import (
SUBJECT_TASK,
SUBJECT_RESULT,
SUBJECT_BPM_PHASE,
SUBJECT_BPM_POMODORO,
KNOWN_TARGETS,
TaskEnvelope,
ResultEnvelope,
SubscriberStatus,
subscribe,
)

__version__ = "0.1.0"

__all__ = [
"__version__",
"PROFILE",
"SCHEMA_PATH",
"EXAMPLE_PATH",
"BootstrapError",
"Bootstrap",
"Identity",
"Meta",
"load_bootstrap",
"stub_bootstrap",
"export_env",
"PMOVES_TOOL_REGISTRY",
"BridgeResult",
"register_pmoves_tools",
"SUBJECT_TASK",
"SUBJECT_RESULT",
"SUBJECT_BPM_PHASE",
"SUBJECT_BPM_POMODORO",
"KNOWN_TARGETS",
"TaskEnvelope",
"ResultEnvelope",
"SubscriberStatus",
"subscribe",
]
91 changes: 91 additions & 0 deletions pmoves_bootstrap/cgp_schema/example.cgp.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Example PMOVES bootstrap CGP - pmoves.bootstrap/v1
# Same shape as the PMOVES.AI example. Vendored here for hermes-side
# validation (the schema is the source of truth, this is the canary
# fixture). If you change the schema, change the example to match
# and re-run the schema-sync test.
#
# Schema: pmoves_bootstrap/cgp_schema/v1.schema.json
# Canonical spec (PMOVES.AI side): pmoves/docs/PMOVESCHIT/CGP_v1.0_SPECIFICATION.md

spec: pmoves.bootstrap/v1

# === Metadata ===
meta:
created_at: "2026-08-08T00:30:00+00:00"
operator: darkxside
source: mavis
encoder_version: 0.1.0
# bootstrap_id omitted - consumers derive from SHA-256(canonical_json)

# === Identity (drives Mavis voice/skin/role) ===
identity:
agent: minimax
role: implementer
skin: dimensional

# === Tools (PMOVES surface available in this session) ===
# The tools_bridge resolves these against Hermes's native toolset.
# Missing tools are silently skipped - the consumer is non-breaking
# (no-override-existing-config + tagged-services-are-advisory).
tools:
- mavis__agent__create
- mavis__cron__self
- mavis__cron__list
- mavis__cron__create
- comfyui_client
- pinokio_launch
- render_skin
- gh
- web_search
- web_fetch

# === MCPs (servers loaded for this session) ===
mcps:
- pmoves-nats-mcp
- pmoves-chit-sign
- google-workspace
- obsidian-brain
- pmoves-neo4j-mcp
- pmoves-cipher-mcp

# === Services (tagged infrastructure) ===
services:
tailscale:
host: powerfullmoves.tail.ts.net
ip: 100.x.y.z
rustdesk:
devices:
- pixel-10-xl-1
- pixel-10-xl-2
- pixel-10
- tab-ultra-s8
- tab-ultra-11
hostinger:
site: powerfullmoves.com
status: pending-mgmt
cloudflare:
account: powerfullmoves
zones: []

# === Routing (peer agents in the fleet) ===
routing:
kiloclaw:
node: "5090"
nats_subject: pmoves.agent.task.v1
target: glm-5.1
hermes:
node: TBD
nats_subject: pmoves.agent.task.v1
target: hermes-3

# === Constraints (non-breaking guarantees) ===
constraints:
- no-override-existing-config
- tagged-services-are-advisory
- no-chit-bypass
- no-force-push
- no-ci-bypass
- preserve-existing-tools

# === CGP-valid geometry payload (empty) ===
super_nodes: []
Loading
Loading