Skip to content

feat(integrations): add hermes-agent-memory native provider for OB1#280

Open
MicScalise wants to merge 4 commits into
NateBJones-Projects:mainfrom
MicScalise:add-hermes-agent-memory
Open

feat(integrations): add hermes-agent-memory native provider for OB1#280
MicScalise wants to merge 4 commits into
NateBJones-Projects:mainfrom
MicScalise:add-hermes-agent-memory

Conversation

@MicScalise
Copy link
Copy Markdown

Summary

Adds integrations/hermes-agent-memory/ — a native Hermes Agent MemoryProvider that connects Hermes runtimes to the OB1 v1 Agent Memory contract. Same backend as the OpenClaw plugin, so OpenClaw agents and Hermes agents share one governed memory.

This is a companion to integrations/openclaw-agent-memory/ — same governance, different runtime.

What it does

  • Auto-recall before each turn via prefetch(), with background warm-up via queue_prefetch() (90s TTL cache)
  • Auto-writeback after each turn via sync_turn() — payload structured as OB1 outputs[]
  • Session-end finding extraction via on_session_end() — heuristic mapping into decisions / lessons / constraints / next_steps / unresolved_questions / failures
  • Pre-compression preservation via on_pre_compress() — extracts findings before compression discards messages
  • Seven explicit tools: ob1_recall, ob1_writeback, ob1_search, ob1_report_usage, ob1_list_review_queue, ob1_review_memory, ob1_get_recall_trace
  • x-brain-key auth (matches OB1 v1 contract — not Authorization: Bearer)
  • Per-turn model + provider attribution — reads on_turn_start kwargs, falls back to ~/.hermes/config.yaml (real yaml.safe_load, not line-scanning)
  • Subagent / cron / flush guards — disables writes for non-primary contexts to prevent corrupting the parent's task_id

Why this exists

OpenClaw and Hermes are sibling agent runtimes. Without a Hermes-native provider, Hermes agents can't participate in the same governed memory the OpenClaw plugin uses — defeating the cross-runtime "shared brain" promise. This plugin closes that gap with no extra infrastructure (talks to the same agent-memory-api Edge Function).

Tests

75 pytest tests, network-mocked at urllib.request.urlopen, run offline in <1 second:

```bash
cd integrations/hermes-agent-memory/plugin
pytest tests/

===== 75 passed in 0.44s =====

```

Coverage: pure helpers, OB1 v1 schema shape on the wire (recall + writeback + report_usage), client error handling, lifecycle (active/inactive transitions, subagent guard, save_config sanitization), prefetch + queue_prefetch caching (cold/warm/stale/burst-dedup/shutdown), sync_turn (skip-trivial, skip-short, ob1-context stripping), session-end finding extraction, pre-compress, and all seven tool handlers.

Verified live

End-to-end against a self-hosted Supabase + agent-memory-api Edge Function deployment. Real Hermes turns auto-recall, auto-write back with correct runtime_name=hermes, model=anthropic/claude-opus-4.6, provider=anthropic, and task_id linkage. Agent self-organizes around the system_prompt_block and calls ob1_writeback explicitly for decision/lesson categorization.

Test plan

  • Tests pass: `cd integrations/hermes-agent-memory/plugin && pytest tests/`
  • Install: copy `plugin/init.py` and `plugin/plugin.yaml` to `~/.hermes/plugins/ob1/`
  • Configure: `~/.hermes/ob1.json` with endpoint + workspace, `OPENBRAIN_KEY` in env
  • Activate: `hermes config set memory.provider ob1`
  • Drive a turn: `hermes -z "Decision: ship v0.1.0."` and verify `agent_memories` row lands

See `integrations/hermes-agent-memory/README.md` for the full setup walkthrough and `CHANGELOG.md` for v0.1.0 release notes.

Related

Filed alongside #279 (NBJ OpenClaw plugin missing memory-host hooks). This PR's Hermes provider is unrelated to that issue — it's a parallel native implementation for the Hermes runtime side.

Native Hermes Agent MemoryProvider that connects Hermes runtimes to
the OB1 v1 Agent Memory contract — same backend the OpenClaw plugin
talks to, so OpenClaw and Hermes agents share one governed memory.

- Auto-recall before each turn (prefetch + queue_prefetch with 90s cache)
- Auto-writeback after each turn (sync_turn → outputs[])
- Synchronous structured-finding writeback at session end
- Background writeback + summary string at compression time
- 7 explicit tools: recall, writeback, search, report_usage,
  list_review_queue, review_memory, get_recall_trace
