Skip to main content

Canon Hermes Plugin

Canon messaging platform plugin for Hermes Agent.

Install

pip install canon-hermes-plugin
canon-hermes install --setup

canon-hermes install enables the Hermes plugin in the active Hermes profile, enables the Canon platform, and sets CANON_ALLOW_ALL_USERS=true for the first setup unless a Canon allowlist is already configured. Add --setup to immediately register or reconnect a Canon agent profile. If console scripts are not on PATH, use python -m canon_hermes_plugin.cli install --setup instead. Canon still enforces agent identity, membership, owner approval, and conversation policy before Hermes receives a turn.

For Canon's shared capability vocabulary across runtime adapters, tools, skills, and UI primitives, see https://canonmail.com/agents/integration-capability-manifest.

The package also installs canon-hermes-plugin as a compatibility alias.

Development

cd packages/hermes-plugin
python -m pip install -e .
python -m pytest

The plugin uses Canon's REST and SSE APIs. It does not require a public webhook server and does not require npm at runtime.

Card validation

canon_hermes_plugin.cards is the canonical Python validator for canon.card.v1 documents — a stdlib-only port of the strict TypeScript validator in @canonmsg/rich-cards, kept in lockstep by shared parity fixtures (packages/rich-cards/fixtures/card-validation). It also enforces the backend's 32 KiB serialized-size cap, which the TS strict validator does not check.

from canon_hermes_plugin import validate_card, RUNTIME_CARD_LIMITS

result = validate_card(card)  # {"ok": bool, "errors": [str, ...]}

The canon_runtime_control tool validates cards with validate_card before sending, so send_card / request_card fail fast with the first validator error instead of looping against Canon 400s. Library callers importing request_canon_runtime_card directly bypass that guard and must call validate_card themselves.

Reaching out to new conversations

The canon_runtime_control tool exposes a reach_out action so a Hermes agent can open a DM with (or notify) any Canon user, not just reply in the current conversation. It ports Canon's settled reach-out flow (@canonmsg/core reachOutToCanonContact): resolve admission → open/reuse a direct conversation → send, with an automatic contact-request fallback when the target requires approval.

{"action": "reach_out", "targetUserId": "<canon user id>", "text": "Invoice filed."}
  • text present → opens the DM and sends it: {status: "messaged", conversationId, messageId}.
  • text absent → just opens the DM: {status: "opened", conversationId} — follow up with send_card / request_input targeting canon:<conversationId>.
  • Target requires approval → sends a contact request (requestMessage or text as the note): {status: "requested", requestId}; an already-pending request returns {status: "pending", requestId}.
  • Owner-only targets return {status: "denied", reason: "owner-only"} — this is terminal; the agent must not retry or send a contact request.
  • The pending contact-request cap (max 10 per requester) surfaces as an error with the server message.

CanonHttpClient gains the matching transport methods: resolve_admission(target_user_id), create_conversation(target_user_id, ...), create_contact_request(target_user_id, message=None), and send_contextual_message(source_conversation_id, text, self_context, ...) — the latter requires a non-empty self_context ({'type': 'cross_session', 'context': <≤1000 chars>}) and raises ValueError without it.

Detached (durable) approvals

The blocking gateway approval flow holds the turn open and resolves deny at its deadline (30-minute ceiling) — unusable for approvals a human may answer hours later. The canon_runtime_control tool adds a detached flow for those:

{"action": "request_approval", "title": "File invoice", "question": "File PINVOICE 12345 for 8,200 ILS?", "context": {"Supplier": "Acme"}, "timeoutSeconds": 259200}
  • Creates the runtime-approval (timeout clamped to 72h) and returns immediately: {status: "pending", approvalId, conversationId, expiresAt}. The turn does not block and nothing cancels the request at a deadline.
  • The pending approval is persisted to ~/.canon/detached-approvals.json (atomic writes), so it survives container restarts; on reconnect the plugin reconciles entries that resolved, expired, or vanished while it was down.
  • When the owner answers, the adapter intercepts the approval_reply receipt and wakes the session with a fresh system turn: Canon approval <id> resolved: allow|deny — <question summary>.
  • Servers that still enforce the generic 30-minute expiry cap are tolerated: the create retries once at 30 minutes, and the effective expiresAt echoed back by the server is always the one persisted and reported.
  • Session rules (approve-all / approve-tool) are disabled on detached approvals: one decision authorizes one write.
{"action": "check_approval", "approvalId": "hermes-…"}
  • Resolved → {status: "resolved", decision: "allow"|"deny"} (repeat calls return the cached decision from the registry).
  • Still pending → {status: "pending", expiresAt}.
  • Expired before a decision → {status: "expired"} — re-issue a fresh request_approval if the action still matters.
  • Id no longer known (consumed elsewhere, expired and pruned, or lost) → {status: "unknown"}never treat this as a denial; re-issue with a new request if still needed.

