[Bug][Hermes] README's MEMORY_TENCENTDB_LLM_* vars never reach the Gateway — L1 extraction fails silently while health stays green

Author: blrain3Created Sep 17, 2026Updated Sep 18, 2026

Summary

The Hermes-side README tells users to configure the Gateway's LLM with MEMORY_TENCENTDB_LLM_API_KEY / _BASE_URL / _MODEL. No code path ever maps those names onto what the Gateway actually reads (TDAI_LLM_API_KEY / _BASE_URL / _MODEL, MemoryCore/src/gateway/config.ts:588-590). Following the documented setup produces a Gateway that boots, reports status: ok, and runs its pipeline with failed=0 — while every L1 extraction has failed and no memory is ever written.

Two defects here, and the second is the one that costs time:

  1. the documented variable names are not the ones the code reads;
  2. the resulting failure is swallowed, so every health signal stays green.

Reproduction

  1. Install the provider per hermes-plugin/memory/memory_tencentdb/README.md, so Hermes spawns the Gateway as its sidecar.

  2. Configure exactly what the README's setup block (lines 146-148) prescribes:

    bash
    export MEMORY_TENCENTDB_LLM_API_KEY="sk-..."
    export MEMORY_TENCENTDB_LLM_BASE_URL="https://api.openai.com/v1"
    export MEMORY_TENCENTDB_LLM_MODEL="gpt-4o"
  3. Capture several turns in one session so the L1 threshold is reached (warm-up: the first conversation triggers it):

    bash
    curl -X POST http://127.0.0.1:8420/capture \
      -H 'Content-Type: application/json' \
      -d '{"session_key":"probe","user_content":"...","assistant_content":"..."}'
  4. Observe: GET /health reports "status":"ok"; the pipeline worker's completed count climbs with failed: 0 and deadLettered: 0; but the L1 record table in the SQLite store stays at 0 rows indefinitely.

Setting the TDAI_LLM_* names instead makes L1 extraction produce records immediately on the same conversation, so the local setup is not otherwise at fault.

Evidence

(1) Documented nameshermes-plugin/memory/memory_tencentdb/README.md:146-148, repeated in that file's env table (lines 236-238), in the config schema the provider advertises to Hermes (__init__.py:975 / 981 / 987), and in scripts/install_hermes_memory_tencentdb.sh:362-364:

bash
export MEMORY_TENCENTDB_LLM_API_KEY="sk-..."
export MEMORY_TENCENTDB_LLM_BASE_URL="https://api.openai.com/v1"   # optional
export MEMORY_TENCENTDB_LLM_MODEL="gpt-4o"                         # optional

(2) Names the Gateway actually readsMemoryCore/src/gateway/config.ts:588-590:

typescript
baseUrl: env("TDAI_LLM_BASE_URL") ?? str(llmConfig, "baseUrl") ?? "https://api.openai.com/v1",
apiKey:  env("TDAI_LLM_API_KEY")  ?? str(llmConfig, "apiKey")  ?? "",
model:   env("TDAI_LLM_MODEL")    ?? str(llmConfig, "model")   ?? "gpt-4o",

A repo-wide grep for MEMORY_TENCENTDB_LLM over *.py / *.ts / *.sh returns only those schema and documentation strings and the installer's commented examples — there is no bridge. The provider itself writes no environment variables at all (grep -n 'os.environ\[' __init__.py supervisor.py → empty), so nothing compensates for the mismatch.

The supervisor does perform exactly this kind of translation for the gateway host/port pair (supervisor.py:224-231), which is what makes the gap easy to miss — the pattern is present in the same file, just not for the LLM trio:

python
env = os.environ.copy()
# The Python provider historically used MEMORY_TENCENTDB_* while
# src/gateway/config.ts reads TDAI_GATEWAY_*. Export both so a
# non-default supervisor port cannot accidentally spawn a child
# that still binds the default 8420.
env["MEMORY_TENCENTDB_GATEWAY_PORT"] = str(self._port)
env["MEMORY_TENCENTDB_GATEWAY_HOST"] = str(self._host)
env["TDAI_GATEWAY_PORT"] = str(self._port)
env["TDAI_GATEWAY_HOST"] = str(self._host)

(3) The failure is swallowed. With the documented names set, the only witness is a debug-level line during extraction:

[l1-extractor] LLM extraction failed: You didn't provide an API key
...
[pipeline-worker] Stopped (consumed=8, completed=8, failed=0, deadLettered=0)

That shutdown line came from a run in which zero L1 records were written.

Mechanically: l1-extractor.ts:214-218 catches the LLM error and returns { success: false, ... }. pipeline-factory.ts:604-652 accumulates extractedCount / storedCount, never branches on l1Result.success, then calls markL1ExtractionComplete unconditionally (:658) and logs L1 complete. The task therefore returns normally, so the worker's counters record a success and no failure counter moves.

Impact

Anyone configuring the Hermes integration from its own README gets a memory system that looks healthy at every observable surface and stores nothing above L0. There is no error in the Hermes logs, no failing counter, and no non-ok status — the only witness is a debug-level log line emitted during extraction. A user could run this for weeks believing memory was accumulating.

Related

  • #709 — same family (a documented environment variable that never reaches the runtime, L1 fails), different root cause: that report concerns the Docker deploy path's MEMORY_LLM_PROTOCOL; this one concerns the Hermes provider path's LLM trio. It also shares the "terse log line" concern noted there.
  • #1028 — [Bug][Hermes] memory_tencentdb tools stay advertised (and loop the LLM) while the Gateway is permanently down.

Proposal

Two independent fixes:

  1. Bridge the names. Either have MemoryTencentdbProvider export TDAI_LLM_{API_KEY,BASE_URL,MODEL} into the child process — it already owns the spawn (supervisor.py:268-269) and already does this for the port/host pair — or rename the documented variables to TDAI_LLM_*. Bridging keeps the Hermes-facing names stable, which is what the schema at __init__.py:975-987 advertises.
  2. Consider surfacing extraction failures. pipeline-factory.ts:604-652 could branch on l1Result.success (or on the emptyReason already carried out of the extractor) so a failed extraction is visible — whether as a tasksFailed increment, a degraded health field, or a WARN naming the missing credential. This is offered as a design consideration rather than a specific request: the counting semantics are the maintainers' call, and I can see arguments for treating an empty extraction as a completed no-op.

I'm happy to open a PR for the first fix (bridging the names) if that direction looks right. I'd rather not presume on the second, since it's a design decision that may be better made on your side.

Source: TencentCloud/TencentDB-Agent-Memory