- x-brain-key auth (matches OB1 v1, not Bearer)
- Per-turn model+provider attribution with config.yaml fallback
- Subagent/cron/flush guard prevents corrupting parent task_id

75 pytest tests, network-mocked, run offline in <1s. Verified live
end-to-end against a self-hosted Supabase + agent-memory-api Edge
Function deployment.

Companion to integrations/openclaw-agent-memory/ — same governance,
different runtime.
@github-actions github-actions Bot added the integration Contribution: MCP extension or capture source label May 9, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 9, 2026

Hey @MicScalise — welcome to Open Brain Source! 👋

Thanks for submitting your first PR. The automated review will run shortly and check things like metadata, folder structure, and README completeness. If anything needs fixing, the review comment will tell you exactly what.

Once the automated checks pass, a human admin will review for quality and clarity. Expect a response within a few days.

If you have questions, check out CONTRIBUTING.md or open an issue.

…unconfirmed=true

Two Edge Function defaults make naive recall return zero rows even when
match_thoughts finds the embedding correctly:

  1. Writeback stores memories with visibility="personal" (the column
     default in agent_memories), but scopeMatches() at /recall drops
     personal-visibility memories unless scope.visibility="personal" is
     passed by the recall.
  2. Writes default to provenance.requires_review=true (governance), which
     gives the row review_status="pending"; scope.include_unconfirmed=false
     filters all pending memories out, including the agent's own writes
     from earlier turns.

The combination meant every Hermes auto-recall returned empty even when
the relevant prior turn was clearly in OB1 — the agent never saw its
own previous memories.

Fix: pass scope.visibility="personal" in all recall paths (prefetch,
do_recall, ob1_recall tool) and default include_unconfirmed_recall=True
in the provider config so pending memories are visible to their author.
Pending memories are still ranked lower than confirmed ones via the
Edge Function's scoreMemory(), so this preserves governance signal.

Verified live: a Hermes turn querying "What did we decide about the
Qwen3.6-27B coder model?" now recalls 8 prior memories (was 0 before
the fix) and synthesizes them correctly.
Mirrors the OpenClaw plugin's workspaceMode option (PR NateBJones-Projects#283 Phase 9) so
both runtimes have symmetric multi-tenant memory semantics.

Config (in $HERMES_HOME/ob1.json or via env vars):

  workspace_mode: "shared" | "per-agent"   (default: "shared")
  workspace_prefix: "<optional-prefix>"     (per-agent mode only)
  workspace_id: "<fallback>"                (used in shared mode + as
                                             fallback in per-agent mode)

  Env: OPENBRAIN_WORKSPACE_MODE, OPENBRAIN_WORKSPACE_PREFIX

In "per-agent" mode workspace_id = workspace_prefix + agent_identity, so
each Hermes agent identity gets its own isolated OB1 workspace. Empty or
"default" agent identities fall back to the configured workspace so the
plugin never sends an empty workspace_id.

Implementation:

  * _resolve_workspace_id() — pure helper applying the mode rules,
    matching the OpenClaw plugin's resolveAgentWorkspaceId() logic so
    cross-runtime behavior is identical.
  * initialize() reads agent_identity BEFORE resolving workspace_id so
    per-agent mode can use it.
  * _load_ob1_config() handles new env vars + validates workspace_mode
    to one of the allowed values (defaults invalid values to "shared").

Tests: 6 new TestResolveWorkspaceId cases covering all four resolution
paths (shared-fallback, per-agent-with-identity, prefix application,
default-identity fallback, empty-identity fallback, unknown-mode-as-shared).
Total suite: 81 tests passing.

Backwards compatible. workspace_mode defaults to "shared" so existing
deployments behave exactly as before.
@alanshurafa alanshurafa added area: integrations Review area: integrations/MCP/capture sources review: ready-for-maintainer Community reviewer recommends maintainer review alan-reviewed Reviewed by Alan Shurafa in Community Reviewer role labels May 20, 2026
@alanshurafa
Copy link
Copy Markdown
Collaborator

Thanks for the contribution. This is a substantial, well-organized integration — a Hermes MemoryProvider with a CHANGELOG, tests (test_ob1_provider.py), and clear docs. It is the Hermes counterpart to the OpenClaw agent-memory plugin: same OB1 Agent Memory backend, different runtime.

Because it sits in the Agent Memory direction, a few things for the maintainer rather than a code fix. It ties to the same direction question as the OpenClaw cluster (#278 / #281 / #283 / #309) — whatever governance model OB1 settles on there should apply consistently here. Worth checking whether prefetch() recall carries the same include_unconfirmed default question raised on #283. And the integration ships its own LICENSE file in the subfolder — worth a quick license-compatibility check against the repo's FSL-1.1-MIT.

Recommend maintainer review, alongside the OpenClaw cluster direction decision.

— Alan (community reviewer; non-binding)

…onfirmed default)

Two changes per @alan's community review on PR NateBJones-Projects#280:

1. Remove subfolder LICENSE (MIT) so this dir inherits the repo's FSL-1.1-MIT.
   The MIT-in-FSL nested-license situation was a real compatibility issue;
   following the same convention as integrations/openclaw-agent-memory/
   which inherits root LICENSE.md.

2. Change include_unconfirmed_recall config fallback from False to True
   (line 159 of plugin/__init__.py) so it matches the DEFAULTS dict at
   line 96 and the design intent documented in phase-8 lessons learned.
   Recall returned 0 with False — pending memories (which is the default
   for governed writes per require_review_by_default=True) were filtered
   out before reaching the caller. Edge Function's scoreMemory still
   ranks pending memories lower so they don't dominate; they just appear.

Same governance direction as PR NateBJones-Projects#283 (OpenClaw companion). Whatever the
maintainer settles on for the OpenClaw cluster (NateBJones-Projects#278/281/283/309) applies
consistently here; current change aligns Hermes-OB1 with the resolved
defaults documented in the Hermes-OB1 phase-8 final report.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@MicScalise
Copy link
Copy Markdown
Author

Thanks Alan — all three points addressed. New commit ff4cce0 lands on the PR branch.

1. LICENSE compatibility (FSL-1.1-MIT)
You're right — shipping a MIT LICENSE inside an FSL-1.1-MIT repo created a real nesting conflict. Removed integrations/hermes-agent-memory/LICENSE; the directory now inherits the repo's root LICENSE.md, matching the convention used by integrations/openclaw-agent-memory/ which has no subfolder LICENSE.

2. prefetch() recall include_unconfirmed default
Same question as #283 — confirmed. There was a real inconsistency in our code:

  • plugin/__init__.py:96 DEFAULTS dict: include_unconfirmed_recall: True
  • plugin/__init__.py:159 config-parse fallback: False
  • plugin/__init__.py:944 + :1292 (both call sites): use the config value
  • The Phase-8 final report explicitly says we landed on True because recall returned 0 with False (pending memories — which is the default for governed writes per require_review_by_default=True — were filtered out before reaching the caller).

Flipped line 159 fallback to True so the resolved default is consistent with design intent. Edge Function's scoreMemory still ranks pending memories lower so they don't dominate confirmed results; they just appear in recall.

3. Governance direction across the cluster
Aligned with #283's pattern (governance-discipline on writes via require_review_by_default=true; permissive recall via include_unconfirmed_recall=true). Happy to update this PR to match whatever the maintainer settles on for the OpenClaw cluster — this Hermes-OB1 integration is the same OB1 backend behind a different runtime, so the resolved governance defaults apply identically. Pinging when the cluster direction lands.

@alanshurafa
Copy link
Copy Markdown
Collaborator

Thanks for the quick turnaround — all three points look addressed.

Removing the nested LICENSE is the right call; inheriting the repo root and matching the openclaw-agent-memory convention is consistent. On include_unconfirmed_recall, good catch on the DEFAULTS-vs-fallback mismatch — that was a real latent inconsistency, and resolving it to True lines up with the recall-returns-zero behavior you documented. On governance, agreed it stays the maintainer's call for the whole OpenClaw/Hermes cluster; aligning with #283's pattern is a sensible holding position until that direction lands.

I haven't re-read the ff4cce0 diff line by line, but the three changes as described are all sound. Recommend maintainer review alongside the cluster direction decision.

— Alan

@MicScalise
Copy link
Copy Markdown
Author

Thanks Alan — appreciate the second look. Happy to rebase or adjust if anything shifts when the cluster direction lands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

alan-reviewed Reviewed by Alan Shurafa in Community Reviewer role area: integrations Review area: integrations/MCP/capture sources integration Contribution: MCP extension or capture source review: ready-for-maintainer Community reviewer recommends maintainer review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants