Skip to content

[codex] add observational memory provider - #4787

Closed
intertwine wants to merge 10 commits into
NousResearch:mainfrom
intertwine:codex/add-observational-memory-provider
Closed

[codex] add observational memory provider#4787
intertwine wants to merge 10 commits into
NousResearch:mainfrom
intertwine:codex/add-observational-memory-provider

Conversation

@intertwine

Copy link
Copy Markdown
Contributor

What changed

  • add a built-in observational_memory memory provider plugin under plugins/memory/
  • expose the provider through hermes memory setup, CLI help text, and user docs
  • add targeted provider tests covering startup context, explicit remembers, and incremental writeback

Why

Hermes's new pluggable memory-provider architecture makes Observational Memory a clean fit as a first-class backend. This gives Hermes access to the same local markdown memory store used by Claude Code and Codex, while also letting Hermes contribute back into that shared memory when writeback is enabled.

User impact

  • users can select observational_memory directly from hermes memory setup
  • Hermes gets shared startup context from Observational Memory's compact profile.md + active.md
  • Hermes can search or store shared memory with om_context, om_search, and om_remember
  • Hermes can write its own sessions back into Observational Memory incrementally or at session end

Validation

  • python -m pytest -o addopts='' tests/agent/test_observational_memory_provider.py tests/agent/test_memory_provider.py tests/agent/test_memory_plugin_e2e.py
  • python -m compileall plugins/memory/observational_memory tests/agent/test_observational_memory_provider.py

@intertwine

Copy link
Copy Markdown
Contributor Author

PR Review — Observational Memory Provider

Reviewed against: observational-memory v0.3.0 source (all integration points verified)

CI: all green. API surface alignment: confirmed correct.


Scope note

This PR bundles 4 independent changes:

  1. Observational Memory provider plugin (the headline)
  2. Redaction fast-path optimization (agent/redact.py)
  3. Gateway model resolution precedence (gateway/run.py)
  4. Supply-chain audit workflow hardening (.github/workflows/supply-chain-audit.yml)

Consider splitting 2–4 into their own PRs for cleaner bisect/revert if needed. Not a blocker.


Issues

1. _may_contain_sensitive_markers: "bot" marker is too broad (agent/redact.py:113)

"bot" is a substring of "about", "robot", "chatbot", etc. This doesn't leak secrets (it falls back to full scan), but it defeats the fast path for virtually all English text. Either remove it or use a word-boundary check. Same concern with "sg." — the dot isn't enforced as literal in a substring check, so "messaging" etc. would match.

2. Dead default search query in om_context (plugins/memory/observational_memory/__init__.py:620)

text = self._build_context(
    query=query or _DEFAULT_SEARCH_QUERY,
    limit=limit,
    include_search=bool(query),
)

When query is empty, _DEFAULT_SEARCH_QUERY is passed but include_search=False means it's never used. The fallback string is dead code in this path. Either drop the or _DEFAULT_SEARCH_QUERY or make the no-query path actually search with the default.

3. Throwaway Config in _build_config (__init__.py:683-684)

preload = OMConfig(memory_dir=memory_dir, env_file=env_file)
preload.load_env_file()
# ...
return OMConfig(**kwargs)  # second Config created

The preload Config exists only for the load_env_file() side effect (loading env vars into the process). Works, but confusing to read. A comment like # load env file for side effects before building final config would help.

4. on_session_end can silently drop the final flush (__init__.py:600-602)

def on_session_end(self, messages):
    if self._sync_thread and self._sync_thread.is_alive():
        self._sync_thread.join(timeout=10.0)
    self._flush_pending(force=True)

If the background sync thread is still alive after the 10s join timeout, _flush_pending checks self._sync_thread.is_alive() and returns immediately — silently dropping the session-end flush. Consider logging a warning when the join times out, or forcing the flush regardless.

5. No size guard on system_prompt_block (__init__.py:525-545)

