Hermes Bort: BORT Agent Integration for Hermes Agent

Connects Hermes Agent to BORT (BAP-578) agent NFTs on BSC mainnet.

Community Bridgesintermediate10 min readVerified Jul 27, 2026
Author
BORT-AGENTS
Stars
3
Language
Python
License
MIT
Upstream updated
2026-05-18
Hermes Bort: BORT Agent Integration for Hermes Agent

Hermes Bort is a plugin for Hermes Agent that enables a Hermes process to read, act for, and anchor memory on BORT (BAP-578) agent NFTs on the BNB Smart Chain mainnet. Once wired up, a Hermes agent can read full on-chain agent states, execute actions via operator-signed writes gated by a local policy file, and manage portable session memory anchored to IPFS and the KnowledgeRegistryV2 contract. This integration turns a BORT agent NFT into a persistent, on-chain-backed AI agent that can trade, manage campaigns, and evolve its skills with verifiable learning records.

What It Does

  • Reads full agent state, IPFS identity, and knowledge registries in a single call.
  • Executes on-chain actions via VaultPermissionManagerV2 with local policy gating and Hermes approval prompts.
  • Anchors local session memory to IPFS and KnowledgeRegistryV2, making memory portable across Hermes instances.
  • Generates owner-signed deep links into the BORT dapp for marketplace flows.
  • Runs the hermes-agent-self-evolution optimizer and commits improved skills on-chain as INSTRUCTION knowledge sources with verifiable LearningRecorded events.
  • Provides a suite of read tools for browsing the marketplace, checking agent health, and listing available actions.
  • Offers write tools for granting operator permissions, invoking actions, committing learning records, and anchoring memory.

These capabilities are delivered through a thin plugin architecture. The BORT runtime API at bap578-nfa-platform.onrender.com already exposes most data as public read endpoints; the plugin uses those instead of re-implementing them. Direct on-chain calls are reserved for what the runtime does not expose: tokenURI, KnowledgeRegistry.getActiveKnowledgeSources, CircuitBreaker.globalPause, the VPM v2 forwarder, and KR v2 delegated writes.

Setup

Diagram: Setup

Install

Install the plugin from PyPI:

pip install hermes-bort

Or install it straight from Hermes:

hermes plugins install BORT-AGENTS/hermes-bort

Either way, enable it in ~/.hermes/config.yaml:

plugins:
  enabled:
    - hermes-bort

From source

git clone https://github.com/BORT-AGENTS/hermes-bort
cd hermes-bort
pip install -e .[dev]

You can also drop the hermes_bort/ package into ~/.hermes/plugins/ directly.

Read-only quickstart

No setup beyond install. Reads hit the public BORT runtime API and BSC public RPC.

from hermes_bort.tools.read_agent import handle as read_agent
import asyncio, json

raw = asyncio.run(read_agent({"token_id": 11100}))
print(json.dumps(json.loads(raw), indent=2)[:800])

Inside Hermes the same call surfaces as a tool: bort_read_agent(token_id=11100).

Configure for writes

Writes need three things: an operator key, a vault grant from the agent owner, and permission from the local policy file.

1. Generate an operator key

hermes bort init-operator

Prints a fresh address and private key. Set BORT_OPERATOR_PRIVATE_KEY in your env and fund the address with approximately 0.01 BNB for gas. The key is never written to disk by the plugin and never leaves your process.

2. Write the default policy

hermes bort init-policy

Writes ~/.hermes/bort-policy.yaml. Each action has a disposition (auto / confirm / block) and an optional per_action_max_bnb cap. Defaults are conservative: exits auto, trades and campaign lifecycle confirm, governance and large transfers block.

3. Have the agent owner grant the operator

from hermes_bort.tools.grant_permission import handle as grant
raw = await grant({"token_id": 11100, "operator": "0xYourOperatorAddress"})

Returns the two-step calldata (createVault then grantPermission) the owner clicks through in their own wallet. Operator scope: WRITE, time-bound, limited to the actions you whitelist in the grant description.

4. Run

By default writes simulate only. Set BORT_ALLOW_BROADCAST=1 to enable real broadcasting once you have confirmed simulation looks right.

export BORT_ALLOW_BROADCAST=1

Doctor

hermes bort doctor

Walks the setup: RPC reachable, operator key valid and funded, policy file present and parseable, Pinata creds set if you plan to anchor memory, and the hermes-agent-self-evolution repo if you plan to use evolve.

Evolve

Run the hermes-agent-self-evolution optimizer for a skill and, if it improved, commit the result on-chain in one step:

hermes bort evolve github-code-review --token-id 11100

It runs the optimizer as a subprocess and, only if metrics.json shows a real improvement, chains two on-chain writes linked by one content hash:

  • commit_evolution pins the evolved skill to IPFS and writes it as an INSTRUCTION knowledge source on KnowledgeRegistryV2.
  • commit_learning emits a LearningRecorded event on the agent's logic contract: a permanent, timestamped, verifiable on-chain learning record.

