All plugins

openloft-memory

OpenLoft Memory — persistent graph-backed long-term memory for any Hermes agent. Recall before every turn, ingest after every turn, loft_* memory tools. Works with the hosted OpenLoft service or a self-hosted backend. Zero Python dependencies. Disclosure — while openloft-memory is the active memory provider, each turn's user message, assistant reply and the core-redacted transcript slice (tool outputs included) are sent to the OpenLoft memory service (default https://api.openloft.xyz/api/memory, or the base_url you set) for storage and server-side LLM extraction, and each user message is sent there as a recall query; transcript archiving before compression, MEMORY.md/USER.md mirroring and backfill are off by default; loft_forget stops retrieval but the service retains the data (not erasure).

blok-hamsterMemoryv0.1.0Updated 3 hours ago

About

OpenLoft Memory — persistent graph-backed long-term memory for any Hermes agent. Recall before every turn, ingest after every turn, loft_* memory tools. Works with the hosted OpenLoft service or a self-hosted backend. Zero Python dependencies. Disclosure — while openloft-memory is the active memory provider, each turn's user message, assistant reply and the core-redacted transcript slice (tool outputs included) are sent to the OpenLoft memory service (default https://api.openloft.xyz/api/memory, or the base_url you set) for storage and server-side LLM extraction, and each user message is sent there as a recall query; transcript archiving before compression, MEMORY.md/USER.md mirroring and backfill are off by default; loft_forget stops retrieval but the service retains the data (not erasure).

OpenLoft Memory — a Hermes memory provider plugin

Persistent, graph-backed long-term memory for any Hermes Agent
deployment, backed by OpenLoft Memory. Recall is injected before every turn,
turns are ingested after every turn, and the agent gets five loft_* memory
tools. Works with the hosted service or a self-hosted memory backend.

Zero Python dependencies — the client is pure stdlib, so the plugin installs
with no dependency consent step anywhere.

What the agent gets

| Capability | What it does |
|---|---|
| Automatic recall | Relevant memory injected as context before every LLM turn (fail-open: an unavailable backend degrades to "no memories", never a failed turn) |
| Automatic ingest | Every completed turn is persisted on a background thread; extraction runs server-side; at most one ingest in flight, queued turns never block the agent |
| loft_recall(query) | On-demand read-only retrieval |
| loft_remember(text) | Explicit curation ("remember that I prefer dark mode") |
| loft_forget(entity_or_id) | Temporal invalidation — the fact stops being retrieved; the write-ahead log retains it. This is not erasure |
| loft_stats() | Turn number plus hot/cold/event counts for this agent's store |
| loft_search(query) | Scored vector search over stored records |

Facts decay on a curve and consolidation folds duplicates server-side — fading
is a designed property (OpenLoft's NM-AFC engine), not data loss.

Only one external memory provider may be active per agent (memory.provider).

Install

# drop-in install (catalog listing is a separate human-reviewed track)
git clone https://github.com/blok-hamster/hermes-plugin-openloft-memory \
      "$HOME/.hermes/plugins/openloft-memory"

hermes plugins enable openloft-memory   # prompts for OPENLOFT_MEMORY_API_KEY
hermes openloft-memory health

Get a key first:

  • Hosted: OpenLoft dashboard → Memory → Connections → mint. The secret is
    shown exactly once. (https://www.openloft.xyz/memory/connections)
    Check the service is live for your account before first use.

  • Self-hosted: run the OpenLoft memory service, then mint from the same
    dashboard when it is wired through the platform backend, or generate a
    service key directly:

    python -c "from app.auth import generate_key; print(generate_key('my_tenant'))"
    

The key embeds the tenant (nmafc_<tenant>.<secret>); nothing a request header
says can switch tenants after that.

Then select the provider:

hermes memory setup        # or the Desktop → Capabilities → Memory panel

Configuration

Stored as the plugin's own flat JSON (the same file the Desktop panel edits):
$HERMES_HOME/openloft-memory/config.json. The key itself is a secret: it is
stored as OPENLOFT_MEMORY_API_KEY in the profile .env and read at runtime
through agent.secret_scope.get_secret() — never from raw environment
variables, so profile isolation holds under multiplexed gateways.

| Key | Default | Purpose |
|---|---|---|
| base_url | https://api.openloft.xyz/api/memory | Hosted edge. Self-hosted: http://<host>:8100/v1/memory |
| api_key | — | nmafc_<tenant>.<secret>; the key carries the tenant |
| agent_id | auto | auto → OPENLOFT_MEMORY_AGENT_ID env → Hermes profile name → hermes. One agent id = one memory pool |
| conversation_scope | agent | agent (recommended, survives session-id rotation) | session (one pool per Hermes session — fragments when sessions rotate) | shared (the tenant's pre-agent store) |
| top_k | 8 | Recall breadth |
| inject_budget | 6000 | Max chars of recalled context injected per turn |
| ingest_enabled | true | Persist turns. Non-primary agent contexts (cron, subagents) are skipped unless ingest_nonprimary is set |
| toolset_enabled | true | Expose the loft_* tools |
| warm_next_turn | true | Cheap background recall after each turn to keep the backend's store instance warm |
| pre_compress_archive | false | Phase 3: archive transcript evidence before context compression via on_pre_compress, idempotent by content digest; forced when Hermes sets require_checkpoint (compression.checkpoint_required: true) |
| memory_write_mirror | false | Phase 3: mirror explicit MEMORY.md / USER.md writes into OpenLoft via on_memory_write; destructive writes are skipped when no previous_content is seen |

Agent ID & Conversation ID

These two values determine where memories are stored in the OpenLoft substrate.

| Setting | Default | What it does |
|---|---|---|
| agent_id | auto | Resolves to: config → OPENLOFT_MEMORY_AGENT_ID env → Hermes profile name → hermes. One agent id = one isolated memory pool. Change it to isolate memories between different agents/users. |
| conversation_scope | agent | agent (recommended) uses one pool per agent, immune to session rotation. session uses one pool per Hermes session — memories fragment if the session id rotates. shared uses the tenant's pre-agent store, visible to all agents. |

Change agent_id:

# Option A: Environment variable (takes effect immediately)
export OPENLOFT_MEMORY_AGENT_ID="my-agent"

# Option B: Via Hermes setup (persisted)
hermes memory setup   # select openloft-memory, set agent_id

Change conversation_scope:

hermes memory setup   # select openloft-memory, set conversation_scope to session or shared

Defaults: agent_id resolves to hermes and conversation_scope is agent — all memories go into the default pool for the hermes agent. This works out of the box for single-agent setups.

Self-hosting

Point base_url at your memory service (http://<host>:8100/v1/memory) and
mint keys per tenant. One caveat for self-hosted operators: the service's
POST /forget currently passes record_id/entity_name to the engine without
the charset allowlist its GET /records/{id} routes use — this plugin's client
refuses values outside [A-Za-z0-9_-] before the wires as defense-in-depth.
Patching the route to call _path_value() closes it server-side (see the
memory service repo's INTEGRATION.md, open item 6).

Disclosure

Things you would want to know before installing:

  • Network calls: every memory operation talks HTTPS to your configured
    backend — the hosted OpenLoft service by default (api.openloft.xyz), or
    your own server. No other third-party endpoints are contacted.
  • Conversation content leaves the machine: completed turns (user and
    assistant text; Hermes may pass tool calls and tool results in the same
    turn's transcript) are sent to that backend for extraction and storage. If
    your workspace content appears in tool output, it can appear in memory.
    Set ingest_enabled: false to keep writes off while keeping the tools.
  • Stored credential: OPENLOFT_MEMORY_API_KEY is stored via Hermes and
    treated like a password.
  • Usage metering: the hosted service meters usage per tenant, server-side.
    The plugin sends no telemetry of its own.
  • Forgetting is invalidation, not deletion: loft_forget and future
    retention semantics keep server-side logs; retention arises from decay.

Design notes

  • Reads fail open, writes fail visible. A recall that hits an unreachable
    backend returns nothing (your turn continues without injected memory — every
    error is logged). Tool calls and ingest report their errors so you can act.
  • The recall budget is bounded: per-socket timeouts (recall 8s) and a
    6,000-char injection cap, head/tail-trimmed.
  • Profile isolation by construction: every client/store binding is made in
    initialize() per instance; background threads are spawned through
    Hermes' spawn_context_thread (contextvars-bound), never a bare thread.
  • Identity mapping works anywhere: agent id resolves from config → env →
    the active Hermes profile name → hermes; conversation ids are sanitized
    into the substrate's [A-Za-z0-9_-] charset with a None-omission for the
    default pool.
  • Phase 3 hardening hooks (stub-verified; Hermes verification pending the
    1.9 session):
    • on_pre_compress(messages, *, require_checkpoint=False) — digests the
      transcript (SHA-256), persists it via remember only when
      pre_compress_archive or require_checkpoint is set, and skips identical
      digests (client-side idempotency). Compression never blocks on it.
    • on_memory_write(action, target, content, metadata=None) — mirrors
      MEMORY.md / USER.md edits into OpenLoft when memory_write_mirror is set;
      destructive writes (edit/delete/replace) are skipped unless
      previous_content is present.
    • Timing: every client call logs duration (ms) and outcome (ok /
      rate-limited / error).

Development

python3 -m pip install pytest        # stdlib-only suite; hermes-dependent tests skip
python3 -m pytest tests/ -q

# inside a Hermes environment:
hermes plugins doctor . --ci
hermes plugins validate . --install-deps    # the same check catalog CI runs

# contract suite against the REAL memory service (boots it right here with
# deterministic provider fakes at the engine boundary — no model downloads,
# no keys in your tree):
../backend/memory-service/.venv-test/bin/python scripts/run_contract_suite.py
# options: --port 8123 --watch   (keep serving to curl by hand)

License

MIT — see LICENSE.

README from the reviewed commit 8edd526; it updates when the author re-pins.

Developer Mode

Explore built-in Hermes toolsets and actions.

HermesAgent

Developer?

Create a Hermes extension for Desktop or agent workflows.

Developer Section