Skip to content

fix(context,compress-synthetic): conversation observations render with full content#974

Open
projectedanx wants to merge 4 commits into
rohitg00:mainfrom
projectedanx:main
Open

fix(context,compress-synthetic): conversation observations render with full content#974
projectedanx wants to merge 4 commits into
rohitg00:mainfrom
projectedanx:main

Conversation

@projectedanx

@projectedanx projectedanx commented Jun 25, 2026

Copy link
Copy Markdown

The Hermes plugin (and any integration using tool_name: "conversation" as a generic catch-all for user/assistant turns) stored conversation observations with type: "other" and title: "conversation". The memory-context renderer then produced lines like:

- [other] conversation: <narrative>

These were easy to misread as type=conversation, empty content — and they were the reason the system prompt's <agentmemory-context> block was showing 100+ "empty" conversation entries across the user's six CLIs.

This PR fixes the issue at three layers.

Root cause

The synthetic-compression path (buildSyntheticCompression in src/functions/compress-synthetic.ts) is the default compression path (AGENTMEMORY_AUTO_COMPRESS=false, the default since v0.8.8). It runs every non-vision observation through buildSyntheticCompression when LLM compression is disabled.

Two issues in this path:

  1. inferType() only returns "conversation" when hookType === "prompt_submit". The Hermes plugin uses hookType: "post_tool_use", so the type falls through to "other".
  2. buildSyntheticCompression title is truncate(toolName || "observation", 80). When the tool name is the literal string "conversation", the title becomes "conversation" — useless in the rendered output.

The renderer's existing filter is o.title && o.importance >= 5. It does NOT check whether title === type or whether the title is a generic tool name. So observations with the bad title/title-collision slip through and produce visually-broken lines.

Fix

Part 1 — inferType() in src/functions/compress-synthetic.ts: Added an explicit check for conversation-shaped tool names ("conversation", "chat", "prompt", "message", "turn") that returns "conversation". New observations are now correctly classified.

Part 2 — Title derivation in buildSyntheticCompression: When the tool name is generic ("conversation", "other", "observation", "tool"), the title is now derived from the user prompt or tool input, not the literal tool name.

Part 3 — Defensive renderer in src/functions/context.ts: When the stored title is empty, equals the type, or is one of a small set of generic names, the renderer now falls back to using the subtitle or first 80 chars of the narrative as the title. This handles observations stored before the inferType fix landed. The renderer also strips a title prefix from the narrative when they overlap, to avoid the user message appearing twice on the same line.

Part 4 — Tighter filter: Observations with empty narrative are now filtered out at the source, so they never reach the renderer and produce dangling-colon lines like - [other] conversation: .

Reproduction

  1. Install agentmemory MCP and the Hermes plugin.
  2. Start a Hermes session. After any user/assistant turn, the plugin calls sync_turn which sends:
    {"hookType": "post_tool_use", "data": {"tool_name": "conversation", "tool_input": "<user msg>", "tool_output": "<assistant msg>"}}
  3. Start a NEW session and look at the system prompt's <agentmemory-context> block.
  4. Before the fix: Conversation lines are - [other] conversation: <narrative> (looks like a type label with no content).
  5. After the fix: Conversation lines are - [conversation] <user msg first 80 chars>: <rest of narrative> (correctly typed with meaningful title and full content).

Test plan

  1. Apply the patch to a running agentmemory instance with existing data. The renderer-side change makes existing observations render correctly without re-processing.
  2. Restart the daemon. Hit POST /agentmemory/context with the same session/project as a previous test. Verify the rendered lines have meaningful titles and full content.
  3. Create a new observation via POST /agentmemory/observe with tool_name: "conversation", tool_input: "hello", tool_output: "world". Verify it has type: "conversation", title: <first 80 chars of "hello">, narrative: "hello | world".
  4. Force LLM compression with AGENTMEMORY_AUTO_COMPRESS=true. Verify the LLM-compressed observation has type and title that don't collide.

Compatibility

  • Backwards-compatible. Existing observations are unaffected in storage.
  • The renderer fix is a no-op for observations with proper titles.
  • The compressor fix only changes how the title is set — for non-generic tool names (file_read, search, command_run, etc.), the behavior is unchanged.
  • The inferType fix adds new branches but doesn't change the order of existing branches.

Background

This bug was identified while debugging why the Hermes agentmemory plugin was producing empty memory-context blocks. The plugin's sync_turn hook had been silently storing observations for months, but the renderer was misformatting them so the system prompt showed - [other] conversation: <content> — easy to misread as a broken type label.

The reporter is an independent AI researcher running SCOS (Sovereign Cognitive OS) — a sovereignty infrastructure for AI — across six LLM CLIs (Codex, Claude Code, Gemini CLI, Jules, Antigravity, OpenCode). Without the fix, none of those CLIs could see the persistent context that the agentmemory plugin was storing. With the fix, all six share the same persistent conversation history.

Summary by CodeRabbit

  • Bug Fixes
    • Improved how conversation and observation entries are labeled, so generic titles are replaced with clearer text when available.
    • Reduced duplicate or repetitive content in rendered observation details.
    • Filtered out empty important observations to keep the display cleaner and more relevant.
    • Updated dependencies to newer versions for improved stability and compatibility.

dependabot Bot and others added 4 commits June 7, 2026 03:09
Bumps the minor-and-patch group with 3 updates: [@anthropic-ai/sdk](https://github.com/anthropics/anthropic-sdk-typescript), iii-sdk and [tsdown](https://github.com/rolldown/tsdown).


Updates `@anthropic-ai/sdk` from 0.100.1 to 0.102.0
- [Release notes](https://github.com/anthropics/anthropic-sdk-typescript/releases)
- [Changelog](https://github.com/anthropics/anthropic-sdk-typescript/blob/main/CHANGELOG.md)
- [Commits](anthropics/anthropic-sdk-typescript@sdk-v0.100.1...sdk-v0.102.0)

Updates `iii-sdk` from 0.11.2 to 0.19.0

Updates `tsdown` from 0.21.10 to 0.22.2
- [Release notes](https://github.com/rolldown/tsdown/releases)
- [Commits](rolldown/tsdown@v0.21.10...v0.22.2)

---
updated-dependencies:
- dependency-name: "@anthropic-ai/sdk"
  dependency-version: 0.102.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
- dependency-name: iii-sdk
  dependency-version: 0.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
- dependency-name: tsdown
  dependency-version: 0.22.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: minor-and-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
…-and-patch-ede7075900

deps(deps): bump the minor-and-patch group with 3 updates
…h full content

The Hermes plugin (and any integration that uses tool_name: "conversation"
as a generic catch-all for user/assistant turns) stored observations via
the synthetic-compression path with type="other" and title="conversation".
The memory-context renderer then produced lines like:

  - [other] conversation: <narrative>

which were easy to misread as 'type=conversation, empty content' and broke
the user's mental model of what was being injected into the system prompt.

This change has three parts:

1. inferType() now recognizes conversation-shaped tool names
   ("conversation", "chat", "prompt", "message", "turn") and returns
   the correct type. New observations are stored with type="conversation"
   and a meaningful title derived from the user prompt or tool input,
   instead of the literal tool name.

2. The context renderer now has a defensive fallback: when the stored
   title is empty, equals the type, or is one of a small set of generic
   tool names, it uses the subtitle or the first 80 chars of the
   narrative as the title. This handles observations that were stored
   before the inferType fix landed.

3. The filter is tightened to drop observations with empty narrative,
   so they never reach the renderer and produce dangling-colon lines
   like '- [other] conversation: '.

The renderer also strips a title prefix from the narrative when they
overlap, to avoid the user message appearing twice on the same line.

Fixes the issue where the 100+ Hermes-plugin conversation observations
were rendering as empty bullets in the system prompt's memory-context
block across all six of the user's CLIs.
…s-in-memory-context

fix(context,compress-synthetic): conversation observations render with full content
@vercel

vercel Bot commented Jun 25, 2026

Copy link
Copy Markdown

@projectedanx is attempting to deploy a commit to the rohitg00's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Updated package versions and adjusted observation text handling. Synthetic compression now classifies conversation-like tool names and chooses titles from prompt or input text. Context rendering now skips empty narratives and applies fallback title and narrative formatting.

Changes

Observation text updates

Layer / File(s) Summary
Synthetic compression labels
src/functions/compress-synthetic.ts
Conversation-like tool names now map to conversation, and synthetic titles now come from prompt or input text when the tool name is generic.
Context observation rendering
src/functions/context.ts
Important observations with blank narratives are skipped, and rendered lines now use fallback titles plus trimmed, de-duplicated narrative text.

Dependency refresh

Layer / File(s) Summary
Package versions
package.json
@anthropic-ai/sdk, iii-sdk, and tsdown version specifiers were updated.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I’m a rabbit with a tidy nose,
I sniffed the prompts and trimmed the prose.
Conversation hops got named just right,
and blank old narrations lost their bite.
🥕 Fresh versions twinkle; all is bright!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main fix for conversation observation rendering in context and synthetic compression.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/functions/context.ts (1)

177-179: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace explanatory comment blocks with clearer helper naming.

This TS block adds “what/why” comments; repo guideline asks to avoid that style in source.

As per coding guidelines, "In TypeScript source code, avoid code comments explaining WHAT — use clear naming instead".

Also applies to: 191-198, 221-222

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/functions/context.ts` around lines 177 - 179, Remove the explanatory
WHAT/WHY comment blocks in context.ts and instead make the intent obvious
through naming and structure in the relevant helpers around the narrative
filtering and rendering path; update the affected logic in the areas of the
observation filter and renderer preparation (including the related blocks near
the referenced helper code) so the code is self-explanatory without inline
comments.

Source: Coding guidelines

src/functions/compress-synthetic.ts (1)

23-34: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove “what/why” inline comment blocks and encode intent in names/helpers.

The new explanatory blocks are verbose and conflict with the TS guideline for this repo. Prefer extracting small helpers with descriptive names instead of narrative comments.

As per coding guidelines, "In TypeScript source code, avoid code comments explaining WHAT — use clear naming instead".

Also applies to: 108-112

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/functions/compress-synthetic.ts` around lines 23 - 34, The inline
narrative comments in compress-synthetic.ts should be removed and the intent
expressed through clearer naming or a small helper instead. Refactor the
normalization and conversation-name handling in the synthetic compressor logic
into descriptive helpers (for example around the toolName normalization and the
conversation/tool-name classification branch) so the code reads clearly without
WHAT/WHY comment blocks, and apply the same cleanup to the related comment block
in the later section noted in the review.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@package.json`:
- Line 67: The CLI install path is hardcoding an outdated iii-sdk version and
overriding the dependency declared in package.json. Update the iii-sdk
install/version reference in src/cli.ts within the CLI install logic to use the
same 0.19.0 version already declared, so the runtime dependency stays
consistent. Use the identifiers around the install command in the CLI entry flow
to locate and change the version string.

In `@src/functions/context.ts`:
- Around line 180-185: The fallback renderer in context.ts is unreachable for
observations without titles because the important filter currently requires
o.title before the new compatibility path can run. Update the filtering logic in
the observations processing block so title-less records can still pass through
when they meet the other criteria, and keep the fallback handling in the same
context flow so older records are rendered instead of being dropped.
- Around line 207-210: The generic-title check in context.ts is doing a direct
GENERIC_TITLES.has(o.title) lookup, so it misses titles with different casing or
extra whitespace; update the title normalization used in the titleIsGeneric
logic before the set lookup. Use the existing title handling around
o.title/o.type to trim and lowercase (or otherwise normalize consistently) and
then compare against GENERIC_TITLES so values like “Conversation” and “
conversation ” correctly fall back.

---

Nitpick comments:
In `@src/functions/compress-synthetic.ts`:
- Around line 23-34: The inline narrative comments in compress-synthetic.ts
should be removed and the intent expressed through clearer naming or a small
helper instead. Refactor the normalization and conversation-name handling in the
synthetic compressor logic into descriptive helpers (for example around the
toolName normalization and the conversation/tool-name classification branch) so
the code reads clearly without WHAT/WHY comment blocks, and apply the same
cleanup to the related comment block in the later section noted in the review.

In `@src/functions/context.ts`:
- Around line 177-179: Remove the explanatory WHAT/WHY comment blocks in
context.ts and instead make the intent obvious through naming and structure in
the relevant helpers around the narrative filtering and rendering path; update
the affected logic in the areas of the observation filter and renderer
preparation (including the related blocks near the referenced helper code) so
the code is self-explanatory without inline comments.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e8f40e74-acf6-48ad-bdd5-0ae40be83122

📥 Commits

Reviewing files that changed from the base of the PR and between f6f9e3c and 877cc51.

📒 Files selected for processing (3)
  • package.json
  • src/functions/compress-synthetic.ts
  • src/functions/context.ts

Comment thread package.json
"@clack/prompts": "^1.2.0",
"dotenv": "^17.4.2",
"iii-sdk": "0.11.2",
"iii-sdk": "0.19.0",

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.

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify all hardcoded iii-sdk version pins and compare with package.json
rg -n --type ts 'iii-sdk@' src
cat package.json | jq '.dependencies["iii-sdk"]'

Repository: rohitg00/agentmemory

Length of output: 425


Hardcoded iii-sdk version in CLI overrides declared dependency.

package.json declares iii-sdk as 0.19.0, but src/cli.ts:2312-2322 explicitly runs install iii-sdk@0.11.2. This forces a downgrade or conflict in runtime dependencies.

Update the hardcoded version in src/cli.ts to match the 0.19.0 specified in package.json.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` at line 67, The CLI install path is hardcoding an outdated
iii-sdk version and overriding the dependency declared in package.json. Update
the iii-sdk install/version reference in src/cli.ts within the CLI install logic
to use the same 0.19.0 version already declared, so the runtime dependency stays
consistent. Use the identifiers around the install command in the CLI entry flow
to locate and change the version string.

Comment thread src/functions/context.ts
Comment on lines 180 to 185
const important = observations.filter(
(o) => o.title && o.importance >= 5,
(o) =>
o.title &&
o.importance >= 5 &&
(o.narrative || "").trim().length > 0,
);

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fallback path is unreachable for title-less observations.

Line 182 requires o.title, so observations with empty titles are filtered out before your new fallback renderer can recover them. That breaks the stated compatibility path for older records.

Proposed fix
 const important = observations.filter(
   (o) =>
-    o.title &&
     o.importance >= 5 &&
     (o.narrative || "").trim().length > 0,
 );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const important = observations.filter(
(o) => o.title && o.importance >= 5,
(o) =>
o.title &&
o.importance >= 5 &&
(o.narrative || "").trim().length > 0,
);
const important = observations.filter(
(o) =>
o.importance >= 5 &&
(o.narrative || "").trim().length > 0,
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/functions/context.ts` around lines 180 - 185, The fallback renderer in
context.ts is unreachable for observations without titles because the important
filter currently requires o.title before the new compatibility path can run.
Update the filtering logic in the observations processing block so title-less
records can still pass through when they meet the other criteria, and keep the
fallback handling in the same context flow so older records are rendered instead
of being dropped.

Comment thread src/functions/context.ts
Comment on lines +207 to +210
const titleIsGeneric =
!o.title ||
o.title === o.type ||
GENERIC_TITLES.has(o.title);

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Generic-title detection should normalize before set lookup.

GENERIC_TITLES.has(o.title) is case-sensitive and doesn’t trim, so values like "Conversation" or " conversation " bypass fallback unexpectedly.

Proposed fix
-const titleIsGeneric =
-  !o.title ||
-  o.title === o.type ||
-  GENERIC_TITLES.has(o.title);
+const normalizedTitle = (o.title || "").trim().toLowerCase();
+const titleIsGeneric =
+  !normalizedTitle ||
+  normalizedTitle === o.type ||
+  GENERIC_TITLES.has(normalizedTitle);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const titleIsGeneric =
!o.title ||
o.title === o.type ||
GENERIC_TITLES.has(o.title);
const normalizedTitle = (o.title || "").trim().toLowerCase();
const titleIsGeneric =
!normalizedTitle ||
normalizedTitle === o.type ||
GENERIC_TITLES.has(normalizedTitle);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/functions/context.ts` around lines 207 - 210, The generic-title check in
context.ts is doing a direct GENERIC_TITLES.has(o.title) lookup, so it misses
titles with different casing or extra whitespace; update the title normalization
used in the titleIsGeneric logic before the set lookup. Use the existing title
handling around o.title/o.type to trim and lowercase (or otherwise normalize
consistently) and then compare against GENERIC_TITLES so values like
“Conversation” and “ conversation ” correctly fall back.

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.

1 participant