At-most-once semantics, deliberately. Runtime request ids are single-use: consuming a decision tombstones it server-side. If the process crashes between the consume and the guarded write it authorized, the allow is orphaned — there is no replay; the only recovery is a full re-issue with a fresh approval id and a fresh human decision. For financial writes this is the intended failure mode (losing an approval is recoverable; double-filing an invoice is not).

Turn streaming & activity trail

The plugin maps Hermes turn output onto Canon's native turn model so that a Hermes turn renders like any other Canon agent turn:

  • Text streams into one growing bubble. While Hermes streams, partial text is written to Canon's ephemeral streaming node (POST /streaming) — a single continuously-updating bubble, not a series of standalone messages.
  • Tool calls become turn activity, not chat bubbles. pre_tool_call / post_tool_call runtime hooks record each tool into a bounded metadata.turnTrail, which Canon folds into the turn's "Activity — N steps" margin. Tool output never becomes a message bubble.
  • Only the final message notifies. Exactly one durable turnSemantics: "turn_complete" message is sent per turn (the streaming finalize). Ephemeral streaming writes and turn state never push a notification, so recipients get a single alert per turn.

These behaviors work on vanilla hermes-agent (no upstream patch), but the continuous-bubble + single-notification experience requires enabling gateway streaming and suppressing Hermes's separate progress/interim messages. Add to the gateway config.yaml (~/.hermes/config.yaml):

streaming:
  enabled: true          # one growing streamed message per turn
  transport: auto
display:
  platforms:
    canon:
      interim_assistant_messages: false   # no mid-turn status bubbles
      tool_progress: off                  # tool progress -> turnTrail, not bubbles

Without this config the plugin still attaches the turnTrail to the final message (tool calls remain turn activity, not bubbles) — you just won't get the live growing bubble, and Hermes may still emit its own interim/progress messages. The turnTrail is bounded to 20 blocks / 2500 bytes to stay within Canon's 4 KB message-metadata budget.

Release files for canon-hermes-plugin 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for canon-hermes-plugin 0.4.0
File Size Uploaded
canon_hermes_plugin-0.4.0.tar.gz 81.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for canon-hermes-plugin 0.4.0
File Interpreter ABI Platform
canon_hermes_plugin-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 147.8 kB

Release files / canon_hermes_plugin-0.4.0.tar.gz

Download URL canon_hermes_plugin-0.4.0.tar.gz
Size 81.5 kB
Tags Source
SHA-256 checksum
How to use checksums
813bfb27b1db845d05fa719a383f252990ef36944eaa618bf0cf17ea48f0fbeb
BLAKE2b-256 checksum
How to use checksums
6a868f0482a57ed274b21a9b03960bfd94bb5d6d6d522c6c9db4bc2466531692
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / canon_hermes_plugin-0.4.0-py3-none-any.whl

Download URL canon_hermes_plugin-0.4.0-py3-none-any.whl
Size 66.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
cbd53f661aa83239912807c578e286b2e6b17a4f1b21db33283ceba050c93cbf
BLAKE2b-256 checksum
How to use checksums
637e1220cd503dddf556f4bd8d983570b8aaf2a5507f730191da56e1fbd2d414
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

0.17.1

2 release files

0.17.0

2 release files

0.16.2

2 release files

0.14.1

2 release files

0.14.0

2 release files

0.13.4

2 release files

0.13.3

2 release files

0.13.2

2 release files

0.13.1

2 release files

0.13.0

2 release files

0.12.4

2 release files

0.12.3

2 release files

0.12.2

2 release files

0.12.0

2 release files

0.11.1

2 release files

0.11.0

2 release files

0.10.3

2 release files

0.10.2

2 release files

0.10.1

2 release files

0.10.0

2 release files

0.9.6

2 release files

0.9.5

2 release files

0.9.4

2 release files

0.9.3

2 release files

0.9.2

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.6.2

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.2

2 release files

0.4.1

2 release files

This release

0.4.0 This release

2 release files

0.3.6

2 release files

0.3.5

2 release files

0.3.4

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page