profile.md and active.md are injected verbatim into the system prompt. OM keeps these compact by design, but there's no defensive truncation. If a user manually inflates these files, the system prompt could blow up. A soft cap (e.g., 8K chars) with truncation would be defensive.

6. _apply_env_bridge mutates global os.environ (__init__.py:661-675)

This propagates OM_HERMES_API_KEYANTHROPIC_API_KEY / OPENAI_API_KEY globally. The not os.environ.get() guard prevents overwriting existing keys, which is correct. Just noting for awareness — if a future provider also bridges keys, ordering could matter.


Nits (non-blocking)

  • _flush_pending error recovery replaces the list object (self._pending_messages = pending + self._pending_messages) rather than extending in-place. Currently safe because all access is lock-protected, but pending.extend(self._pending_messages); self._pending_messages = pending would be semantically clearer.

  • Test test_incremental_sync_flushes_to_observer relies on the threshold being exactly 5 (from Config.min_messages default). If OM changes that default, the test breaks. Consider setting it explicitly in the fake config.

  • The approval test refactor (test_approve_deny_commands.py) replacing sleep(0.3) with a polling loop is a solid improvement — nice cleanup.


Verdict

The core provider implementation is solid — correct API usage, proper thread safety, good test coverage. The issues above are all minor/moderate. #4 (silent flush drop) is the one I'd want addressed before merge; the rest are fine to defer.

@intertwine

Copy link
Copy Markdown
Contributor Author

Correction: Review was verified against observational-memory v0.3.1 (current main), not v0.3.0 as originally stated. The v0.3.0→v0.3.1 delta is additive only (launchd scheduler support) and doesn't affect any API surface this PR uses. All findings stand.

@intertwine

Copy link
Copy Markdown
Contributor Author

Re-review after 5166364

All six findings addressed:

# Finding Resolution
1 "bot" / "sg." markers too broad Replaced with targeted regexes (bot\d{8,}: for Telegram, sg\.[A-Za-z0-9_-]{10,} for SendGrid). New tests confirm both patterns still trigger redaction in large payloads.
2 Dead _DEFAULT_SEARCH_QUERY Removed entirely.
3 Throwaway Config confusing Renamed preloadbootstrap_cfg, added explanatory comment.
4 Silent flush drop (blocker) on_session_end now warns and defers via _defer_final_flush — waits for active thread to finish, then forces flush. New threaded test validates the full sequence.
5 No size guard on prompt _truncate_prompt_section caps at 4K chars with notice. New test confirms.
6 Env var mutation (informational) No change needed.

Nits also addressed: _restore_pending_messages uses in-place prepend, sync test pins min_messages explicitly.

_flush_pending decomposition into _take_pending_messages / _restore_pending_messages / _run_observer_batch / _defer_final_flush is clean. The current_thread() guard correctly prevents the deferred flush from self-blocking.

CI: all green. LGTM — ready to merge.

@intertwine
intertwine marked this pull request as ready for review April 3, 2026 18:19
@intertwine

Copy link
Copy Markdown
Contributor Author

Adding a short positioning note here because it may help frame where this provider fits in the Hermes memory lineup.

Observational Memory is strongest when the goal is cross-agent continuity that stays local and inspectable. In practice that means Hermes can share the same markdown memory store with Claude Code and Codex, while still giving users readable files, local search, compact startup context, and optional writeback.

Relative to the other providers:

  • compared with Honcho, Mem0, and RetainDB, this is much more local and transparent, with less SaaS / black-box behavior
  • compared with OpenViking, Hindsight, and ByteRover, this is less about hierarchy / graph-style knowledge management and more about stable session continuity plus derived startup context
  • compared with Holographic, this is less of a local fact DB and more of a shared observation layer across multiple agent tools

The underlying observational-memory package is based on Mastra's Observational Memory pattern: an Observer + Reflector that compresses conversation history into a stable observation log and compact startup memory, instead of depending only on per-turn dynamic retrieval.

