Hermes Bort: BORT Agent Integration for Hermes Agent
Connects Hermes Agent to BORT (BAP-578) agent NFTs on BSC mainnet.
- Author
- BORT-AGENTS
- Stars
- 3
- Language
- Python
- License
- MIT
- Upstream updated
- 2026-05-18

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

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_evolutionpins the evolved skill to IPFS and writes it as anINSTRUCTIONknowledge source onKnowledgeRegistryV2.commit_learningemits aLearningRecordedevent 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).
| Flag | Effect |
|---|---|
--iterations N | GEPA iterations (default 10). |
--optimizer-model M | LLM for GEPA reflections, litellm format (anthropic/claude-sonnet-4-6, deepseek/deepseek-chat, openrouter/...). Default: the optimizer's own default. |
--eval-model M | LLM for evaluations, litellm format. |
--repo PATH | Path to the hermes-agent-self-evolution repo. |
--commit-only | Skip the optimizer; commit the latest existing run dir. |
--only {both,evolution,learning} | Which on-chain steps to run (default both). |
--min-improvement F | Only 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.
| Setting | Env var | Default |
|---|---|---|
| Runtime API base | BORT_API_URL | https://bap578-nfa-platform.onrender.com |
| BSC RPC | BSC_RPC_URL | https://bsc-dataseed.binance.org |
| Dapp URL (deep links) | BORT_DAPP_URL | https://www.bortagent.xyz |
| Memory dir | BORT_MEMORY_DIR | ~/.hermes/bort-memory |
| Operator key | BORT_OPERATOR_PRIVATE_KEY | unset (writes disabled) |
| Allow broadcast | BORT_ALLOW_BROADCAST | unset (simulate only) |
| Policy file | BORT_POLICY_PATH | ~/.hermes/bort-policy.yaml |
| Pinata key | PINATA_API_KEY | unset (needed for anchor tools) |
| Pinata secret | PINATA_API_SECRET | unset (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 toKnowledgeRegistryV2as aMEMORYsource through the operator's WRITE grant. - On next session start: the provider pulls active
MEMORYsources fromKnowledgeRegistry.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 sourcedescription, 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_KEYandPINATA_API_SECRET). Without them, anchor tools return a clear error. - The
evolvecommand requires thehermes-agent-self-evolutionrepository to be available locally, located via--repo,$BORT_SELF_EVOLUTION_REPO,~/.hermes/hermes-agent-self-evolution, or a sibling directory. - The
commit_evolutionstep requires Pinata credentials because it pins the evolved skill to IPFS. Thecommit_learningstep 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-evolutionrepo present (if you plan to useevolve)
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.
- 1.BORT-AGENTS/hermes-bortBlog · primary source
- 2.BORT-AGENTS/hermes-bort READMEOfficial documentation
The #1 AI Newsletter
The most important ai updates, guides, and fixes — one weekly email.
No spam, unsubscribe anytime. Privacy policy