The self-evolution repo is located via --repo, $BORT_SELF_EVOLUTION_REPO, ~/.hermes/hermes-agent-self-evolution, or a sibling directory. With BORT_ALLOW_BROADCAST unset the whole loop is a dry run (the skill is still pinned to IPFS; both chain writes only simulate).

FlagEffect
--iterations NGEPA iterations (default 10).
--optimizer-model MLLM for GEPA reflections, litellm format (anthropic/claude-sonnet-4-6, deepseek/deepseek-chat, openrouter/...). Default: the optimizer's own default.
--eval-model MLLM for evaluations, litellm format.
--repo PATHPath to the hermes-agent-self-evolution repo.
--commit-onlySkip the optimizer; commit the latest existing run dir.
--only {both,evolution,learning}Which on-chain steps to run (default both).
--min-improvement FOnly commit if metrics.improvement >= F.

LLM provider. The optimizer runs on litellm, so any provider works: --optimizer-model anthropic/claude-sonnet-4-6, deepseek/..., openrouter/..., etc. The provider's API key (ANTHROPIC_API_KEY, DEEPSEEK_API_KEY, ...) is read from the environment.

Without Pinata. commit_evolution pins the evolved skill to IPFS and needs Pinata credentials. commit_learning does not: record_learning only emits the content hash. So --only learning records the verifiable on-chain learning event with no IPFS and no Pinata key at all.

If commit_evolution succeeds but commit_learning fails, retry just the learning step: hermes bort evolve <skill> --token-id N --commit-only --only learning. Each run is a distinct on-chain event; the knowledge registry does not dedup.

Configuration Reference

Configuration is read from plugin.yaml or environment variables.

SettingEnv varDefault
Runtime API baseBORT_API_URLhttps://bap578-nfa-platform.onrender.com
BSC RPCBSC_RPC_URLhttps://bsc-dataseed.binance.org
Dapp URL (deep links)BORT_DAPP_URLhttps://www.bortagent.xyz
Memory dirBORT_MEMORY_DIR~/.hermes/bort-memory
Operator keyBORT_OPERATOR_PRIVATE_KEYunset (writes disabled)
Allow broadcastBORT_ALLOW_BROADCASTunset (simulate only)
Policy fileBORT_POLICY_PATH~/.hermes/bort-policy.yaml
Pinata keyPINATA_API_KEYunset (needed for anchor tools)
Pinata secretPINATA_API_SECRETunset (needed for anchor tools)

How It Works

Architecture

The plugin is organized into a flat module structure under hermes_bort/:

hermes_bort/
  plugin.yaml                 metadata + config keys
  __init__.py                 register(ctx) entry point
  bort_chain.py               web3.py reads + VPM v2 / KR v2 ABIs
  bort_ipfs.py                Pinata pin + dual-gateway fetch (pinata, ipfs.io)
  bort_api.py                 async HTTP client to the BORT runtime API
  bort_logic_adapters.py      HunterAdapter / TradingV5Adapter / CTOAdapter
  bort_kr.py                  KR v2 delegated-write helper
  bort_marketplace.py         MarketplaceV3 constants + dapp deep-link builders
  action_codec.py             encode/decode the 13 handleAction payloads
  bort_signer.py              operator key load + broadcast
  bort_policy.py              ~/.hermes/bort-policy.yaml loader + writer
  approval.py                 Hermes approval prompt integration
  cli.py                      hermes bort init-operator / init-policy / doctor / anchor-memory / commit-evolution / evolve
  evolution_loop.py           self-evolution optimizer runner + on-chain commit chain
  tools/
    read_agent.py health_check.py list_actions.py
    marketplace_browse.py marketplace_agent.py list_agent_uri.py
    grant_permission.py commit_learning.py invoke.py
    anchor_memory.py commit_evolution.py
  memory/
    provider.py               BortMemoryProvider

Memory portability

BortMemoryProvider keys session memory by the agent's tokenId (passed to Hermes as agent_identity at session start).

  • During a chat: turns go into ~/.hermes/bort-memory/<tokenId>.jsonl.
  • On session end (or bort_anchor_memory): the buffer is pinned to IPFS via Pinata and committed to KnowledgeRegistryV2 as a MEMORY source through the operator's WRITE grant.
  • On next session start: the provider pulls active MEMORY sources from KnowledgeRegistry.getActiveKnowledgeSources(tokenId) and exposes them to prefetch.

The agent's memory travels with the NFT: a buyer who imports the same tokenId in their own Hermes instance picks up the same history without anyone sharing files.

Threat model

A BORT agent NFT carries text that the owner controls: the IPFS-pinned identity (name, description, attribute values), KnowledgeRegistry source descriptions, and the IPFS content of those sources. All of it can reach the LLM through bort_read_agent results and BortMemoryProvider.prefetch() output. Hermes itself does not sanitize tool returns or memory prefetch: whatever a plugin gives back is injected into context as-is.

The plugin treats those surfaces as untrusted and applies a narrow data-boundary layer in hermes_bort.bort_sanitize:

  • Free-form text fields (identity name / description / external_url, knowledge source description, prefetched memory blocks) are wrapped in an <external-data source="...">...</external-data> envelope with a one-line preamble telling the model to treat the contents as data, not instructions.
  • Each string is capped at 2 KB with an explicit [truncated, N chars total] marker so an attacker cannot flood the context.
  • C0 controls, ANSI escape sequences, zero-width characters, and bidi-override Unicode are stripped before wrapping (same character set Hermes' own cron scanner blocks).
  • Numeric ids, addresses, on-chain enums, and structural keys are not wrapped: only the strings that came from owner-controlled inputs.

This raises the bar from "trivial injection" to "the model has to ignore an explicit data-boundary marker." It is not a complete defense, and it is not meant to be: the operator-key boundary below is what binds what a tricked model can actually cause on-chain.

Security boundary

The plugin holds the operator key, not the NFT owner's key. The operator can only do what the owner has granted in VaultPermissionManagerV2 and what ~/.hermes/bort-policy.yaml allows. The Hermes approval prompt fires when an action's disposition is confirm. With BORT_ALLOW_BROADCAST unset, every write returns simulated_only.

A leaked operator key can be revoked by the owner with one revokePermission transaction in VPM v2; it cannot mint, transfer, or list the NFT. The plugin never writes the key to disk: hermes bort init-operator prints it once to stdout for the user to set in their env.

Requirements and Limitations

  • The plugin requires Python 3.x (the exact minimum version is not stated in the sources, but it is published on PyPI and installable via pip).
  • It is designed for BSC mainnet; the default RPC is https://bsc-dataseed.binance.org.
  • Writes require the operator address to be funded with approximately 0.01 BNB for gas.
  • The operator key must be set as an environment variable (BORT_OPERATOR_PRIVATE_KEY); it is never written to disk by the plugin.
  • By default, all writes simulate only. Real broadcasting requires explicitly setting BORT_ALLOW_BROADCAST=1.
  • Anchoring memory to IPFS requires Pinata credentials (PINATA_API_KEY and PINATA_API_SECRET). Without them, anchor tools return a clear error.
  • The evolve command requires the hermes-agent-self-evolution repository to be available locally, located via --repo, $BORT_SELF_EVOLUTION_REPO, ~/.hermes/hermes-agent-self-evolution, or a sibling directory.
  • The commit_evolution step requires Pinata credentials because it pins the evolved skill to IPFS. The commit_learning step does not require Pinata.
  • The plugin does not deduplicate learning records; each run is a distinct on-chain event.
  • The sanitization layer is not a complete defense against prompt injection; it is a data-boundary marker that raises the bar from trivial injection to requiring the model to ignore an explicit marker.
  • The plugin has 99 tests, most of which are integration-style and hit BSC mainnet RPC and the BORT runtime API. Network tests are skipped or short-circuit when the relevant env var is unset.

Troubleshooting

Doctor command

If you encounter issues, run hermes bort doctor. It walks through the setup and reports:

  • RPC reachable
  • Operator key valid and funded
  • Policy file present and parseable
  • Pinata creds set (if you plan to anchor memory)
  • hermes-agent-self-evolution repo present (if you plan to use evolve)

Write simulation only

If writes return simulated_only when you expect them to broadcast, check that BORT_ALLOW_BROADCAST=1 is set in your environment. By default, all writes simulate only.

Operator key not found

If the plugin cannot find the operator key, ensure BORT_OPERATOR_PRIVATE_KEY is set in your environment. The key is never written to disk by the plugin.

Pinata errors for anchor tools

If you get an error when running anchor tools or commit_evolution, ensure PINATA_API_KEY and PINATA_API_SECRET are set. Without them, these tools return a clear error. If you only need to record a learning event without IPFS, use --only learning with the evolve command.

commit_evolution succeeds but commit_learning fails

Retry just the learning step:

hermes bort evolve <skill> --token-id N --commit-only --only learning

Each run is a distinct on-chain event; the knowledge registry does not dedup.

Network tests fail

Network tests are skipped or short-circuit when the relevant env var is unset. For example, Pinata pin tools return a clear error without PINATA_API_KEY. Set the required environment variables to run the full test suite:

BORT_TEST_TOKEN_ID=11100 pytest -q

Sources & References

This page was researched from 2 independent sources, combined and verified for completeness.

Newsletter

The #1 AI Newsletter

The most important ai updates, guides, and fixes — one weekly email.

No spam, unsubscribe anytime. Privacy policy

Other Hermes Agent integrations