feat(memory): add Contexto memory provider plugin - #23217
Closed
amiller wants to merge 4 commits into
Closed
Conversation
Wires the self-hosted Contexto memory engine (port 4010 by default) into the MemoryProvider interface. Per-turn ingest of (user, assistant) pairs to /v1/ingest, background-threaded prefetch via /v1/search before each turn, and a contexto_search tool for explicit lookups. Agent slug is one-per-hermes-profile (derived from kwargs.agent_identity); userId is per-platform-user (kwargs.user_id, gateway-supplied). The plugin idempotently registers the agent slug on initialize. Pip dep points at the Python client in the contexto repo's clients/python/ subdirectory via git URL. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…_delegation
Two changes that go together:
1. Honor the MemoryProvider ABC contract: skip writes when agent_context
is non-primary (subagent, cron, flush). Subagent turns are
tool-output-heavy — 5-10x larger than primary turns — so ingesting
them under the parent's slug both corrupts recall semantics and
reliably blows the extraction timeout against Gemini Flash. Without
this guard, every delegated task triggered a 120s+ Gemini extract
that often timed out.
2. Implement on_delegation so the parent agent captures (task, result)
pairs as a single curated turn under its own slug. Subagent raw
turns are still silenced, but the work they did still reaches memory
in a clean, recallable form ("[delegated subtask] ... / [delegation
result] ..."). This means delegating "research X" doesn't lose the
findings — they're remembered as a parent observation.
Adds tests/test_plugin.py covering all branches: subagent + cron + flush
silenced, primary still ingests, on_delegation captures and is recallable.
Tests skip if the selfhost isn't reachable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Daemon-thread errors print raw tracebacks to stderr by default. For the contexto plugin running inside an interactive hermes session, that means every transient ingest failure (Gemini extract timeout, network blip, container restart) prints a 30-line traceback right into the user's chat UI mid-stream. Worse, those tracebacks NEVER made it to agent.log, so failures were both maximally visible to the user and invisible to me when debugging. Wrap _sync and queue_prefetch._run in try/except + logger.warning. The error type and message (including the response body, thanks to _raise_with_body in the client) still surface — they just land in agent.log under "contexto sync failed" / "contexto prefetch failed" instead of stderr. Same pattern mem0 and supermemory use. This is logging, not swallowing: the agent's response is non-blocking on memory writes, the error info is preserved, and the user can grep agent.log to see what's actually happening with their ingests. Adds a regression test that captures stderr + caplog and verifies failures route through the logger only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hermes CLI sessions don't have a platform user_id (gateway-supplied), so the plugin was passing user_id=None to Contexto's /v1/ingest. Without a userId, the (now-merged-upstream) extractor user-identity anchoring doesn't kick in — the model produces bare "User" / "I" subjects in extracted triples, which is exactly the contamination root cause we filed ekailabs/contexto#144 for. Fall back to agent_slug as user_id when the platform doesn't supply one. The agent slug is the hermes profile name (e.g. "default", "coder"), which is the closest stable identity we have for a CLI user talking to themselves. Triples will then anchor to that identity: Before (CLI session, user_id=None): User → has favorite color → teal User → is working on → matthammer After: default → has favorite color → teal default → is working on → matthammer Gateway sessions (Telegram/Discord/etc) keep their per-platform user_id unchanged — the fallback only applies when kwargs.get("user_id") is falsy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Collaborator
|
Thanks for the contribution! Per the updated CONTRIBUTING.md, new memory providers are no longer accepted as in-tree additions to
Closing this in line with that policy. The path forward is to publish it as a standalone plugin so users can install it directly without touching the Hermes source tree. Once it's published, a small docs PR adding it to the Community plugins section of the README is welcome. Sorry for the bump — appreciate the time you put into this. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds Contexto (the OSS self-hosted memory engine) as a
MemoryProviderplugin. Per-turn ingest of (user, assistant) pairs into the cognitive store (episodic / semantic / procedural sectors), semantic recall before each turn, plus acontexto_searchtool exposed to the model. Sits alongside the existingmem0,honcho,supermemory, etc. plugins — same lifecycle, same interface contract.Draft because it depends on two changes still in flight upstream at ekailabs/contexto:
clients/python/(the pip dependency this plugin imports)Usersubjects in extracted triples)The
pip_dependenciesline inplugin.yamlcurrently points at the contexto fork (amiller/contexto); will switch toekailabs/contexto:mainonce #143 lands.What's in the plugin
plugins/memory/contexto/__init__.pyContextoMemoryProviderimplplugins/memory/contexto/plugin.yamlplugins/memory/contexto/README.mdplugins/memory/contexto/tests/test_plugin.pyBehavior
http://localhost:4010by default, override viaCONTEXTO_BASE_URL). Not compatible with the hostedapi.getcontexto.commindmap surface — different endpoints, different abstractions.agent_identity(the active hermes profile name, e.g.default,coder). One slug per profile, so all sessions of a profile share recall — same scoping convention as the other memory plugins.user_idfallback — when the gateway doesn't supply a platform user_id (CLI sessions), usesagent_identityas the contexto userId. Without this, the contexto extractor produces bareUser/Itriple subjects instead of resolvable identities.agent_context— only the primary agent ingests. Subagents and cron contexts skip writes (their tool-output-heavy turns would corrupt recall semantics and reliably blow extraction timeouts on Gemini Flash). Subagent results still reach memory viaon_delegation(task, result)as a single curated parent turn.logger.warning(so they land inagent.log) instead of dumping raw tracebacks to stderr (which polluted the chat UI in earlier iterations).contexto_search, for explicit recall queries from the model.Testing
tests/test_plugin.pycovers:on_delegationcaptures(task, result)pairs as a single curated parent turn, recallable via searchAll 6 tests pass against a running selfhost (skip if unreachable, so they're CI-safe even without infra).
Activation
# .env (optional, defaults to localhost:4010) CONTEXTO_BASE_URL=http://localhost:4010Or via the wizard:
hermes memory setup→ pickcontextofrom the list (auto-detected fromplugins/memory/contexto/).Commit history
34d15b1c1initial plugine0735d2adhonoragent_context+ capture subagent results viaon_delegation92d8a0712route ingest/prefetch failures throughlogger.warninginstead of stderrc4c7b9288defaultuser_idtoagent_slugwhen no platform user_id is setTest plan
hermes-contextoprofile against a self-hosted Contexto stack🤖 Generated with Claude Code