Mastra's published results for that underlying OM architecture report 84.23% on LongMemEval with gpt-4o and 94.87% with gpt-5-mini. Important precision note: those benchmark numbers are for the underlying OM approach, not for this Hermes provider implementation specifically, but they are the main reason I thought it was worth bringing this pattern into Hermes.

I also added a longer version of this positioning note to the standalone plugin README here:
https://github.com/intertwine/hermes-observational-memory

intertwine and others added 10 commits April 6, 2026 18:36
The 0.4.1 release includes a dedicated Hermes JSONL session log parser
that filters to user/assistant prose only, achieving ~19x noise reduction
on typical sessions. This is required for effective cron-based observation
extraction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
results is a dict keyed by command string — iterating over the dict
directly yields keys (strings), not the result dicts, causing
"string indices must be integers" TypeError.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… writeback

Sync from intertwine/hermes-observational-memory@ad91c68:
- register() no-ops when called by the general plugin loader (avoids
  noisy 'no attribute register_memory_provider' warning at startup)
- Log explicit warning when writeback is configured but LLM provider
  is missing, instead of silently disabling

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sync from intertwine/hermes-observational-memory@3d7d3d5:
- After each observer batch, check if reflections have fallen behind
  and run the reflector inline if needed. Builds long-term memory
  without requiring a separate background scheduler.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@intertwine
intertwine force-pushed the codex/add-observational-memory-provider branch from 9d623b1 to 20db63a Compare April 6, 2026 22:41

@ZaynJarvis ZaynJarvis left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Observational Memory Provider + Bundled Fixes

Verdict: Request Changes — please split into two PRs

This PR bundles four independent changes. Three of them (agent/redact.py, gateway/run.py, supply-chain-audit.yml) are clean, independently valuable fixes ready to merge. The OM provider is the main feature and should be reviewed separately.

Part 1: Ready to Extract (cherry-pick into a separate PR) ✅

agent/redact.py — fast-path pre-filter:
The _may_contain_sensitive_markers() optimization is well-designed: cheap in checks on lowercased text before running the full regex suite, with correct E.164 phone fallback. The _FAST_SCAN_MIN_LEN = 4096 threshold is reasonable. This is a clean performance win with no behavioral risk.

gateway/run.py — model resolution fix:
The _resolve_active_model() (or equivalent) now checks HERMES_MODEL env var first, then config, then LLM_MODEL fallback. This prevents auxiliary AIAgent instances (memory flush, /compress) from falling back to the wrong model. Clean and correct.

.github/workflows/supply-chain-audit.yml:
Job summary output for fork PRs (where comment posting is blocked) + regex anchoring fix are both correct CI improvements.

Part 2: Observational Memory Provider — Review After Split

The OM provider itself (plugins/memory/observational_memory/) needs its own review pass:

  • Confirm it uses get_hermes_home() (not hardcoded ~/.hermes) for any state files
  • Verify is_available(), initialize(), on_session_end() follow the MemoryProvider interface contract
  • Check test coverage for the provider logic (264 lines of tests referenced in analysis)
  • Verify no circular imports

Action: Please split the three independent fixes (redact.py, gateway/run.py, CI workflow) into one PR and keep the OM provider in a separate focused PR. Both PRs will be reviewed quickly since the non-OM parts are already approved above.

@intertwine

intertwine commented Apr 19, 2026

Copy link
Copy Markdown
Contributor Author

Opened a focused replacement PR for the Observational Memory provider here:

#12583

This refresh is split off from the broader mixed-scope branch so reviewers can look at the OM integration on its own, rebased onto current main and bumped to observational-memory>=0.5.1.

I’m leaving #4787 in place for history/context, but the new PR is the one to review for the OM-only version.

@ZaynJarvis

@intertwine intertwine closed this Apr 19, 2026
@intertwine

Copy link
Copy Markdown
Contributor Author

Closing this in favor of resolved PR scope issues in a new followup PR #12583

@intertwine
intertwine deleted the codex/add-observational-memory-provider branch April 19, 2026 15:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants