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
11 changes: 5 additions & 6 deletions .github/workflows/hermes-drift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,12 @@ jobs:
with:
repository: NousResearch/hermes-agent
path: .hermes-agent-upstream
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
- name: Install the locked toolchain (mise.toml)
uses: jdx/mise-action@82a612c5ab8bcda7cc5db6b7e81a8e7584c3dc6e # v5.2.3
with:
python-version: "3.11"
enable-cache: true
- uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
with:
just-version: "1.58.0"
# Pinned so every run, release jobs included, installs the same mise;
# the action still verifies it against mise's signed checksums.
version: "2026.10.18"
- name: Record the upstream revision under test
id: upstream
run: |
Expand Down
52 changes: 20 additions & 32 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,16 @@ jobs:
fetch-depth: 0
persist-credentials: false
ref: ${{ github.sha }}
- name: Install Just
uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
- name: Install the locked toolchain (mise.toml)
uses: jdx/mise-action@82a612c5ab8bcda7cc5db6b7e81a8e7584c3dc6e # v5.2.3
with:
just-version: "1.58.0"
# Pinned so every run, release jobs included, installs the same mise;
# the action still verifies it against mise's signed checksums.
version: "2026.10.18"
- name: Attach the exact trigger to local main
env:
EXPECTED_SHA: ${{ github.sha }}
run: just release-attach-trigger
- name: Install uv
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
with:
python-version: "3.11"
enable-cache: true
- name: Install the frozen release toolchain
run: uv sync --frozen
- name: Validate the tagged baseline and strict release intent
Expand Down Expand Up @@ -177,15 +174,12 @@ jobs:
fetch-depth: 0
persist-credentials: false
ref: ${{ github.sha }}
- name: Install uv
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
with:
python-version: "3.11"
enable-cache: true
- name: Install Just
uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
- name: Install the locked toolchain (mise.toml)
uses: jdx/mise-action@82a612c5ab8bcda7cc5db6b7e81a8e7584c3dc6e # v5.2.3
with:
just-version: "1.58.0"
# Pinned so every run, release jobs included, installs the same mise;
# the action still verifies it against mise's signed checksums.
version: "2026.10.18"
- name: Download the immutable release source
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
Expand Down Expand Up @@ -233,15 +227,12 @@ jobs:
fetch-depth: 0
persist-credentials: false
ref: ${{ github.sha }}
- name: Install uv
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
- name: Install the locked toolchain (mise.toml)
uses: jdx/mise-action@82a612c5ab8bcda7cc5db6b7e81a8e7584c3dc6e # v5.2.3
with:
python-version: "3.11"
enable-cache: true
- name: Install Just
uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
with:
just-version: "1.58.0"
# Pinned so every run, release jobs included, installs the same mise;
# the action still verifies it against mise's signed checksums.
version: "2026.10.18"
- name: Download the immutable release source
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
Expand Down Expand Up @@ -317,15 +308,12 @@ jobs:
fetch-depth: 0
persist-credentials: false
ref: ${{ github.sha }}
- name: Install uv
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
with:
python-version: "3.11"
enable-cache: true
- name: Install Just
uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
- name: Install the locked toolchain (mise.toml)
uses: jdx/mise-action@82a612c5ab8bcda7cc5db6b7e81a8e7584c3dc6e # v5.2.3
with:
just-version: "1.58.0"
# Pinned so every run, release jobs included, installs the same mise;
# the action still verifies it against mise's signed checksums.
version: "2026.10.18"
- name: Install ORAS
uses: oras-project/setup-oras@1d808f7d7f6995cc68b7bf507bfe5c5446e1dc9d # v2.0.1
with:
Expand Down
39 changes: 18 additions & 21 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,25 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
- name: Install the locked toolchain (mise.toml)
uses: jdx/mise-action@82a612c5ab8bcda7cc5db6b7e81a8e7584c3dc6e # v5.2.3
with:
python-version: "3.11"
enable-cache: true
- uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
with:
just-version: "1.58.0"
# Pinned so every run, release jobs included, installs the same mise;
# the action still verifies it against mise's signed checksums.
version: "2026.10.18"
- name: Run unittest suite
run: just test

package:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
with:
python-version: "3.11"
enable-cache: true
- uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
- name: Install the locked toolchain (mise.toml)
uses: jdx/mise-action@82a612c5ab8bcda7cc5db6b7e81a8e7584c3dc6e # v5.2.3
with:
just-version: "1.58.0"
# Pinned so every run, release jobs included, installs the same mise;
# the action still verifies it against mise's signed checksums.
version: "2026.10.18"
- name: Build and validate package metadata
run: |
just build
Expand All @@ -50,25 +48,24 @@ jobs:
# Amber admission gates on the exact deployed Hermes fork contract. The pin
# below is test-enforced against hermes_plugin_kit.testing's constant; the
# workflow cannot read that constant, because actions/checkout consumes the
# ref before setup-uv installs Python.
# ref before mise installs Python.
hermes-deployed-contract:
runs-on: ubuntu-latest
env:
DEPLOYED_HERMES_REVISION: f80f453ae0679347e38abc917c7f94f717bf96c5
DEPLOYED_HERMES_REVISION: 939e45c91d751fadd94dcd1b873ac3cb44846213
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: NousResearch/hermes-agent
ref: f80f453ae0679347e38abc917c7f94f717bf96c5
ref: 939e45c91d751fadd94dcd1b873ac3cb44846213
path: .hermes-agent-deployed
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
with:
python-version: "3.11"
enable-cache: true
- uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
- name: Install the locked toolchain (mise.toml)
uses: jdx/mise-action@82a612c5ab8bcda7cc5db6b7e81a8e7584c3dc6e # v5.2.3
with:
just-version: "1.58.0"
# Pinned so every run, release jobs included, installs the same mise;
# the action still verifies it against mise's signed checksums.
version: "2026.10.18"
# The full suite, not the context-engine module alone. It is green at
# this pin, so widening adds coverage rather than risk.
- name: Run the full contract suite against the deployed Hermes revision
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ dist/
.hermes-agent-pinned/
.hermes-agent-upstream/
.DS_Store
.hermes-venv-pinned/
.hermes-venv-upstream/
7 changes: 5 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,11 @@ plugin.
and recovery dispatch on `get_tool_schemas` / `handle_tool_call`; do not
duplicate native engine tools through `@tool`.
- Redact secret-looking values in logs and avoid logging full untrusted payloads.
- Use `uv` and the `justfile` for local development:
`just install`, `just test`,
- Get Python, `uv` and `just` from `mise.toml` and `mise.lock`
(`mise trust && mise install --locked`), the same toolchain CI installs;
after changing a tool pin, regenerate the lock with
`mise lock --platform linux-x64,linux-arm64,macos-arm64`. Then use the
`justfile` for local development: `just install`, `just test`,
`just test-one tests.test_kit.SchemaConventionTests`, and `just build`.

## Release Notes
Expand Down
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -859,10 +859,16 @@ so the linked directory is self-contained.

## Development

Uses [uv](https://docs.astral.sh/uv/) and
[Just](https://github.com/casey/just). On macOS, install both with
`brew install uv just`; otherwise install them using their platform-specific
instructions.
Uses [mise](https://mise.jdx.dev/) to install the pinned Python,
[uv](https://docs.astral.sh/uv/) and [Just](https://github.com/casey/just)
from `mise.toml` and `mise.lock`, the same toolchain CI installs. Once per
checkout:

```bash
mise trust && mise install --locked
```

Then:

```bash
just install # uv sync — create/sync the dev environment
Expand Down
4 changes: 2 additions & 2 deletions hermes_plugin_kit/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@
#:
#: The CI workflow keeps its own literal -- ``actions/checkout`` consumes the
#: ``ref:`` before a Python toolchain exists, so the workflow cannot read this
#: value -- and a release-contract test asserts the two agree.
DEPLOYED_HERMES_REVISION = "f80f453ae0679347e38abc917c7f94f717bf96c5"
#: value.
DEPLOYED_HERMES_REVISION = "939e45c91d751fadd94dcd1b873ac3cb44846213"

#: Parameter names the kit's registrars pass positionally, in order.
#:
Expand Down
42 changes: 33 additions & 9 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ openspec_min := "1.13.1"
hermes_agent_repo := env_var_or_default("HERMES_AGENT_REPO", "https://github.com/NousResearch/hermes-agent.git")
hermes_pinned_dir := env_var_or_default("HERMES_PINNED_DIR", ".hermes-agent-pinned")
hermes_upstream_dir := env_var_or_default("HERMES_UPSTREAM_DIR", ".hermes-agent-upstream")
hermes_pinned_venv := env_var_or_default("HERMES_PINNED_VENV", ".hermes-venv-pinned")
hermes_upstream_venv := env_var_or_default("HERMES_UPSTREAM_VENV", ".hermes-venv-upstream")

# Show available recipes.
default:
Expand Down Expand Up @@ -59,6 +61,26 @@ prepare-hermes-ref ref dir:
git -C "{{ dir }}" checkout -q FETCH_HEAD
echo "hermes-agent at $(git -C "{{ dir }}" rev-parse HEAD)"

# Build the host's own environment from the host's own lockfile.
#
# The kit's venv lacks the host's dependencies (ruamel, dotenv, ...), so the
# contract modules died at import there. The host's `pyproject.toml` alone
# does not resolve either, so its `uv.lock` is the only faithful source.
[private]
provision-hermes-host dir venv:
#!/usr/bin/env bash
set -euo pipefail
host="$(cd "{{ dir }}" && pwd)"
mkdir -p "{{ venv }}"
venv="$(cd "{{ venv }}" && pwd)"
test -f "$host/uv.lock" || { echo "no uv.lock in $host; cannot provision the host faithfully" >&2; exit 1; }
spec="$({{ uv }} run python -c 'import sys, tomllib; print(tomllib.load(open(sys.argv[1], "rb"))["project"]["requires-python"])' "$host/pyproject.toml")"
py="$({{ uv }} python find --system "$spec")" \
|| { echo "no interpreter on PATH satisfies the host's requires-python $spec; add one to mise.toml" >&2; exit 1; }
echo "hermes host python: $("$py" -V 2>&1) at $py (host requires-python $spec)"
(cd "$host" && UV_PROJECT_ENVIRONMENT="$venv" {{ uv }} sync --frozen --quiet --python "$py")
{{ uv }} pip install --quiet --python "$venv/bin/python" --reinstall-package hermes-plugin-kit .

# Run the full contract suite against the deployed pin.
#
# HERMES_EXPECTED_REVISION makes the lane refuse a checkout at any other
Expand All @@ -71,9 +93,10 @@ test-contract-pinned:
if [[ -z "${HERMES_AGENT_PATH:-}" ]]; then
just prepare-hermes-ref "$pin" "{{ hermes_pinned_dir }}"
fi
just provision-hermes-host "$dir" "{{ hermes_pinned_venv }}"
HERMES_CONTRACT_REQUIRED=1 HERMES_EXPECTED_REVISION="$pin" \
HERMES_AGENT_PATH="$(cd "$dir" && pwd)" \
just _run-contract-suite "pinned $pin"
just _run-contract-suite "pinned $pin" "{{ hermes_pinned_venv }}"

# Run the full contract suite against upstream main -- the drift lane.
test-contract-upstream:
Expand All @@ -84,29 +107,29 @@ test-contract-upstream:
just prepare-hermes-ref main "{{ hermes_upstream_dir }}"
fi
rev="$(git -C "$dir" rev-parse HEAD)"
just provision-hermes-host "$dir" "{{ hermes_upstream_venv }}"
HERMES_CONTRACT_REQUIRED=1 HERMES_AGENT_PATH="$(cd "$dir" && pwd)" \
just _run-contract-suite "upstream $rev"
just _run-contract-suite "upstream $rev" "{{ hermes_upstream_venv }}"

# Backwards-compatible alias for the upstream lane.
test-contract: test-contract-upstream

# Run both contract modules and prove the run actually executed checks.
#
# The exit status alone cannot say whether anything ran: on Python 3.11 --
# the version CI pins -- unittest exits 0 both when every test skips and when
# zero tests are collected. So the executed count is the evidence.
# The exit status alone cannot say whether anything ran: unittest exits 0 when
# every test skips. So the executed count is the evidence.
[private]
_run-contract-suite label:
_run-contract-suite label venv:
#!/usr/bin/env bash
set -euo pipefail
log="$(mktemp)"
trap 'rm -f "$log"' EXIT
set +e
{{ uv }} run python -m unittest tests.test_hermes_contract tests.test_context_engine_contract -v \
"{{ venv }}/bin/python" -m unittest tests.test_hermes_contract tests.test_context_engine_contract -v \
> "$log" 2>&1
echo "EXIT=$?" >> "$log"
set -e
tail -25 "$log"
cat "$log"
grep -qE '^EXIT=0' "$log" || { echo "contract suite failed ({{ label }})" >&2; exit 1; }
ran="$(grep -oE '^Ran [0-9]+' "$log" | grep -oE '[0-9]+' | head -n1)"
test -n "$ran" && [ "$ran" -gt 0 ] \
Expand All @@ -122,7 +145,8 @@ test-context-engine-contract:
#!/usr/bin/env bash
set -euo pipefail
: "${HERMES_AGENT_PATH:?set it to a hermes-agent checkout; this lane does not guess}"
HERMES_CONTRACT_REQUIRED=1 {{ uv }} run python -m unittest tests.test_context_engine_contract -v
just provision-hermes-host "$HERMES_AGENT_PATH" "{{ hermes_pinned_venv }}"
HERMES_CONTRACT_REQUIRED=1 "{{ hermes_pinned_venv }}/bin/python" -m unittest tests.test_context_engine_contract -v

# Refuse to run when the installed OpenSpec predates the archive-correctness fixes.
[private]
Expand Down
Loading
Loading